mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-26 13:01:27 +03:00
rustdoc: Inherit inline attributes for declarative macros
When explicitly re-exporting a declarative macro by name, rustdoc previously bypassed intermediate re-exports and dropped `#[doc(inline)]` attributes, causing the macro to be incorrectly stripped if the original definition was `#[doc(hidden)]`. This updates `generate_item_with_correct_attrs` to walk the `reexport_chain` specifically for declarative macros, allowing them to inherit inline attributes exactly as glob imports do, while preserving strict visibility rules for standard items.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -478,11 +478,21 @@ fn visit_item_inner(
|
||||
// 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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user