diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 89b561bd2e901..1776702dd2e31 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -200,10 +200,10 @@ pub fn add_doc_fragment(out: &mut String, frag: &DocFragment) { pub fn attrs_to_doc_fragments<'a, A: AttributeExt + Clone + 'a>( attrs: impl Iterator)>, doc_only: bool, -) -> (Vec, ThinVec) { +) -> (ThinVec, ThinVec) { let (min_size, max_size) = attrs.size_hint(); let size_hint = max_size.unwrap_or(min_size); - let mut doc_fragments = Vec::with_capacity(size_hint); + let mut doc_fragments = ThinVec::with_capacity(size_hint); let mut other_attrs = ThinVec::::with_capacity(if doc_only { 0 } else { size_hint }); for (attr, item_id) in attrs { if let Some((doc_str, fragment_kind)) = attr.doc_str_and_fragment_kind() { diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 8656378a1e264..b191c5123a83f 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1,7 +1,7 @@ use std::fmt::Write; use std::hash::Hash; use std::path::PathBuf; -use std::sync::{Arc, OnceLock as OnceCell}; +use std::sync::{Arc, OnceLock}; use std::{fmt, iter}; use arrayvec::ArrayVec; @@ -483,7 +483,7 @@ impl Item { .iter() .filter_map(|attr| attr.deprecation_note().map(|note| note.span)); - span_of_fragments(&self.attrs.doc_strings) + span_of_fragments(&self.attrs.doc_strings()) .into_iter() .chain(deprecation_notes) .reduce(|a, b| a.to(b)) @@ -491,14 +491,14 @@ impl Item { } /// Combine all doc strings into a single value handling indentation and newlines as needed. - pub(crate) fn doc_value(&self) -> String { + pub(crate) fn doc_value(&self) -> &str { self.attrs.doc_value() } /// Combine all doc strings into a single value handling indentation and newlines as needed. /// Returns `None` is there's no documentation at all, and `Some("")` if there is some /// documentation but it is empty (e.g. `#[doc = ""]`). - pub(crate) fn opt_doc_value(&self) -> Option { + pub(crate) fn opt_doc_value(&self) -> Option<&str> { self.attrs.opt_doc_value() } @@ -550,7 +550,7 @@ impl Item { pub(crate) fn item_or_reexport_id(&self) -> ItemId { // added documentation on a reexport is always prepended. self.attrs - .doc_strings + .doc_strings() .first() .map(|x| x.item_id) .flatten() @@ -1004,11 +1004,21 @@ pub struct RenderedLink { /// as well as doc comments. #[derive(Clone, Debug, Default)] pub(crate) struct Attributes { - pub(crate) doc_strings: Vec, + /// IMPORTANT! This should not be mutated since then `doc_value_cache` will be invalid. + doc_strings: ThinVec, pub(crate) other_attrs: ThinVec, + doc_value_cache: Box>>>, } impl Attributes { + fn new(doc_strings: ThinVec, other_attrs: ThinVec) -> Self { + Self { doc_strings, other_attrs, doc_value_cache: Box::new(OnceLock::new()) } + } + + pub(crate) fn doc_strings(&self) -> &[DocFragment] { + &self.doc_strings + } + pub(crate) fn has_doc_flag bool>(&self, callback: F) -> bool { find_attr!(&self.other_attrs, Doc(d) if callback(d)) } @@ -1036,26 +1046,30 @@ impl Attributes { doc_only: bool, ) -> Attributes { let (doc_strings, other_attrs) = attrs_to_doc_fragments(attrs, doc_only); - Attributes { doc_strings, other_attrs } + Attributes::new(doc_strings, other_attrs) } /// Combine all doc strings into a single value handling indentation and newlines as needed. - pub(crate) fn doc_value(&self) -> String { + pub(crate) fn doc_value(&self) -> &str { self.opt_doc_value().unwrap_or_default() } /// Combine all doc strings into a single value handling indentation and newlines as needed. /// Returns `None` is there's no documentation at all, and `Some("")` if there is some /// documentation but it is empty (e.g. `#[doc = ""]`). - pub(crate) fn opt_doc_value(&self) -> Option { - (!self.doc_strings.is_empty()).then(|| { - let mut res = String::new(); - for frag in &self.doc_strings { - add_doc_fragment(&mut res, frag); - } - res.pop(); - res - }) + pub(crate) fn opt_doc_value(&self) -> Option<&str> { + self.doc_value_cache + .get_or_init(|| { + (!self.doc_strings.is_empty()).then(|| { + let mut res = String::new(); + for frag in &self.doc_strings { + add_doc_fragment(&mut res, frag); + } + res.pop(); + res.into_boxed_str() + }) + }) + .as_deref() } pub(crate) fn get_doc_aliases(&self) -> Box<[Symbol]> { @@ -1673,7 +1687,7 @@ impl PrimitiveType { pub(crate) fn simplified_types() -> &'static SimplifiedTypes { use PrimitiveType::*; use ty::{FloatTy, IntTy, UintTy}; - static CELL: OnceCell = OnceCell::new(); + static CELL: OnceLock = OnceLock::new(); let single = |x| iter::once(x).collect(); CELL.get_or_init(move || { @@ -1779,7 +1793,7 @@ impl PrimitiveType { /// `rustc_doc_primitive`, then it's entirely random whether `std` or the other crate is picked. /// (no_std crates are usually fine unless multiple dependencies define a primitive.) pub(crate) fn primitive_locations(tcx: TyCtxt<'_>) -> &FxIndexMap { - static PRIMITIVE_LOCATIONS: OnceCell> = OnceCell::new(); + static PRIMITIVE_LOCATIONS: OnceLock> = OnceLock::new(); PRIMITIVE_LOCATIONS.get_or_init(|| { let mut primitive_locations = FxIndexMap::default(); // NOTE: technically this misses crates that are only passed with `--extern` and not loaded when checking the crate. @@ -2418,7 +2432,7 @@ mod size_asserts { static_assert_size!(GenericParamDef, 40); static_assert_size!(Generics, 16); static_assert_size!(Item, 8); - static_assert_size!(ItemInner, 144); + static_assert_size!(ItemInner, 136); static_assert_size!(ItemKind, 48); static_assert_size!(PathSegment, 32); static_assert_size!(Type, 32); diff --git a/src/librustdoc/clean/types/tests.rs b/src/librustdoc/clean/types/tests.rs index 10a3857878c87..7dfc496fc955a 100644 --- a/src/librustdoc/clean/types/tests.rs +++ b/src/librustdoc/clean/types/tests.rs @@ -4,15 +4,15 @@ use rustc_span::create_default_session_globals_then; use super::*; -fn create_doc_fragment(s: &str) -> Vec { - vec![DocFragment { +fn create_doc_fragment(s: &str) -> ThinVec { + ThinVec::from([DocFragment { span: DUMMY_SP, item_id: None, doc: Symbol::intern(s), kind: DocFragmentKind::Sugared(CommentKind::Line), indent: 0, from_expansion: false, - }] + }]) } #[track_caller] @@ -20,7 +20,7 @@ fn run_test(input: &str, expected: &str) { create_default_session_globals_then(|| { let mut s = create_doc_fragment(input); unindent_doc_fragments(&mut s); - let attrs = Attributes { doc_strings: s, other_attrs: Default::default() }; + let attrs = Attributes::new(s, Default::default()); assert_eq!(attrs.doc_value(), expected); }); } diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index b4397b1f01ffa..cdae4fc0d2eb6 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -181,7 +181,7 @@ impl HirCollector<'_> { // anything else, this will combine them for us. let attrs = Attributes::from_hir(ast_attrs); if let Some(doc) = attrs.opt_doc_value() { - let span = span_of_fragments(&attrs.doc_strings).unwrap_or(sp); + let span = span_of_fragments(&attrs.doc_strings()).unwrap_or(sp); self.collector.position = if span.edition().at_least_rust_2024() { span } else { diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 599d7b10005d9..63e815cee8fdb 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -41,7 +41,7 @@ impl JsonRenderer<'_> { (String::from(&**link), self.id_from_item_default(id.into())) }) .collect(); - let docs = item.opt_doc_value(); + let docs = item.opt_doc_value().map(|s| s.to_owned()); let attrs = item .attrs .other_attrs diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs index 77b3a2e9c9f2e..a6bfa3a28fa48 100644 --- a/src/librustdoc/passes/calculate_doc_coverage.rs +++ b/src/librustdoc/passes/calculate_doc_coverage.rs @@ -211,7 +211,7 @@ impl DocVisitor<'_> for CoverageCalculator<'_, '_> { // doesn't make sense, as all methods on a type are in one single impl block clean::ImplItem(_) => {} _ => { - let has_docs = !i.attrs.doc_strings.is_empty(); + let has_docs = !i.attrs.doc_strings().is_empty(); let mut tests = Tests { found_tests: 0 }; find_testable_code(&i.doc_value(), &mut tests, ErrorCodes::No, None); diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 1da191c903871..0ad3484862204 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -1095,7 +1095,7 @@ impl LinkCollector<'_, '_> { // In the presence of re-exports, this is not the same as the module of the item. // Rather than merging all documentation into one, resolve it one attribute at a time // so we know which module it came from. - for (item_id, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings) { + for (item_id, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings()) { if !may_have_doc_links(&doc) { continue; } @@ -1464,7 +1464,7 @@ impl LinkCollector<'_, '_> { self.cx.tcx, dox, ori_link.inner_range(), - &item.attrs.doc_strings, + &item.attrs.doc_strings(), ) { Some((sp, _)) => sp, None => item.attr_span(self.cx.tcx), @@ -1911,7 +1911,7 @@ fn report_diagnostic( MarkdownLinkRange::Destination(md_range) => { let mut md_range = md_range.clone(); let sp = - source_span_for_markdown_range(tcx, dox, &md_range, &item.attrs.doc_strings) + source_span_for_markdown_range(tcx, dox, &md_range, &item.attrs.doc_strings()) .map(|(mut sp, _)| { while dox.as_bytes().get(md_range.start) == Some(&b' ') || dox.as_bytes().get(md_range.start) == Some(&b'`') @@ -1930,7 +1930,7 @@ fn report_diagnostic( (sp, MarkdownLinkRange::Destination(md_range)) } MarkdownLinkRange::WholeLink(md_range) => ( - source_span_for_markdown_range(tcx, dox, md_range, &item.attrs.doc_strings) + source_span_for_markdown_range(tcx, dox, md_range, &item.attrs.doc_strings()) .map(|(sp, _)| sp), link_range.clone(), ), diff --git a/src/librustdoc/passes/lint/bare_urls.rs b/src/librustdoc/passes/lint/bare_urls.rs index 49fca2ded6773..6b67657b0e1d4 100644 --- a/src/librustdoc/passes/lint/bare_urls.rs +++ b/src/librustdoc/passes/lint/bare_urls.rs @@ -21,8 +21,9 @@ pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & msg: &'static str, range: Range, without_brackets: Option<&str>| { - let maybe_sp = source_span_for_markdown_range(cx.tcx, dox, &range, &item.attrs.doc_strings) - .map(|(sp, _)| sp); + let maybe_sp = + source_span_for_markdown_range(cx.tcx, dox, &range, &item.attrs.doc_strings()) + .map(|(sp, _)| sp); let sp = maybe_sp.unwrap_or_else(|| item.attr_span(cx.tcx)); cx.tcx.node_span_lint(crate::lint::BARE_URLS, hir_id, sp, |lint| { lint.primary_message(msg) diff --git a/src/librustdoc/passes/lint/check_code_block_syntax.rs b/src/librustdoc/passes/lint/check_code_block_syntax.rs index b3199f11a680a..f38a35dd266b2 100644 --- a/src/librustdoc/passes/lint/check_code_block_syntax.rs +++ b/src/librustdoc/passes/lint/check_code_block_syntax.rs @@ -83,7 +83,7 @@ fn check_rust_syntax( cx.tcx, dox, &code_block.range, - &item.attrs.doc_strings, + &item.attrs.doc_strings(), ) { Some((sp, _)) => (sp, true), None => (item.attr_span(cx.tcx), false), diff --git a/src/librustdoc/passes/lint/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs index 4e374fe3cd56a..9779f22b6ddd5 100644 --- a/src/librustdoc/passes/lint/html_tags.rs +++ b/src/librustdoc/passes/lint/html_tags.rs @@ -17,7 +17,7 @@ use crate::html::markdown::main_body_opts; pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) { let tcx = cx.tcx; let report_diag = |msg: String, range: &Range, is_open_tag: bool| { - let sp = match source_span_for_markdown_range(tcx, dox, range, &item.attrs.doc_strings) { + let sp = match source_span_for_markdown_range(tcx, dox, range, &item.attrs.doc_strings()) { Some((sp, _)) => sp, None => item.attr_span(tcx), }; @@ -55,7 +55,7 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & tcx, dox, &(generics_start..generics_end), - &item.attrs.doc_strings, + &item.attrs.doc_strings(), ) { Some((sp, _)) => sp, None => item.attr_span(tcx), diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs index e49f54f6df946..d7413e374992a 100644 --- a/src/librustdoc/passes/lint/redundant_explicit_links.rs +++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs @@ -25,7 +25,7 @@ struct LinkData { } pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId) { - let hunks = prepare_to_doc_link_resolution(&item.attrs.doc_strings); + let hunks = prepare_to_doc_link_resolution(&item.attrs.doc_strings()); for (item_id, doc) in hunks { if let Some(item_id) = item_id.or(item.def_id()) && !doc.is_empty() @@ -160,22 +160,25 @@ fn check_inline_or_reference_unknown_redundancy( (find_resolution(resolutions, &dest)?, find_resolution(resolutions, resolvable_link)?); if dest_res == display_res { - let link_span = - match source_span_for_markdown_range(cx.tcx, doc, &link_range, &item.attrs.doc_strings) - { - Some((sp, from_expansion)) => { - if from_expansion { - return None; - } - sp + let link_span = match source_span_for_markdown_range( + cx.tcx, + doc, + &link_range, + &item.attrs.doc_strings(), + ) { + Some((sp, from_expansion)) => { + if from_expansion { + return None; } - None => item.attr_span(cx.tcx), - }; + sp + } + None => item.attr_span(cx.tcx), + }; let (explicit_span, false) = source_span_for_markdown_range( cx.tcx, doc, &offset_explicit_range(doc, link_range, open, close), - &item.attrs.doc_strings, + &item.attrs.doc_strings(), )? else { // This `span` comes from macro expansion so skipping it. @@ -185,7 +188,7 @@ fn check_inline_or_reference_unknown_redundancy( cx.tcx, doc, resolvable_link_range, - &item.attrs.doc_strings, + &item.attrs.doc_strings(), )? else { // This `span` comes from macro expansion so skipping it. @@ -221,22 +224,25 @@ fn check_reference_redundancy( (find_resolution(resolutions, dest)?, find_resolution(resolutions, resolvable_link)?); if dest_res == display_res { - let link_span = - match source_span_for_markdown_range(cx.tcx, doc, &link_range, &item.attrs.doc_strings) - { - Some((sp, from_expansion)) => { - if from_expansion { - return None; - } - sp + let link_span = match source_span_for_markdown_range( + cx.tcx, + doc, + &link_range, + &item.attrs.doc_strings(), + ) { + Some((sp, from_expansion)) => { + if from_expansion { + return None; } - None => item.attr_span(cx.tcx), - }; + sp + } + None => item.attr_span(cx.tcx), + }; let (explicit_span, false) = source_span_for_markdown_range( cx.tcx, doc, &offset_explicit_range(doc, link_range.clone(), b'[', b']'), - &item.attrs.doc_strings, + &item.attrs.doc_strings(), )? else { // This `span` comes from macro expansion so skipping it. @@ -246,7 +252,7 @@ fn check_reference_redundancy( cx.tcx, doc, resolvable_link_range, - &item.attrs.doc_strings, + &item.attrs.doc_strings(), )? else { // This `span` comes from macro expansion so skipping it. @@ -256,7 +262,7 @@ fn check_reference_redundancy( cx.tcx, doc, &offset_reference_def_range(doc, dest, link_range), - &item.attrs.doc_strings, + &item.attrs.doc_strings(), )?; cx.tcx.node_span_lint(crate::lint::REDUNDANT_EXPLICIT_LINKS, hir_id, explicit_span, |lint| { diff --git a/src/librustdoc/passes/lint/unescaped_backticks.rs b/src/librustdoc/passes/lint/unescaped_backticks.rs index b791e024545fd..e110ea594c048 100644 --- a/src/librustdoc/passes/lint/unescaped_backticks.rs +++ b/src/librustdoc/passes/lint/unescaped_backticks.rs @@ -46,7 +46,7 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & tcx, dox, &(backtick_index..backtick_index + 1), - &item.attrs.doc_strings, + &item.attrs.doc_strings(), ) { Some((sp, _)) => sp, None => item.attr_span(tcx), @@ -425,7 +425,7 @@ fn suggest_insertion( cx.tcx, dox, &(insert_index..insert_index), - &item.attrs.doc_strings, + &item.attrs.doc_strings(), ) { lint.span_suggestion(span, message, suggestion, Applicability::MaybeIncorrect); } else {