diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index d6dae29c932e0..cda2ea532c377 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -176,6 +176,35 @@ fn is_glob_import(tcx: TyCtxt<'_>, import_id: LocalDefId) -> bool { } } +/// Returns true if `def_id` is a macro and should be inlined. +pub(crate) fn macro_reexport_is_inline( + tcx: TyCtxt<'_>, + import_id: LocalDefId, + def_id: DefId, +) -> bool { + if !matches!(tcx.def_kind(def_id), DefKind::Macro(MacroKinds::BANG)) { + return false; + } + + for reexport_def_id in reexport_chain(tcx, import_id, def_id).iter().flat_map(|r| r.id()) { + let is_hidden = tcx.is_doc_hidden(reexport_def_id); + let is_inline = find_attr!( + inline::load_attrs(tcx, reexport_def_id), + Doc(d) + if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline) + ); + + // hidden takes absolute priority over inline on the same node + if is_hidden { + return false; + } + if is_inline { + return true; + } + } + false +} + fn generate_item_with_correct_attrs( cx: &mut DocContext<'_>, kind: ItemKind, @@ -201,7 +230,8 @@ fn generate_item_with_correct_attrs( Doc(d) if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline) ) || (is_glob_import(tcx, import_id) - && (cx.document_hidden() || !tcx.is_doc_hidden(def_id))); + && (cx.document_hidden() || !tcx.is_doc_hidden(def_id))) + || macro_reexport_is_inline(tcx, import_id, def_id); attrs.extend(get_all_import_attributes(cx, import_id, def_id, is_inline)); is_inline = is_inline || import_is_inline; } diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 906289ba755e3..fda03563c79f2 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -478,11 +478,21 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // If there was a private module in the current path then don't bother inlining // anything as it will probably be stripped anyway. if is_pub && self.inside_public_path { - let please_inline = find_attr!( - attrs, - Doc(d) - if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline) - ); + let please_inline = if let Some(res_did) = res.opt_def_id() + && matches!(tcx.def_kind(res_did), DefKind::Macro(MacroKinds::BANG)) + { + crate::clean::macro_reexport_is_inline( + tcx, + item.owner_id.def_id, + res_did, + ) + } else { + find_attr!( + attrs, + Doc(d) + if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline) + ) + }; let ident = match kind { hir::UseKind::Single(ident) => Some(ident.name), hir::UseKind::Glob => None, diff --git a/tests/rustdoc-html/macro-reexport-inline.rs b/tests/rustdoc-html/macro-reexport-inline.rs new file mode 100644 index 0000000000000..4707d958f8c97 --- /dev/null +++ b/tests/rustdoc-html/macro-reexport-inline.rs @@ -0,0 +1,129 @@ +// Regression test for . +// The goal is to ensure that declarative macros re-exported by name +// inherit the `#[doc(inline)]` attribute from intermediate re-exports, +// matching the behavior of glob re-exports. + +#![crate_name = "foo"] + +#[macro_use] +mod macros { + #[macro_export] + #[doc(hidden)] + macro_rules! explicit_macro { + () => {}; + } + + #[macro_export] + #[doc(hidden)] + macro_rules! wild_macro { + () => {}; + } + + #[macro_export] + #[doc(hidden)] + macro_rules! actually_hidden_macro { + () => {}; + } + + #[macro_export] + #[doc(hidden)] + macro_rules! actually_hidden_wild_macro { + () => {}; + } + + #[macro_export] + #[doc(hidden)] + macro_rules! actually_hidden_indirect_macro { + () => {}; + } +} + +// Standard items (like structs) are provided as control cases to ensure +// macro inlining behavior maintains parity. +#[doc(hidden)] +pub struct HiddenStruct; + +#[doc(hidden)] +pub struct IndirectlyHiddenStruct; + +pub mod bar { + mod hidden_explicit { + #[doc(inline)] + pub use crate::explicit_macro; + } + + mod hidden_wild { + #[doc(inline)] + pub use crate::wild_macro; + } + + mod actually_hidden { + // BUG: as demonstrated by the `actually_hidden_struct` module, when both + // `doc(hidden)` and `doc(inline)` are specified, `doc(hidden)` + // should take priority. + #[doc(hidden)] + #[doc(inline)] + pub use crate::actually_hidden_macro; + } + + mod actually_hidden_indirect_inner { + #[doc(inline)] + pub use crate::actually_hidden_indirect_macro; + } + + mod actually_hidden_indirect { + // BUG: when there is a chain of imports, we should stop looking as soon as soon as we hit + // something with `doc(hidden)`. + #[doc(hidden)] + pub use super::actually_hidden_indirect_inner::actually_hidden_indirect_macro; + } + + mod actually_hidden_indirect_struct_inner { + #[doc(inline)] + pub use crate::IndirectlyHiddenStruct; + } + + mod actually_hidden_indirect_struct { + #[doc(hidden)] + pub use super::actually_hidden_indirect_struct_inner::IndirectlyHiddenStruct; + } + + mod actually_hidden_wild { + #[doc(hidden)] + #[doc(inline)] + pub use crate::actually_hidden_wild_macro; + } + + mod actually_hidden_struct { + #[doc(inline)] + #[doc(hidden)] + pub use crate::HiddenStruct; + } + + // First, we check that the explicitly named macro inherits the inline attribute + // from `hidden_explicit` and is successfully rendered. + //@ has 'foo/bar/macro.explicit_macro.html' + //@ has 'foo/bar/index.html' '//a[@href="macro.explicit_macro.html"]' 'explicit_macro' + pub use self::hidden_explicit::explicit_macro; + + // Next, we ensure that the glob-imported macro continues to render correctly + // as a control case. + //@ has 'foo/bar/macro.wild_macro.html' + //@ has 'foo/bar/index.html' '//a[@href="macro.wild_macro.html"]' 'wild_macro' + pub use self::hidden_wild::*; + + //@ !has 'foo/bar/macro.actually_hidden_macro.html' + pub use self::actually_hidden::actually_hidden_macro; + + //@ !has 'foo/bar/macro.actually_hidden_wild_macro.html' + pub use self::actually_hidden_wild::*; + + //@ !has 'foo/bar/struct.HiddenStruct.html' + pub use self::actually_hidden_struct::HiddenStruct; + + //@ !has 'foo/bar/macro.actually_hidden_indirect_macro.html' + pub use self::actually_hidden_indirect::actually_hidden_indirect_macro; + + //@ !has 'foo/bar/struct.IndirectlyHiddenStruct.html' + pub use self::actually_hidden_indirect_struct::IndirectlyHiddenStruct; +}