Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/librustdoc/clean/auto_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ fn synthesize_auto_trait_impl<'tcx>(
item_id: clean::ItemId::Auto { trait_: trait_def_id, for_: item_def_id },
cfg: None,
inline_stmt_id: None,
cfg_parent_ids: Default::default(),
}),
})
}
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/clean/blanket_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ pub(crate) fn synthesize_blanket_impls(
})),
cfg: None,
inline_stmt_id: None,
cfg_parent_ids: Default::default(),
}),
});
}
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/clean/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@ fn build_module_items(
)),
cfg: None,
inline_stmt_id: None,
cfg_parent_ids: Default::default(),
}),
});
} else if let Some(i) = try_inline(cx, res, item.ident.name, attrs, visited) {
Expand Down
13 changes: 9 additions & 4 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,16 @@ 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 mut v = clean_maybe_renamed_item(cx, item, entry.renamed, &entry.import_ids);
for item in &mut v {
item.inner.cfg_parent_ids = entry.cfg_parent_ids.iter().copied().collect();
}
for item in &v {
if let Some(name) = item.name
&& (cx.document_hidden() || !item.is_doc_hidden())
Expand Down Expand Up @@ -130,10 +134,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()
Expand Down
6 changes: 5 additions & 1 deletion src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,9 @@ pub(crate) struct ItemInner {
/// The crate metadata doesn't hold this information, so the `use` statement
/// always belongs to the current crate.
pub(crate) inline_stmt_id: Option<LocalDefId>,
/// Lexical parents whose cfg/doc(cfg) information applies even though the item is documented
/// elsewhere, such as `#[macro_export] macro_rules!` items rendered at the crate root.
pub(crate) cfg_parent_ids: ThinVec<LocalDefId>,
pub(crate) cfg: Option<Arc<Cfg>>,
}

Expand Down Expand Up @@ -557,6 +560,7 @@ impl Item {
name,
cfg,
inline_stmt_id: None,
cfg_parent_ids: ThinVec::new(),
}),
}
}
Expand Down Expand Up @@ -2445,7 +2449,7 @@ mod size_asserts {
static_assert_size!(GenericParamDef, 40);
static_assert_size!(Generics, 16);
static_assert_size!(Item, 8);
static_assert_size!(ItemInner, 136);
static_assert_size!(ItemInner, 144);
static_assert_size!(ItemKind, 48);
static_assert_size!(PathSegment, 32);
static_assert_size!(Type, 32);
Expand Down
27 changes: 27 additions & 0 deletions src/librustdoc/passes/propagate_doc_cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@ fn add_only_cfg_attributes(attrs: &mut Vec<Attribute>, 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<Attribute>, new_attrs: &[Attribute]) {
Copy link
Copy Markdown
Member Author

@cijiugechu cijiugechu Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept them(this function and add_only_cfg_attributes) separate because a shared helper would make the logic harder to follow.

View changes since the review

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.
Expand All @@ -69,6 +88,14 @@ impl CfgPropagator<'_, '_> {
next_def_id = parent_def_id;
}
}
for parent_def_id in &item.cfg_parent_ids {
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,
Expand Down
52 changes: 41 additions & 11 deletions src/librustdoc/visit_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub(crate) struct Module<'hir> {
pub(crate) def_id: LocalDefId,
pub(crate) renamed: Option<Symbol>,
pub(crate) import_id: Option<LocalDefId>,
/// The key is the item `ItemId` and the value is: (item, renamed, Vec<import_id>).
/// 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
Expand All @@ -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<Symbol>),
(&'hir hir::Item<'hir>, Option<Symbol>, Vec<LocalDefId>),
>,
pub(crate) items: FxIndexMap<(LocalDefId, Option<Symbol>), ItemEntry<'hir>>,

/// (def_id, renamed) -> (res, local_import_id)
///
Expand All @@ -70,6 +67,14 @@ pub(crate) struct Module<'hir> {
pub(crate) foreigns: Vec<(&'hir hir::ForeignItem<'hir>, Option<Symbol>, Option<LocalDefId>)>,
}

#[derive(Debug)]
pub(crate) struct ItemEntry<'hir> {
pub(crate) item: &'hir hir::Item<'hir>,
pub(crate) renamed: Option<Symbol>,
pub(crate) import_ids: Vec<LocalDefId>,
pub(crate) cfg_parent_ids: Vec<LocalDefId>,
}

impl Module<'_> {
pub(crate) fn new(
name: Symbol,
Expand Down Expand Up @@ -145,6 +150,21 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
self.exact_paths.entry(did).or_insert_with(|| def_id_to_path(tcx, did));
}

fn cfg_parent_ids_for_detached_item(&self, def_id: LocalDefId) -> Vec<LocalDefId> {
let tcx = self.cx.tcx;
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
}

pub(crate) fn visit(mut self) -> Module<'tcx> {
let root_module = self.cx.tcx.hir_root_module();
self.visit_mod_contents(CRATE_DEF_ID, root_module);
Expand Down Expand Up @@ -172,9 +192,11 @@ 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()));
let cfg_parent_ids = self.cfg_parent_ids_for_detached_item(local_def_id);
top_level_module.items.insert(
(local_def_id, Some(ident.name)),
ItemEntry { item, renamed: None, import_ids: Vec::new(), cfg_parent_ids },
);
}
}

Expand Down Expand Up @@ -413,10 +435,18 @@ 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],
cfg_parent_ids: Vec::new(),
});
} 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(), cfg_parent_ids: Vec::new() },
);
}
}
}
Expand Down
68 changes: 68 additions & 0 deletions tests/rustdoc-html/doc-cfg/decl-macro.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Regression test for <https://github.com/rust-lang/rust/issues/100916>
//@ 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 {
() => {};
}
}
}
Loading