diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 1ec56868f378f..3b6f29d51fc13 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -188,6 +188,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { notable_trait => doc_notable_trait } "meant for internal use only" { + attribute => rustdoc_internals keyword => rustdoc_internals fake_variadic => rustdoc_internals search_unbox => rustdoc_internals diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 7c237d708c0ad..7631043bdace5 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -161,6 +161,10 @@ passes_doc_alias_start_end = passes_doc_attr_not_crate_level = `#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute +passes_doc_attribute_not_attribute = + nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = "...")]` + .help = only existing builtin attributes are allowed in core/std + passes_doc_cfg_hide_takes_list = `#[doc(cfg_hide(...))]` takes a list of attributes @@ -189,16 +193,16 @@ passes_doc_inline_only_use = passes_doc_invalid = invalid `doc` attribute -passes_doc_keyword_empty_mod = - `#[doc(keyword = "...")]` should be used on empty modules +passes_doc_keyword_attribute_empty_mod = + `#[doc({$attr_name} = "...")]` should be used on empty modules + +passes_doc_keyword_attribute_not_mod = + `#[doc({$attr_name} = "...")]` should be used on modules passes_doc_keyword_not_keyword = nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]` .help = only existing keywords are allowed in core/std -passes_doc_keyword_not_mod = - `#[doc(keyword = "...")]` should be used on modules - passes_doc_keyword_only_impl = `#[doc(keyword = "...")]` should be used on impl blocks diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 4e2be8ff0b817..f4f67d8ebdcb7 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1069,7 +1069,12 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - fn check_doc_keyword(&self, meta: &MetaItemInner, hir_id: HirId) { + fn check_doc_keyword_and_attribute( + &self, + meta: &MetaItemInner, + hir_id: HirId, + is_keyword: bool, + ) { fn is_doc_keyword(s: Symbol) -> bool { // FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we // can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the @@ -1077,9 +1082,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> { s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy } - let doc_keyword = match meta.value_str() { + fn is_builtin_attr(s: Symbol) -> bool { + rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&s) + } + + fn get_attr_name(is_keyword: bool) -> &'static str { + if is_keyword { "keyword" } else { "attribute " } + } + + let value = match meta.value_str() { Some(value) if value != sym::empty => value, - _ => return self.doc_attr_str_error(meta, "keyword"), + _ => return self.doc_attr_str_error(meta, get_attr_name(is_keyword)), }; let item_kind = match self.tcx.hir_node(hir_id) { @@ -1089,19 +1102,32 @@ impl<'tcx> CheckAttrVisitor<'tcx> { match item_kind { Some(ItemKind::Mod(_, module)) => { if !module.item_ids.is_empty() { - self.dcx().emit_err(errors::DocKeywordEmptyMod { span: meta.span() }); + self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod { + span: meta.span(), + attr_name: get_attr_name(is_keyword), + }); return; } } _ => { - self.dcx().emit_err(errors::DocKeywordNotMod { span: meta.span() }); + self.dcx().emit_err(errors::DocKeywordAttributeNotMod { + span: meta.span(), + attr_name: get_attr_name(is_keyword), + }); return; } } - if !is_doc_keyword(doc_keyword) { - self.dcx().emit_err(errors::DocKeywordNotKeyword { + if is_keyword { + if !is_doc_keyword(value) { + self.dcx().emit_err(errors::DocKeywordNotKeyword { + span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()), + keyword: value, + }); + } + } else if !is_builtin_attr(value) { + self.dcx().emit_err(errors::DocAttributeNotAttribute { span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()), - keyword: doc_keyword, + attribute: value, }); } } @@ -1356,7 +1382,13 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Some(sym::keyword) => { if self.check_attr_not_crate_level(meta, hir_id, "keyword") { - self.check_doc_keyword(meta, hir_id); + self.check_doc_keyword_and_attribute(meta, hir_id, true); + } + } + + Some(sym::attribute) => { + if self.check_attr_not_crate_level(meta, hir_id, "attribute") { + self.check_doc_keyword_and_attribute(meta, hir_id, false); } } diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index f0d4b610f6384..279fa11206c86 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -229,10 +229,11 @@ pub(crate) struct DocAliasMalformed { } #[derive(Diagnostic)] -#[diag(passes_doc_keyword_empty_mod)] -pub(crate) struct DocKeywordEmptyMod { +#[diag(passes_doc_keyword_attribute_empty_mod)] +pub(crate) struct DocKeywordAttributeEmptyMod { #[primary_span] pub span: Span, + pub attr_name: &'static str, } #[derive(Diagnostic)] @@ -245,10 +246,20 @@ pub(crate) struct DocKeywordNotKeyword { } #[derive(Diagnostic)] -#[diag(passes_doc_keyword_not_mod)] -pub(crate) struct DocKeywordNotMod { +#[diag(passes_doc_attribute_not_attribute)] +#[help] +pub(crate) struct DocAttributeNotAttribute { + #[primary_span] + pub span: Span, + pub attribute: Symbol, +} + +#[derive(Diagnostic)] +#[diag(passes_doc_keyword_attribute_not_mod)] +pub(crate) struct DocKeywordAttributeNotMod { #[primary_span] pub span: Span, + pub attr_name: &'static str, } #[derive(Diagnostic)] diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index fa4b024c422f0..5dbe4a145da88 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -5072,7 +5072,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } ResolveDocLinks::Exported if !maybe_exported.eval(self.r) - && !rustdoc::has_primitive_or_keyword_docs(attrs) => + && !rustdoc::has_primitive_or_keyword_or_attribute_docs(attrs) => { return; } diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index fa839d2748d86..b46f93b4fc5af 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -359,8 +359,8 @@ pub fn inner_docs(attrs: &[impl AttributeExt]) -> bool { attrs.iter().find(|a| a.doc_str().is_some()).is_none_or(|a| a.style() == ast::AttrStyle::Inner) } -/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]`. -pub fn has_primitive_or_keyword_docs(attrs: &[impl AttributeExt]) -> bool { +/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]` or `#[doc(attribute)]`. +pub fn has_primitive_or_keyword_or_attribute_docs(attrs: &[impl AttributeExt]) -> bool { for attr in attrs { if attr.has_name(sym::rustc_doc_primitive) { return true; @@ -368,7 +368,7 @@ pub fn has_primitive_or_keyword_docs(attrs: &[impl AttributeExt]) -> bool { && let Some(items) = attr.meta_item_list() { for item in items { - if item.has_name(sym::keyword) { + if item.has_name(sym::keyword) || item.has_name(sym::attribute) { return true; } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index cb9ccf4cc3f23..6c4e57e76a22f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -540,6 +540,7 @@ symbols! { att_syntax, attr, attr_literals, + attribute, attributes, audit_that, augmented_assignments, diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 27910ad0ab796..79d2194d995e8 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -211,6 +211,23 @@ To do so, the `#[doc(keyword = "...")]` attribute is used. Example: mod empty_mod {} ``` +### Document builtin attributes + +This is for Rust compiler internal use only. + +Rust builtin attributes are documented in the standard library (look for `repr` for example). + +To do so, the `#[doc(attribute = "...")]` attribute is used. Example: + +```rust +#![feature(rustdoc_internals)] +#![allow(internal_features)] + +/// Some documentation about the attribute. +#[doc(attribute = "repr")] +mod empty_mod {} +``` + ### Use the Rust logo as the crate logo This is for official Rust project use only. diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 58e05bd1e8574..8ff9a73a0bfd9 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -200,16 +200,27 @@ impl ExternalCrate { } pub(crate) fn keywords(&self, tcx: TyCtxt<'_>) -> ThinVec<(DefId, Symbol)> { + self.retrieve_keywords_or_documented_attributes(tcx, sym::keyword) + } + pub(crate) fn documented_attributes(&self, tcx: TyCtxt<'_>) -> ThinVec<(DefId, Symbol)> { + self.retrieve_keywords_or_documented_attributes(tcx, sym::attribute) + } + + fn retrieve_keywords_or_documented_attributes( + &self, + tcx: TyCtxt<'_>, + name: Symbol, + ) -> ThinVec<(DefId, Symbol)> { let root = self.def_id(); - let as_keyword = |res: Res| { + let as_target = |res: Res| { if let Res::Def(DefKind::Mod, def_id) = res { let mut keyword = None; let meta_items = tcx .get_attrs(def_id, sym::doc) .flat_map(|attr| attr.meta_item_list().unwrap_or_default()); for meta in meta_items { - if meta.has_name(sym::keyword) + if meta.has_name(name) && let Some(v) = meta.value_str() { keyword = Some(v); @@ -228,14 +239,14 @@ impl ExternalCrate { let item = tcx.hir_item(id); match item.kind { hir::ItemKind::Mod(..) => { - as_keyword(Res::Def(DefKind::Mod, id.owner_id.to_def_id())) + as_target(Res::Def(DefKind::Mod, id.owner_id.to_def_id())) } _ => None, } }) .collect() } else { - tcx.module_children(root).iter().map(|item| item.res).filter_map(as_keyword).collect() + tcx.module_children(root).iter().map(|item| item.res).filter_map(as_target).collect() } } @@ -597,6 +608,20 @@ impl Item { pub(crate) fn is_keyword(&self) -> bool { self.type_() == ItemType::Keyword } + pub(crate) fn is_attribute(&self) -> bool { + self.type_() == ItemType::Attribute + } + /// Returns `true` if the item kind is one of the following: + /// + /// * `ItemType::Primitive` + /// * `ItemType::Keyword` + /// * `ItemType::Attribute` + /// + /// They are considered fake because they only exist thanks to their + /// `#[doc(primitive|keyword|attribute)]` attribute. + pub(crate) fn is_fake_item(&self) -> bool { + matches!(self.type_(), ItemType::Primitive | ItemType::Keyword | ItemType::Attribute) + } pub(crate) fn is_stripped(&self) -> bool { match self.kind { StrippedItem(..) => true, @@ -727,7 +752,9 @@ impl Item { // Primitives and Keywords are written in the source code as private modules. // The modules need to be private so that nobody actually uses them, but the // keywords and primitives that they are documenting are public. - ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) => return Some(Visibility::Public), + ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) | ItemKind::AttributeItem => { + return Some(Visibility::Public); + } // Variant fields inherit their enum's visibility. StructFieldItem(..) if is_field_vis_inherited(tcx, def_id) => { return None; @@ -943,7 +970,12 @@ pub(crate) enum ItemKind { AssocTypeItem(Box, Vec), /// An item that has been stripped by a rustdoc pass StrippedItem(Box), + /// This item represents a module with a `#[doc(keyword = "...")]` attribute which is used + /// to generate documentation for Rust keywords. KeywordItem, + /// This item represents a module with a `#[doc(attribute = "...")]` attribute which is used + /// to generate documentation for Rust builtin attributes. + AttributeItem, } impl ItemKind { @@ -984,7 +1016,8 @@ impl ItemKind { | RequiredAssocTypeItem(..) | AssocTypeItem(..) | StrippedItem(_) - | KeywordItem => [].iter(), + | KeywordItem + | AttributeItem => [].iter(), } } diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index c58b07a5b6731..522fb487fc823 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -59,6 +59,7 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { let local_crate = ExternalCrate { crate_num: LOCAL_CRATE }; let primitives = local_crate.primitives(cx.tcx); let keywords = local_crate.keywords(cx.tcx); + let documented_attributes = local_crate.documented_attributes(cx.tcx); { let ItemKind::ModuleItem(m) = &mut module.inner.kind else { unreachable!() }; m.items.extend(primitives.iter().map(|&(def_id, prim)| { @@ -72,6 +73,9 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { m.items.extend(keywords.into_iter().map(|(def_id, kw)| { Item::from_def_id_and_parts(def_id, Some(kw), ItemKind::KeywordItem, cx) })); + m.items.extend(documented_attributes.into_iter().map(|(def_id, kw)| { + Item::from_def_id_and_parts(def_id, Some(kw), ItemKind::AttributeItem, cx) + })); } Crate { module, external_traits: Box::new(mem::take(&mut cx.external_traits)) } diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs index c03d16ad081bf..ee5f260615db5 100644 --- a/src/librustdoc/fold.rs +++ b/src/librustdoc/fold.rs @@ -96,7 +96,8 @@ pub(crate) trait DocFolder: Sized { | ImplAssocConstItem(..) | RequiredAssocTypeItem(..) | AssocTypeItem(..) - | KeywordItem => kind, + | KeywordItem + | AttributeItem => kind, } } diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 4989bd718c9f9..64a47f3d8e2c0 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -357,7 +357,8 @@ impl DocFolder for CacheBuilder<'_, '_> { | clean::RequiredAssocTypeItem(..) | clean::AssocTypeItem(..) | clean::StrippedItem(..) - | clean::KeywordItem => { + | clean::KeywordItem + | clean::AttributeItem => { // FIXME: Do these need handling? // The person writing this comment doesn't know. // So would rather leave them to an expert, diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index 3aba7a370adb2..38e3a82963d0a 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -57,6 +57,7 @@ pub(crate) enum ItemType { TraitAlias = 25, // This number is reserved for use in JavaScript // Generic = 26, + Attribute = 27, } impl Serialize for ItemType { @@ -102,6 +103,7 @@ impl<'a> From<&'a clean::Item> for ItemType { clean::RequiredAssocTypeItem(..) | clean::AssocTypeItem(..) => ItemType::AssocType, clean::ForeignTypeItem => ItemType::ForeignType, clean::KeywordItem => ItemType::Keyword, + clean::AttributeItem => ItemType::Attribute, clean::TraitAliasItem(..) => ItemType::TraitAlias, clean::ProcMacroItem(mac) => match mac.kind { MacroKind::Bang => ItemType::Macro, @@ -189,6 +191,7 @@ impl ItemType { ItemType::ProcAttribute => "attr", ItemType::ProcDerive => "derive", ItemType::TraitAlias => "traitalias", + ItemType::Attribute => "attribute", } } pub(crate) fn is_method(&self) -> bool { diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 3821445165754..112a7b92e73bd 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -206,7 +206,7 @@ impl<'tcx> Context<'tcx> { if !is_module { title.push_str(it.name.unwrap().as_str()); } - if !it.is_primitive() && !it.is_keyword() { + if !it.is_fake_item() { if !is_module { title.push_str(" in "); } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 66d5aafa3c1ef..ba98544a8ce09 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -2416,6 +2416,7 @@ pub(crate) enum ItemSection { AssociatedConstants, ForeignTypes, Keywords, + Attributes, AttributeMacros, DeriveMacros, TraitAliases, @@ -2448,6 +2449,7 @@ impl ItemSection { AssociatedConstants, ForeignTypes, Keywords, + Attributes, AttributeMacros, DeriveMacros, TraitAliases, @@ -2477,6 +2479,7 @@ impl ItemSection { Self::AssociatedConstants => "associated-consts", Self::ForeignTypes => "foreign-types", Self::Keywords => "keywords", + Self::Attributes => "attributes", Self::AttributeMacros => "attributes", Self::DeriveMacros => "derives", Self::TraitAliases => "trait-aliases", @@ -2506,6 +2509,7 @@ impl ItemSection { Self::AssociatedConstants => "Associated Constants", Self::ForeignTypes => "Foreign Types", Self::Keywords => "Keywords", + Self::Attributes => "Attributes", Self::AttributeMacros => "Attribute Macros", Self::DeriveMacros => "Derive Macros", Self::TraitAliases => "Trait Aliases", @@ -2536,6 +2540,7 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection { ItemType::AssocConst => ItemSection::AssociatedConstants, ItemType::ForeignType => ItemSection::ForeignTypes, ItemType::Keyword => ItemSection::Keywords, + ItemType::Attribute => ItemSection::Attributes, ItemType::ProcAttribute => ItemSection::AttributeMacros, ItemType::ProcDerive => 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 a75088d27ccda..d2b5eb093eef9 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -178,6 +178,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp clean::ConstantItem(..) => "Constant ", clean::ForeignTypeItem => "Foreign Type ", clean::KeywordItem => "Keyword ", + clean::AttributeItem => "Attribute ", clean::TraitAliasItem(..) => "Trait Alias ", _ => { // We don't generate pages for any other type. @@ -198,7 +199,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp let src_href = if cx.info.include_sources && !item.is_primitive() { cx.src_href(item) } else { None }; - let path_components = if item.is_primitive() || item.is_keyword() { + let path_components = if item.is_fake_item() { vec![] } else { let cur = &cx.current; @@ -257,7 +258,9 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp clean::ForeignTypeItem => { write!(buf, "{}", item_foreign_type(cx, item)) } - clean::KeywordItem => write!(buf, "{}", item_keyword(cx, item)), + clean::KeywordItem | clean::AttributeItem => { + write!(buf, "{}", item_keyword_or_attribute(cx, item)) + } clean::TraitAliasItem(ta) => { write!(buf, "{}", item_trait_alias(cx, item, ta)) } @@ -2151,7 +2154,7 @@ fn item_foreign_type(cx: &Context<'_>, it: &clean::Item) -> impl fmt::Display { }) } -fn item_keyword(cx: &Context<'_>, it: &clean::Item) -> impl fmt::Display { +fn item_keyword_or_attribute(cx: &Context<'_>, it: &clean::Item) -> impl fmt::Display { document(cx, it, None, HeadingOffset::H2) } diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css index a3c6bf9816169..5c02e2eb26a3e 100644 --- a/src/librustdoc/html/static/css/noscript.css +++ b/src/librustdoc/html/static/css/noscript.css @@ -75,6 +75,7 @@ nav.sub { --function-link-color: #ad7c37; --macro-link-color: #068000; --keyword-link-color: #3873ad; + --attribute-link-color: #3873ad; --mod-link-color: #3873ad; --link-color: #3873ad; --sidebar-link-color: #356da4; @@ -180,6 +181,7 @@ nav.sub { --function-link-color: #2bab63; --macro-link-color: #09bd00; --keyword-link-color: #d2991d; + --attribute-link-color: #d2991d; --mod-link-color: #d2991d; --link-color: #d2991d; --sidebar-link-color: #fdbf35; diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 7be83b65fbfaf..4b15ab73756db 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -376,6 +376,10 @@ span.keyword, a.keyword { color: var(--keyword-link-color); } +span.attribute, a.attribute { + color: var(--attribute-link-color); +} + a { color: var(--link-color); text-decoration: none; @@ -2953,6 +2957,7 @@ by default. --function-link-color: #ad7c37; --macro-link-color: #068000; --keyword-link-color: #3873ad; + --attribute-link-color: #3873ad; --mod-link-color: #3873ad; --link-color: #3873ad; --sidebar-link-color: #356da4; @@ -3057,6 +3062,7 @@ by default. --function-link-color: #2bab63; --macro-link-color: #09bd00; --keyword-link-color: #d2991d; + --attribute-link-color: #d2991d; --mod-link-color: #d2991d; --link-color: #d2991d; --sidebar-link-color: #fdbf35; @@ -3170,6 +3176,7 @@ Original by Dempfi (https://github.com/dempfi/ayu) --function-link-color: #fdd687; --macro-link-color: #a37acc; --keyword-link-color: #39afd7; + --attribute-link-color: #39afd7; --mod-link-color: #39afd7; --link-color: #39afd7; --sidebar-link-color: #53b1db; diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 7b1a61a3ffa45..ca15dadf8abf8 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -666,6 +666,7 @@ function preLoadCss(cssUrl) { //block("associatedconstant", "associated-consts", "Associated Constants"); block("foreigntype", "foreign-types", "Foreign Types"); block("keyword", "keywords", "Keywords"); + block("attribute", "attributes", "Attributes"); block("attr", "attributes", "Attribute Macros"); block("derive", "derives", "Derive Macros"); block("traitalias", "trait-aliases", "Trait Aliases"); diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index dce5fddb3177e..62b5127e49b6b 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -78,6 +78,7 @@ const itemTypes = [ "derive", "traitalias", // 25 "generic", + "attribute", ]; // used for special search precedence @@ -2703,7 +2704,7 @@ class DocSearch { displayPath = item.path + "::"; href = this.rootPath + item.path.replace(/::/g, "/") + "/index.html#reexport." + name; - } else if (type === "primitive" || type === "keyword") { + } else if (type === "primitive" || type === "keyword" || type === "attribute") { displayPath = ""; exactPath = ""; href = this.rootPath + path.replace(/::/g, "/") + @@ -4690,6 +4691,8 @@ const longItemTypes = [ "attribute macro", "derive macro", "trait alias", + "", + "attribute", ]; // @ts-expect-error let currentResults; diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 6bdf3b5fe3876..50ac8cf5d5a79 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -47,7 +47,7 @@ impl JsonRenderer<'_> { let clean::ItemInner { name, item_id, .. } = *item.inner; let id = self.id_from_item(&item); let inner = match item.kind { - clean::KeywordItem => return None, + clean::KeywordItem | clean::AttributeItem => return None, clean::StrippedItem(ref inner) => { match &**inner { // We document stripped modules as with `Module::is_stripped` set to @@ -111,7 +111,7 @@ impl JsonRenderer<'_> { fn ids(&self, items: &[clean::Item]) -> Vec { items .iter() - .filter(|i| !i.is_stripped() && !i.is_keyword()) + .filter(|i| !i.is_stripped() && !i.is_keyword() && !i.is_attribute()) .map(|i| self.id_from_item(&i)) .collect() } @@ -119,7 +119,10 @@ impl JsonRenderer<'_> { fn ids_keeping_stripped(&self, items: &[clean::Item]) -> Vec> { items .iter() - .map(|i| (!i.is_stripped() && !i.is_keyword()).then(|| self.id_from_item(&i))) + .map(|i| { + (!i.is_stripped() && !i.is_keyword() && !i.is_attribute()) + .then(|| self.id_from_item(&i)) + }) .collect() } } @@ -304,8 +307,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 and keywords. - KeywordItem => unreachable!(), + // `convert_item` early returns `None` for stripped items, keywords and attributes. + KeywordItem | AttributeItem => unreachable!(), StrippedItem(inner) => { match inner.as_ref() { ModuleItem(m) => ItemEnum::Module(Module { @@ -857,6 +860,7 @@ impl FromClean for ItemKind { AssocType => ItemKind::AssocType, ForeignType => ItemKind::ExternType, Keyword => ItemKind::Keyword, + Attribute => ItemKind::Attribute, TraitAlias => ItemKind::TraitAlias, ProcAttribute => ItemKind::ProcAttribute, ProcDerive => ItemKind::ProcDerive, diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs index 8028afea363d5..e0ea760cf3ba5 100644 --- a/src/librustdoc/passes/check_doc_test_visibility.rs +++ b/src/librustdoc/passes/check_doc_test_visibility.rs @@ -67,6 +67,7 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) - | clean::ImportItem(_) | clean::PrimitiveItem(_) | clean::KeywordItem + | clean::AttributeItem | clean::ModuleItem(_) | clean::TraitAliasItem(_) | clean::ForeignFunctionItem(..) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 1daaba3b86c5c..73369e501f8dc 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -19,7 +19,7 @@ use rustc_hir::{Mutability, Safety}; use rustc_middle::ty::{Ty, TyCtxt}; use rustc_middle::{bug, span_bug, ty}; use rustc_resolve::rustdoc::{ - MalformedGenerics, has_primitive_or_keyword_docs, prepare_to_doc_link_resolution, + MalformedGenerics, has_primitive_or_keyword_or_attribute_docs, prepare_to_doc_link_resolution, source_span_for_markdown_range, strip_generics_from_path, }; use rustc_session::config::CrateType; @@ -1031,7 +1031,7 @@ impl LinkCollector<'_, '_> { && let Some(def_id) = item.item_id.as_def_id() && let Some(def_id) = def_id.as_local() && !self.cx.tcx.effective_visibilities(()).is_exported(def_id) - && !has_primitive_or_keyword_docs(&item.attrs.other_attrs) + && !has_primitive_or_keyword_or_attribute_docs(&item.attrs.other_attrs) { // Skip link resolution for non-exported items. return; diff --git a/src/librustdoc/passes/propagate_stability.rs b/src/librustdoc/passes/propagate_stability.rs index 7b3da8d7c0fbd..368ca9c5e3e7e 100644 --- a/src/librustdoc/passes/propagate_stability.rs +++ b/src/librustdoc/passes/propagate_stability.rs @@ -106,7 +106,8 @@ impl DocFolder for StabilityPropagator<'_, '_> { | ItemKind::RequiredAssocTypeItem(..) | ItemKind::AssocTypeItem(..) | ItemKind::PrimitiveItem(..) - | ItemKind::KeywordItem => own_stability, + | ItemKind::KeywordItem + | ItemKind::AttributeItem => own_stability, ItemKind::StrippedItem(..) => unreachable!(), } diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index eedbbca0f8dfc..99d22526f85b7 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -133,6 +133,8 @@ impl DocFolder for Stripper<'_, '_> { // Keywords are never stripped clean::KeywordItem => {} + // Attributes are never stripped + clean::AttributeItem => {} } let fastreturn = match i.kind { diff --git a/src/librustdoc/visit.rs b/src/librustdoc/visit.rs index b8b619514aad9..4d31409afe825 100644 --- a/src/librustdoc/visit.rs +++ b/src/librustdoc/visit.rs @@ -49,7 +49,8 @@ pub(crate) trait DocVisitor<'a>: Sized { | ImplAssocConstItem(..) | RequiredAssocTypeItem(..) | AssocTypeItem(..) - | KeywordItem => {} + | KeywordItem + | AttributeItem => {} } } diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index 8a3ab6f864072..d2e6fb22efeaf 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -30,7 +30,7 @@ pub type FxHashMap = HashMap; // re-export for use in src/librustdoc /// This integer is incremented with every breaking change to the API, /// and is returned along with the JSON blob as [`Crate::format_version`]. /// Consuming code should assert that this value matches the format version(s) that it supports. -pub const FORMAT_VERSION: u32 = 46; +pub const FORMAT_VERSION: u32 = 47; /// The root of the emitted JSON blob. /// @@ -460,6 +460,11 @@ pub enum ItemKind { /// [`Item`]s of this kind only come from the come library and exist solely /// to carry documentation for the respective keywords. Keyword, + /// An attribute declaration. + /// + /// [`Item`]s of this kind only come from the core library and exist solely + /// to carry documentation for the respective builtin attributes. + Attribute, } /// Specific fields of an item. diff --git a/src/tools/jsondoclint/src/item_kind.rs b/src/tools/jsondoclint/src/item_kind.rs index 51146831efa40..e2738636a1458 100644 --- a/src/tools/jsondoclint/src/item_kind.rs +++ b/src/tools/jsondoclint/src/item_kind.rs @@ -26,6 +26,7 @@ pub(crate) enum Kind { AssocType, Primitive, Keyword, + Attribute, // Not in ItemKind ProcMacro, } @@ -53,6 +54,7 @@ impl Kind { ExternType => true, // FIXME(adotinthevoid): I'm not sure if these are correct + Attribute => false, Keyword => false, ProcAttribute => false, ProcDerive => false, @@ -109,6 +111,7 @@ impl Kind { Kind::Primitive => false, Kind::Keyword => false, Kind::ProcMacro => false, + Kind::Attribute => false, } } @@ -163,6 +166,7 @@ impl Kind { match s.kind { ItemKind::AssocConst => AssocConst, ItemKind::AssocType => AssocType, + ItemKind::Attribute => Attribute, ItemKind::Constant => Constant, ItemKind::Enum => Enum, ItemKind::ExternCrate => ExternCrate, diff --git a/tests/rustdoc-gui/links-color.goml b/tests/rustdoc-gui/links-color.goml index f11920cdd8c2d..a363175c1ddbb 100644 --- a/tests/rustdoc-gui/links-color.goml +++ b/tests/rustdoc-gui/links-color.goml @@ -9,7 +9,7 @@ show-text: true define-function: ( "check-colors", [theme, mod, macro, struct, enum, trait, fn, type, union, keyword, - sidebar, sidebar_current, sidebar_current_background], + attribute, sidebar, sidebar_current, sidebar_current_background], block { call-function: ("switch-theme", {"theme": |theme|}) // Checking results colors. @@ -22,6 +22,7 @@ define-function: ( assert-css: (".item-table .type", {"color": |type|}, ALL) assert-css: (".item-table .union", {"color": |union|}, ALL) assert-css: (".item-table .keyword", {"color": |keyword|}, ALL) + assert-css: (".item-table .attribute", {"color": |attribute|}, ALL) // Checking sidebar elements. assert-css: ( ".sidebar-elems li:not(.current) a", @@ -58,6 +59,7 @@ call-function: ( "type": "#ffa0a5", "union": "#ffa0a5", "keyword": "#39afd7", + "attribute": "#39afd7", "sidebar": "#53b1db", "sidebar_current": "#ffb44c", "sidebar_current_background": "transparent", @@ -76,6 +78,7 @@ call-function: ( "type": "#2dbfb8", "union": "#2dbfb8", "keyword": "#d2991d", + "attribute": "#d2991d", "sidebar": "#fdbf35", "sidebar_current": "#fdbf35", "sidebar_current_background": "#444", @@ -94,6 +97,7 @@ call-function: ( "type": "#ad378a", "union": "#ad378a", "keyword": "#3873ad", + "attribute": "#3873ad", "sidebar": "#356da4", "sidebar_current": "#356da4", "sidebar_current_background": "#fff", diff --git a/tests/rustdoc-gui/module-items-font.goml b/tests/rustdoc-gui/module-items-font.goml index 0e6dd81c05b8d..bed95b378c6ab 100644 --- a/tests/rustdoc-gui/module-items-font.goml +++ b/tests/rustdoc-gui/module-items-font.goml @@ -65,3 +65,12 @@ assert-css: ( "#keywords + .item-table dd", {"font-family": '"Source Serif 4", NanumBarunGothic, serif'}, ) +// attributes +assert-css: ( + "#attributes + .item-table dt a", + {"font-family": '"Fira Sans", Arial, NanumBarunGothic, sans-serif'}, +) +assert-css: ( + "#attributes + .item-table dd", + {"font-family": '"Source Serif 4", NanumBarunGothic, serif'}, +) diff --git a/tests/rustdoc-gui/search-result-color.goml b/tests/rustdoc-gui/search-result-color.goml index e6dd504d7036f..a760acd14135c 100644 --- a/tests/rustdoc-gui/search-result-color.goml +++ b/tests/rustdoc-gui/search-result-color.goml @@ -7,7 +7,8 @@ define-function: ( [ theme, count_color, desc_color, path_color, bottom_border_color, keyword_color, struct_color, associatedtype_color, tymethod_color, method_color, structfield_color, - structfield_hover_color, macro_color, fn_color, hover_path_color, hover_background, grey + structfield_hover_color, macro_color, fn_color, hover_path_color, hover_background, + attribute_color, grey ], block { call-function: ("switch-theme", {"theme": |theme|}) @@ -45,6 +46,11 @@ define-function: ( "color": |keyword_color|, "hover_color": |keyword_color|, }) + call-function: ("check-result-color", { + "result_kind": "attribute", + "color": |attribute_color|, + "hover_color": |attribute_color|, + }) call-function: ("check-result-color", { "result_kind": "struct", "color": |struct_color|, @@ -154,6 +160,7 @@ call-function: ("check-search-color", { "path_color": "#0096cf", "bottom_border_color": "#aaa3", "keyword_color": "#39afd7", + "attribute_color": "#39afd7", "struct_color": "#ffa0a5", "associatedtype_color": "#39afd7", "tymethod_color": "#fdd687", @@ -175,6 +182,7 @@ call-function: ("check-search-color", { "path_color": "#ddd", "bottom_border_color": "#aaa3", "keyword_color": "#d2991d", + "attribute_color": "#d2991d", "struct_color": "#2dbfb8", "associatedtype_color": "#d2991d", "tymethod_color": "#2bab63", @@ -196,6 +204,7 @@ call-function: ("check-search-color", { "path_color": "#000", "bottom_border_color": "#aaa3", "keyword_color": "#3873ad", + "attribute_color": "#3873ad", "struct_color": "#ad378a", "associatedtype_color": "#3873ad", "tymethod_color": "#ad7c37", diff --git a/tests/rustdoc-gui/sidebar-links-color.goml b/tests/rustdoc-gui/sidebar-links-color.goml index 57c45555a76bd..9a398655f8fa0 100644 --- a/tests/rustdoc-gui/sidebar-links-color.goml +++ b/tests/rustdoc-gui/sidebar-links-color.goml @@ -12,6 +12,7 @@ define-function: ( enum_hover_background, union, union_hover, union_hover_background, trait, trait_hover, trait_hover_background, fn, fn_hover, fn_hover_background, type, type_hover, type_hover_background, keyword, keyword_hover, keyword_hover_background, + attribute, attribute_hover, attribute_hover_background, ], block { call-function: ("switch-theme", {"theme": |theme|}) @@ -85,6 +86,16 @@ define-function: ( ".sidebar .block.keyword a:hover", {"color": |keyword_hover|, "background-color": |keyword_hover_background|}, ) + // Attribute + assert-css: ( + ".sidebar .block.attribute a", + {"color": |attribute|, "background-color": "rgba(0, 0, 0, 0)"}, + ) + move-cursor-to: ".sidebar .block.attribute a" + assert-css: ( + ".sidebar .block.attribute a:hover", + {"color": |attribute_hover|, "background-color": |attribute_hover_background|}, + ) } ) @@ -113,6 +124,9 @@ call-function: ( "keyword": "#53b1db", "keyword_hover": "#ffb44c", "keyword_hover_background": "transparent", + "attribute": "#53b1db", + "attribute_hover": "#ffb44c", + "attribute_hover_background": "transparent", } ) call-function: ( @@ -140,6 +154,9 @@ call-function: ( "keyword": "#fdbf35", "keyword_hover": "#fdbf35", "keyword_hover_background": "#444", + "attribute": "#fdbf35", + "attribute_hover": "#fdbf35", + "attribute_hover_background": "#444", } ) call-function: ( @@ -167,5 +184,8 @@ call-function: ( "keyword": "#356da4", "keyword_hover": "#356da4", "keyword_hover_background": "#fff", + "attribute": "#356da4", + "attribute_hover": "#356da4", + "attribute_hover_background": "#fff", } ) diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs index e8afe8b568727..d8dbdc608aaeb 100644 --- a/tests/rustdoc-gui/src/test_docs/lib.rs +++ b/tests/rustdoc-gui/src/test_docs/lib.rs @@ -161,6 +161,10 @@ pub enum AnEnum { /// Some keyword. pub mod keyword {} +#[doc(attribute = "forbid")] +/// Some attribute. +pub mod repr {} + /// Just some type alias. pub type SomeType = u32; diff --git a/tests/rustdoc-json/doc_attribute.rs b/tests/rustdoc-json/doc_attribute.rs new file mode 100644 index 0000000000000..9e1a711f0b7b5 --- /dev/null +++ b/tests/rustdoc-json/doc_attribute.rs @@ -0,0 +1,18 @@ +// Doc attributes (`#[doc(attribute = "...")]` should not be generated in rustdoc JSON output +// and this test ensures it. + +#![feature(rustdoc_internals)] +#![no_std] + +//@ !has "$.index[?(@.name=='repr')]" +//@ has "$.index[?(@.name=='foo')]" + +#[doc(attribute = "repr")] +/// this is a test! +pub mod foo {} + +//@ !has "$.index[?(@.name=='forbid')]" +//@ !has "$.index[?(@.name=='bar')]" +#[doc(attribute = "forbid")] +/// hello +mod bar {} diff --git a/tests/rustdoc-ui/invalid-attribute.rs b/tests/rustdoc-ui/invalid-attribute.rs new file mode 100644 index 0000000000000..2fc7be2cf341d --- /dev/null +++ b/tests/rustdoc-ui/invalid-attribute.rs @@ -0,0 +1,10 @@ +// Testing the output when an invalid builtin attribute is passed as value +// to `doc(attribute = "...")`. + +#![feature(rustdoc_internals)] + +#[doc(attribute = "foo df")] //~ ERROR +mod foo {} + +#[doc(attribute = "fooyi")] //~ ERROR +mod foo2 {} diff --git a/tests/rustdoc-ui/invalid-attribute.stderr b/tests/rustdoc-ui/invalid-attribute.stderr new file mode 100644 index 0000000000000..66e68ce44b406 --- /dev/null +++ b/tests/rustdoc-ui/invalid-attribute.stderr @@ -0,0 +1,18 @@ +error: nonexistent builtin attribute `foo df` used in `#[doc(attribute = "...")]` + --> $DIR/invalid-attribute.rs:6:19 + | +LL | #[doc(attribute = "foo df")] + | ^^^^^^^^ + | + = help: only existing builtin attributes are allowed in core/std + +error: nonexistent builtin attribute `fooyi` used in `#[doc(attribute = "...")]` + --> $DIR/invalid-attribute.rs:9:19 + | +LL | #[doc(attribute = "fooyi")] + | ^^^^^^^ + | + = help: only existing builtin attributes are allowed in core/std + +error: aborting due to 2 previous errors + diff --git a/tests/rustdoc/doc-attribute.rs b/tests/rustdoc/doc-attribute.rs new file mode 100644 index 0000000000000..92e603ae6e551 --- /dev/null +++ b/tests/rustdoc/doc-attribute.rs @@ -0,0 +1,24 @@ +// Test checking the `#[doc(attribute = "...")]` attribute. + +#![crate_name = "foo"] + +#![feature(rustdoc_internals)] + +//@ has foo/index.html '//h2[@id="attributes"]' 'Attributes' +//@ has foo/index.html '//a[@href="attribute.no_mangle.html"]' 'no_mangle' +//@ has foo/index.html '//div[@class="sidebar-elems"]//li/a' 'Attributes' +//@ has foo/index.html '//div[@class="sidebar-elems"]//li/a/@href' '#attributes' +//@ has foo/attribute.no_mangle.html '//h1' 'Attribute no_mangle' +//@ has foo/attribute.no_mangle.html '//section[@id="main-content"]//div[@class="docblock"]//p' 'this is a test!' +//@ has foo/index.html '//a/@href' '../foo/index.html' +//@ !has foo/foo/index.html +//@ !has-dir foo/foo +//@ !has foo/index.html '//span' '🔒' +#[doc(attribute = "no_mangle")] +/// this is a test! +mod foo{} + +//@ has foo/attribute.repr.html '//section[@id="main-content"]//div[@class="docblock"]//p' 'hello' +#[doc(attribute = "repr")] +/// hello +mod bar {} diff --git a/tests/ui/feature-gates/feature-gate-rustdoc_internals.rs b/tests/ui/feature-gates/feature-gate-rustdoc_internals.rs index 57d6b59128703..0ad3b2aead481 100644 --- a/tests/ui/feature-gates/feature-gate-rustdoc_internals.rs +++ b/tests/ui/feature-gates/feature-gate-rustdoc_internals.rs @@ -2,6 +2,10 @@ /// wonderful mod foo {} +#[doc(attribute = "repr")] //~ ERROR: `#[doc(attribute)]` is meant for internal use only +/// wonderful +mod foo2 {} + trait Mine {} #[doc(fake_variadic)] //~ ERROR: `#[doc(fake_variadic)]` is meant for internal use only diff --git a/tests/ui/feature-gates/feature-gate-rustdoc_internals.stderr b/tests/ui/feature-gates/feature-gate-rustdoc_internals.stderr index f3c00a2156bf9..5a6d4d3b45e0f 100644 --- a/tests/ui/feature-gates/feature-gate-rustdoc_internals.stderr +++ b/tests/ui/feature-gates/feature-gate-rustdoc_internals.stderr @@ -8,8 +8,18 @@ LL | #[doc(keyword = "match")] = help: add `#![feature(rustdoc_internals)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +error[E0658]: `#[doc(attribute)]` is meant for internal use only + --> $DIR/feature-gate-rustdoc_internals.rs:5:1 + | +LL | #[doc(attribute = "repr")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #90418 for more information + = help: add `#![feature(rustdoc_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error[E0658]: `#[doc(fake_variadic)]` is meant for internal use only - --> $DIR/feature-gate-rustdoc_internals.rs:7:1 + --> $DIR/feature-gate-rustdoc_internals.rs:11:1 | LL | #[doc(fake_variadic)] | ^^^^^^^^^^^^^^^^^^^^^ @@ -19,7 +29,7 @@ LL | #[doc(fake_variadic)] = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[doc(search_unbox)]` is meant for internal use only - --> $DIR/feature-gate-rustdoc_internals.rs:10:1 + --> $DIR/feature-gate-rustdoc_internals.rs:14:1 | LL | #[doc(search_unbox)] | ^^^^^^^^^^^^^^^^^^^^ @@ -28,6 +38,6 @@ LL | #[doc(search_unbox)] = help: add `#![feature(rustdoc_internals)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 3 previous errors +error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0658`.