Skip to content

Commit e821a6e

Browse files
committed
Auto merge of #80261 - GuillaumeGomez:attr-rework, r=jyn514
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`
2 parents 05dfaba + df2df14 commit e821a6e

File tree

13 files changed

+145
-164
lines changed

13 files changed

+145
-164
lines changed

src/librustdoc/clean/types.rs

+109-22
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ impl Item {
122122

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

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

476-
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
478+
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
477479
crate enum DocFragmentKind {
478480
/// A doc fragment created from a `///` or `//!` doc comment.
479481
SugaredDoc,
480482
/// A doc fragment created from a "raw" `#[doc=""]` attribute.
481483
RawDoc,
482484
/// A doc fragment created from a `#[doc(include="filename")]` attribute. Contains both the
483485
/// given filename and the file contents.
484-
Include { filename: String },
486+
Include { filename: Symbol },
487+
}
488+
489+
// The goal of this function is to apply the `DocFragment` transformations that are required when
490+
// transforming into the final markdown. So the transformations in here are:
491+
//
492+
// * Applying the computed indent to each lines in each doc fragment (a `DocFragment` can contain
493+
// multiple lines in case of `#[doc = ""]`).
494+
// * Adding backlines between `DocFragment`s and adding an extra one if required (stored in the
495+
// `need_backline` field).
496+
fn add_doc_fragment(out: &mut String, frag: &DocFragment) {
497+
let s = frag.doc.as_str();
498+
let mut iter = s.lines().peekable();
499+
while let Some(line) = iter.next() {
500+
if line.chars().any(|c| !c.is_whitespace()) {
501+
assert!(line.len() >= frag.indent);
502+
out.push_str(&line[frag.indent..]);
503+
} else {
504+
out.push_str(line);
505+
}
506+
if iter.peek().is_some() {
507+
out.push('\n');
508+
}
509+
}
510+
if frag.need_backline {
511+
out.push('\n');
512+
}
485513
}
486514

