diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 0d98c64bbde5f..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,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, + }, + None, + ), + 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, + }, + 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) => { // 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 4ff94cc6f3b64..95601895047b1 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2845,19 +2845,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, + }, + None, + ), + 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, + }, + 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, .. } => { 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..743d8a01a1a27 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}; @@ -642,6 +642,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)) => { @@ -924,7 +936,9 @@ pub(crate) enum ItemKind { ForeignStaticItem(Static, hir::Safety), /// `type`s from an extern block ForeignTypeItem, - MacroItem(Macro), + MacroItem(Macro, Option), + AttrMacroItem, + DeriveMacroItem, ProcMacroItem(ProcMacro), PrimitiveItem(PrimitiveType), /// A required associated constant in a trait declaration. @@ -974,7 +988,9 @@ impl ItemKind { | ForeignFunctionItem(_, _) | ForeignStaticItem(_, _) | ForeignTypeItem - | MacroItem(_) + | MacroItem(..) + | AttrMacroItem + | DeriveMacroItem | ProcMacroItem(_) | PrimitiveItem(_) | RequiredAssocConstItem(..) @@ -1005,7 +1021,7 @@ impl ItemKind { | ForeignFunctionItem(_, _) | ForeignStaticItem(_, _) | ForeignTypeItem - | MacroItem(_) + | MacroItem(..) | ProcMacroItem(_) | PrimitiveItem(_) ) diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs index c03d16ad081bf..33363ee7ed362 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 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 5ceb1fc988dc9..09798d63b52c2 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::{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; @@ -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 @@ -264,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) => { @@ -276,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()); } @@ -290,7 +312,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(); @@ -299,23 +321,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_actually_macro: 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 => {} @@ -776,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() { @@ -832,22 +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); - } // 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 9525984707583..af3b36c65d6e5 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -381,13 +381,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)), diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 759f53974f573..b73e5c4f4c7a1 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,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 { @@ -334,11 +341,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 +361,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 +389,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), + title1 = myitem.type_(), + title2 = full_path(cx, myitem), + )?; + } } } - } - - if last_section.is_some() { w.write_str(ITEM_TABLE_CLOSE)?; } + Ok(()) }) } @@ -1877,8 +1885,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 +1899,14 @@ 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)) }) } @@ -2257,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/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..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 }) } @@ -305,7 +305,9 @@ 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()), + // 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 b8b619514aad9..cd1d1903a65ab 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..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() () => {}; + () => {}; +}