diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index b470af50f68fe..e95b9bd3c33ee 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,31 @@ pub(crate) fn try_inline( }) } Res::Def(DefKind::Macro(kinds), did) => { - let mac = build_macro(cx, did, name, kinds); + let (mac, others) = 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"), + _ if kinds.contains(MacroKinds::BANG) => ItemType::Macro, + _ => panic!("unsupported macro kind {kinds:?}"), }; record_extern_fqn(cx, did, type_kind); - mac + let first = try_inline_inner(cx, mac, did, name, import_def_id); + if let Some(others) = others { + for mac_kind in others { + let mut mac = first.clone(); + mac.inner.kind = mac_kind; + ret.push(mac); + } + } + ret.push(first); + 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) } @@ -762,24 +780,52 @@ fn build_macro( def_id: DefId, name: Symbol, macro_kinds: MacroKinds, -) -> clean::ItemKind { +) -> (clean::ItemKind, Option>) { 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"), + MacroKinds::BANG => ( + clean::MacroItem( + clean::Macro { + source: utils::display_macro_source(cx, name, &def), + macro_rules: def.macro_rules, + }, + MacroKinds::BANG, + ), + None, + ), + MacroKinds::DERIVE => ( + clean::ProcMacroItem(clean::ProcMacro { + kind: MacroKind::Derive, + helpers: Vec::new(), + }), + None, + ), + MacroKinds::ATTR => ( + clean::ProcMacroItem(clean::ProcMacro { + kind: MacroKind::Attr, + helpers: Vec::new(), + }), + None, + ), + _ if macro_kinds.contains(MacroKinds::BANG) => { + let kind = clean::MacroItem( + clean::Macro { + source: utils::display_macro_source(cx, name, &def), + macro_rules: def.macro_rules, + }, + macro_kinds, + ); + let mut ret = vec![]; + for kind in macro_kinds.iter().filter(|kind| *kind != MacroKinds::BANG) { + match kind { + MacroKinds::ATTR => ret.push(clean::AttrMacroItem), + MacroKinds::DERIVE => ret.push(clean::DeriveMacroItem), + _ => panic!("unsupported macro kind {kind:?}"), + } + } + (kind, Some(ret)) + } + _ => panic!("unsupported macro kind {macro_kinds:?}"), }, LoadedMacro::ProcMacro(ext) => { // Proc macros can only have a single kind @@ -789,7 +835,7 @@ fn build_macro( MacroKinds::DERIVE => MacroKind::Derive, _ => unreachable!(), }; - clean::ProcMacroItem(clean::ProcMacro { kind, helpers: ext.helper_attrs }) + (clean::ProcMacroItem(clean::ProcMacro { kind, helpers: ext.helper_attrs }), None) } } } diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 4a95f21a3a5bd..0591c60251ca1 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2841,19 +2841,54 @@ 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(_, _, _) => todo!("Handle macros with multiple kinds"), + ItemKind::Macro(_, macro_def, kinds) => match kinds { + MacroKinds::BANG => MacroItem( + Macro { + source: display_macro_source(cx, name, macro_def), + macro_rules: macro_def.macro_rules, + }, + MacroKinds::BANG, + ), + MacroKinds::ATTR => clean_proc_macro(item, &mut name, MacroKind::Attr, cx), + MacroKinds::DERIVE => clean_proc_macro(item, &mut name, MacroKind::Derive, cx), + _ if kinds.contains(MacroKinds::BANG) => { + let kind = MacroItem( + Macro { + source: display_macro_source(cx, name, macro_def), + macro_rules: macro_def.macro_rules, + }, + kinds, + ); + let mac = generate_item_with_correct_attrs( + cx, + kind, + item.owner_id.def_id.to_def_id(), + name, + import_ids, + renamed, + ); + + let mut ret = Vec::with_capacity(3); + for kind in kinds.iter().filter(|kind| *kind != MacroKinds::BANG) { + match kind { + MacroKinds::ATTR => { + let mut attr = mac.clone(); + attr.inner.kind = AttrMacroItem; + ret.push(attr); + } + MacroKinds::DERIVE => { + let mut derive = mac.clone(); + derive.inner.kind = DeriveMacroItem; + ret.push(derive); + } + _ => panic!("unsupported macro kind {kind:?}"), + } + } + ret.push(mac); + return ret; + } + _ => panic!("unsupported macro kind {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..08c5425fd464c 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -9,7 +9,7 @@ use rustc_abi::{ExternAbi, VariantIdx}; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::thin_vec::ThinVec; use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation}; -use rustc_hir::def::{CtorKind, DefKind, Res}; +use rustc_hir::def::{CtorKind, DefKind, MacroKinds, Res}; use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::lang_items::LangItem; use rustc_hir::{BodyId, ConstStability, Mutability, Stability, StableSince, find_attr}; @@ -669,6 +669,18 @@ impl Item { ItemType::from(self) } + /// Generates the HTML file name based on the item kind. + pub(crate) fn html_filename(&self) -> String { + let type_ = if self.is_macro_placeholder() { ItemType::Macro } else { self.type_() }; + format!("{type_}.{}.html", self.name.unwrap()) + } + + /// If the current item is a "fake" macro (ie, `AttrMacroItem | ItemKind::DeriveMacroItem` which + /// don't hold any data), it returns `true`. + pub(crate) fn is_macro_placeholder(&self) -> bool { + matches!(self.kind, ItemKind::AttrMacroItem | ItemKind::DeriveMacroItem) + } + pub(crate) fn is_default(&self) -> bool { match self.kind { ItemKind::MethodItem(_, Some(defaultness)) => { @@ -834,7 +846,13 @@ pub(crate) enum ItemKind { ForeignStaticItem(Static, hir::Safety), /// `type`s from an extern block ForeignTypeItem, - MacroItem(Macro), + MacroItem(Macro, MacroKinds), + /// This is NOT an attribute proc-macro but a bang macro with support for being used as an + /// attribute macro. + AttrMacroItem, + /// This is NOT an attribute proc-macro but a bang macro with support for being used as a + /// derive macro. + DeriveMacroItem, ProcMacroItem(ProcMacro), PrimitiveItem(PrimitiveType), /// A required associated constant in a trait declaration. @@ -889,7 +907,9 @@ impl ItemKind { | ForeignFunctionItem(_, _) | ForeignStaticItem(_, _) | ForeignTypeItem - | MacroItem(_) + | MacroItem(..) + | AttrMacroItem + | DeriveMacroItem | ProcMacroItem(_) | PrimitiveItem(_) | RequiredAssocConstItem(..) diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs index ee5f260615db5..8beea6aad11b4 100644 --- a/src/librustdoc/fold.rs +++ b/src/librustdoc/fold.rs @@ -88,7 +88,9 @@ pub(crate) trait DocFolder: Sized { | ForeignFunctionItem(..) | ForeignStaticItem(..) | ForeignTypeItem - | MacroItem(_) + | MacroItem(..) + | AttrMacroItem + | DeriveMacroItem | ProcMacroItem(_) | PrimitiveItem(_) | RequiredAssocConstItem(..) diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 5e5592269af60..d89b862bbedb9 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -378,8 +378,10 @@ impl DocFolder for CacheBuilder<'_, '_> { | clean::RequiredAssocTypeItem(..) | clean::AssocTypeItem(..) | clean::StrippedItem(..) - | clean::KeywordItem - | clean::AttributeItem => { + | clean::AttributeItem + | clean::AttrMacroItem + | clean::DeriveMacroItem + | clean::KeywordItem => { // FIXME: Do these need handling? // The person writing this comment doesn't know. // So would rather leave them to an expert, diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index ab40c01cb369d..8b969d92a494f 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -99,6 +99,8 @@ item_type! { // This number is reserved for use in JavaScript // Generic = 26, Attribute = 27, + BangMacroAttribute = 28, + BangMacroDerive = 29, } impl<'a> From<&'a clean::Item> for ItemType { @@ -128,6 +130,8 @@ impl<'a> From<&'a clean::Item> for ItemType { clean::ForeignFunctionItem(..) => ItemType::Function, // no ForeignFunction clean::ForeignStaticItem(..) => ItemType::Static, // no ForeignStatic clean::MacroItem(..) => ItemType::Macro, + clean::AttrMacroItem => ItemType::BangMacroAttribute, + clean::DeriveMacroItem => ItemType::BangMacroDerive, clean::PrimitiveItem(..) => ItemType::Primitive, clean::RequiredAssocConstItem(..) | clean::ProvidedAssocConstItem(..) @@ -221,8 +225,8 @@ impl ItemType { ItemType::AssocConst => "associatedconstant", ItemType::ForeignType => "foreigntype", ItemType::Keyword => "keyword", - ItemType::ProcAttribute => "attr", - ItemType::ProcDerive => "derive", + ItemType::ProcAttribute | ItemType::BangMacroAttribute => "attr", + ItemType::ProcDerive | ItemType::BangMacroDerive => "derive", ItemType::TraitAlias => "traitalias", ItemType::Attribute => "attribute", } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 752cd2e610a54..5614f3a266bdf 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -2038,7 +2038,7 @@ fn is_default_id(id: &str) -> bool { | "crate-search" | "crate-search-div" // This is the list of IDs used in HTML generated in Rust (including the ones - // used in tera template files). + // used in askama template files). | "themeStyle" | "settings-menu" | "help-button" @@ -2077,7 +2077,7 @@ fn is_default_id(id: &str) -> bool { | "blanket-implementations-list" | "deref-methods" | "layout" - | "aliased-type" + | "aliased-type", ) } diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 4c06d0da47013..ad2bc3612603a 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -13,9 +13,10 @@ use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::edition::Edition; use rustc_span::{BytePos, FileName, Symbol, sym}; +use serde::ser::SerializeSeq; use tracing::info; -use super::print_item::{full_path, print_item, print_item_path}; +use super::print_item::{full_path, print_item, print_item_path, print_ty_path}; use super::sidebar::{ModuleLike, Sidebar, print_sidebar, sidebar_module_like}; use super::{AllTypes, LinkFromSrc, StylePath, collect_spans_and_sources, scrape_examples_help}; use crate::clean::types::ExternalLocation; @@ -166,6 +167,30 @@ impl SharedContext<'_> { } } +struct SidebarItem { + name: String, + /// Bang macros can now be used as attribute/derive macros, making it tricky to correctly + /// handle all their cases at once, which means that even if they are categorized as + /// derive/attribute macros, they should still link to a "macro_rules" URL. + is_macro_rules: bool, +} + +impl serde::Serialize for SidebarItem { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + if self.is_macro_rules { + let mut seq = serializer.serialize_seq(Some(2))?; + seq.serialize_element(&self.name)?; + seq.serialize_element(&1)?; + seq.end() + } else { + serializer.serialize_some(&Some(&self.name)) + } + } +} + impl<'tcx> Context<'tcx> { pub(crate) fn tcx(&self) -> TyCtxt<'tcx> { self.shared.tcx @@ -280,7 +305,7 @@ impl<'tcx> Context<'tcx> { for name in &names[..names.len() - 1] { write!(f, "{name}/")?; } - write!(f, "{}", print_item_path(ty, names.last().unwrap().as_str())) + write!(f, "{}", print_ty_path(ty, names.last().unwrap().as_str())) }); match self.shared.redirections { Some(ref redirections) => { @@ -292,7 +317,7 @@ impl<'tcx> Context<'tcx> { let _ = write!( current_path, "{}", - print_item_path(ty, names.last().unwrap().as_str()) + print_ty_path(ty, names.last().unwrap().as_str()) ); redirections.borrow_mut().insert(current_path, path.to_string()); } @@ -306,7 +331,7 @@ impl<'tcx> Context<'tcx> { } /// Construct a map of items shown in the sidebar to a plain-text summary of their docs. - fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap> { + fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap> { // BTreeMap instead of HashMap to get a sorted output let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new(); let mut inserted: FxHashMap> = FxHashMap::default(); @@ -315,23 +340,26 @@ impl<'tcx> Context<'tcx> { if item.is_stripped() { continue; } - - let short = item.type_(); - let myname = match item.name { + let name = match item.name { None => continue, Some(s) => s, }; - if inserted.entry(short).or_default().insert(myname) { - let short = short.to_string(); - let myname = myname.to_string(); - map.entry(short).or_default().push(myname); + + let type_ = item.type_(); + + if inserted.entry(type_).or_default().insert(name) { + let type_ = type_.to_string(); + let name = name.to_string(); + map.entry(type_) + .or_default() + .push(SidebarItem { name, is_macro_rules: item.is_macro_placeholder() }); } } match self.shared.module_sorting { ModuleSorting::Alphabetical => { for items in map.values_mut() { - items.sort(); + items.sort_by(|a, b| a.name.cmp(&b.name)); } } ModuleSorting::DeclarationOrder => {} @@ -797,7 +825,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { info!("Recursing into {}", self.dst.display()); - if !item.is_stripped() { + if !item.is_stripped() && !item.is_macro_placeholder() { let buf = self.render_item(item, true); // buf will be empty if the module is stripped and there is no redirect for it if !buf.is_empty() { @@ -853,22 +881,29 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { self.info.render_redirect_pages = item.is_stripped(); } + if item.is_macro_placeholder() { + if !self.info.render_redirect_pages { + self.shared.all.borrow_mut().append(full_path(self, item), &item); + } + return Ok(()); + } + let buf = self.render_item(item, false); // buf will be empty if the item is stripped and there is no redirect for it if !buf.is_empty() { - let name = item.name.as_ref().unwrap(); - let item_type = item.type_(); - let file_name = print_item_path(item_type, name.as_str()).to_string(); + if !self.info.render_redirect_pages { + self.shared.all.borrow_mut().append(full_path(self, item), &item); + } + + let file_name = print_item_path(item).to_string(); self.shared.ensure_dir(&self.dst)?; let joint_dst = self.dst.join(&file_name); self.shared.fs.write(joint_dst, buf)?; - - if !self.info.render_redirect_pages { - self.shared.all.borrow_mut().append(full_path(self, item), &item_type); - } // If the item is a macro, redirect from the old macro URL (with !) // to the new one (without). + let item_type = item.type_(); if item_type == ItemType::Macro { + let name = item.name.as_ref().unwrap(); let redir_name = format!("{item_type}.{name}!.html"); if let Some(ref redirections) = self.shared.redirections { let crate_name = &self.shared.layout.krate; diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 13178ee4e9934..726f2da5fb9be 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -505,13 +505,14 @@ impl AllTypes { } } - fn append(&mut self, item_name: String, item_type: &ItemType) { + fn append(&mut self, item_name: String, item: &clean::Item) { let mut url: Vec<_> = item_name.split("::").skip(1).collect(); if let Some(name) = url.pop() { - let new_url = format!("{}/{item_type}.{name}.html", url.join("/")); + let new_url = format!("{}/{}", url.join("/"), item.html_filename()); url.push(name); + let item_type = item.type_(); let name = url.join("::"); - match *item_type { + match item_type { ItemType::Struct => self.structs.insert(ItemEntry::new(new_url, name)), ItemType::Enum => self.enums.insert(ItemEntry::new(new_url, name)), ItemType::Union => self.unions.insert(ItemEntry::new(new_url, name)), @@ -522,10 +523,12 @@ 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::ProcAttribute | ItemType::BangMacroAttribute => { self.attribute_macros.insert(ItemEntry::new(new_url, name)) } - ItemType::ProcDerive => self.derive_macros.insert(ItemEntry::new(new_url, name)), + ItemType::ProcDerive | ItemType::BangMacroDerive => { + self.derive_macros.insert(ItemEntry::new(new_url, name)) + } ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(new_url, name)), _ => true, }; @@ -2548,7 +2551,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 +2612,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::ProcAttribute | ItemType::BangMacroAttribute => ItemSection::AttributeMacros, + ItemType::ProcDerive | ItemType::BangMacroDerive => 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..2d77d41454839 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -7,7 +7,7 @@ use rustc_abi::VariantIdx; use rustc_ast::join_path_syms; use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; use rustc_hir as hir; -use rustc_hir::def::CtorKind; +use rustc_hir::def::{CtorKind, MacroKinds}; use rustc_hir::def_id::DefId; use rustc_index::IndexVec; use rustc_middle::ty::{self, TyCtxt}; @@ -212,6 +212,9 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp let item_vars = ItemVars { typ, name: item.name.as_ref().unwrap().as_str(), + // If `type_` returns `None`, it means it's a bang macro with multiple kinds, but + // since we're generating its documentation page, we can default to the "parent" type, + // ie "bang macro". item_type: &item.type_().to_string(), path_components, stability_since_raw: &stability_since_raw, @@ -236,7 +239,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp clean::TypeAliasItem(t) => { write!(buf, "{}", item_type_alias(cx, item, t)) } - clean::MacroItem(m) => write!(buf, "{}", item_macro(cx, item, m)), + clean::MacroItem(m, kinds) => write!(buf, "{}", item_macro(cx, item, m, *kinds)), clean::ProcMacroItem(m) => { write!(buf, "{}", item_proc_macro(cx, item, m)) } @@ -307,8 +310,19 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i fmt::from_fn(|w| { write!(w, "{}", document(cx, item, None, HeadingOffset::H2))?; - let mut not_stripped_items = - items.iter().filter(|i| !i.is_stripped()).enumerate().collect::>(); + let mut not_stripped_items: FxHashMap> = + FxHashMap::default(); + + for (index, item) in items.iter().filter(|i| !i.is_stripped()).enumerate() { + // To prevent having new "bang macro attribute/derive" sections in the module, + // we cheat by turning them into their "proc-macro equivalent". + let type_ = match item.type_() { + ItemType::BangMacroAttribute => ItemType::ProcAttribute, + ItemType::BangMacroDerive => ItemType::ProcDerive, + type_ => type_, + }; + not_stripped_items.entry(type_).or_default().push((index, item)); + } // the order of item types in the listing fn reorder(ty: ItemType) -> u8 { @@ -331,11 +345,6 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i } fn cmp(i1: &clean::Item, i2: &clean::Item, tcx: TyCtxt<'_>) -> Ordering { - let rty1 = reorder(i1.type_()); - let rty2 = reorder(i2.type_()); - if rty1 != rty2 { - return rty1.cmp(&rty2); - } let is_stable1 = i1.stability(tcx).as_ref().map(|s| s.level.is_stable()).unwrap_or(true); let is_stable2 = @@ -356,8 +365,12 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i let tcx = cx.tcx(); match cx.shared.module_sorting { - ModuleSorting::Alphabetical => { - not_stripped_items.sort_by(|(_, i1), (_, i2)| cmp(i1, i2, tcx)); + ModuleSorting::Alphabetical => + { + #[allow(rustc::potential_query_instability)] + for items in not_stripped_items.values_mut() { + items.sort_by(|(_, i1), (_, i2)| cmp(i1, i2, tcx)); + } } ModuleSorting::DeclarationOrder => {} } @@ -380,155 +393,158 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i // can be identical even if the elements are different (mostly in imports). // So in case this is an import, we keep everything by adding a "unique id" // (which is the position in the vector). - not_stripped_items.dedup_by_key(|(idx, i)| { - ( - i.item_id, - if i.name.is_some() { Some(full_path(cx, i)) } else { None }, - i.type_(), - if i.is_import() { *idx } else { 0 }, - ) - }); + #[allow(rustc::potential_query_instability)] + for items in not_stripped_items.values_mut() { + items.dedup_by_key(|(idx, i)| { + ( + i.item_id, + if i.name.is_some() { Some(full_path(cx, i)) } else { None }, + i.type_(), + if i.is_import() { *idx } else { 0 }, + ) + }); + } debug!("{not_stripped_items:?}"); - let mut last_section = None; - for (_, myitem) in ¬_stripped_items { - let my_section = item_ty_to_section(myitem.type_()); - if Some(my_section) != last_section { - if last_section.is_some() { - w.write_str(ITEM_TABLE_CLOSE)?; - } - last_section = Some(my_section); - let section_id = my_section.id(); - let tag = - if section_id == "reexports" { REEXPORTS_TABLE_OPEN } else { ITEM_TABLE_OPEN }; - write!( - w, - "{}", - write_section_heading(my_section.name(), &cx.derive_id(section_id), None, tag) - )?; - } + #[allow(rustc::potential_query_instability)] + let mut types = not_stripped_items.keys().copied().collect::>(); + types.sort_unstable_by(|a, b| reorder(*a).cmp(&reorder(*b))); + + for type_ in types { + let my_section = item_ty_to_section(type_); + let tag = if my_section == super::ItemSection::Reexports { + REEXPORTS_TABLE_OPEN + } else { + ITEM_TABLE_OPEN + }; + write!( + w, + "{}", + write_section_heading(my_section.name(), &cx.derive_id(my_section.id()), None, tag) + )?; - match myitem.kind { - clean::ExternCrateItem { ref src } => { - use crate::html::format::print_anchor; + for (_, myitem) in ¬_stripped_items[&type_] { + match myitem.kind { + clean::ExternCrateItem { ref src } => { + use crate::html::format::print_anchor; - match *src { - Some(src) => { - write!( - w, - "
{}extern crate {} as {};", - visibility_print_with_space(myitem, cx), - print_anchor(myitem.item_id.expect_def_id(), src, cx), - EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()) - )?; - } - None => { - write!( - w, - "
{}extern crate {};", - visibility_print_with_space(myitem, cx), - print_anchor( - myitem.item_id.expect_def_id(), - myitem.name.unwrap(), - cx - ) - )?; + match *src { + Some(src) => { + write!( + w, + "
{}extern crate {} as {};", + visibility_print_with_space(myitem, cx), + print_anchor(myitem.item_id.expect_def_id(), src, cx), + EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()) + )?; + } + None => { + write!( + w, + "
{}extern crate {};", + visibility_print_with_space(myitem, cx), + print_anchor( + myitem.item_id.expect_def_id(), + myitem.name.unwrap(), + cx + ) + )?; + } } } - w.write_str("
")?; - } - - clean::ImportItem(ref import) => { - let stab_tags = import.source.did.map_or_else(String::new, |import_def_id| { - print_extra_info_tags(tcx, myitem, item, Some(import_def_id)).to_string() - }); - let id = match import.kind { - clean::ImportKind::Simple(s) => { - format!(" id=\"{}\"", cx.derive_id(format!("reexport.{s}"))) - } - clean::ImportKind::Glob => String::new(), - }; - write!( - w, - "\ - " - )?; - render_attributes_in_code(w, myitem, "", cx); - write!( - w, - "{vis}{imp}{stab_tags}\ - ", - vis = visibility_print_with_space(myitem, cx), - imp = import.print(cx) - )?; - } + clean::ImportItem(ref import) => { + let stab_tags = + import.source.did.map_or_else(String::new, |import_def_id| { + print_extra_info_tags(tcx, myitem, item, Some(import_def_id)) + .to_string() + }); - _ => { - if myitem.name.is_none() { - continue; + let id = match import.kind { + clean::ImportKind::Simple(s) => { + format!(" id=\"{}\"", cx.derive_id(format!("reexport.{s}"))) + } + clean::ImportKind::Glob => String::new(), + }; + write!( + w, + "\ + " + )?; + render_attributes_in_code(w, myitem, "", cx); + write!( + w, + "{vis}{imp}{stab_tags}\ + ", + vis = visibility_print_with_space(myitem, cx), + imp = import.print(cx) + )?; } - let unsafety_flag = match myitem.kind { - clean::FunctionItem(_) | clean::ForeignFunctionItem(..) - if myitem.fn_header(tcx).unwrap().safety - == hir::HeaderSafety::Normal(hir::Safety::Unsafe) => - { - "" + _ => { + if myitem.name.is_none() { + continue; } - clean::ForeignStaticItem(_, hir::Safety::Unsafe) => { - "" - } - _ => "", - }; - - let visibility_and_hidden = match myitem.visibility(tcx) { - Some(ty::Visibility::Restricted(_)) => { - if myitem.is_doc_hidden() { - // Don't separate with a space when there are two of them - " 🔒👻 " - } else { - " 🔒 " + + let unsafety_flag = match myitem.kind { + clean::FunctionItem(_) | clean::ForeignFunctionItem(..) + if myitem.fn_header(tcx).unwrap().safety + == hir::HeaderSafety::Normal(hir::Safety::Unsafe) => + { + "" } - } - _ if myitem.is_doc_hidden() => { - " 👻 " - } - _ => "", - }; + clean::ForeignStaticItem(_, hir::Safety::Unsafe) => { + "" + } + _ => "", + }; - let docs = - MarkdownSummaryLine(&myitem.doc_value(), &myitem.links(cx)).into_string(); - let (docs_before, docs_after) = - if docs.is_empty() { ("", "") } else { ("
", "
") }; - write!( - w, - "
\ - \ - {name}\ - \ - {visibility_and_hidden}\ - {unsafety_flag}\ - {stab_tags}\ -
\ - {docs_before}{docs}{docs_after}", - name = EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()), - visibility_and_hidden = visibility_and_hidden, - stab_tags = print_extra_info_tags(tcx, myitem, item, None), - class = myitem.type_(), - unsafety_flag = unsafety_flag, - href = print_item_path(myitem.type_(), myitem.name.unwrap().as_str()), - title1 = myitem.type_(), - title2 = full_path(cx, myitem), - )?; + let visibility_and_hidden = match myitem.visibility(tcx) { + Some(ty::Visibility::Restricted(_)) => { + if myitem.is_doc_hidden() { + // Don't separate with a space when there are two of them + " 🔒👻 " + } else { + " 🔒 " + } + } + _ if myitem.is_doc_hidden() => { + " 👻 " + } + _ => "", + }; + + let docs = MarkdownSummaryLine(&myitem.doc_value(), &myitem.links(cx)) + .into_string(); + let (docs_before, docs_after) = + if docs.is_empty() { ("", "") } else { ("
", "
") }; + write!( + w, + "
\ + \ + {name}\ + \ + {visibility_and_hidden}\ + {unsafety_flag}\ + {stab_tags}\ +
\ + {docs_before}{docs}{docs_after}", + name = EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()), + visibility_and_hidden = visibility_and_hidden, + stab_tags = print_extra_info_tags(tcx, myitem, item, None), + class = type_, + unsafety_flag = unsafety_flag, + href = print_item_path(myitem), + title1 = myitem.type_(), + title2 = full_path(cx, myitem), + )?; + } } } - } - - if last_section.is_some() { w.write_str(ITEM_TABLE_CLOSE)?; } + Ok(()) }) } @@ -1878,8 +1894,13 @@ fn item_variants( }) } -fn item_macro(cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) -> impl fmt::Display { - fmt::from_fn(|w| { +fn item_macro( + cx: &Context<'_>, + it: &clean::Item, + t: &clean::Macro, + kinds: MacroKinds, +) -> impl fmt::Display { + fmt::from_fn(move |w| { wrap_item(w, |w| { // FIXME: Also print `#[doc(hidden)]` for `macro_rules!` if it `is_doc_hidden`. render_attributes_in_code(w, it, "", cx); @@ -1888,6 +1909,14 @@ fn item_macro(cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) -> impl fmt: } write!(w, "{}", Escape(&t.source)) })?; + if kinds != MacroKinds::BANG { + write!( + w, + "

ⓘ This is {} {}

", + kinds.article(), + kinds.descr(), + )?; + } write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) }) } @@ -2264,7 +2293,16 @@ pub(super) fn full_path(cx: &Context<'_>, item: &clean::Item) -> String { s } -pub(super) fn print_item_path(ty: ItemType, name: &str) -> impl Display { +pub(super) fn print_item_path(item: &clean::Item) -> impl Display { + fmt::from_fn(move |f| match item.kind { + clean::ItemKind::ModuleItem(..) => { + write!(f, "{}index.html", ensure_trailing_slash(item.name.unwrap().as_str())) + } + _ => f.write_str(&item.html_filename()), + }) +} + +pub(super) fn print_ty_path(ty: ItemType, name: &str) -> impl Display { fmt::from_fn(move |f| match ty { ItemType::Module => write!(f, "{}index.html", ensure_trailing_slash(name)), _ => write!(f, "{ty}.{name}.html"), diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 3ea9de381eca6..bc32e78e53535 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -736,12 +736,20 @@ function preLoadCss(cssUrl) { const ul = document.createElement("ul"); ul.className = "block " + shortty; - for (const name of filtered) { + for (const item of filtered) { + let name = item; + let isMacro = false; + if (Array.isArray(item)) { + name = item[0]; + isMacro = true; + } let path; if (shortty === "mod") { path = `${modpath}${name}/index.html`; - } else { + } else if (!isMacro) { path = `${modpath}${shortty}.${name}.html`; + } else { + path = `${modpath}macro.${name}.html`; } let current_page = document.location.href.toString(); if (current_page.endsWith("/")) { @@ -791,7 +799,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/html/static/js/rustdoc.d.ts b/src/librustdoc/html/static/js/rustdoc.d.ts index e206d6633e630..a68e3791db34c 100644 --- a/src/librustdoc/html/static/js/rustdoc.d.ts +++ b/src/librustdoc/html/static/js/rustdoc.d.ts @@ -244,6 +244,27 @@ declare namespace rustdoc { traitParent: number?, deprecated: boolean, associatedItemDisambiguator: string?, + /** + * If `true`, this item is a `macro_rules!` macro that supports + * multiple usage syntaxes, as described in RFC 3697 and 3698. + * The syntax for such a macro looks like this: + * + * ```rust + * /// Doc Comment. + * macro_rules! NAME { + * attr(key = $value:literal) ($attached:item) => { ... }; + * derive() ($attached:item) => { ... }; + * ($bang:tt) => { ... }; + * } + * ``` + * + * Each usage syntax gets a separate EntryData---one for the attr, + * one for the derive, and one for the bang syntax---with a corresponding + * `ty` field that can be used for filtering and presenting results. + * But the documentation lives in a single `macro.NAME.html` page, and + * this boolean flag is used for generating that HREF. + */ + isBangMacro: boolean, } /** @@ -300,7 +321,7 @@ declare namespace rustdoc { type ItemType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | - 21 | 22 | 23 | 24 | 25 | 26; + 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29; /** * The viewmodel for the search engine results page. diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 0929d351463cc..eb88b8068df02 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -120,6 +120,8 @@ const itemTypes = [ "traitalias", // 25 "generic", "attribute", + null, // bang macro attribute + null, // bang macro derive ]; // used for special search precedence @@ -1640,7 +1642,7 @@ class DocSearch { * ], [string]>} */ const raw = JSON.parse(encoded); - return { + const item = { krate: raw[0], ty: raw[1], modulePath: raw[2] === 0 ? null : raw[2] - 1, @@ -1649,7 +1651,15 @@ class DocSearch { traitParent: raw[5] === 0 ? null : raw[5] - 1, deprecated: raw[6] === 1 ? true : false, associatedItemDisambiguator: raw.length === 7 ? null : raw[7], + isBangMacro: false, }; + if (item.ty === 28 || item.ty === 29) { + // "proc attribute" is 23, "proc derive" is 24 whereas "bang macro attribute" is 28 and + // "bang macro derive" is 29, so 5 of difference to go from the latter to the former. + item.ty -= 5; + item.isBangMacro = true; + } + return item; } /** @@ -2146,7 +2156,7 @@ class DocSearch { let displayPath; let href; let traitPath = null; - const type = itemTypes[item.ty]; + const type = item.entry && item.entry.isBangMacro ? "macro" : itemTypes[item.ty]; const name = item.name; let path = item.modulePath; let exactPath = item.exactModulePath; @@ -3949,7 +3959,7 @@ class DocSearch { * @param {Promise[]} data * @returns {AsyncGenerator} */ - const flush = async function* (data) { + const flush = async function*(data) { const satr = sortAndTransformResults( await Promise.all(data), null, diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 909262d563e9f..a446df38bd46c 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -52,7 +52,12 @@ impl JsonRenderer<'_> { let clean::ItemInner { name, item_id, .. } = *item.inner; let id = self.id_from_item(item); let inner = match item.kind { - clean::KeywordItem | clean::AttributeItem => return None, + clean::KeywordItem + | clean::AttributeItem + // Placeholder so no need to handle it. + | clean::AttrMacroItem + // Placeholder so no need to handle it. + | clean::DeriveMacroItem => return None, clean::StrippedItem(ref inner) => { match &**inner { // We document stripped modules as with `Module::is_stripped` set to @@ -308,7 +313,7 @@ fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum type_: ci.type_.into_json(renderer), const_: ci.kind.into_json(renderer), }, - MacroItem(m) => ItemEnum::Macro(m.source.clone()), + MacroItem(m, _) => ItemEnum::Macro(m.source.clone()), ProcMacroItem(m) => ItemEnum::ProcMacro(m.into_json(renderer)), PrimitiveItem(p) => { ItemEnum::Primitive(Primitive { @@ -335,8 +340,9 @@ fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum bounds: b.into_json(renderer), type_: Some(t.item_type.as_ref().unwrap_or(&t.type_).into_json(renderer)), }, - // `convert_item` early returns `None` for stripped items, keywords and attributes. - KeywordItem | AttributeItem => unreachable!(), + // `convert_item` early returns `None` for stripped items, keywords, attributes and + // "special" macro rules. + KeywordItem | AttributeItem | AttrMacroItem | DeriveMacroItem => unreachable!(), StrippedItem(inner) => { match inner.as_ref() { ModuleItem(m) => ItemEnum::Module(Module { @@ -892,8 +898,8 @@ impl FromClean for ItemKind { Keyword => ItemKind::Keyword, Attribute => ItemKind::Attribute, TraitAlias => ItemKind::TraitAlias, - ProcAttribute => ItemKind::ProcAttribute, - ProcDerive => ItemKind::ProcDerive, + ProcAttribute | BangMacroAttribute => ItemKind::ProcAttribute, + ProcDerive | BangMacroDerive => ItemKind::ProcDerive, } } } diff --git a/src/librustdoc/passes/propagate_stability.rs b/src/librustdoc/passes/propagate_stability.rs index 5139ca301dd3d..dca0b509782d6 100644 --- a/src/librustdoc/passes/propagate_stability.rs +++ b/src/librustdoc/passes/propagate_stability.rs @@ -88,6 +88,8 @@ impl DocFolder for StabilityPropagator<'_, '_> { | ItemKind::ForeignStaticItem(..) | ItemKind::ForeignTypeItem | ItemKind::MacroItem(..) + | ItemKind::AttrMacroItem + | ItemKind::DeriveMacroItem | ItemKind::ProcMacroItem(..) | ItemKind::ConstantItem(..) => { // If any of the item's parents was stabilized later or is still unstable, diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index 99d22526f85b7..b625c61b9be45 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -64,6 +64,8 @@ impl DocFolder for Stripper<'_, '_> { | clean::UnionItem(..) | clean::TraitAliasItem(..) | clean::MacroItem(..) + | clean::AttrMacroItem + | clean::DeriveMacroItem | clean::ForeignTypeItem => { let item_id = i.item_id; if item_id.is_local() diff --git a/src/librustdoc/visit.rs b/src/librustdoc/visit.rs index 4d31409afe825..2cd39f8d34e62 100644 --- a/src/librustdoc/visit.rs +++ b/src/librustdoc/visit.rs @@ -41,7 +41,9 @@ pub(crate) trait DocVisitor<'a>: Sized { | ForeignFunctionItem(..) | ForeignStaticItem(..) | ForeignTypeItem - | MacroItem(_) + | MacroItem(..) + | AttrMacroItem + | DeriveMacroItem | ProcMacroItem(_) | PrimitiveItem(_) | RequiredAssocConstItem(..) diff --git a/tests/rustdoc-gui/attr-macros.goml b/tests/rustdoc-gui/attr-macros.goml new file mode 100644 index 0000000000000..8fde3d54fa7ea --- /dev/null +++ b/tests/rustdoc-gui/attr-macros.goml @@ -0,0 +1,76 @@ +// 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, info], + block { + // It should be present twice in the sidebar. + assert-count: ("#rustdoc-modnav a[href='macro." + |name| + ".html']", 2) + 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']", 2) + 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|) + // Since the item is present twice in the sidebar, we should have two "current" items. + assert-count: ("#rustdoc-modnav .current", 2) + // We check it has the expected information. + assert-text: ("h3.macro-info", "ⓘ This is " + |info| + "/function macro") + } +) + +call-function: ("check_macro", {"name": "attr_macro", "info": "an attribute"}) +call-function: ("check_macro", {"name": "derive_macro", "info": "a derive"}) + +define-function: ( + "crate_page", + [name, section_id], + block { + // It should be only present twice. + assert-count: ("#main-content a[href='macro." + |name| + ".html']", 2) + // 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='macro." + |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", "section_id": "attribute-macros"}) +call-function: ("crate_page", {"name": "derive_macro", "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, section_id], + block { + // It should be only present twice. + assert-count: ("#main-content a[href='macro." + |name| + ".html']", 2) + // 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='macro." + |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", "section_id": "attribute-macros"}) +call-function: ("all_items_page", {"name": "derive_macro", "section_id": "derives"}) diff --git a/tests/rustdoc-gui/module-items-font.goml b/tests/rustdoc-gui/module-items-font.goml index bed95b378c6ab..822e0adc3b71e 100644 --- a/tests/rustdoc-gui/module-items-font.goml +++ b/tests/rustdoc-gui/module-items-font.goml @@ -74,3 +74,12 @@ 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..01da9c3bbdeb7 100644 --- a/tests/rustdoc-gui/src/test_docs/macros.rs +++ b/tests/rustdoc-gui/src/test_docs/macros.rs @@ -2,3 +2,24 @@ 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..c752814e8f29a --- /dev/null +++ b/tests/rustdoc-js/macro-kinds.js @@ -0,0 +1,27 @@ +// 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/macro.macro1.html' }, + { 'path': 'macro_kinds', 'name': 'macro2', 'href': '../macro_kinds/attr.macro2.html' }, + ], + }, + { + 'query': 'derive:macro', + 'others': [ + { 'path': 'macro_kinds', 'name': 'macro1', 'href': '../macro_kinds/macro.macro1.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..3bd753cadc677 --- /dev/null +++ b/tests/rustdoc-js/macro-kinds.rs @@ -0,0 +1,28 @@ +// 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() () => {}; +}