diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index a4e4126c1b9b2..4b52ce0e36cec 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -95,12 +95,13 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext< // This covers the case where somebody does an import which should pull in an item, // but there's already an item with the same namespace and same name. Rust gives // priority to the not-imported one, so we should, too. - items.extend(doc.items.values().flat_map(|(item, renamed, import_ids)| { + items.extend(doc.items.values().flat_map(|entry| { // First, lower everything other than glob imports. + let item = entry.item; if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) { return Vec::new(); } - let v = clean_maybe_renamed_item(cx, item, *renamed, import_ids); + let v = clean_maybe_renamed_item(cx, item, entry.renamed, &entry.import_ids); for item in &v { if let Some(name) = item.name && (cx.document_hidden() || !item.is_doc_hidden()) @@ -130,10 +131,11 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext< _ => unreachable!(), } })); - items.extend(doc.items.values().flat_map(|(item, renamed, _)| { + items.extend(doc.items.values().flat_map(|entry| { // Now we actually lower the imports, skipping everything else. + let item = entry.item; if let hir::ItemKind::Use(path, hir::UseKind::Glob) = item.kind { - clean_use_statement(item, *renamed, path, hir::UseKind::Glob, cx, &mut inserted) + clean_use_statement(item, entry.renamed, path, hir::UseKind::Glob, cx, &mut inserted) } else { // skip everything else Vec::new() diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index e81c6eae1996f..e2db913232076 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -25,6 +25,7 @@ use rustc_resolve::rustdoc::{ DocFragment, add_doc_fragment, attrs_to_doc_fragments, inner_docs, span_of_fragments, }; use rustc_session::Session; +use rustc_span::def_id::CRATE_DEF_ID; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{Symbol, kw, sym}; use rustc_span::{DUMMY_SP, FileName, Ident, Loc, RemapPathScopeComponents}; @@ -405,6 +406,23 @@ fn is_field_vis_inherited(tcx: TyCtxt<'_>, def_id: DefId) -> bool { } impl Item { + pub(crate) fn cfg_parent_ids_for_detached_item(&self, tcx: TyCtxt<'_>) -> Vec { + let Some(def_id) = self.inline_stmt_id.or(self.item_id.as_local_def_id()) else { + return Vec::new(); + }; + let mut ids = Vec::new(); + let mut next = def_id; + while let Some(parent) = tcx.opt_local_parent(next) { + if parent == CRATE_DEF_ID { + break; + } + ids.push(parent); + next = parent; + } + ids.reverse(); + ids + } + /// Returns the effective stability of the item. /// /// This method should only be called after the `propagate-stability` pass has been run. diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index 92798bb9bb011..1334595272f60 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -51,12 +51,31 @@ fn add_only_cfg_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) } } +/// This function goes through the attributes list (`new_attrs`) and extracts the attributes that +/// affect the cfg state propagated to detached items. +fn add_cfg_state_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) { + for attr in new_attrs { + if let Attribute::Parsed(AttributeKind::Doc(d)) = attr + && (!d.cfg.is_empty() || !d.auto_cfg.is_empty() || !d.auto_cfg_change.is_empty()) + { + let mut new_attr = DocAttribute::default(); + new_attr.cfg = d.cfg.clone(); + new_attr.auto_cfg = d.auto_cfg.clone(); + new_attr.auto_cfg_change = d.auto_cfg_change.clone(); + attrs.push(Attribute::Parsed(AttributeKind::Doc(Box::new(new_attr)))); + } else if let Attribute::Parsed(AttributeKind::CfgTrace(..)) = attr { + // If it's a `cfg()` attribute, we keep it. + attrs.push(attr.clone()); + } + } +} + impl CfgPropagator<'_, '_> { // Some items need to merge their attributes with their parents' otherwise a few of them // (mostly `cfg` ones) will be missing. fn merge_with_parent_attributes(&mut self, item: &mut Item) { let mut attrs = Vec::new(); - // We only need to merge an item attributes with its parent's in case it's an impl as an + // We need to merge an item attributes with its parent's in case it's an impl as an // impl might not be defined in the same module as the item it implements. // // Otherwise, `cfg_info` already tracks everything we need so nothing else to do! @@ -69,6 +88,27 @@ impl CfgPropagator<'_, '_> { next_def_id = parent_def_id; } } + // We also need to merge an item attributes with its parent's in case it's a macro with + // the `#[macro_export]` attribute, because it might not be defined at crate root. + if matches!(item.kind, ItemKind::MacroItem(_)) + && item.inner.attrs.other_attrs.iter().any(|attr| { + matches!( + attr, + rustc_hir::Attribute::Parsed( + rustc_hir::attrs::AttributeKind::MacroExport { .. } + ) + ) + }) + { + for parent_def_id in &item.cfg_parent_ids_for_detached_item(self.cx.tcx) { + let mut parent_attrs = Vec::new(); + add_cfg_state_attributes( + &mut parent_attrs, + load_attrs(self.cx.tcx, parent_def_id.to_def_id()), + ); + merge_attrs(self.cx.tcx, &[], Some((&parent_attrs, None)), &mut self.cfg_info); + } + } let (_, cfg) = merge_attrs( self.cx.tcx, diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 8746253d6ebba..5f119c23841e3 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -32,7 +32,7 @@ pub(crate) struct Module<'hir> { pub(crate) def_id: LocalDefId, pub(crate) renamed: Option, pub(crate) import_id: Option, - /// The key is the item `ItemId` and the value is: (item, renamed, Vec). + /// The key is the item `ItemId`. /// We use `FxIndexMap` to keep the insert order. /// /// `import_id` needs to be a `Vec` because we live in a dark world where you can have code @@ -52,10 +52,7 @@ pub(crate) struct Module<'hir> { /// So in this case, we don't want to have two items but just one with attributes from all /// non-glob imports to be merged. Glob imports attributes are always ignored, whether they're /// shadowed or not. - pub(crate) items: FxIndexMap< - (LocalDefId, Option), - (&'hir hir::Item<'hir>, Option, Vec), - >, + pub(crate) items: FxIndexMap<(LocalDefId, Option), ItemEntry<'hir>>, /// (def_id, renamed) -> (res, local_import_id) /// @@ -70,6 +67,13 @@ pub(crate) struct Module<'hir> { pub(crate) foreigns: Vec<(&'hir hir::ForeignItem<'hir>, Option, Option)>, } +#[derive(Debug)] +pub(crate) struct ItemEntry<'hir> { + pub(crate) item: &'hir hir::Item<'hir>, + pub(crate) renamed: Option, + pub(crate) import_ids: Vec, +} + impl Module<'_> { pub(crate) fn new( name: Symbol, @@ -172,9 +176,10 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { { let item = self.cx.tcx.hir_expect_item(local_def_id); let (ident, _, _) = item.expect_macro(); - top_level_module - .items - .insert((local_def_id, Some(ident.name)), (item, None, Vec::new())); + top_level_module.items.insert( + (local_def_id, Some(ident.name)), + ItemEntry { item, renamed: None, import_ids: Vec::new() }, + ); } } @@ -413,10 +418,14 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { .unwrap() .items .entry(key) - .and_modify(|v| v.2.push(import_id)) - .or_insert_with(|| (item, renamed, vec![import_id])); + .and_modify(|v| v.import_ids.push(import_id)) + .or_insert_with(|| ItemEntry { item, renamed, import_ids: vec![import_id] }); } else { - self.modules.last_mut().unwrap().items.insert(key, (item, renamed, Vec::new())); + self.modules + .last_mut() + .unwrap() + .items + .insert(key, ItemEntry { item, renamed, import_ids: Vec::new() }); } } } diff --git a/tests/rustdoc-html/doc-cfg/decl-macro.rs b/tests/rustdoc-html/doc-cfg/decl-macro.rs new file mode 100644 index 0000000000000..e97da8647a6ab --- /dev/null +++ b/tests/rustdoc-html/doc-cfg/decl-macro.rs @@ -0,0 +1,68 @@ +// Regression test for +//@ compile-flags: --cfg feature="routing" + +#![crate_name = "foo"] +#![feature(doc_cfg)] + +#[cfg(feature = "routing")] +pub mod routing { + //@ has 'foo/macro.vpath.html' '//*[@class="stab portability"]' 'Available on crate feature routing only.' + #[macro_export] + macro_rules! vpath { + () => {}; + } +} + +#[doc(cfg(feature = "manual"))] +pub mod manual { + //@ has 'foo/macro.manual_macro.html' '//*[@class="stab portability"]' 'Available on crate feature manual only.' + #[macro_export] + macro_rules! manual_macro { + () => {}; + } +} + +#[doc(cfg(feature = "outer"))] +pub mod outer { + #[cfg(feature = "routing")] + pub mod inner { + //@ has 'foo/macro.nested_macro.html' '//*[@class="stab portability"]' 'Available on crate features outer and routing only.' + #[macro_export] + macro_rules! nested_macro { + () => {}; + } + } +} + +#[cfg(feature = "routing")] +#[doc(auto_cfg = false)] +pub mod auto_cfg_disabled { + //@ count 'foo/macro.no_auto_cfg_macro.html' '//*[@class="stab portability"]' 0 + #[macro_export] + macro_rules! no_auto_cfg_macro { + () => {}; + } +} + +#[cfg(feature = "routing")] +#[doc(auto_cfg(hide(feature = "routing")))] +pub mod auto_cfg_hidden { + //@ count 'foo/macro.hidden_cfg_macro.html' '//*[@class="stab portability"]' 0 + #[macro_export] + macro_rules! hidden_cfg_macro { + () => {}; + } +} + +#[cfg(feature = "routing")] +#[doc(auto_cfg(hide(feature = "routing")))] +pub mod auto_cfg_shown { + #[doc(auto_cfg(show(feature = "routing")))] + pub mod inner { + //@ has 'foo/macro.shown_cfg_macro.html' '//*[@class="stab portability"]' 'Available on crate feature routing only.' + #[macro_export] + macro_rules! shown_cfg_macro { + () => {}; + } + } +}