Skip to content

Commit

Permalink
Auto merge of #80261 - GuillaumeGomez:attr-rework, r=jyn514
Browse files Browse the repository at this point in the history
rustdoc DocFragment rework

Kind of a follow-up of #80119.

A few things are happening in this PR. I'm not sure about the impact on perf though so I'm very interested about that too (if the perf is worse, then we can just close this PR).

The idea here is mostly about reducing the memory usage by relying even more on `Symbol` instead of `String`. The only issue is that `DocFragment` has 3 modifications performed on it:
 1. Unindenting
 2. Collapsing similar comments into one
 3. "Beautifying" (weird JS-like comments handling).

To do so, I saved the information about unindent and the "collapse" is now on-demand (which is why I'm not sure the perf will be better, it has to be run multiple times...).

r? `@jyn514`
  • Loading branch information
bors committed Jan 3, 2021
2 parents 05dfaba + df2df14 commit e821a6e
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 164 deletions.
131 changes: 109 additions & 22 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ impl Item {

/// Finds the `doc` attribute as a NameValue and returns the corresponding
/// value found.
crate fn doc_value(&self) -> Option<&str> {
crate fn doc_value(&self) -> Option<String> {
self.attrs.doc_value()
}

Expand Down Expand Up @@ -469,31 +469,66 @@ crate struct DocFragment {
/// This allows distinguishing between the original documentation and a pub re-export.
/// If it is `None`, the item was not re-exported.
crate parent_module: Option<DefId>,
crate doc: String,
crate doc: Symbol,
crate kind: DocFragmentKind,
crate need_backline: bool,
crate indent: usize,
}

#[derive(Clone, PartialEq, Eq, Debug, Hash)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
crate enum DocFragmentKind {
/// A doc fragment created from a `///` or `//!` doc comment.
SugaredDoc,
/// A doc fragment created from a "raw" `#[doc=""]` attribute.
RawDoc,
/// A doc fragment created from a `#[doc(include="filename")]` attribute. Contains both the
/// given filename and the file contents.
Include { filename: String },
Include { filename: Symbol },
}

// The goal of this function is to apply the `DocFragment` transformations that are required when
// transforming into the final markdown. So the transformations in here are:
//
// * Applying the computed indent to each lines in each doc fragment (a `DocFragment` can contain
// multiple lines in case of `#[doc = ""]`).
// * Adding backlines between `DocFragment`s and adding an extra one if required (stored in the
// `need_backline` field).
fn add_doc_fragment(out: &mut String, frag: &DocFragment) {
let s = frag.doc.as_str();
let mut iter = s.lines().peekable();
while let Some(line) = iter.next() {
if line.chars().any(|c| !c.is_whitespace()) {
assert!(line.len() >= frag.indent);
out.push_str(&line[frag.indent..]);
} else {
out.push_str(line);
}
if iter.peek().is_some() {
out.push('\n');
}
}
if frag.need_backline {
out.push('\n');
}
}

impl<'a> FromIterator<&'a DocFragment> for String {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = &'a DocFragment>,
{
let mut prev_kind: Option<DocFragmentKind> = None;
iter.into_iter().fold(String::new(), |mut acc, frag| {
if !acc.is_empty() {
if !acc.is_empty()
&& prev_kind
.take()
.map(|p| matches!(p, DocFragmentKind::Include { .. }) && p != frag.kind)
.unwrap_or(false)
{
acc.push('\n');
}
acc.push_str(&frag.doc);
add_doc_fragment(&mut acc, &frag);
prev_kind = Some(frag.kind);
acc
})
}
Expand Down Expand Up @@ -565,25 +600,25 @@ impl Attributes {
/// Reads a `MetaItem` from within an attribute, looks for whether it is a
/// `#[doc(include="file")]`, and returns the filename and contents of the file as loaded from
/// its expansion.
crate fn extract_include(mi: &ast::MetaItem) -> Option<(String, String)> {
crate fn extract_include(mi: &ast::MetaItem) -> Option<(Symbol, Symbol)> {
mi.meta_item_list().and_then(|list| {
for meta in list {
if meta.has_name(sym::include) {
// the actual compiled `#[doc(include="filename")]` gets expanded to
// `#[doc(include(file="filename", contents="file contents")]` so we need to
// look for that instead
return meta.meta_item_list().and_then(|list| {
let mut filename: Option<String> = None;
let mut contents: Option<String> = None;
let mut filename: Option<Symbol> = None;
let mut contents: Option<Symbol> = None;

for it in list {
if it.has_name(sym::file) {
if let Some(name) = it.value_str() {
filename = Some(name.to_string());
filename = Some(name);
}
} else if it.has_name(sym::contents) {
if let Some(docs) = it.value_str() {
contents = Some(docs.to_string());
contents = Some(docs);
}
}
}
Expand Down Expand Up @@ -622,30 +657,51 @@ impl Attributes {
attrs: &[ast::Attribute],
additional_attrs: Option<(&[ast::Attribute], DefId)>,
) -> Attributes {
let mut doc_strings = vec![];
let mut doc_strings: Vec<DocFragment> = vec![];
let mut sp = None;
let mut cfg = Cfg::True;
let mut doc_line = 0;

fn update_need_backline(doc_strings: &mut Vec<DocFragment>, frag: &DocFragment) {
if let Some(prev) = doc_strings.last_mut() {
if matches!(prev.kind, DocFragmentKind::Include { .. })
|| prev.kind != frag.kind
|| prev.parent_module != frag.parent_module
{
// add a newline for extra padding between segments
prev.need_backline = prev.kind == DocFragmentKind::SugaredDoc
|| prev.kind == DocFragmentKind::RawDoc
} else {
prev.need_backline = true;
}
}
}

let clean_attr = |(attr, parent_module): (&ast::Attribute, _)| {
if let Some(value) = attr.doc_str() {
trace!("got doc_str={:?}", value);
let value = beautify_doc_string(value).to_string();
let value = beautify_doc_string(value);
let kind = if attr.is_doc_comment() {
DocFragmentKind::SugaredDoc
} else {
DocFragmentKind::RawDoc
};

let line = doc_line;
doc_line += value.lines().count();
doc_strings.push(DocFragment {
doc_line += value.as_str().lines().count();
let frag = DocFragment {
line,
span: attr.span,
doc: value,
kind,
parent_module,
});
need_backline: false,
indent: 0,
};

update_need_backline(&mut doc_strings, &frag);

doc_strings.push(frag);

if sp.is_none() {
sp = Some(attr.span);
Expand All @@ -663,14 +719,18 @@ impl Attributes {
} else if let Some((filename, contents)) = Attributes::extract_include(&mi)
{
let line = doc_line;
doc_line += contents.lines().count();
doc_strings.push(DocFragment {
doc_line += contents.as_str().lines().count();
let frag = DocFragment {
line,
span: attr.span,
doc: contents,
kind: DocFragmentKind::Include { filename },
parent_module,
});
need_backline: false,
indent: 0,
};
update_need_backline(&mut doc_strings, &frag);
doc_strings.push(frag);
}
}
}
Expand Down Expand Up @@ -721,14 +781,41 @@ impl Attributes {

/// Finds the `doc` attribute as a NameValue and returns the corresponding
/// value found.
crate fn doc_value(&self) -> Option<&str> {
self.doc_strings.first().map(|s| s.doc.as_str())
crate fn doc_value(&self) -> Option<String> {
let mut iter = self.doc_strings.iter();

let ori = iter.next()?;
let mut out = String::new();
add_doc_fragment(&mut out, &ori);
while let Some(new_frag) = iter.next() {
if matches!(ori.kind, DocFragmentKind::Include { .. })
|| new_frag.kind != ori.kind
|| new_frag.parent_module != ori.parent_module
{
break;
}
add_doc_fragment(&mut out, &new_frag);
}
if out.is_empty() { None } else { Some(out) }
}

/// Return the doc-comments on this item, grouped by the module they came from.
///
/// The module can be different if this is a re-export with added documentation.
crate fn collapsed_doc_value_by_module_level(&self) -> FxHashMap<Option<DefId>, String> {
let mut ret = FxHashMap::default();

for new_frag in self.doc_strings.iter() {
let out = ret.entry(new_frag.parent_module).or_default();
add_doc_fragment(out, &new_frag);
}
ret
}

/// Finds all `doc` attributes as NameValues and returns their corresponding values, joined
/// with newlines.
crate fn collapsed_doc_value(&self) -> Option<String> {
if !self.doc_strings.is_empty() { Some(self.doc_strings.iter().collect()) } else { None }
if self.doc_strings.is_empty() { None } else { Some(self.doc_strings.iter().collect()) }
}

/// Gets links as a vector
Expand Down
5 changes: 4 additions & 1 deletion src/librustdoc/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ crate fn run_global_ctxt(
let mut krate = tcx.sess.time("clean_crate", || clean::krate(&mut ctxt));

if let Some(ref m) = krate.module {
if let None | Some("") = m.doc_value() {
if m.doc_value().map(|d| d.is_empty()).unwrap_or(true) {
let help = "The following guide may be of use:\n\
https://doc.rust-lang.org/nightly/rustdoc/how-to-write-documentation.html";
tcx.struct_lint_node(
Expand Down Expand Up @@ -623,6 +623,9 @@ crate fn run_global_ctxt(

ctxt.sess().abort_if_errors();

// The main crate doc comments are always collapsed.
krate.collapsed = true;

(krate, ctxt.renderinfo.into_inner(), ctxt.render_options)
}

Expand Down
1 change: 0 additions & 1 deletion src/librustdoc/doctest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,6 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
self.collector.names.push(name);
}

attrs.collapse_doc_comments();
attrs.unindent_doc_comments();
// The collapse-docs pass won't combine sugared/raw doc attributes, or included files with
// anything else, this will combine them for us.
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/formats/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ impl DocFolder for Cache {
path: path.join("::"),
desc: item
.doc_value()
.map_or_else(|| String::new(), short_markdown_summary),
.map_or_else(String::new, |x| short_markdown_summary(&x.as_str())),
parent,
parent_idx: None,
search_type: get_index_search_type(&item),
Expand Down
4 changes: 2 additions & 2 deletions src/librustdoc/html/render/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ crate fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
ty: item.type_(),
name: item.name.unwrap().to_string(),
path: fqp[..fqp.len() - 1].join("::"),
desc: item.doc_value().map_or_else(|| String::new(), short_markdown_summary),
desc: item.doc_value().map_or_else(String::new, |s| short_markdown_summary(&s)),
parent: Some(did),
parent_idx: None,
search_type: get_index_search_type(&item),
Expand Down Expand Up @@ -127,7 +127,7 @@ crate fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
let crate_doc = krate
.module
.as_ref()
.map(|module| module.doc_value().map_or_else(|| String::new(), short_markdown_summary))
.map(|module| module.doc_value().map_or_else(String::new, |s| short_markdown_summary(&s)))
.unwrap_or_default();

#[derive(Serialize)]
Expand Down
17 changes: 6 additions & 11 deletions src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ crate mod cache;
#[cfg(test)]
mod tests;

use std::borrow::Cow;
use std::cell::{Cell, RefCell};
use std::cmp::Ordering;
use std::collections::{BTreeMap, VecDeque};
Expand Down Expand Up @@ -198,12 +197,8 @@ impl SharedContext<'_> {

/// Based on whether the `collapse-docs` pass was run, return either the `doc_value` or the
/// `collapsed_doc_value` of the given item.
crate fn maybe_collapsed_doc_value<'a>(&self, item: &'a clean::Item) -> Option<Cow<'a, str>> {
if self.collapsed {
item.collapsed_doc_value().map(|s| s.into())
} else {
item.doc_value().map(|s| s.into())
}
crate fn maybe_collapsed_doc_value<'a>(&self, item: &'a clean::Item) -> Option<String> {
if self.collapsed { item.collapsed_doc_value() } else { item.doc_value() }
}
}

Expand Down Expand Up @@ -1622,7 +1617,7 @@ impl Context<'_> {
let short = short.to_string();
map.entry(short).or_default().push((
myname,
Some(item.doc_value().map_or_else(|| String::new(), plain_text_summary)),
Some(item.doc_value().map_or_else(String::new, |s| plain_text_summary(&s))),
));
}

Expand Down Expand Up @@ -1880,7 +1875,7 @@ fn document_short(
return;
}
if let Some(s) = item.doc_value() {
let mut summary_html = MarkdownSummaryLine(s, &item.links()).into_string();
let mut summary_html = MarkdownSummaryLine(&s, &item.links()).into_string();

if s.contains('\n') {
let link = format!(r#" <a href="{}">Read more</a>"#, naive_assoc_href(item, link));
Expand Down Expand Up @@ -2197,7 +2192,7 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl
let stab = myitem.stability_class(cx.tcx());
let add = if stab.is_some() { " " } else { "" };

let doc_value = myitem.doc_value().unwrap_or("");
let doc_value = myitem.doc_value().unwrap_or_default();
write!(
w,
"<tr class=\"{stab}{add}module-item\">\
Expand All @@ -2207,7 +2202,7 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl
</tr>",
name = *myitem.name.as_ref().unwrap(),
stab_tags = extra_info_tags(myitem, item, cx.tcx()),
docs = MarkdownSummaryLine(doc_value, &myitem.links()).into_string(),
docs = MarkdownSummaryLine(&doc_value, &myitem.links()).into_string(),
class = myitem.type_(),
add = add,
stab = stab.unwrap_or_else(String::new),
Expand Down
7 changes: 1 addition & 6 deletions src/librustdoc/passes/calculate_doc_coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,7 @@ impl<'a, 'b> fold::DocFolder for CoverageCalculator<'a, 'b> {
let mut tests = Tests { found_tests: 0 };

find_testable_code(
&i.attrs
.doc_strings
.iter()
.map(|d| d.doc.as_str())
.collect::<Vec<_>>()
.join("\n"),
&i.attrs.collapsed_doc_value().unwrap_or_default(),
&mut tests,
ErrorCodes::No,
false,
Expand Down
Loading

0 comments on commit e821a6e

Please sign in to comment.