Skip to content
Merged
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
10 changes: 6 additions & 4 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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()
Expand Down
18 changes: 18 additions & 0 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<LocalDefId> {
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.
Expand Down
42 changes: 41 additions & 1 deletion src/librustdoc/passes/propagate_doc_cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,31 @@ 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.
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!
Expand All @@ -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,
Expand Down
31 changes: 20 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,13 @@ 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>,
}

impl Module<'_> {
pub(crate) fn new(
name: Symbol,
Expand Down Expand Up @@ -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() },
);
}
}

Expand Down Expand Up @@ -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() });
}
}
}
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