487515
impl<'a> FromIterator<&'a DocFragment> for String {
488516
fn from_iter<T>(iter: T) -> Self
489517
where
490518
T: IntoIterator<Item = &'a DocFragment>,
491519
{
520+
let mut prev_kind: Option<DocFragmentKind> = None;
492521
iter.into_iter().fold(String::new(), |mut acc, frag| {
493-
if !acc.is_empty() {
522+
if !acc.is_empty()
523+
&& prev_kind
524+
.take()
525+
.map(|p| matches!(p, DocFragmentKind::Include { .. }) && p != frag.kind)
526+
.unwrap_or(false)
527+
{
494528
acc.push('\n');
495529
}
496-
acc.push_str(&frag.doc);
530+
add_doc_fragment(&mut acc, &frag);
531+
prev_kind = Some(frag.kind);
497532
acc
498533
})
499534
}
@@ -565,25 +600,25 @@ impl Attributes {
565600
/// Reads a `MetaItem` from within an attribute, looks for whether it is a
566601
/// `#[doc(include="file")]`, and returns the filename and contents of the file as loaded from
567602
/// its expansion.
568-
crate fn extract_include(mi: &ast::MetaItem) -> Option<(String, String)> {
603+
crate fn extract_include(mi: &ast::MetaItem) -> Option<(Symbol, Symbol)> {
569604
mi.meta_item_list().and_then(|list| {
570605
for meta in list {
571606
if meta.has_name(sym::include) {
572607
// the actual compiled `#[doc(include="filename")]` gets expanded to
573608
// `#[doc(include(file="filename", contents="file contents")]` so we need to
574609
// look for that instead
575610
return meta.meta_item_list().and_then(|list| {
576-
let mut filename: Option<String> = None;
577-
let mut contents: Option<String> = None;
611+
let mut filename: Option<Symbol> = None;
612+
let mut contents: Option<Symbol> = None;
578613

579614
for it in list {
580615
if it.has_name(sym::file) {
581616
if let Some(name) = it.value_str() {
582-
filename = Some(name.to_string());
617+
filename = Some(name);
583618
}
584619
} else if it.has_name(sym::contents) {
585620
if let Some(docs) = it.value_str() {
586-
contents = Some(docs.to_string());
621+
contents = Some(docs);
587622
}
588623
}
589624
}
@@ -622,30 +657,51 @@ impl Attributes {
622657
attrs: &[ast::Attribute],
623658
additional_attrs: Option<(&[ast::Attribute], DefId)>,
624659
) -> Attributes {
625-
let mut doc_strings = vec![];
660+
let mut doc_strings: Vec<DocFragment> = vec![];
626661
let mut sp = None;
627662
let mut cfg = Cfg::True;
628663
let mut doc_line = 0;
629664

665+
fn update_need_backline(doc_strings: &mut Vec<DocFragment>, frag: &DocFragment) {
666+
if let Some(prev) = doc_strings.last_mut() {
667+
if matches!(prev.kind, DocFragmentKind::Include { .. })
668+
|| prev.kind != frag.kind
669+
|| prev.parent_module != frag.parent_module
670+
{
671+
// add a newline for extra padding between segments
672+
prev.need_backline = prev.kind == DocFragmentKind::SugaredDoc
673+
|| prev.kind == DocFragmentKind::RawDoc
674+
} else {
675+
prev.need_backline = true;
676+
}
677+
}
678+
}
679+
630680
let clean_attr = |(attr, parent_module): (&ast::Attribute, _)| {
631681
if let Some(value) = attr.doc_str() {
632682
trace!("got doc_str={:?}", value);
633-
let value = beautify_doc_string(value).to_string();
683+
let value = beautify_doc_string(value);
634684
let kind = if attr.is_doc_comment() {
635685
DocFragmentKind::SugaredDoc
636686
} else {
637687
DocFragmentKind::RawDoc
638688
};
639689

640690
let line = doc_line;
641-
doc_line += value.lines().count();
642-
doc_strings.push(DocFragment {
691+
doc_line += value.as_str().lines().count();
692+
let frag = DocFragment {
643693
line,
644694
span: attr.span,
645695
doc: value,
646696
kind,
647697
parent_module,
648-
});
698+
need_backline: false,
699+
indent: 0,
700+
};
701+
702+
update_need_backline(&mut doc_strings, &frag);
703+
704+
doc_strings.push(frag);
649705

650706
if sp.is_none() {
651707
sp = Some(attr.span);
@@ -663,14 +719,18 @@ impl Attributes {
663719
} else if let Some((filename, contents)) = Attributes::extract_include(&mi)
664720
{
665721
let line = doc_line;
666-
doc_line += contents.lines().count();
667-
doc_strings.push(DocFragment {
722+
doc_line += contents.as_str().lines().count();
723+
let frag = DocFragment {
668724
line,
669725
span: attr.span,
670726
doc: contents,
671727
kind: DocFragmentKind::Include { filename },
672728
parent_module,
673-
});
729+
need_backline: false,
730+
indent: 0,
731+
};
732+
update_need_backline(&mut doc_strings, &frag);
733+
doc_strings.push(frag);
674734
}
675735
}
676736
}
@@ -721,14 +781,41 @@ impl Attributes {
721781

722782
/// Finds the `doc` attribute as a NameValue and returns the corresponding
723783
/// value found.
724-
crate fn doc_value(&self) -> Option<&str> {
725-
self.doc_strings.first().map(|s| s.doc.as_str())
784+
crate fn doc_value(&self) -> Option<String> {
785+
let mut iter = self.doc_strings.iter();
786+
787+
let ori = iter.next()?;
788+
let mut out = String::new();
789+
add_doc_fragment(&mut out, &ori);
790+
while let Some(new_frag) = iter.next() {
791+
if matches!(ori.kind, DocFragmentKind::Include { .. })
792+
|| new_frag.kind != ori.kind
793+
|| new_frag.parent_module != ori.parent_module
794+
{
795+
break;
796+
}
797+
add_doc_fragment(&mut out, &new_frag);
798+
}
799+
if out.is_empty() { None } else { Some(out) }
800+
}
801+
802+
/// Return the doc-comments on this item, grouped by the module they came from.
803+
///
804+
/// The module can be different if this is a re-export with added documentation.
805+
crate fn collapsed_doc_value_by_module_level(&self) -> FxHashMap<Option<DefId>, String> {
806+
let mut ret = FxHashMap::default();
807+
808+
for new_frag in self.doc_strings.iter() {
809+
let out = ret.entry(new_frag.parent_module).or_default();
810+
add_doc_fragment(out, &new_frag);
811+
}
812+
ret
726813
}
727814

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

734821
/// Gets links as a vector

src/librustdoc/core.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ crate fn run_global_ctxt(
525525
let mut krate = tcx.sess.time("clean_crate", || clean::krate(&mut ctxt));
526526

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

624624
ctxt.sess().abort_if_errors();
625625

626+
// The main crate doc comments are always collapsed.
627+
krate.collapsed = true;
628+
626629
(krate, ctxt.renderinfo.into_inner(), ctxt.render_options)
627630
}
628631

src/librustdoc/doctest.rs

-1
Original file line numberDiff line numberDiff line change
@@ -987,7 +987,6 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
987987
self.collector.names.push(name);
988988
}
989989

990-
attrs.collapse_doc_comments();
991990
attrs.unindent_doc_comments();
992991
// The collapse-docs pass won't combine sugared/raw doc attributes, or included files with
993992
// anything else, this will combine them for us.

src/librustdoc/formats/cache.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ impl DocFolder for Cache {
316316
path: path.join("::"),
317317
desc: item
318318
.doc_value()
319-
.map_or_else(|| String::new(), short_markdown_summary),
319+
.map_or_else(String::new, |x| short_markdown_summary(&x.as_str())),
320320
parent,
321321
parent_idx: None,
322322
search_type: get_index_search_type(&item),

src/librustdoc/html/render/cache.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ crate fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
7878
ty: item.type_(),
7979
name: item.name.unwrap().to_string(),
8080
path: fqp[..fqp.len() - 1].join("::"),
81-
desc: item.doc_value().map_or_else(|| String::new(), short_markdown_summary),
81+
desc: item.doc_value().map_or_else(String::new, |s| short_markdown_summary(&s)),
8282
parent: Some(did),
8383
parent_idx: None,
8484
search_type: get_index_search_type(&item),
@@ -127,7 +127,7 @@ crate fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
127127
let crate_doc = krate
128128
.module
129129
.as_ref()
130-
.map(|module| module.doc_value().map_or_else(|| String::new(), short_markdown_summary))
130+
.map(|module| module.doc_value().map_or_else(String::new, |s| short_markdown_summary(&s)))
131131
.unwrap_or_default();
132132

133133
#[derive(Serialize)]

src/librustdoc/html/render/mod.rs

+6-11
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ crate mod cache;
3030
#[cfg(test)]
3131
mod tests;
3232

33-
use std::borrow::Cow;
3433
use std::cell::{Cell, RefCell};
3534
use std::cmp::Ordering;
3635
use std::collections::{BTreeMap, VecDeque};
@@ -198,12 +197,8 @@ impl SharedContext<'_> {
198197

199198
/// Based on whether the `collapse-docs` pass was run, return either the `doc_value` or the
200199
/// `collapsed_doc_value` of the given item.
201-
crate fn maybe_collapsed_doc_value<'a>(&self, item: &'a clean::Item) -> Option<Cow<'a, str>> {
202-
if self.collapsed {
203-
item.collapsed_doc_value().map(|s| s.into())
204-
} else {
205-
item.doc_value().map(|s| s.into())
206-
}
200+
crate fn maybe_collapsed_doc_value<'a>(&self, item: &'a clean::Item) -> Option<String> {
201+
if self.collapsed { item.collapsed_doc_value() } else { item.doc_value() }
207202
}
208203
}
209204

@@ -1622,7 +1617,7 @@ impl Context<'_> {
16221617
let short = short.to_string();
16231618
map.entry(short).or_default().push((
16241619
myname,
1625-
Some(item.doc_value().map_or_else(|| String::new(), plain_text_summary)),
1620+
Some(item.doc_value().map_or_else(String::new, |s| plain_text_summary(&s))),
16261621
));
16271622
}
16281623

@@ -1880,7 +1875,7 @@ fn document_short(
18801875
return;
18811876
}
18821877
if let Some(s) = item.doc_value() {
1883-
let mut summary_html = MarkdownSummaryLine(s, &item.links()).into_string();
1878+
let mut summary_html = MarkdownSummaryLine(&s, &item.links()).into_string();
18841879

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

2200-
let doc_value = myitem.doc_value().unwrap_or("");
2195+
let doc_value = myitem.doc_value().unwrap_or_default();
22012196
write!(
22022197
w,
22032198
"<tr class=\"{stab}{add}module-item\">\
@@ -2207,7 +2202,7 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl
22072202
</tr>",
22082203
name = *myitem.name.as_ref().unwrap(),
22092204
stab_tags = extra_info_tags(myitem, item, cx.tcx()),
2210-
docs = MarkdownSummaryLine(doc_value, &myitem.links()).into_string(),
2205+
docs = MarkdownSummaryLine(&doc_value, &myitem.links()).into_string(),
22112206
class = myitem.type_(),
22122207
add = add,
22132208
stab = stab.unwrap_or_else(String::new),

src/librustdoc/passes/calculate_doc_coverage.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -235,12 +235,7 @@ impl<'a, 'b> fold::DocFolder for CoverageCalculator<'a, 'b> {
235235
let mut tests = Tests { found_tests: 0 };
236236

237237
find_testable_code(
238-
&i.attrs
239-
.doc_strings
240-
.iter()
241-
.map(|d| d.doc.as_str())
242-
.collect::<Vec<_>>()
243-
.join("\n"),
238+
&i.attrs.collapsed_doc_value().unwrap_or_default(),
244239
&mut tests,
245240
ErrorCodes::No,
246241
false,

0 commit comments

Comments
 (0)