From 800f435810499d8652eda3e5a13f441c23e4fafa Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 22 Oct 2025 15:03:35 -0700 Subject: [PATCH] rustdoc: add support for macro_rules macros of multiple kinds This adds support for advanced RFC 3697 and 3698 macros, that look like this: ``` /// Doc Comment. macro_rules! NAME { attr(key = $value:literal) ($attached:item) => { ... }; derive() ($attached:item) => { ... }; ($bang:tt) => { ... }; } ``` And can be used like this: ``` /*attr*/ #[NAME] /*derive*/ #[derive(NAME)] /*bang*/ NAME!{} ``` The basic problem is that there are three different ways to use this macro, and, historically, these three ways were implemented by fully-separated proc macro items with separate doc comments. We want these new declarative macros to work the same way, so they appear in the same section of the module table of contents and can be filtered for in the search engine. This commit makes the feature work by generating separate pages for each macro kind. These separate pages appear in separate sections of the module table of contents, produce separate search results, and have duplicate copies of the doc comment. The following features would also be helpful, but are left for future commits to implement: - To improve the produced documentation, we should probably add support for adding `///` attributes to macro rules arms, so that the different syntaxes can actually be documented separately. - This commit doesn't test intra-doc links. Parts of https://github.com/rust-lang/rust/pull/145458 are directly used in this code. Co-Authored-By: Guillaume Gomez --- src/librustdoc/clean/inline.rs | 96 +++++++++++++---------- src/librustdoc/clean/mod.rs | 40 +++++++--- src/librustdoc/clean/types.rs | 1 + src/librustdoc/clean/utils.rs | 9 ++- src/librustdoc/formats/item_type.rs | 88 ++++++++++++--------- src/librustdoc/html/format.rs | 4 +- src/librustdoc/html/render/mod.rs | 10 +-- src/librustdoc/html/render/print_item.rs | 6 +- src/librustdoc/html/static/js/main.js | 2 +- src/librustdoc/json/conversions.rs | 4 +- src/rustdoc-json-types/lib.rs | 4 +- tests/rustdoc-gui/attr-macros.goml | 89 +++++++++++++++++++++ tests/rustdoc-gui/module-items-font.goml | 10 +++ tests/rustdoc-gui/src/test_docs/lib.rs | 2 + tests/rustdoc-gui/src/test_docs/macros.rs | 20 +++++ tests/rustdoc-js/macro-kinds.js | 33 ++++++++ tests/rustdoc-js/macro-kinds.rs | 34 ++++++++ 17 files changed, 344 insertions(+), 108 deletions(-) create mode 100644 tests/rustdoc-gui/attr-macros.goml create mode 100644 tests/rustdoc-js/macro-kinds.js create mode 100644 tests/rustdoc-js/macro-kinds.rs diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index b470af50f68fe..d8e56868d6cf5 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -46,6 +46,27 @@ pub(crate) fn try_inline( attrs: Option<(&[hir::Attribute], Option)>, visited: &mut DefIdSet, ) -> Option> { + fn try_inline_inner( + cx: &mut DocContext<'_>, + kind: clean::ItemKind, + did: DefId, + name: Symbol, + import_def_id: Option, + ) -> clean::Item { + cx.inlined.insert(did.into()); + let mut item = crate::clean::generate_item_with_correct_attrs( + cx, + kind, + did, + name, + import_def_id.as_slice(), + None, + ); + // The visibility needs to reflect the one from the reexport and not from the "source" DefId. + item.inner.inline_stmt_id = import_def_id; + item + } + let did = res.opt_def_id()?; if did.is_local() { return None; @@ -138,34 +159,37 @@ pub(crate) fn try_inline( }) } Res::Def(DefKind::Macro(kinds), did) => { - let mac = build_macro(cx, did, name, kinds); - - // FIXME: handle attributes and derives that aren't proc macros, and macros with - // multiple kinds - let type_kind = match kinds { - MacroKinds::BANG => ItemType::Macro, - MacroKinds::ATTR => ItemType::ProcAttribute, - MacroKinds::DERIVE => ItemType::ProcDerive, - _ => todo!("Handle macros with multiple kinds"), - }; - record_extern_fqn(cx, did, type_kind); - mac + for mac_kind in kinds { + let kind = build_macro( + cx, + did, + name, + match mac_kind { + MacroKinds::BANG => MacroKind::Bang, + MacroKinds::ATTR => MacroKind::Attr, + MacroKinds::DERIVE => MacroKind::Derive, + _ => unreachable!( + "Iterating over macro kinds always yields one kind at a time" + ), + }, + ); + let type_kind = match mac_kind { + MacroKinds::BANG => ItemType::Macro, + MacroKinds::ATTR => ItemType::MacroAttribute, + MacroKinds::DERIVE => ItemType::MacroDerive, + _ => { + unreachable!("Iterating over macro kinds always yields one kind at a time") + } + }; + record_extern_fqn(cx, did, type_kind); + ret.push(try_inline_inner(cx, kind, did, name, import_def_id)); + } + return Some(ret); } _ => return None, }; - cx.inlined.insert(did.into()); - let mut item = crate::clean::generate_item_with_correct_attrs( - cx, - kind, - did, - name, - import_def_id.as_slice(), - None, - ); - // The visibility needs to reflect the one from the reexport and not from the "source" DefId. - item.inner.inline_stmt_id = import_def_id; - ret.push(item); + ret.push(try_inline_inner(cx, kind, did, name, import_def_id)); Some(ret) } @@ -761,26 +785,14 @@ fn build_macro( cx: &mut DocContext<'_>, def_id: DefId, name: Symbol, - macro_kinds: MacroKinds, + kind: MacroKind, ) -> clean::ItemKind { match CStore::from_tcx(cx.tcx).load_macro_untracked(def_id, cx.tcx) { - // FIXME: handle attributes and derives that aren't proc macros, and macros with multiple - // kinds - LoadedMacro::MacroDef { def, .. } => match macro_kinds { - MacroKinds::BANG => clean::MacroItem(clean::Macro { - source: utils::display_macro_source(cx, name, &def), - macro_rules: def.macro_rules, - }), - MacroKinds::DERIVE => clean::ProcMacroItem(clean::ProcMacro { - kind: MacroKind::Derive, - helpers: Vec::new(), - }), - MacroKinds::ATTR => clean::ProcMacroItem(clean::ProcMacro { - kind: MacroKind::Attr, - helpers: Vec::new(), - }), - _ => todo!("Handle macros with multiple kinds"), - }, + LoadedMacro::MacroDef { def, .. } => clean::MacroItem(clean::Macro { + source: utils::display_macro_source(cx, name, &def), + macro_rules: def.macro_rules, + kind, + }), LoadedMacro::ProcMacro(ext) => { // Proc macros can only have a single kind let kind = match ext.macro_kinds() { diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 4a95f21a3a5bd..5ea8d064d456e 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -249,7 +249,7 @@ pub(crate) fn clean_trait_ref_with_constraints<'tcx>( trait_ref: ty::PolyTraitRef<'tcx>, constraints: ThinVec, ) -> Path { - let kind = cx.tcx.def_kind(trait_ref.def_id()).into(); + let kind = ItemType::from_def_kind(cx.tcx.def_kind(trait_ref.def_id()), None)[0]; if !matches!(kind, ItemType::Trait | ItemType::TraitAlias) { span_bug!(cx.tcx.def_span(trait_ref.def_id()), "`TraitRef` had unexpected kind {kind:?}"); } @@ -2841,19 +2841,33 @@ fn clean_maybe_renamed_item<'tcx>( generics: clean_generics(generics, cx), fields: variant_data.fields().iter().map(|x| clean_field(x, cx)).collect(), }), - // FIXME: handle attributes and derives that aren't proc macros, and macros with - // multiple kinds - ItemKind::Macro(_, macro_def, MacroKinds::BANG) => MacroItem(Macro { - source: display_macro_source(cx, name, macro_def), - macro_rules: macro_def.macro_rules, - }), - ItemKind::Macro(_, _, MacroKinds::ATTR) => { - clean_proc_macro(item, &mut name, MacroKind::Attr, cx) - } - ItemKind::Macro(_, _, MacroKinds::DERIVE) => { - clean_proc_macro(item, &mut name, MacroKind::Derive, cx) + ItemKind::Macro(_, macro_def, macro_kinds) => { + return macro_kinds + .into_iter() + .map(|macro_kind| { + let kind = MacroItem(Macro { + source: display_macro_source(cx, name, macro_def), + macro_rules: macro_def.macro_rules, + kind: match macro_kind { + MacroKinds::BANG => MacroKind::Bang, + MacroKinds::ATTR => MacroKind::Attr, + MacroKinds::DERIVE => MacroKind::Derive, + _ => unreachable!( + "Iterating over macro kinds always yields one kind at a time" + ), + }, + }); + generate_item_with_correct_attrs( + cx, + kind, + item.owner_id.def_id.to_def_id(), + name, + import_ids, + renamed, + ) + }) + .collect(); } - ItemKind::Macro(_, _, _) => todo!("Handle macros with multiple kinds"), // proc macros can have a name set by attributes ItemKind::Fn { ref sig, generics, body: body_id, .. } => { clean_fn_or_proc_macro(item, sig, generics, body_id, &mut name, cx) diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 73d0f40275404..ed92614eba07f 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -2367,6 +2367,7 @@ pub(crate) struct Macro { pub(crate) source: String, /// Whether the macro was defined via `macro_rules!` as opposed to `macro`. pub(crate) macro_rules: bool, + pub(crate) kind: MacroKind, } #[derive(Clone, Debug)] diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index bfb7962213972..cd96477c4b0d8 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -26,6 +26,7 @@ use crate::clean::{ }; use crate::core::DocContext; use crate::display::Joined as _; +use crate::formats::item_type::ItemType; #[cfg(test)] mod tests; @@ -495,7 +496,7 @@ pub(crate) fn register_res(cx: &mut DocContext<'_>, res: Res) -> DefId { use DefKind::*; debug!("register_res({res:?})"); - let (kind, did) = match res { + let (kinds, did) = match res { Res::Def( kind @ (AssocTy | AssocFn @@ -514,14 +515,16 @@ pub(crate) fn register_res(cx: &mut DocContext<'_>, res: Res) -> DefId { | Macro(..) | TraitAlias), did, - ) => (kind.into(), did), + ) => (ItemType::from_def_kind(kind, None), did), _ => panic!("register_res: unexpected {res:?}"), }; if did.is_local() { return did; } - inline::record_extern_fqn(cx, did, kind); + for kind in kinds.iter().copied() { + inline::record_extern_fqn(cx, did, kind); + } did } diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index ab40c01cb369d..c8d6f56a06a9c 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -93,8 +93,8 @@ item_type! { Union = 20, ForeignType = 21, // OpaqueTy used to be here, but it was removed in #127276 - ProcAttribute = 23, - ProcDerive = 24, + MacroAttribute = 23, + MacroDerive = 24, TraitAlias = 25, // This number is reserved for use in JavaScript // Generic = 26, @@ -127,7 +127,11 @@ impl<'a> From<&'a clean::Item> for ItemType { clean::VariantItem(..) => ItemType::Variant, clean::ForeignFunctionItem(..) => ItemType::Function, // no ForeignFunction clean::ForeignStaticItem(..) => ItemType::Static, // no ForeignStatic - clean::MacroItem(..) => ItemType::Macro, + clean::MacroItem(mac) => match mac.kind { + MacroKind::Bang => ItemType::Macro, + MacroKind::Attr => ItemType::MacroAttribute, + MacroKind::Derive => ItemType::MacroDerive, + }, clean::PrimitiveItem(..) => ItemType::Primitive, clean::RequiredAssocConstItem(..) | clean::ProvidedAssocConstItem(..) @@ -139,48 +143,56 @@ impl<'a> From<&'a clean::Item> for ItemType { clean::TraitAliasItem(..) => ItemType::TraitAlias, clean::ProcMacroItem(mac) => match mac.kind { MacroKind::Bang => ItemType::Macro, - MacroKind::Attr => ItemType::ProcAttribute, - MacroKind::Derive => ItemType::ProcDerive, + MacroKind::Attr => ItemType::MacroAttribute, + MacroKind::Derive => ItemType::MacroDerive, }, clean::StrippedItem(..) => unreachable!(), } } } -impl From for ItemType { - fn from(other: DefKind) -> Self { - Self::from_def_kind(other, None) - } -} - impl ItemType { /// Depending on the parent kind, some variants have a different translation (like a `Method` /// becoming a `TyMethod`). - pub(crate) fn from_def_kind(kind: DefKind, parent_kind: Option) -> Self { + pub(crate) fn from_def_kind(kind: DefKind, parent_kind: Option) -> &'static [Self] { match kind { - DefKind::Enum => Self::Enum, - DefKind::Fn => Self::Function, - DefKind::Mod => Self::Module, - DefKind::Const => Self::Constant, - DefKind::Static { .. } => Self::Static, - DefKind::Struct => Self::Struct, - DefKind::Union => Self::Union, - DefKind::Trait => Self::Trait, - DefKind::TyAlias => Self::TypeAlias, - DefKind::TraitAlias => Self::TraitAlias, - DefKind::Macro(MacroKinds::BANG) => ItemType::Macro, - DefKind::Macro(MacroKinds::ATTR) => ItemType::ProcAttribute, - DefKind::Macro(MacroKinds::DERIVE) => ItemType::ProcDerive, - DefKind::Macro(_) => todo!("Handle macros with multiple kinds"), - DefKind::ForeignTy => Self::ForeignType, - DefKind::Variant => Self::Variant, - DefKind::Field => Self::StructField, - DefKind::AssocTy => Self::AssocType, - DefKind::AssocFn if let Some(DefKind::Trait) = parent_kind => Self::TyMethod, - DefKind::AssocFn => Self::Method, - DefKind::Ctor(CtorOf::Struct, _) => Self::Struct, - DefKind::Ctor(CtorOf::Variant, _) => Self::Variant, - DefKind::AssocConst => Self::AssocConst, + DefKind::Enum => &[Self::Enum], + DefKind::Fn => &[Self::Function], + DefKind::Mod => &[Self::Module], + DefKind::Const => &[Self::Constant], + DefKind::Static { .. } => &[Self::Static], + DefKind::Struct => &[Self::Struct], + DefKind::Union => &[Self::Union], + DefKind::Trait => &[Self::Trait], + DefKind::TyAlias => &[Self::TypeAlias], + DefKind::TraitAlias => &[Self::TraitAlias], + DefKind::Macro(MacroKinds::ATTR) => &[ItemType::MacroAttribute], + DefKind::Macro(MacroKinds::DERIVE) => &[ItemType::MacroDerive], + DefKind::Macro(MacroKinds::BANG) => &[ItemType::Macro], + DefKind::Macro(kinds) if kinds == MacroKinds::ATTR | MacroKinds::DERIVE => { + &[ItemType::MacroAttribute, ItemType::MacroDerive] + } + DefKind::Macro(kinds) if kinds == MacroKinds::ATTR | MacroKinds::BANG => { + &[ItemType::Macro, ItemType::MacroAttribute] + } + DefKind::Macro(kinds) if kinds == MacroKinds::DERIVE | MacroKinds::BANG => { + &[ItemType::Macro, ItemType::MacroDerive] + } + DefKind::Macro(kinds) + if kinds == MacroKinds::BANG | MacroKinds::ATTR | MacroKinds::DERIVE => + { + &[ItemType::Macro, ItemType::MacroAttribute, ItemType::MacroDerive] + } + DefKind::Macro(kinds) => unimplemented!("unsupported macro kinds {kinds:?}"), + DefKind::ForeignTy => &[Self::ForeignType], + DefKind::Variant => &[Self::Variant], + DefKind::Field => &[Self::StructField], + DefKind::AssocTy => &[Self::AssocType], + DefKind::AssocFn if let Some(DefKind::Trait) = parent_kind => &[Self::TyMethod], + DefKind::AssocFn => &[Self::Method], + DefKind::Ctor(CtorOf::Struct, _) => &[Self::Struct], + DefKind::Ctor(CtorOf::Variant, _) => &[Self::Variant], + DefKind::AssocConst => &[Self::AssocConst], DefKind::TyParam | DefKind::ConstParam | DefKind::ExternCrate @@ -193,7 +205,7 @@ impl ItemType { | DefKind::GlobalAsm | DefKind::Impl { .. } | DefKind::Closure - | DefKind::SyntheticCoroutineBody => Self::ForeignType, + | DefKind::SyntheticCoroutineBody => &[Self::ForeignType], } } @@ -221,8 +233,8 @@ impl ItemType { ItemType::AssocConst => "associatedconstant", ItemType::ForeignType => "foreigntype", ItemType::Keyword => "keyword", - ItemType::ProcAttribute => "attr", - ItemType::ProcDerive => "derive", + ItemType::MacroAttribute => "attr", + ItemType::MacroDerive => "derive", ItemType::TraitAlias => "traitalias", ItemType::Attribute => "attribute", } diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 856e637a4587b..9633adfea7687 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -443,14 +443,14 @@ fn generate_item_def_id_path( let fqp: Vec = once(crate_name).chain(relative).collect(); let def_kind = tcx.def_kind(def_id); - let shortty = def_kind.into(); + let shortty = ItemType::from_def_kind(def_kind, None)[0]; let module_fqp = to_module_fqp(shortty, &fqp); let mut is_remote = false; let url_parts = url_parts(cx.cache(), def_id, module_fqp, &cx.current, &mut is_remote)?; let mut url_parts = make_href(root_path, shortty, url_parts, &fqp, is_remote); if def_id != original_def_id { - let kind = ItemType::from_def_kind(original_def_kind, Some(def_kind)); + let kind = ItemType::from_def_kind(original_def_kind, Some(def_kind))[0]; url_parts = format!("{url_parts}#{kind}.{}", tcx.item_name(original_def_id)) }; Ok((url_parts, shortty, fqp)) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 13178ee4e9934..792e12d257965 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -522,10 +522,10 @@ impl AllTypes { ItemType::TypeAlias => self.type_aliases.insert(ItemEntry::new(new_url, name)), ItemType::Static => self.statics.insert(ItemEntry::new(new_url, name)), ItemType::Constant => self.constants.insert(ItemEntry::new(new_url, name)), - ItemType::ProcAttribute => { + ItemType::MacroAttribute => { self.attribute_macros.insert(ItemEntry::new(new_url, name)) } - ItemType::ProcDerive => self.derive_macros.insert(ItemEntry::new(new_url, name)), + ItemType::MacroDerive => self.derive_macros.insert(ItemEntry::new(new_url, name)), ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(new_url, name)), _ => true, }; @@ -2548,7 +2548,7 @@ impl ItemSection { Self::ForeignTypes => "foreign-types", Self::Keywords => "keywords", Self::Attributes => "attributes", - Self::AttributeMacros => "attributes", + Self::AttributeMacros => "attribute-macros", Self::DeriveMacros => "derives", Self::TraitAliases => "trait-aliases", } @@ -2609,8 +2609,8 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection { ItemType::ForeignType => ItemSection::ForeignTypes, ItemType::Keyword => ItemSection::Keywords, ItemType::Attribute => ItemSection::Attributes, - ItemType::ProcAttribute => ItemSection::AttributeMacros, - ItemType::ProcDerive => ItemSection::DeriveMacros, + ItemType::MacroAttribute => ItemSection::AttributeMacros, + ItemType::MacroDerive => ItemSection::DeriveMacros, ItemType::TraitAlias => ItemSection::TraitAliases, } } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index adfc7481c73ae..5df1a33a8e88a 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -162,7 +162,11 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp clean::UnionItem(..) => "Union ", clean::EnumItem(..) => "Enum ", clean::TypeAliasItem(..) => "Type Alias ", - clean::MacroItem(..) => "Macro ", + clean::MacroItem(ref mac) => match mac.kind { + MacroKind::Bang => "Macro ", + MacroKind::Attr => "Attribute Macro ", + MacroKind::Derive => "Derive Macro ", + }, clean::ProcMacroItem(ref mac) => match mac.kind { MacroKind::Bang => "Macro ", MacroKind::Attr => "Attribute Macro ", diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 3ea9de381eca6..93ecd600198fa 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -791,7 +791,7 @@ function preLoadCss(cssUrl) { block("foreigntype", "foreign-types", "Foreign Types"); block("keyword", "keywords", "Keywords"); block("attribute", "attributes", "Attributes"); - block("attr", "attributes", "Attribute Macros"); + block("attr", "attribute-macros", "Attribute Macros"); block("derive", "derives", "Derive Macros"); block("traitalias", "trait-aliases", "Trait Aliases"); } diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 909262d563e9f..b57f27f529738 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -892,8 +892,8 @@ impl FromClean for ItemKind { Keyword => ItemKind::Keyword, Attribute => ItemKind::Attribute, TraitAlias => ItemKind::TraitAlias, - ProcAttribute => ItemKind::ProcAttribute, - ProcDerive => ItemKind::ProcDerive, + MacroAttribute => ItemKind::ProcAttribute, + MacroDerive => ItemKind::ProcDerive, } } } diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index 658d3791d2578..ebc28754cf8bb 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -531,13 +531,15 @@ pub enum ItemKind { /// Corresponds to either `ItemEnum::Macro(_)` /// or `ItemEnum::ProcMacro(ProcMacro { kind: MacroKind::Bang })` Macro, - /// A procedural macro attribute. + /// A macro attribute. /// /// Corresponds to `ItemEnum::ProcMacro(ProcMacro { kind: MacroKind::Attr })` + /// and `ItemEnum::Macro(_)`; this should probably be renamed. ProcAttribute, /// A procedural macro usable in the `#[derive()]` attribute. /// /// Corresponds to `ItemEnum::ProcMacro(ProcMacro { kind: MacroKind::Derive })` + /// and `ItemEnum::Macro(_)`; this should probably be renamed. ProcDerive, /// An associated constant of a trait or a type. AssocConst, diff --git a/tests/rustdoc-gui/attr-macros.goml b/tests/rustdoc-gui/attr-macros.goml new file mode 100644 index 0000000000000..6223a1d101598 --- /dev/null +++ b/tests/rustdoc-gui/attr-macros.goml @@ -0,0 +1,89 @@ +// This test ensures that a bang macro which is also an attribute macro is listed correctly in +// the sidebar and in the module. + +go-to: "file://" + |DOC_PATH| + "/test_docs/macro.b.html" +// We check that the current item in the sidebar is the correct one. +assert-text: ("#rustdoc-modnav .block.macro .current", "b") + +define-function: ( + "check_macro", + [name, kind], + block { + // It should be present twice in the sidebar. + assert-count: ("#rustdoc-modnav a[href='macro." + |name| + ".html']", 1) + assert-count: ("#rustdoc-modnav a[href='" + |kind| + "." + |name| + ".html']", 1) + assert-count: ("//*[@id='rustdoc-modnav']//a[text()='" + |name| + "']", 2) + + // We now go to the macro page. + click: "#rustdoc-modnav a[href='macro." + |name| + ".html']" + // It should be present twice in the sidebar. + assert-count: ("#rustdoc-modnav a[href='macro." + |name| + ".html']", 1) + assert-count: ("#rustdoc-modnav a[href='" + |kind| + "." + |name| + ".html']", 1) + assert-count: ("//*[@id='rustdoc-modnav']//a[text()='" + |name| + "']", 2) + // We check that the current item is the macro. + assert-text: ("#rustdoc-modnav .block.macro .current", |name|) + // This is still only one page, and should only activate one of them. + assert-count: ("#rustdoc-modnav .current", 1) + + // We now go to the |kind| page. + click: "#rustdoc-modnav a[href='" + |kind| + "." + |name| + ".html']" + // It should be present twice in the sidebar. + assert-count: ("#rustdoc-modnav a[href='macro." + |name| + ".html']", 1) + assert-count: ("#rustdoc-modnav a[href='" + |kind| + "." + |name| + ".html']", 1) + assert-count: ("//*[@id='rustdoc-modnav']//a[text()='" + |name| + "']", 2) + // We check that the current item is the macro. + assert-text: ("#rustdoc-modnav .block." + |kind| + " .current", |name|) + // This is still only one page, and should only activate one of them. + assert-count: ("#rustdoc-modnav .current", 1) + } +) + +call-function: ("check_macro", {"name": "attr_macro", "kind": "attr"}) +call-function: ("check_macro", {"name": "derive_macro", "kind": "derive"}) + +define-function: ( + "crate_page", + [name, kind, section_id], + block { + // It should be only present twice. + assert-count: ("#main-content a[href='macro." + |name| + ".html']", 1) + assert-count: ("#main-content a[href='" + |kind| + "." + |name| + ".html']", 1) + // First in the "Macros" section. + assert-text: ("#macros + .item-table a[href='macro." + |name| + ".html']", |name|) + // Then in the other macro section. + assert-text: ( + "#" + |section_id| + " + .item-table a[href='" + |kind| + "." + |name| + ".html']", + |name|, + ) + } +) + +// Now we check it's correctly listed in the crate page. +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" +call-function: ("crate_page", {"name": "attr_macro", "kind": "attr", "section_id": "attribute-macros"}) +call-function: ("crate_page", {"name": "derive_macro", "kind": "derive", "section_id": "derives"}) +// We also check we don't have duplicated sections. +assert-count: ("//*[@id='main-content']/h2[text()='Attribute Macros']", 1) +assert-count: ("//*[@id='main-content']/h2[text()='Derive Macros']", 1) + +define-function: ( + "all_items_page", + [name, kind, section_id], + block { + // It should be only present twice. + assert-count: ("#main-content a[href='macro." + |name| + ".html']", 1) + assert-count: ("#main-content a[href='" + |kind| + "." + |name| + ".html']", 1) + // First in the "Macros" section. + assert-text: ("#macros + .all-items a[href='macro." + |name| + ".html']", |name|) + // Then in the "Attribute Macros" section. + assert-text: ( + "#" + |section_id| + " + .all-items a[href='" + |kind| + "." + |name| + ".html']", + |name|, + ) + } +) + +// And finally we check it's correctly listed in the "all items" page. +go-to: "file://" + |DOC_PATH| + "/test_docs/all.html" +call-function: ("all_items_page", {"name": "attr_macro", "kind": "attr", "section_id": "attribute-macros"}) +call-function: ("all_items_page", {"name": "derive_macro", "kind": "derive", "section_id": "derives"}) diff --git a/tests/rustdoc-gui/module-items-font.goml b/tests/rustdoc-gui/module-items-font.goml index bed95b378c6ab..7547ddc6e666e 100644 --- a/tests/rustdoc-gui/module-items-font.goml +++ b/tests/rustdoc-gui/module-items-font.goml @@ -74,3 +74,13 @@ assert-css: ( "#attributes + .item-table dd", {"font-family": '"Source Serif 4", NanumBarunGothic, serif'}, ) + +// attribute macros +assert-css: ( + "#attribute-macros + .item-table dt a", + {"font-family": '"Fira Sans", Arial, NanumBarunGothic, sans-serif'}, +) +assert-css: ( + "#attribute-macros + .item-table dd", + {"font-family": '"Source Serif 4", NanumBarunGothic, serif'}, +) diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs index c0771583ab658..7f1d495d27993 100644 --- a/tests/rustdoc-gui/src/test_docs/lib.rs +++ b/tests/rustdoc-gui/src/test_docs/lib.rs @@ -8,6 +8,8 @@ #![feature(rustdoc_internals)] #![feature(doc_cfg)] #![feature(associated_type_defaults)] +#![feature(macro_attr)] +#![feature(macro_derive)] /*! Enable the feature some-feature to enjoy diff --git a/tests/rustdoc-gui/src/test_docs/macros.rs b/tests/rustdoc-gui/src/test_docs/macros.rs index 07b2b97926d43..56e8e67c58b5a 100644 --- a/tests/rustdoc-gui/src/test_docs/macros.rs +++ b/tests/rustdoc-gui/src/test_docs/macros.rs @@ -2,3 +2,23 @@ macro_rules! a{ () => {}} #[macro_export] macro_rules! b{ () => {}} +/// An attribute bang macro. +#[macro_export] +macro_rules! attr_macro { + attr() () => {}; + () => {}; +} + +/// An attribute bang macro. +#[macro_export] +macro_rules! derive_macro { + derive() () => {}; + () => {}; +} + +#[macro_export] +macro_rules! one_for_all_macro { + attr() () => {}; + derive() () => {}; + () => {}; +} diff --git a/tests/rustdoc-js/macro-kinds.js b/tests/rustdoc-js/macro-kinds.js new file mode 100644 index 0000000000000..352b80ee42a6d --- /dev/null +++ b/tests/rustdoc-js/macro-kinds.js @@ -0,0 +1,33 @@ +// exact-check + +const EXPECTED = [ + { + 'query': 'macro:macro', + 'others': [ + { 'path': 'macro_kinds', 'name': 'macro1', 'href': '../macro_kinds/macro.macro1.html' }, + { 'path': 'macro_kinds', 'name': 'macro3', 'href': '../macro_kinds/macro.macro3.html' }, + ], + }, + { + 'query': 'attr:macro', + 'others': [ + { 'path': 'macro_kinds', 'name': 'macro1', 'href': '../macro_kinds/attr.macro1.html' }, + { 'path': 'macro_kinds', 'name': 'macro2', 'href': '../macro_kinds/attr.macro2.html' }, + { 'path': 'macro_kinds', 'name': 'macro5', 'href': '../macro_kinds/attr.macro5.html' }, + ], + }, + { + 'query': 'derive:macro', + 'others': [ + { + 'path': 'macro_kinds', 'name': 'macro1', 'href': '../macro_kinds/derive.macro1.html' + }, + { + 'path': 'macro_kinds', 'name': 'macro5', 'href': '../macro_kinds/derive.macro5.html' + }, + { + 'path': 'macro_kinds', 'name': 'macro4', 'href': '../macro_kinds/derive.macro4.html' + }, + ], + }, +]; diff --git a/tests/rustdoc-js/macro-kinds.rs b/tests/rustdoc-js/macro-kinds.rs new file mode 100644 index 0000000000000..dd0fc1c110a06 --- /dev/null +++ b/tests/rustdoc-js/macro-kinds.rs @@ -0,0 +1,34 @@ +// Test which ensures that attribute macros are correctly handled by the search. +// For example: `macro1` should appear in both `attr` and `macro` filters whereas +// `macro2` and `macro3` should only appear in `attr` or `macro` filters respectively. + +#![feature(macro_attr)] +#![feature(macro_derive)] + +#[macro_export] +macro_rules! macro1 { + attr() () => {}; + derive() () => {}; + () => {}; +} + +#[macro_export] +macro_rules! macro2 { + attr() () => {}; +} + +#[macro_export] +macro_rules! macro3 { + () => {}; +} + +#[macro_export] +macro_rules! macro4 { + derive() () => {}; +} + +#[macro_export] +macro_rules! macro5 { + attr() () => {}; + derive() () => {}; +}