diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 8461e15c6c3f5..fa21d2c894b2a 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) } @@ -753,24 +771,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 @@ -780,7 +826,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 93932936a2e0e..6121f4622fe5c 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2843,19 +2843,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 dcd67cb7ebc77..a56e76ac88af9 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -8,7 +8,7 @@ use itertools::Either; use rustc_abi::{ExternAbi, VariantIdx}; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; 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)) => { @@ -953,7 +965,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. @@ -1008,7 +1026,9 @@ impl ItemKind { | ForeignFunctionItem(_, _) | ForeignStaticItem(_, _) | ForeignTypeItem - | MacroItem(_) + | MacroItem(..) + | AttrMacroItem + | DeriveMacroItem | ProcMacroItem(_) | PrimitiveItem(_) | RequiredAssocConstItem(..) @@ -1040,7 +1060,7 @@ impl ItemKind { | ForeignFunctionItem(_, _) | ForeignStaticItem(_, _) | ForeignTypeItem - | MacroItem(_) + | MacroItem(..) | ProcMacroItem(_) | PrimitiveItem(_) ) 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 29b4c4caaf86d..aaf9e4fd0e10b 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -372,8 +372,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..b80bc446e1152 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -128,6 +128,10 @@ impl<'a> From<&'a clean::Item> for ItemType { clean::ForeignFunctionItem(..) => ItemType::Function, // no ForeignFunction clean::ForeignStaticItem(..) => ItemType::Static, // no ForeignStatic clean::MacroItem(..) => ItemType::Macro, + // Is this a good idea? + clean::AttrMacroItem => ItemType::ProcAttribute, + // Is this a good idea? + clean::DeriveMacroItem => ItemType::ProcDerive, clean::PrimitiveItem(..) => ItemType::Primitive, clean::RequiredAssocConstItem(..) | clean::ProvidedAssocConstItem(..) diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 4addf2c3c9644..518ecc89953b4 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -2035,7 +2035,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" @@ -2074,7 +2074,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 5f92ab2fada9c..0c5619ecc5f54 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; @@ -165,6 +166,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 @@ -279,7 +304,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) => { @@ -291,7 +316,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()); } @@ -305,7 +330,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(); @@ -314,23 +339,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 => {} @@ -796,7 +824,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() { @@ -852,22 +880,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 b4ef47d1e2694..804ef4d41486c 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -503,13 +503,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)), @@ -2599,7 +2600,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", } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index afa438f259690..e34d8324b4935 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,12 @@ 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() { + not_stripped_items.entry(item.type_()).or_default().push((index, item)); + } // the order of item types in the listing fn reorder(ty: ItemType) -> u8 { @@ -331,11 +338,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 +358,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 +386,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))); - match myitem.kind { - clean::ExternCrateItem { ref src } => { - use crate::html::format::print_anchor; + 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 *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 - ) - )?; + 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 + ) + )?; + } } } - 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() - }); + 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) - )?; - } - - _ => { - 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) => - { - "" - } - clean::ForeignStaticItem(_, hir::Safety::Unsafe) => { - "" + _ => { + if myitem.name.is_none() { + continue; } - _ => "", - }; - - 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 +1887,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 +1902,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 +2286,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 75febd6f737e6..1ebce15eb8ff9 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/json/conversions.rs b/src/librustdoc/json/conversions.rs index 6fe94f9d291b4..b09bfbe4b813d 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 { 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..cf7337703651f --- /dev/null +++ b/tests/rustdoc-gui/attr-macros.goml @@ -0,0 +1,39 @@ +// 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" +// It should be present twice in the sidebar. +assert-count: ("#rustdoc-modnav a[href='macro.attr_macro.html']", 2) +assert-count: ("//*[@id='rustdoc-modnav']//a[text()='attr_macro']", 2) +// We check that the current item in the sidebar is the correct one. +assert-text: ("#rustdoc-modnav .block.macro .current", "b") + +// We now go to the attribute macro page. +click: "#rustdoc-modnav a[href='macro.attr_macro.html']" +// It should be present twice in the sidebar. +assert-count: ("#rustdoc-modnav a[href='macro.attr_macro.html']", 2) +assert-count: ("//*[@id='rustdoc-modnav']//a[text()='attr_macro']", 2) +// We check that the current item is the "attr_macro". +assert-text: ("#rustdoc-modnav .block.macro .current", "attr_macro") +// 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 an attribute/function macro") + +// Now we check it's correctly listed in the crate page. +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" +// It should be only present twice. +assert-count: ("#main-content a[href='macro.attr_macro.html']", 2) +// First in the "Macros" section. +assert-text: ("#macros + .item-table a[href='macro.attr_macro.html']", "attr_macro") +// Then in the "Attribute Macros" section. +assert-text: ("#attribute-macros + .item-table a[href='macro.attr_macro.html']", "attr_macro") + +// And finally we check it's correctly listed in the "all items" page. +go-to: "file://" + |DOC_PATH| + "/test_docs/all.html" +// It should be only present twice. +assert-count: ("#main-content a[href='macro.attr_macro.html']", 2) +// First in the "Macros" section. +assert-text: ("#macros + .all-items a[href='macro.attr_macro.html']", "attr_macro") +// Then in the "Attribute Macros" section. +assert-text: ("#attribute-macros + .all-items a[href='macro.attr_macro.html']", "attr_macro") 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 42f2fbd93b1ee..445fe1b9077f2 100644 --- a/tests/rustdoc-gui/src/test_docs/lib.rs +++ b/tests/rustdoc-gui/src/test_docs/lib.rs @@ -7,6 +7,7 @@ #![feature(rustdoc_internals)] #![feature(doc_cfg)] #![feature(associated_type_defaults)] +#![feature(macro_attr)] /*! 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..9d8e8baca9d57 100644 --- a/tests/rustdoc-gui/src/test_docs/macros.rs +++ b/tests/rustdoc-gui/src/test_docs/macros.rs @@ -2,3 +2,10 @@ macro_rules! a{ () => {}} #[macro_export] macro_rules! b{ () => {}} + +/// An attribute bang macro. +#[macro_export] +macro_rules! attr_macro { + attr() () => {}; + () => {}; +}