diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 1451b6db8095d..9f1544f5f6bd8 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -45,6 +45,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; @@ -139,32 +160,21 @@ pub(crate) fn try_inline( Res::Def(DefKind::Macro(kinds), did) => { let mac = build_macro(cx.tcx, 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"), + // Then it means it's more than one type so we default to "macro". + _ => ItemType::Macro, }; record_extern_fqn(cx, did, type_kind); - mac + ret.push(try_inline_inner(cx, mac, did, name, import_def_id)); + return Some(ret); } _ => return None, }; - cx.inlined.insert(did.into()); - let mut item = crate::clean::generate_item_with_correct_attrs( - cx, - kind, - did, - name, - import_def_id.as_slice(), - None, - ); - // The visibility needs to reflect the one from the reexport and not from the "source" DefId. - item.inner.inline_stmt_id = import_def_id; - ret.push(item); + ret.push(try_inline_inner(cx, kind, did, name, import_def_id)); Some(ret) } @@ -777,13 +787,7 @@ fn build_macro( macro_kinds: MacroKinds, ) -> clean::ItemKind { match CStore::from_tcx(tcx).load_macro_untracked(tcx, def_id) { - // 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(tcx, name, &def), - macro_rules: def.macro_rules, - }), MacroKinds::DERIVE => clean::ProcMacroItem(clean::ProcMacro { kind: MacroKind::Derive, helpers: Vec::new(), @@ -792,7 +796,13 @@ fn build_macro( kind: MacroKind::Attr, helpers: Vec::new(), }), - _ => todo!("Handle macros with multiple kinds"), + _ => clean::MacroItem( + clean::Macro { + source: utils::display_macro_source(tcx, name, &def), + macro_rules: def.macro_rules, + }, + 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 501420bdc88fc..cdaf9870dd25c 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2898,19 +2898,17 @@ 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.tcx, name, macro_def), - macro_rules: macro_def.macro_rules, - }), - ItemKind::Macro(_, _, MacroKinds::ATTR) => { - clean_proc_macro(item, &mut name, MacroKind::Attr, cx.tcx) - } - ItemKind::Macro(_, _, MacroKinds::DERIVE) => { - clean_proc_macro(item, &mut name, MacroKind::Derive, cx.tcx) - } - ItemKind::Macro(_, _, _) => todo!("Handle macros with multiple kinds"), + ItemKind::Macro(_, macro_def, kinds) => match kinds { + MacroKinds::ATTR => clean_proc_macro(item, &mut name, MacroKind::Attr, cx.tcx), + MacroKinds::DERIVE => clean_proc_macro(item, &mut name, MacroKind::Derive, cx.tcx), + _ => MacroItem( + Macro { + source: display_macro_source(cx.tcx, name, macro_def), + macro_rules: macro_def.macro_rules, + }, + 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 a1eb093d7fcf2..dd244f6a6e673 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -13,7 +13,7 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::thin_vec::ThinVec; use rustc_hir as hir; use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation, DocAttribute}; -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::{Attribute, BodyId, ConstStability, Mutability, Stability, StableSince, find_attr}; @@ -745,11 +745,33 @@ impl Item { find_attr!(&self.attrs.other_attrs, NonExhaustive(..)) } - /// Returns a documentation-level item type from the item. + /// Returns a documentation-level item type from the item. In case of a `macro_rules!` which + /// contains an attr/derive kind, it will always return `ItemType::Macro`. If you want all + /// kinds, you need to use [`Item::types`]. pub(crate) fn type_(&self) -> ItemType { ItemType::from(self) } + /// Returns an item types. There is only one case where it can return more than one kind: + /// for `macro_rules!` items which contain an attr/derive kind. + pub(crate) fn types(&self) -> impl Iterator { + if let ItemKind::MacroItem(_, macro_kinds) = self.kind { + Either::Right(macro_kinds.iter().map(|kind| match kind { + MacroKinds::ATTR => ItemType::BangMacroAttribute, + MacroKinds::DERIVE => ItemType::BangMacroDerive, + MacroKinds::BANG => ItemType::Macro, + _ => panic!("unsupported macro kind {kind:?}"), + })) + } else { + Either::Left(std::iter::once(self.type_())) + } + } + + /// Returns true if this a macro declared with the `macro` keyword or with `macro_rules!. + pub(crate) fn is_bang_macro_or_macro_rules(&self) -> bool { + matches!(self.kind, ItemKind::MacroItem(..)) + } + pub(crate) fn defaultness(&self) -> Option { match self.kind { ItemKind::MethodItem(_, defaultness) | ItemKind::RequiredMethodItem(_, defaultness) => { @@ -759,6 +781,11 @@ impl Item { } } + /// Generates the HTML file name based on the item kind. + pub(crate) fn html_filename(&self) -> String { + format!("{type_}.{name}.html", type_ = self.type_(), name = self.name.unwrap()) + } + /// Returns a `FnHeader` if `self` is a function item, otherwise returns `None`. pub(crate) fn fn_header(&self, tcx: TyCtxt<'_>) -> Option { fn build_fn_header( @@ -918,7 +945,13 @@ pub(crate) enum ItemKind { ForeignStaticItem(Static, hir::Safety), /// `type`s from an extern block ForeignTypeItem, - MacroItem(Macro), + /// A macro defined with `macro_rules` or the `macro` keyword. It can be multiple things (macro, + /// derive and attribute, potentially multiple at once). Don't forget to look into the + ///`MacroKinds` values. + /// + /// If a `macro_rules!` only contains a `attr`/`derive` branch, then it's not stored in this + /// variant but in the `ProcMacroItem` variant. + MacroItem(Macro, MacroKinds), ProcMacroItem(ProcMacro), PrimitiveItem(PrimitiveType), /// A required associated constant in a trait declaration. @@ -973,7 +1006,7 @@ impl ItemKind { | ForeignFunctionItem(_, _) | ForeignStaticItem(_, _) | ForeignTypeItem - | MacroItem(_) + | MacroItem(..) | ProcMacroItem(_) | PrimitiveItem(_) | RequiredAssocConstItem(..) diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 2284b815a09a9..047b254d36766 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -5,6 +5,7 @@ use std::{ascii, mem}; use rustc_ast as ast; use rustc_ast::join_path_idents; +use rustc_ast::token::{Token, TokenKind}; use rustc_ast::tokenstream::TokenTree; use rustc_data_structures::thin_vec::{ThinVec, thin_vec}; use rustc_hir as hir; @@ -585,38 +586,77 @@ pub(crate) static RUSTDOC_VERSION: Lazy<&'static str> = /// Render a sequence of macro arms in a format suitable for displaying to the user /// as part of an item declaration. -fn render_macro_arms<'a>( +fn render_macro_arms( tcx: TyCtxt<'_>, - matchers: impl Iterator, + tokens: &rustc_ast::tokenstream::TokenStream, arm_delim: &str, ) -> String { + let mut tokens = tokens.iter(); let mut out = String::new(); - for matcher in matchers { + while let Some(mut token) = tokens.next() { + // If this an attr/derive rule, it looks like `attr() () => {}`, so the token needs to be + // handled at the same time as the actual matcher. + // + // Without that, we would end up with `attr()` on one line and the matcher `()` on another. + let pre = if matches!(token, TokenTree::Token(..)) { + let pre = format!("{}() ", render_macro_matcher(tcx, token)); + // Skipping the always empty `()` following the attr/derive ident. + tokens.next(); + let Some(next) = tokens.next() else { + return out; + }; + token = next; + pre + } else { + String::new() + }; writeln!( out, - " {matcher} => {{ ... }}{arm_delim}", - matcher = render_macro_matcher(tcx, matcher), + " {pre}{matcher} => {{ ... }}{arm_delim}", + matcher = render_macro_matcher(tcx, token), ) .unwrap(); + // We skip the `=>`, macro "body" and the delimiter closing that "body" since we don't + // render them. + let _token = tokens.next(); + // The `=>`. + debug_assert_matches!( + _token, + Some(TokenTree::Token(Token { kind: TokenKind::FatArrow, .. }, _)) + ); + let _token = tokens.next(); + // The arm body. + debug_assert_matches!(_token, Some(TokenTree::Delimited(..))); + // The delimiter (which may be omitted on the last arm's body). + let _token = tokens.next(); + debug_assert_matches!(_token, None | Some(TokenTree::Token(Token { .. }, _))); } out } pub(super) fn display_macro_source(tcx: TyCtxt<'_>, name: Symbol, def: &ast::MacroDef) -> String { // Extract the spans of all matchers. They represent the "interface" of the macro. - let matchers = def.body.tokens.chunks(4).map(|arm| &arm[0]); - if def.macro_rules { - format!("macro_rules! {name} {{\n{arms}}}", arms = render_macro_arms(tcx, matchers, ";")) + format!( + "macro_rules! {name} {{\n{arms}}}", + arms = render_macro_arms(tcx, &def.body.tokens, ";") + ) } else { - if matchers.len() <= 1 { + if def.body.tokens.len() <= 4 { format!( "macro {name}{matchers} {{\n ...\n}}", - matchers = - matchers.map(|matcher| render_macro_matcher(tcx, matcher)).collect::(), + matchers = def + .body + .tokens + .get(0) + .map(|matcher| render_macro_matcher(tcx, matcher)) + .unwrap_or_default(), ) } else { - format!("macro {name} {{\n{arms}}}", arms = render_macro_arms(tcx, matchers, ",")) + format!( + "macro {name} {{\n{arms}}}", + arms = render_macro_arms(tcx, &def.body.tokens, ",") + ) } } } diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs index 8b9db4638e473..41e3064b42066 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/formats/cache.rs b/src/librustdoc/formats/cache.rs index 35071f47a182b..8ba3217363d36 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -597,8 +597,9 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It let aliases = item.attrs.get_doc_aliases(); let is_deprecated = item.is_deprecated(tcx); let is_unstable = item.is_unstable(); + let mut types = item.types(); let index_item = IndexItem { - ty: item.type_(), + ty: types.next().unwrap(), defid: Some(defid), name, module_path: parent_path.to_vec(), @@ -614,7 +615,11 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It is_deprecated, is_unstable, }; - + for type_ in types { + let mut index_item_copy = index_item.clone(); + index_item_copy.ty = type_; + cache.search_index.push(index_item_copy); + } cache.search_index.push(index_item); } diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index eb3492e4625be..e6db950a4236e 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -101,6 +101,12 @@ item_type! { // This number is reserved for use in JavaScript // Generic = 26, Attribute = 27, + // The two next ones represent an attr/derive macro declared as a `macro_rules!`. We need this + // distinction because they will point to a `macro.[name].html` file and not + // `[attr|derive].[name].html` file, so the link generation needs to take it into account while + // still having the filtering working as expected. + BangMacroAttribute = 28, + BangMacroDerive = 29, } impl<'a> From<&'a clean::Item> for ItemType { @@ -163,10 +169,9 @@ impl ItemType { DefKind::Trait => Self::Trait, DefKind::TyAlias => Self::TypeAlias, DefKind::TraitAlias => Self::TraitAlias, - DefKind::Macro(MacroKinds::BANG) => ItemType::Macro, DefKind::Macro(MacroKinds::ATTR) => ItemType::ProcAttribute, DefKind::Macro(MacroKinds::DERIVE) => ItemType::ProcDerive, - DefKind::Macro(_) => todo!("Handle macros with multiple kinds"), + DefKind::Macro(_) => ItemType::Macro, DefKind::ForeignTy => Self::ForeignType, DefKind::Variant => Self::Variant, DefKind::Field => Self::StructField, @@ -221,8 +226,8 @@ impl ItemType { ItemType::AssocConst => "associatedconstant", ItemType::ForeignType => "foreigntype", ItemType::Keyword => "keyword", - ItemType::ProcAttribute => "attr", - ItemType::ProcDerive => "derive", + ItemType::ProcAttribute | ItemType::BangMacroAttribute => "attr", + ItemType::ProcDerive | ItemType::BangMacroDerive => "derive", ItemType::TraitAlias => "traitalias", ItemType::Attribute => "attribute", } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 2034abdfd1566..6679d162520df 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -2051,7 +2051,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" @@ -2090,7 +2090,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 4a7b1d1d6c563..75f40b11819c0 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -15,9 +15,10 @@ use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::edition::Edition; use rustc_span::{BytePos, FileName, RemapPathScopeComponents, Symbol}; +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; @@ -168,6 +169,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 @@ -286,7 +311,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 +323,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()); } @@ -312,7 +337,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(); @@ -321,23 +346,25 @@ 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 is_macro_rules = item.is_bang_macro_or_macro_rules(); + for type_ in item.types() { + 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 }); + } } } 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 => {} @@ -870,19 +897,19 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { 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 c0c380447f2cb..74adc43f0ed90 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -126,7 +126,7 @@ enum RenderMode { /// Struct representing one entry in the JS search index. These are all emitted /// by hand to a large JS file at the end of cache-creation. -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct IndexItem { pub(crate) ty: ItemType, pub(crate) defid: Option, @@ -507,30 +507,38 @@ impl AllTypes { } } - fn append(&mut self, item_name: String, item_type: &ItemType) { + fn add_item_entry(&mut self, item_type: ItemType, new_url: String, name: String) { + 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 | ItemType::BangMacroAttribute => { + self.attribute_macros.insert(ItemEntry::new(new_url, name)) + } + ItemType::ProcDerive | ItemType::BangMacroDerive => { + self.derive_macros.insert(ItemEntry::new(new_url, name)) + } + ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(new_url, name)), + _ => true, + }; + } + + 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 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)) - } - ItemType::ProcDerive => self.derive_macros.insert(ItemEntry::new(new_url, name)), - ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(new_url, name)), - _ => true, - }; + for type_ in item.types() { + self.add_item_entry(type_, new_url.clone(), name.clone()); + } } } @@ -2574,7 +2582,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", } @@ -2635,8 +2643,8 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection { ItemType::ForeignType => ItemSection::ForeignTypes, ItemType::Keyword => ItemSection::Keywords, ItemType::Attribute => ItemSection::Attributes, - ItemType::ProcAttribute => ItemSection::AttributeMacros, - ItemType::ProcDerive => ItemSection::DeriveMacros, + ItemType::ProcAttribute | ItemType::BangMacroAttribute => ItemSection::AttributeMacros, + ItemType::ProcDerive | ItemType::BangMacroDerive => ItemSection::DeriveMacros, ItemType::TraitAlias => ItemSection::TraitAliases, } } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 5363755a0da7a..33efa4e57e593 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, FxIndexMap, 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}; @@ -129,6 +129,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(), + // It's fine to use `type_` here because, even if it's a bang macro with multiple kinds, + // since we're generating its documentation page, we can default to the "parent" type, + // ie "macro". item_type: &item.type_().to_string(), path_components, stability_since_raw: &stability_since_raw, @@ -153,7 +156,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)) } @@ -228,7 +231,16 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i FxIndexMap::default(); for (index, item) in items.iter().filter(|i| !i.is_stripped()).enumerate() { - not_stripped_items.entry(item.type_()).or_default().push((index, item)); + // To prevent having new "bang macro attribute/derive" sections in the module, + // we cheat by turning them into their "proc-macro equivalent". + for type_ in item.types() { + let type_ = match type_ { + ItemType::BangMacroAttribute => ItemType::ProcAttribute, + ItemType::BangMacroDerive => ItemType::ProcDerive, + type_ => type_, + }; + not_stripped_items.entry(type_).or_default().push((index, item)); + } } // the order of item types in the listing @@ -314,34 +326,18 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i let mut types = not_stripped_items.keys().copied().collect::>(); types.sort_unstable_by(|a, b| reorder(*a).cmp(&reorder(*b))); - let mut last_section: Option = None; - for type_ in types { let my_section = item_ty_to_section(type_); - - // Only render section heading if the section changed - if last_section != Some(my_section) { - // Close the previous section if there was one - if last_section.is_some() { - w.write_str(ITEM_TABLE_CLOSE)?; - } - 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 - ) - )?; - last_section = Some(my_section); - } + 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) + )?; for (_, myitem) in ¬_stripped_items[&type_] { let visibility_and_hidden = |item: &clean::Item| match item.visibility(tcx) { @@ -468,16 +464,13 @@ 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(type_, item_name.as_str()), + href = myitem.html_filename(), title1 = myitem.type_(), title2 = full_path(cx, myitem), )?; } } } - } - // Close the final section - if last_section.is_some() { w.write_str(ITEM_TABLE_CLOSE)?; } @@ -1876,8 +1869,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| { render_attributes_in_code(w, it, "", cx)?; if !t.macro_rules { @@ -1885,6 +1883,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)) }) } @@ -2260,7 +2266,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 360f8bdf642e2..95cf34b251b3b 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -659,25 +659,27 @@ fn sidebar_module( ids: &mut IdMap, module_like: ModuleLike, ) -> LinkBlock<'static> { - let item_sections_in_use: FxHashSet<_> = items - .iter() - .filter(|it| { - !it.is_stripped() - && it - .name - .or_else(|| { - if let clean::ImportItem(ref i) = it.kind - && let clean::ImportKind::Simple(s) = i.kind - { - Some(s) - } else { - None - } - }) - .is_some() - }) - .map(|it| item_ty_to_section(it.type_())) - .collect(); + let mut item_sections_in_use: FxHashSet<_> = Default::default(); + + for item in items.iter().filter(|it| { + !it.is_stripped() + && it + .name + .or_else(|| { + if let clean::ImportItem(ref i) = it.kind + && let clean::ImportKind::Simple(s) = i.kind + { + Some(s) + } else { + None + } + }) + .is_some() + }) { + for type_ in item.types() { + item_sections_in_use.insert(item_ty_to_section(type_)); + } + } 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 8a72382cf90d4..c921e9703a5e4 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -731,12 +731,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("/")) { @@ -786,7 +794,7 @@ function preLoadCss(cssUrl) { block("foreigntype", "foreign-types", "Foreign Types"); block("keyword", "keywords", "Keywords"); block("attribute", "attributes", "Attributes"); - block("attr", "attributes", "Attribute Macros"); + block("attr", "attribute-macros", "Attribute Macros"); block("derive", "derives", "Derive Macros"); block("traitalias", "trait-aliases", "Trait Aliases"); } diff --git a/src/librustdoc/html/static/js/rustdoc.d.ts b/src/librustdoc/html/static/js/rustdoc.d.ts index 2d2baf22e0f63..de2155a47a8cb 100644 --- a/src/librustdoc/html/static/js/rustdoc.d.ts +++ b/src/librustdoc/html/static/js/rustdoc.d.ts @@ -245,6 +245,27 @@ declare namespace rustdoc { deprecated: boolean, unstable: boolean, associatedItemDisambiguatorOrExternCrateUrl: string?, + /** + * If `true`, this item is a `macro_rules!` macro that supports + * multiple usage syntaxes, as described in RFC 3697 and 3698. + * The syntax for such a macro looks like this: + * + * ```rust + * /// Doc Comment. + * macro_rules! NAME { + * attr(key = $value:literal) ($attached:item) => { ... }; + * derive() ($attached:item) => { ... }; + * ($bang:tt) => { ... }; + * } + * ``` + * + * Each usage syntax gets a separate EntryData---one for the attr, + * one for the derive, and one for the bang syntax---with a corresponding + * `ty` field that can be used for filtering and presenting results. + * But the documentation lives in a single `macro.NAME.html` page, and + * this boolean flag is used for generating that HREF. + */ + isBangMacro: boolean, } /** @@ -302,7 +323,7 @@ declare namespace rustdoc { type ItemType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | - 21 | 22 | 23 | 24 | 25 | 26; + 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29; /** * The viewmodel for the search engine results page. diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index e9968bedebe00..d184b74293369 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1648,7 +1648,7 @@ class DocSearch { * ], [string]>} */ const raw = JSON.parse(encoded); - return { + const item = { krate: raw[0], ty: raw[1], modulePath: raw[2] === 0 ? null : raw[2] - 1, @@ -1658,7 +1658,15 @@ class DocSearch { deprecated: raw[6] === 1 ? true : false, unstable: raw[7] === 1 ? true : false, associatedItemDisambiguatorOrExternCrateUrl: raw.length === 8 ? null : raw[8], + isBangMacro: false, }; + if (item.ty === 28 || item.ty === 29) { + // "proc attribute" is 23, "proc derive" is 24 whereas "bang macro attribute" is 28 and + // "bang macro derive" is 29, so 5 of difference to go from the latter to the former. + item.ty -= 5; + item.isBangMacro = true; + } + return item; } /** @@ -2156,7 +2164,7 @@ class DocSearch { let displayPath; let href; let traitPath = null; - const type = itemTypesName[item.ty]; + const type = item.entry && item.entry.isBangMacro ? "macro" : itemTypesName[item.ty]; const name = item.name; let path = item.modulePath; let exactPath = item.exactModulePath; @@ -3972,7 +3980,7 @@ class DocSearch { * @param {Promise[]} data * @returns {AsyncGenerator} */ - const flush = async function* (data) { + const flush = async function*(data) { const satr = sortAndTransformResults( await Promise.all(data), null, diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 5d1f4778f1c52..3e05003ab4874 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -309,7 +309,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 { @@ -336,7 +336,8 @@ 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. + // `convert_item` early returns `None` for stripped items, keywords, attributes and + // "special" macro rules. KeywordItem | AttributeItem => unreachable!(), StrippedItem(inner) => { match inner.as_ref() { @@ -898,8 +899,8 @@ impl FromClean for ItemKind { Keyword => ItemKind::Keyword, Attribute => ItemKind::Attribute, TraitAlias => ItemKind::TraitAlias, - ProcAttribute => ItemKind::ProcAttribute, - ProcDerive => ItemKind::ProcDerive, + ProcAttribute | BangMacroAttribute => ItemKind::ProcAttribute, + ProcDerive | BangMacroDerive => ItemKind::ProcDerive, } } } diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 8b66672cfa5ed..ad9f3630d1506 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -117,9 +117,9 @@ impl Res { DefKind::Fn | DefKind::AssocFn => return Suggestion::Function, // FIXME: handle macros with multiple kinds, and attribute/derive macros that aren't // proc macros - DefKind::Macro(MacroKinds::BANG) => return Suggestion::Macro, - + DefKind::Macro(MacroKinds::ATTR) => "attribute", DefKind::Macro(MacroKinds::DERIVE) => "derive", + DefKind::Macro(_) => return Suggestion::Macro, DefKind::Struct => "struct", DefKind::Enum => "enum", DefKind::Trait => "trait", diff --git a/src/librustdoc/visit.rs b/src/librustdoc/visit.rs index 9cf7d6b29b101..86115f3853011 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(..) diff --git a/tests/rustdoc-gui/attr-macros.goml b/tests/rustdoc-gui/attr-macros.goml new file mode 100644 index 0000000000000..23ad12fb582e5 --- /dev/null +++ b/tests/rustdoc-gui/attr-macros.goml @@ -0,0 +1,91 @@ +// This test ensures that a bang macro which is also an attribute macro is listed correctly in +// the sidebar and in the module. + +go-to: "file://" + |DOC_PATH| + "/test_docs/macro.b.html" +// We check that the current item in the sidebar is the correct one. +assert-text: ("#rustdoc-modnav .block.macro .current", "b") + +define-function: ( + "check_macro", + [name, info, kind], + block { + // It should be present twice in the sidebar. + assert-count: ("#rustdoc-modnav a[href='macro." + |name| + ".html']", 2) + assert-count: ("//*[@id='rustdoc-modnav']//a[text()='" + |name| + "']", 2) + + // We now go to the macro page. + click: "#rustdoc-modnav a[href='macro." + |name| + ".html']" + // It should be present twice in the sidebar. + assert-count: ("#rustdoc-modnav a[href='macro." + |name| + ".html']", 2) + assert-count: ("//*[@id='rustdoc-modnav']//a[text()='" + |name| + "']", 2) + // We check that the current item is the macro. + assert-text: ("#rustdoc-modnav .block.macro .current", |name|) + // Since the item is present twice in the sidebar, we should have two "current" items. + assert-count: ("#rustdoc-modnav .current", 2) + // We check it has the expected information. + assert-text: ("h3.macro-info", "ⓘ This is " + |info| + "/function macro") + // We check how the item declaration looks like. + assert-text: (".item-decl", "macro_rules! " + |name| + " { + " + |kind| + "() () => { ... }; + () => { ... }; +}") + } +) + +call-function: ("check_macro", {"name": "attr_macro", "info": "an attribute", "kind": "attr"}) +call-function: ("check_macro", {"name": "derive_macro", "info": "a derive", "kind": "derive"}) + +define-function: ( + "crate_page", + [name, section_id], + block { + // It should be only present twice. + assert-count: ("#main-content a[href='macro." + |name| + ".html']", 2) + // First in the "Macros" section. + assert-text: ("#macros + .item-table a[href='macro." + |name| + ".html']", |name|) + // Then in the other macro section. + assert-text: ( + "#" + |section_id| + " + .item-table a[href='macro." + |name| + ".html']", + |name|, + ) + } +) + +// Now we check it's correctly listed in the crate page. +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" +call-function: ("crate_page", {"name": "attr_macro", "section_id": "attribute-macros"}) +call-function: ("crate_page", {"name": "derive_macro", "section_id": "derives"}) +// We also check we don't have duplicated sections. +assert-count: ("//*[@id='main-content']/h2[text()='Attribute Macros']", 1) +assert-count: ("//*[@id='main-content']/h2[text()='Derive Macros']", 1) + +define-function: ( + "all_items_page", + [name, section_id], + block { + // It should be only present twice. + assert-count: ("#main-content a[href='macro." + |name| + ".html']", 2) + // First in the "Macros" section. + assert-text: ("#macros + .all-items a[href='macro." + |name| + ".html']", |name|) + // Then in the "Attribute Macros" section. + assert-text: ( + "#" + |section_id| + " + .all-items a[href='macro." + |name| + ".html']", + |name|, + ) + } +) + +// And finally we check it's correctly listed in the "all items" page. +go-to: "file://" + |DOC_PATH| + "/test_docs/all.html" +call-function: ("all_items_page", {"name": "attr_macro", "section_id": "attribute-macros"}) +call-function: ("all_items_page", {"name": "derive_macro", "section_id": "derives"}) + +// We now check a macro with all 3 different kinds. +go-to: "file://" + |DOC_PATH| + "/test_docs/macro.one_for_all_macro.html" +assert-text: (".item-decl", "macro_rules! one_for_all_macro { + attr() () => { ... }; + derive() () => { ... }; + () => { ... }; +}") +// We check it has the expected information. +assert-text: ("h3.macro-info", "ⓘ This is an attribute/derive/function 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 0ee2a66d4b689..f219291970450 100644 --- a/tests/rustdoc-gui/src/test_docs/lib.rs +++ b/tests/rustdoc-gui/src/test_docs/lib.rs @@ -8,6 +8,8 @@ #![feature(rustdoc_internals)] #![feature(doc_cfg)] #![feature(associated_type_defaults)] +#![feature(macro_attr)] +#![feature(macro_derive)] /*! Enable the feature some-feature to enjoy diff --git a/tests/rustdoc-gui/src/test_docs/macros.rs b/tests/rustdoc-gui/src/test_docs/macros.rs index 07b2b97926d43..39c91bc186206 100644 --- a/tests/rustdoc-gui/src/test_docs/macros.rs +++ b/tests/rustdoc-gui/src/test_docs/macros.rs @@ -2,3 +2,24 @@ macro_rules! a{ () => {}} #[macro_export] macro_rules! b{ () => {}} + +/// An attribute bang macro. +#[macro_export] +macro_rules! attr_macro { + attr() () => {}; + () => {}; +} + +/// A derive bang macro. +#[macro_export] +macro_rules! derive_macro { + derive() () => {}; + () => {}; +} + +#[macro_export] +macro_rules! one_for_all_macro { + attr() () => {}; + derive() () => {}; + () => {}; +} diff --git a/tests/rustdoc-html/macro/decl_macro-sidebar.rs b/tests/rustdoc-html/macro/decl_macro-sidebar.rs new file mode 100644 index 0000000000000..468b36c746db3 --- /dev/null +++ b/tests/rustdoc-html/macro/decl_macro-sidebar.rs @@ -0,0 +1,15 @@ +// This test ensures that the `foo` decl macro is present in the module sidebar. + +#![feature(decl_macro)] +#![crate_name = "foo"] + +//@has 'foo/bar/index.html' +//@has - '//*[@id="rustdoc-modnav"]/ul[@class="block macro"]//a[@href="../macro.foo.html"]' 'foo' + +pub macro foo { + () => { "bar" } +} + +/// docs +pub mod bar { +} diff --git a/tests/rustdoc-html/macro/macro-attr-derive.rs b/tests/rustdoc-html/macro/macro-attr-derive.rs new file mode 100644 index 0000000000000..2ecd72a7f568a --- /dev/null +++ b/tests/rustdoc-html/macro/macro-attr-derive.rs @@ -0,0 +1,29 @@ +// Test for the `macro_attr` and `macro_derive` features. + +#![feature(macro_attr)] +#![feature(macro_derive)] + +#![crate_name = "foo"] + +//@ has 'foo/index.html' +//@ count - '//*[@id="main-content"]/h2[@class="section-header"]' 3 +//@ has - '//*[@id="main-content"]/h2[@class="section-header"]' 'Attribute Macros' +//@ has - '//*[@id="main-content"]/h2[@class="section-header"]' 'Derive Macros' +//@ has - '//*[@id="main-content"]/h2[@class="section-header"]' 'Macros' +//@ has - '//a[@href="macro.all.html"]' 'all' + +//@ has 'foo/macro.all.html' +//@ has - '//*[@class="macro-info"]' 'ⓘ This is an attribute/derive/function macro' + +//@ has 'foo/all.html' +//@ count - '//*[@id="main-content"]/h3' 3 +//@ has - '//*[@id="main-content"]/h3' 'Attribute Macros' +//@ has - '//*[@id="main-content"]/h3' 'Derive Macros' +//@ has - '//*[@id="main-content"]/h3' 'Macros' + +#[macro_export] +macro_rules! all { + () => {}; + attr() () => {}; + derive() () => {}; +} diff --git a/tests/rustdoc-html/macro/macro-attr.rs b/tests/rustdoc-html/macro/macro-attr.rs new file mode 100644 index 0000000000000..1ab94e0a78645 --- /dev/null +++ b/tests/rustdoc-html/macro/macro-attr.rs @@ -0,0 +1,22 @@ +// Test for the `macro_attr` and `macro_derive` features. + +#![feature(macro_attr)] + +#![crate_name = "foo"] + +//@ has 'foo/index.html' +//@ count - '//*[@id="main-content"]/h2[@class="section-header"]' 1 +//@ has - '//*[@id="main-content"]/h2[@class="section-header"]' 'Attribute Macros' +//@ has - '//a[@href="attr.attr.html"]' 'attr' + +//@ has 'foo/attr.attr.html' +//@ has - '//*[@class="rust item-decl"]/code' '#[attr]' + +//@ has 'foo/all.html' +//@ count - '//*[@id="main-content"]/h3' 1 +//@ has - '//*[@id="main-content"]/h3' 'Attribute Macros' + +#[macro_export] +macro_rules! attr { + attr() () => {}; +} diff --git a/tests/rustdoc-html/macro/macro-derive.rs b/tests/rustdoc-html/macro/macro-derive.rs new file mode 100644 index 0000000000000..e4af31ef565c6 --- /dev/null +++ b/tests/rustdoc-html/macro/macro-derive.rs @@ -0,0 +1,22 @@ +// Test for the `macro_attr` and `macro_derive` features. + +#![feature(macro_derive)] + +#![crate_name = "foo"] + +//@ has 'foo/index.html' +//@ count - '//*[@id="main-content"]/h2[@class="section-header"]' 1 +//@ has - '//*[@id="main-content"]/h2[@class="section-header"]' 'Derive Macros' +//@ has - '//a[@href="derive.derive.html"]' 'derive' + +//@ has 'foo/derive.derive.html' +//@ has - '//*[@class="rust item-decl"]/code' '#[derive(derive)]' + +//@ has 'foo/all.html' +//@ count - '//*[@id="main-content"]/h3' 1 +//@ has - '//*[@id="main-content"]/h3' 'Derive Macros' + +#[macro_export] +macro_rules! derive { + derive() () => {}; +} diff --git a/tests/rustdoc-html/macro/macro-no-bang.rs b/tests/rustdoc-html/macro/macro-no-bang.rs new file mode 100644 index 0000000000000..f51b6081d2003 --- /dev/null +++ b/tests/rustdoc-html/macro/macro-no-bang.rs @@ -0,0 +1,26 @@ +// Test for the `macro_attr` and `macro_derive` features. + +#![feature(macro_attr)] +#![feature(macro_derive)] + +#![crate_name = "foo"] + +//@ has 'foo/index.html' +//@ count - '//*[@id="main-content"]/h2[@class="section-header"]' 2 +//@ has - '//*[@id="main-content"]/h2[@class="section-header"]' 'Attribute Macros' +//@ has - '//*[@id="main-content"]/h2[@class="section-header"]' 'Derive Macros' +//@ has - '//a[@href="macro.no_bang.html"]' 'no_bang' + +//@ has 'foo/macro.no_bang.html' +//@ has - '//*[@class="macro-info"]' 'ⓘ This is an attribute/derive macro' + +//@ has 'foo/all.html' +//@ count - '//*[@id="main-content"]/h3' 2 +//@ has - '//*[@id="main-content"]/h3' 'Attribute Macros' +//@ has - '//*[@id="main-content"]/h3' 'Derive Macros' + +#[macro_export] +macro_rules! no_bang { + attr() () => {}; + derive() () => {}; +} diff --git a/tests/rustdoc-js/macro-kinds.js b/tests/rustdoc-js/macro-kinds.js new file mode 100644 index 0000000000000..b22b0498b1e6e --- /dev/null +++ b/tests/rustdoc-js/macro-kinds.js @@ -0,0 +1,61 @@ +// exact-check + +const EXPECTED = [ + { + 'query': 'macro:macro', + 'others': [ + { + 'path': 'macro_kinds', + 'name': 'macro1', + 'href': '../macro_kinds/macro.macro1.html', + 'ty': 16, + }, + { + 'path': 'macro_kinds', + 'name': 'macro1', + 'href': '../macro_kinds/macro.macro1.html', + 'ty': 23, + }, + { + 'path': 'macro_kinds', + 'name': 'macro1', + 'href': '../macro_kinds/macro.macro1.html', + 'ty': 24, + }, + { + 'path': 'macro_kinds', + 'name': 'macro2', + 'href': '../macro_kinds/attr.macro2.html', + 'ty': 23, + }, + { + 'path': 'macro_kinds', + 'name': 'macro4', + 'href': '../macro_kinds/derive.macro4.html', + 'ty': 24, + }, + { + 'path': 'macro_kinds', + 'name': 'macro3', + 'href': '../macro_kinds/macro.macro3.html', + 'ty': 16, + }, + ], + }, + { + 'query': 'attr:macro', + 'others': [ + { 'path': 'macro_kinds', 'name': 'macro1', 'href': '../macro_kinds/macro.macro1.html' }, + { 'path': 'macro_kinds', 'name': 'macro2', 'href': '../macro_kinds/attr.macro2.html' }, + ], + }, + { + 'query': 'derive:macro', + 'others': [ + { 'path': 'macro_kinds', 'name': 'macro1', 'href': '../macro_kinds/macro.macro1.html' }, + { + 'path': 'macro_kinds', 'name': 'macro4', 'href': '../macro_kinds/derive.macro4.html' + }, + ], + }, +]; diff --git a/tests/rustdoc-js/macro-kinds.rs b/tests/rustdoc-js/macro-kinds.rs new file mode 100644 index 0000000000000..3bd753cadc677 --- /dev/null +++ b/tests/rustdoc-js/macro-kinds.rs @@ -0,0 +1,28 @@ +// Test which ensures that attribute macros are correctly handled by the search. +// For example: `macro1` should appear in both `attr` and `macro` filters whereas +// `macro2` and `macro3` should only appear in `attr` or `macro` filters respectively. + +#![feature(macro_attr)] +#![feature(macro_derive)] + +#[macro_export] +macro_rules! macro1 { + attr() () => {}; + derive() () => {}; + () => {}; +} + +#[macro_export] +macro_rules! macro2 { + attr() () => {}; +} + +#[macro_export] +macro_rules! macro3 { + () => {}; +} + +#[macro_export] +macro_rules! macro4 { + derive() () => {}; +}