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
32 changes: 31 additions & 1 deletion src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
}
Expand Down
20 changes: 15 additions & 5 deletions src/librustdoc/visit_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
129 changes: 129 additions & 0 deletions tests/rustdoc-html/macro-reexport-inline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Regression test for <https://github.com/rust-lang/rust/issues/154694>.
// 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;
}
Loading