From 3552177fc1e304145b026dd1bb76a4c728f3027d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 14 Aug 2025 17:51:40 +0200 Subject: [PATCH 1/3] Support new bang macro kinds in rustdoc --- src/librustdoc/clean/inline.rs | 27 +- src/librustdoc/clean/mod.rs | 31 ++- src/librustdoc/clean/types.rs | 26 +- src/librustdoc/fold.rs | 2 +- src/librustdoc/html/render/context.rs | 60 ++++- src/librustdoc/html/render/mod.rs | 49 ++-- src/librustdoc/html/render/print_item.rs | 303 ++++++++++++----------- src/librustdoc/html/render/sidebar.rs | 8 +- src/librustdoc/html/static/js/main.js | 12 +- src/librustdoc/json/conversions.rs | 2 +- src/librustdoc/visit.rs | 2 +- 11 files changed, 322 insertions(+), 200 deletions(-) diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 0d98c64bbde5f..2560ad474be1a 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -140,13 +140,12 @@ 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"), + _ if kinds.contains(MacroKinds::BANG) => ItemType::Macro, + _ => panic!("unsupported macro kind {kinds:?}"), }; record_extern_fqn(cx, did, type_kind); mac @@ -755,13 +754,14 @@ fn build_macro( macro_kinds: MacroKinds, ) -> 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::BANG => clean::MacroItem( + clean::Macro { + source: utils::display_macro_source(cx, name, &def), + macro_rules: def.macro_rules, + }, + None, + ), MacroKinds::DERIVE => clean::ProcMacroItem(clean::ProcMacro { kind: MacroKind::Derive, helpers: Vec::new(), @@ -770,7 +770,14 @@ fn build_macro( kind: MacroKind::Attr, helpers: Vec::new(), }), - _ => todo!("Handle macros with multiple kinds"), + _ if macro_kinds == (MacroKinds::BANG | MacroKinds::ATTR) => clean::MacroItem( + clean::Macro { + source: utils::display_macro_source(cx, name, &def), + macro_rules: def.macro_rules, + }, + Some(macro_kinds), + ), + _ => panic!("unsupported macro kind {macro_kinds:?}"), }, LoadedMacro::ProcMacro(ext) => { // Proc macros can only have a single kind diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 4ff94cc6f3b64..e18dbbc461ff1 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2845,19 +2845,24 @@ 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, + }, + None, + ), + MacroKinds::ATTR => clean_proc_macro(item, &mut name, MacroKind::Attr, cx), + MacroKinds::DERIVE => clean_proc_macro(item, &mut name, MacroKind::Derive, cx), + _ => MacroItem( + Macro { + source: display_macro_source(cx, name, macro_def), + macro_rules: macro_def.macro_rules, + }, + Some(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 26b087feb1684..6479c656b097f 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}; @@ -637,6 +637,24 @@ impl Item { find_attr!(&self.attrs.other_attrs, AttributeKind::NonExhaustive(..)) } + pub(crate) fn bang_macro_types(&self) -> Option> { + match self.kind { + ItemKind::MacroItem(_, None) => Some(vec![ItemType::Macro]), + ItemKind::MacroItem(_, Some(kinds)) => Some( + kinds + .iter() + .map(|kind| match kind { + MacroKinds::BANG => ItemType::Macro, + MacroKinds::ATTR => ItemType::ProcAttribute, + MacroKinds::DERIVE => ItemType::ProcDerive, + _ => panic!("unexpected macro kind {kind:?}"), + }) + .collect::>(), + ), + _ => None, + } + } + /// Returns a documentation-level item type from the item. pub(crate) fn type_(&self) -> ItemType { ItemType::from(self) @@ -924,7 +942,7 @@ pub(crate) enum ItemKind { ForeignStaticItem(Static, hir::Safety), /// `type`s from an extern block ForeignTypeItem, - MacroItem(Macro), + MacroItem(Macro, Option), ProcMacroItem(ProcMacro), PrimitiveItem(PrimitiveType), /// A required associated constant in a trait declaration. @@ -974,7 +992,7 @@ impl ItemKind { | ForeignFunctionItem(_, _) | ForeignStaticItem(_, _) | ForeignTypeItem - | MacroItem(_) + | MacroItem(..) | ProcMacroItem(_) | PrimitiveItem(_) | RequiredAssocConstItem(..) @@ -1005,7 +1023,7 @@ impl ItemKind { | ForeignFunctionItem(_, _) | ForeignStaticItem(_, _) | ForeignTypeItem - | MacroItem(_) + | MacroItem(..) | ProcMacroItem(_) | PrimitiveItem(_) ) diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs index c03d16ad081bf..e924b7d51f796 100644 --- a/src/librustdoc/fold.rs +++ b/src/librustdoc/fold.rs @@ -88,7 +88,7 @@ pub(crate) trait DocFolder: Sized { | ForeignFunctionItem(..) | ForeignStaticItem(..) | ForeignTypeItem - | MacroItem(_) + | MacroItem(..) | ProcMacroItem(_) | PrimitiveItem(_) | RequiredAssocConstItem(..) diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 5ceb1fc988dc9..a911e9e3bbc69 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -13,6 +13,7 @@ use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::edition::Edition; use rustc_span::{FileName, Symbol, sym}; +use serde::ser::SerializeSeq; use tracing::info; use super::print_item::{full_path, print_item, print_item_path}; @@ -163,6 +164,27 @@ impl SharedContext<'_> { } } +struct SidebarItem { + name: String, + is_actually_macro: bool, +} + +impl serde::Serialize for SidebarItem { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + if self.is_actually_macro { + 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 @@ -290,7 +312,20 @@ 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> { + fn build_sidebar_items_inner( + name: Symbol, + type_: ItemType, + map: &mut BTreeMap>, + inserted: &mut FxHashMap>, + is_actually_macro: bool, + ) { + 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_actually_macro }); + } + } // BTreeMap instead of HashMap to get a sorted output let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new(); let mut inserted: FxHashMap> = FxHashMap::default(); @@ -299,23 +334,24 @@ 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); + + if let Some(types) = item.bang_macro_types() { + for type_ in types { + build_sidebar_items_inner(name, type_, &mut map, &mut inserted, true); + } + } else { + build_sidebar_items_inner(name, item.type_(), &mut map, &mut inserted, false); } } 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 => {} @@ -843,7 +879,11 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { 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); + self.shared.all.borrow_mut().append( + full_path(self, item), + &item_type, + item.bang_macro_types(), + ); } // If the item is a macro, redirect from the old macro URL (with !) // to the new one (without). diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 9525984707583..9611751c7ef57 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -381,33 +381,46 @@ impl AllTypes { } } - fn append(&mut self, item_name: String, item_type: &ItemType) { + fn append( + &mut self, + item_name: String, + item_type: &ItemType, + extra_types: Option>, + ) { 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("/")); url.push(name); let name = url.join("::"); - 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)), - ItemType::Primitive => self.primitives.insert(ItemEntry::new(new_url, name)), - ItemType::Trait => self.traits.insert(ItemEntry::new(new_url, name)), - ItemType::Macro => self.macros.insert(ItemEntry::new(new_url, name)), - ItemType::Function => self.functions.insert(ItemEntry::new(new_url, name)), - 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 => { - self.attribute_macros.insert(ItemEntry::new(new_url, name)) + if let Some(extra_types) = extra_types { + for extra_type in extra_types { + self.append_with_url(name.clone(), &extra_type, new_url.clone()); } - ItemType::ProcDerive => self.derive_macros.insert(ItemEntry::new(new_url, name)), - ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(new_url, name)), - _ => true, - }; + } else { + self.append_with_url(name, item_type, new_url); + } } } + fn append_with_url(&mut self, name: String, item_type: &ItemType, url: String) { + match *item_type { + ItemType::Struct => self.structs.insert(ItemEntry::new(url, name)), + ItemType::Enum => self.enums.insert(ItemEntry::new(url, name)), + ItemType::Union => self.unions.insert(ItemEntry::new(url, name)), + ItemType::Primitive => self.primitives.insert(ItemEntry::new(url, name)), + ItemType::Trait => self.traits.insert(ItemEntry::new(url, name)), + ItemType::Macro => self.macros.insert(ItemEntry::new(url, name)), + ItemType::Function => self.functions.insert(ItemEntry::new(url, name)), + ItemType::TypeAlias => self.type_aliases.insert(ItemEntry::new(url, name)), + ItemType::Static => self.statics.insert(ItemEntry::new(url, name)), + ItemType::Constant => self.constants.insert(ItemEntry::new(url, name)), + ItemType::ProcAttribute => self.attribute_macros.insert(ItemEntry::new(url, name)), + ItemType::ProcDerive => self.derive_macros.insert(ItemEntry::new(url, name)), + ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(url, name)), + _ => true, + }; + } + fn item_sections(&self) -> FxHashSet { let mut sections = FxHashSet::default(); diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 759f53974f573..3ec1fc9f3d61f 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}; @@ -217,6 +217,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, @@ -241,7 +244,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)) } @@ -310,8 +313,21 @@ 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(); + + let mut index = 0; + for item in items.iter().filter(|i| !i.is_stripped()) { + if let Some(types) = item.bang_macro_types() { + for type_ in types { + not_stripped_items.entry(type_).or_default().push((index, item)); + index += 1; + } + } else { + not_stripped_items.entry(item.type_()).or_default().push((index, item)); + index += 1; + } + } // the order of item types in the listing fn reorder(ty: ItemType) -> u8 { @@ -334,11 +350,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 = @@ -359,8 +370,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 => {} } @@ -383,150 +398,152 @@ 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 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) + )?; - 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("
")?; } - 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, - "\ - {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, + "\ + {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.type_(), myitem.name.unwrap().as_str()), + title1 = myitem.type_(), + title2 = full_path(cx, myitem), + )?; + } } } - } - - if last_section.is_some() { w.write_str(ITEM_TABLE_CLOSE)?; } + Ok(()) }) } @@ -1877,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: Option, +) -> 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`. if !t.macro_rules { @@ -1886,6 +1908,9 @@ fn item_macro(cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) -> impl fmt: } write!(w, "{}", Escape(&t.source)) })?; + if let Some(kinds) = kinds { + write!(w, "

ⓘ This is {} {}

", kinds.article(), kinds.descr(),)?; + } write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) }) } diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index b9f5ada417c7f..ad87799a77c85 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -653,7 +653,13 @@ fn sidebar_module( }) .is_some() }) - .map(|it| item_ty_to_section(it.type_())) + .flat_map(|it| { + if let Some(types) = it.bang_macro_types() { + types.into_iter().map(|type_| item_ty_to_section(type_)).collect::>() + } else { + vec![item_ty_to_section(it.type_())] + } + }) .collect(); sidebar_module_like(item_sections_in_use, ids, module_like) diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 8e3d07b3a1c28..281027ca05fbe 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -616,12 +616,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("/")) { diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index f966d9265628b..2438c07c2f651 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -305,7 +305,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 { diff --git a/src/librustdoc/visit.rs b/src/librustdoc/visit.rs index b8b619514aad9..c3067984b2e94 100644 --- a/src/librustdoc/visit.rs +++ b/src/librustdoc/visit.rs @@ -41,7 +41,7 @@ pub(crate) trait DocVisitor<'a>: Sized { | ForeignFunctionItem(..) | ForeignStaticItem(..) | ForeignTypeItem - | MacroItem(_) + | MacroItem(..) | ProcMacroItem(_) | PrimitiveItem(_) | RequiredAssocConstItem(..) From 07871bd349a52a4c66a808244a54d2c5ec008f72 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 15 Aug 2025 15:19:57 +0200 Subject: [PATCH 2/3] Add tests for attribute bang macros in rustdoc --- tests/rustdoc-gui/attr-macros.goml | 39 +++++++++++++++++++++++ tests/rustdoc-gui/src/test_docs/lib.rs | 1 + tests/rustdoc-gui/src/test_docs/macros.rs | 7 ++++ 3 files changed, 47 insertions(+) create mode 100644 tests/rustdoc-gui/attr-macros.goml diff --git a/tests/rustdoc-gui/attr-macros.goml b/tests/rustdoc-gui/attr-macros.goml new file mode 100644 index 0000000000000..0d16c3e764d9e --- /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: ("#attributes + .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: ("#attributes + .all-items a[href='macro.attr_macro.html']", "attr_macro") diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs index 623f5b33e9bc9..c54d2e8935fa9 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..6564088c7be70 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() () => {}; + () => {}; +} From e173857f7c38753c990acd133ce958ff384e0576 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 15 Aug 2025 17:43:59 +0200 Subject: [PATCH 3/3] Use `ItemKind` placeholders for alternative macro kinds --- src/librustdoc/clean/inline.rs | 109 +++++++++++++------ src/librustdoc/clean/mod.rs | 44 ++++++-- src/librustdoc/clean/types.rs | 34 +++--- src/librustdoc/fold.rs | 2 + src/librustdoc/formats/cache.rs | 2 + src/librustdoc/formats/item_type.rs | 4 + src/librustdoc/html/render/context.rs | 60 +++++----- src/librustdoc/html/render/mod.rs | 52 ++++----- src/librustdoc/html/render/print_item.rs | 33 +++--- src/librustdoc/html/render/sidebar.rs | 8 +- src/librustdoc/json/conversions.rs | 12 +- src/librustdoc/passes/propagate_stability.rs | 2 + src/librustdoc/passes/stripper.rs | 2 + src/librustdoc/visit.rs | 2 + 14 files changed, 214 insertions(+), 152 deletions(-) diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 2560ad474be1a..8dc4f82bd479a 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,7 +159,7 @@ 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); let type_kind = match kinds { MacroKinds::BANG => ItemType::Macro, @@ -148,23 +169,21 @@ pub(crate) fn try_inline( _ => 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) } @@ -752,31 +771,51 @@ 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) { 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::BANG => ( + clean::MacroItem( + clean::Macro { + source: utils::display_macro_source(cx, name, &def), + macro_rules: def.macro_rules, + }, + None, + ), + None, + ), + MacroKinds::DERIVE => ( + clean::ProcMacroItem(clean::ProcMacro { + kind: MacroKind::Derive, + helpers: Vec::new(), + }), None, ), - MacroKinds::DERIVE => clean::ProcMacroItem(clean::ProcMacro { - kind: MacroKind::Derive, - helpers: Vec::new(), - }), - MacroKinds::ATTR => clean::ProcMacroItem(clean::ProcMacro { - kind: MacroKind::Attr, - helpers: Vec::new(), - }), - _ if macro_kinds == (MacroKinds::BANG | MacroKinds::ATTR) => clean::MacroItem( - clean::Macro { - source: utils::display_macro_source(cx, name, &def), - macro_rules: def.macro_rules, - }, - Some(macro_kinds), + 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, + }, + Some(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) => { @@ -787,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 e18dbbc461ff1..95601895047b1 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2855,13 +2855,43 @@ fn clean_maybe_renamed_item<'tcx>( ), MacroKinds::ATTR => clean_proc_macro(item, &mut name, MacroKind::Attr, cx), MacroKinds::DERIVE => clean_proc_macro(item, &mut name, MacroKind::Derive, cx), - _ => MacroItem( - Macro { - source: display_macro_source(cx, name, macro_def), - macro_rules: macro_def.macro_rules, - }, - Some(kinds), - ), + _ if kinds.contains(MacroKinds::BANG) => { + let kind = MacroItem( + Macro { + source: display_macro_source(cx, name, macro_def), + macro_rules: macro_def.macro_rules, + }, + Some(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, .. } => { diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 6479c656b097f..743d8a01a1a27 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -637,29 +637,23 @@ impl Item { find_attr!(&self.attrs.other_attrs, AttributeKind::NonExhaustive(..)) } - pub(crate) fn bang_macro_types(&self) -> Option> { - match self.kind { - ItemKind::MacroItem(_, None) => Some(vec![ItemType::Macro]), - ItemKind::MacroItem(_, Some(kinds)) => Some( - kinds - .iter() - .map(|kind| match kind { - MacroKinds::BANG => ItemType::Macro, - MacroKinds::ATTR => ItemType::ProcAttribute, - MacroKinds::DERIVE => ItemType::ProcDerive, - _ => panic!("unexpected macro kind {kind:?}"), - }) - .collect::>(), - ), - _ => None, - } - } - /// Returns a documentation-level item type from the item. pub(crate) fn type_(&self) -> ItemType { 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)) => { @@ -943,6 +937,8 @@ pub(crate) enum ItemKind { /// `type`s from an extern block ForeignTypeItem, MacroItem(Macro, Option), + AttrMacroItem, + DeriveMacroItem, ProcMacroItem(ProcMacro), PrimitiveItem(PrimitiveType), /// A required associated constant in a trait declaration. @@ -993,6 +989,8 @@ impl ItemKind { | ForeignStaticItem(_, _) | ForeignTypeItem | MacroItem(..) + | AttrMacroItem + | DeriveMacroItem | ProcMacroItem(_) | PrimitiveItem(_) | RequiredAssocConstItem(..) diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs index e924b7d51f796..33363ee7ed362 100644 --- a/src/librustdoc/fold.rs +++ b/src/librustdoc/fold.rs @@ -89,6 +89,8 @@ pub(crate) trait DocFolder: Sized { | ForeignStaticItem(..) | ForeignTypeItem | MacroItem(..) + | AttrMacroItem + | DeriveMacroItem | ProcMacroItem(_) | PrimitiveItem(_) | RequiredAssocConstItem(..) diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 918b292466d31..4baa7f9740c7b 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -373,6 +373,8 @@ impl DocFolder for CacheBuilder<'_, '_> { | clean::RequiredAssocTypeItem(..) | clean::AssocTypeItem(..) | clean::StrippedItem(..) + | clean::AttrMacroItem + | clean::DeriveMacroItem | clean::KeywordItem => { // FIXME: Do these need handling? // The person writing this comment doesn't know. diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index 1dba84aa44cc1..fdfb181142900 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -95,6 +95,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/render/context.rs b/src/librustdoc/html/render/context.rs index a911e9e3bbc69..09798d63b52c2 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -16,7 +16,7 @@ use rustc_span::{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; @@ -286,7 +286,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) => { @@ -298,7 +298,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()); } @@ -313,19 +313,6 @@ 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_inner( - name: Symbol, - type_: ItemType, - map: &mut BTreeMap>, - inserted: &mut FxHashMap>, - is_actually_macro: bool, - ) { - 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_actually_macro }); - } - } // BTreeMap instead of HashMap to get a sorted output let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new(); let mut inserted: FxHashMap> = FxHashMap::default(); @@ -339,12 +326,14 @@ impl<'tcx> Context<'tcx> { Some(s) => s, }; - if let Some(types) = item.bang_macro_types() { - for type_ in types { - build_sidebar_items_inner(name, type_, &mut map, &mut inserted, true); - } - } else { - build_sidebar_items_inner(name, item.type_(), &mut map, &mut inserted, false); + 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_actually_macro: item.is_macro_placeholder() }); } } @@ -812,7 +801,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() { @@ -868,26 +857,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, - item.bang_macro_types(), - ); - } // 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 9611751c7ef57..af3b36c65d6e5 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -381,46 +381,34 @@ impl AllTypes { } } - fn append( - &mut self, - item_name: String, - item_type: &ItemType, - extra_types: Option>, - ) { + 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("::"); - if let Some(extra_types) = extra_types { - for extra_type in extra_types { - self.append_with_url(name.clone(), &extra_type, new_url.clone()); + 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)), + ItemType::Primitive => self.primitives.insert(ItemEntry::new(new_url, name)), + ItemType::Trait => self.traits.insert(ItemEntry::new(new_url, name)), + ItemType::Macro => self.macros.insert(ItemEntry::new(new_url, name)), + ItemType::Function => self.functions.insert(ItemEntry::new(new_url, name)), + 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 => { + self.attribute_macros.insert(ItemEntry::new(new_url, name)) } - } else { - self.append_with_url(name, item_type, new_url); - } + ItemType::ProcDerive => self.derive_macros.insert(ItemEntry::new(new_url, name)), + ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(new_url, name)), + _ => true, + }; } } - fn append_with_url(&mut self, name: String, item_type: &ItemType, url: String) { - match *item_type { - ItemType::Struct => self.structs.insert(ItemEntry::new(url, name)), - ItemType::Enum => self.enums.insert(ItemEntry::new(url, name)), - ItemType::Union => self.unions.insert(ItemEntry::new(url, name)), - ItemType::Primitive => self.primitives.insert(ItemEntry::new(url, name)), - ItemType::Trait => self.traits.insert(ItemEntry::new(url, name)), - ItemType::Macro => self.macros.insert(ItemEntry::new(url, name)), - ItemType::Function => self.functions.insert(ItemEntry::new(url, name)), - ItemType::TypeAlias => self.type_aliases.insert(ItemEntry::new(url, name)), - ItemType::Static => self.statics.insert(ItemEntry::new(url, name)), - ItemType::Constant => self.constants.insert(ItemEntry::new(url, name)), - ItemType::ProcAttribute => self.attribute_macros.insert(ItemEntry::new(url, name)), - ItemType::ProcDerive => self.derive_macros.insert(ItemEntry::new(url, name)), - ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(url, name)), - _ => true, - }; - } - fn item_sections(&self) -> FxHashSet { let mut sections = FxHashSet::default(); diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 3ec1fc9f3d61f..b73e5c4f4c7a1 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -316,17 +316,8 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i let mut not_stripped_items: FxHashMap> = FxHashMap::default(); - let mut index = 0; - for item in items.iter().filter(|i| !i.is_stripped()) { - if let Some(types) = item.bang_macro_types() { - for type_ in types { - not_stripped_items.entry(type_).or_default().push((index, item)); - index += 1; - } - } else { - not_stripped_items.entry(item.type_()).or_default().push((index, item)); - index += 1; - } + 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 @@ -534,7 +525,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i stab_tags = print_extra_info_tags(tcx, myitem, item, None), class = type_, unsafety_flag = unsafety_flag, - href = print_item_path(myitem.type_(), myitem.name.unwrap().as_str()), + href = print_item_path(myitem), title1 = myitem.type_(), title2 = full_path(cx, myitem), )?; @@ -1909,7 +1900,12 @@ fn item_macro( write!(w, "{}", Escape(&t.source)) })?; if let Some(kinds) = kinds { - write!(w, "

ⓘ This is {} {}

", kinds.article(), kinds.descr(),)?; + write!( + w, + "

ⓘ This is {} {}

", + kinds.article(), + kinds.descr(), + )?; } write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) }) @@ -2282,7 +2278,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/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index ad87799a77c85..b9f5ada417c7f 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -653,13 +653,7 @@ fn sidebar_module( }) .is_some() }) - .flat_map(|it| { - if let Some(types) = it.bang_macro_types() { - types.into_iter().map(|type_| item_ty_to_section(type_)).collect::>() - } else { - vec![item_ty_to_section(it.type_())] - } - }) + .map(|it| item_ty_to_section(it.type_())) .collect(); sidebar_module_like(item_sections_in_use, ids, module_like) diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 2438c07c2f651..0d88714694e06 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -61,12 +61,12 @@ impl JsonRenderer<'_> { clean::ModuleItem(_) if self.imported_items.contains(&item_id.expect_def_id()) => { - from_clean_item(item, self) + from_clean_item(item, self)? } _ => return None, } } - _ => from_clean_item(item, self), + _ => from_clean_item(item, self)?, }; Some(Item { id, @@ -265,13 +265,13 @@ impl FromClean for AssocItemConstraintKind { } } -fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum { +fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> Option { use clean::ItemKind::*; let name = item.name; let is_crate = item.is_crate(); let header = item.fn_header(renderer.tcx); - match &item.inner.kind { + Some(match &item.inner.kind { ModuleItem(m) => { ItemEnum::Module(Module { is_crate, items: renderer.ids(&m.items), is_stripped: false }) } @@ -306,6 +306,8 @@ fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum const_: ci.kind.into_json(renderer), }, MacroItem(m, _) => ItemEnum::Macro(m.source.clone()), + // They are just placeholders so no need to handle them. + AttrMacroItem | DeriveMacroItem => return None, ProcMacroItem(m) => ItemEnum::ProcMacro(m.into_json(renderer)), PrimitiveItem(p) => { ItemEnum::Primitive(Primitive { @@ -349,7 +351,7 @@ fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum name: name.as_ref().unwrap().to_string(), rename: src.map(|x| x.to_string()), }, - } + }) } impl FromClean for Struct { diff --git a/src/librustdoc/passes/propagate_stability.rs b/src/librustdoc/passes/propagate_stability.rs index 14ec58702e356..2f52be4c51391 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 eedbbca0f8dfc..ae0a431dc18b8 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 c3067984b2e94..cd1d1903a65ab 100644 --- a/src/librustdoc/visit.rs +++ b/src/librustdoc/visit.rs @@ -42,6 +42,8 @@ pub(crate) trait DocVisitor<'a>: Sized { | ForeignStaticItem(..) | ForeignTypeItem | MacroItem(..) + | AttrMacroItem + | DeriveMacroItem | ProcMacroItem(_) | PrimitiveItem(_) | RequiredAssocConstItem(..)