Skip to content

Commit bc61314

Browse files
Add new doc(attribute = "...") attribute
1 parent 0cbc076 commit bc61314

File tree

27 files changed

+164
-45
lines changed

27 files changed

+164
-45
lines changed

compiler/rustc_ast_passes/src/feature_gate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
188188
notable_trait => doc_notable_trait
189189
}
190190
"meant for internal use only" {
191+
attribute => rustdoc_internals
191192
keyword => rustdoc_internals
192193
fake_variadic => rustdoc_internals
193194
search_unbox => rustdoc_internals

compiler/rustc_passes/messages.ftl

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ passes_doc_alias_start_end =
161161
passes_doc_attr_not_crate_level =
162162
`#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute
163163
164+
passes_doc_attribute_not_attribute =
165+
nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = "...")]`
166+
.help = only existing builtin attributes are allowed in core/std
167+
164168
passes_doc_cfg_hide_takes_list =
165169
`#[doc(cfg_hide(...))]` takes a list of attributes
166170
@@ -189,16 +193,16 @@ passes_doc_inline_only_use =
189193
passes_doc_invalid =
190194
invalid `doc` attribute
191195
192-
passes_doc_keyword_empty_mod =
193-
`#[doc(keyword = "...")]` should be used on empty modules
196+
passes_doc_keyword_attribute_empty_mod =
197+
`#[doc({$attr_name} = "...")]` should be used on empty modules
198+
199+
passes_doc_keyword_attribute_not_mod =
200+
`#[doc({$attr_name} = "...")]` should be used on modules
194201
195202
passes_doc_keyword_not_keyword =
196203
nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]`
197204
.help = only existing keywords are allowed in core/std
198205
199-
passes_doc_keyword_not_mod =
200-
`#[doc(keyword = "...")]` should be used on modules
201-
202206
passes_doc_keyword_only_impl =
203207
`#[doc(keyword = "...")]` should be used on impl blocks
204208

compiler/rustc_passes/src/check_attr.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,17 +1069,30 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
10691069
}
10701070
}
10711071

1072-
fn check_doc_keyword(&self, meta: &MetaItemInner, hir_id: HirId) {
1072+
fn check_doc_keyword_and_attribute(
1073+
&self,
1074+
meta: &MetaItemInner,
1075+
hir_id: HirId,
1076+
is_keyword: bool,
1077+
) {
10731078
fn is_doc_keyword(s: Symbol) -> bool {
10741079
// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
10751080
// can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the
10761081
// `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.
10771082
s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy
10781083
}
10791084

1080-
let doc_keyword = match meta.value_str() {
1085+
fn is_builtin_attr(s: Symbol) -> bool {
1086+
rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&s)
1087+
}
1088+
1089+
fn get_attr_name(is_keyword: bool) -> &'static str {
1090+
if is_keyword { "keyword" } else { "attribute " }
1091+
}
1092+
1093+
let value = match meta.value_str() {
10811094
Some(value) if value != sym::empty => value,
1082-
_ => return self.doc_attr_str_error(meta, "keyword"),
1095+
_ => return self.doc_attr_str_error(meta, get_attr_name(is_keyword)),
10831096
};
10841097

10851098
let item_kind = match self.tcx.hir_node(hir_id) {
@@ -1089,19 +1102,32 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
10891102
match item_kind {
10901103
Some(ItemKind::Mod(_, module)) => {
10911104
if !module.item_ids.is_empty() {
1092-
self.dcx().emit_err(errors::DocKeywordEmptyMod { span: meta.span() });
1105+
self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod {
1106+
span: meta.span(),
1107+
attr_name: get_attr_name(is_keyword),
1108+
});
10931109
return;
10941110
}
10951111
}
10961112
_ => {
1097-
self.dcx().emit_err(errors::DocKeywordNotMod { span: meta.span() });
1113+
self.dcx().emit_err(errors::DocKeywordAttributeNotMod {
1114+
span: meta.span(),
1115+
attr_name: get_attr_name(is_keyword),
1116+
});
10981117
return;
10991118
}
11001119
}
1101-
if !is_doc_keyword(doc_keyword) {
1102-
self.dcx().emit_err(errors::DocKeywordNotKeyword {
1120+
if is_keyword {
1121+
if !is_doc_keyword(value) {
1122+
self.dcx().emit_err(errors::DocKeywordNotKeyword {
1123+
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
1124+
keyword: value,
1125+
});
1126+
}
1127+
} else if !is_builtin_attr(value) {
1128+
self.dcx().emit_err(errors::DocAttributeNotAttribute {
11031129
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
1104-
keyword: doc_keyword,
1130+
attribute: value,
11051131
});
11061132
}
11071133
}
@@ -1356,7 +1382,13 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
13561382

13571383
Some(sym::keyword) => {
13581384
if self.check_attr_not_crate_level(meta, hir_id, "keyword") {
1359-
self.check_doc_keyword(meta, hir_id);
1385+
self.check_doc_keyword_and_attribute(meta, hir_id, true);
1386+
}
1387+
}
1388+
1389+
Some(sym::attribute) => {
1390+
if self.check_attr_not_crate_level(meta, hir_id, "attribute") {
1391+
self.check_doc_keyword_and_attribute(meta, hir_id, false);
13601392
}
13611393
}
13621394

compiler/rustc_passes/src/errors.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,11 @@ pub(crate) struct DocAliasMalformed {
229229
}
230230

231231
#[derive(Diagnostic)]
232-
#[diag(passes_doc_keyword_empty_mod)]
233-
pub(crate) struct DocKeywordEmptyMod {
232+
#[diag(passes_doc_keyword_attribute_empty_mod)]
233+
pub(crate) struct DocKeywordAttributeEmptyMod {
234234
#[primary_span]
235235
pub span: Span,
236+
pub attr_name: &'static str,
236237
}
237238

238239
#[derive(Diagnostic)]
@@ -245,10 +246,20 @@ pub(crate) struct DocKeywordNotKeyword {
245246
}
246247

247248
#[derive(Diagnostic)]
248-
#[diag(passes_doc_keyword_not_mod)]
249-
pub(crate) struct DocKeywordNotMod {
249+
#[diag(passes_doc_attribute_not_attribute)]
250+
#[help]
251+
pub(crate) struct DocAttributeNotAttribute {
252+
#[primary_span]
253+
pub span: Span,
254+
pub attribute: Symbol,
255+
}
256+
257+
#[derive(Diagnostic)]
258+
#[diag(passes_doc_keyword_attribute_not_mod)]
259+
pub(crate) struct DocKeywordAttributeNotMod {
250260
#[primary_span]
251261
pub span: Span,
262+
pub attr_name: &'static str,
252263
}
253264

254265
#[derive(Diagnostic)]

compiler/rustc_resolve/src/late.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5072,7 +5072,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
50725072
}
50735073
ResolveDocLinks::Exported
50745074
if !maybe_exported.eval(self.r)
5075-
&& !rustdoc::has_primitive_or_keyword_docs(attrs) =>
5075+
&& !rustdoc::has_primitive_or_keyword_or_attribute_docs(attrs) =>
50765076
{
50775077
return;
50785078
}

compiler/rustc_resolve/src/rustdoc.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,16 +359,16 @@ pub fn inner_docs(attrs: &[impl AttributeExt]) -> bool {
359359
attrs.iter().find(|a| a.doc_str().is_some()).is_none_or(|a| a.style() == ast::AttrStyle::Inner)
360360
}
361361

362-
/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]`.
363-
pub fn has_primitive_or_keyword_docs(attrs: &[impl AttributeExt]) -> bool {
362+
/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]` or `#[doc(attribute)]`.
363+
pub fn has_primitive_or_keyword_or_attribute_docs(attrs: &[impl AttributeExt]) -> bool {
364364
for attr in attrs {
365365
if attr.has_name(sym::rustc_doc_primitive) {
366366
return true;
367367
} else if attr.has_name(sym::doc)
368368
&& let Some(items) = attr.meta_item_list()
369369
{
370370
for item in items {
371-
if item.has_name(sym::keyword) {
371+
if item.has_name(sym::keyword) || item.has_name(sym::attribute) {
372372
return true;
373373
}
374374
}

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@ symbols! {
540540
att_syntax,
541541
attr,
542542
attr_literals,
543+
attribute,
543544
attributes,
544545
audit_that,
545546
augmented_assignments,

src/librustdoc/clean/types.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,16 +200,27 @@ impl ExternalCrate {
200200
}
201201

202202
pub(crate) fn keywords(&self, tcx: TyCtxt<'_>) -> ThinVec<(DefId, Symbol)> {
203+
self.retrieve_keywords_or_documented_attributes(tcx, sym::keyword)
204+
}
205+
pub(crate) fn documented_attributes(&self, tcx: TyCtxt<'_>) -> ThinVec<(DefId, Symbol)> {
206+
self.retrieve_keywords_or_documented_attributes(tcx, sym::attribute)
207+
}
208+
209+
fn retrieve_keywords_or_documented_attributes(
210+
&self,
211+
tcx: TyCtxt<'_>,
212+
name: Symbol,
213+
) -> ThinVec<(DefId, Symbol)> {
203214
let root = self.def_id();
204215

205-
let as_keyword = |res: Res<!>| {
216+
let as_target = |res: Res<!>| {
206217
if let Res::Def(DefKind::Mod, def_id) = res {
207218
let mut keyword = None;
208219
let meta_items = tcx
209220
.get_attrs(def_id, sym::doc)
210221
.flat_map(|attr| attr.meta_item_list().unwrap_or_default());
211222
for meta in meta_items {
212-
if meta.has_name(sym::keyword)
223+
if meta.has_name(name)
213224
&& let Some(v) = meta.value_str()
214225
{
215226
keyword = Some(v);
@@ -228,14 +239,14 @@ impl ExternalCrate {
228239
let item = tcx.hir_item(id);
229240
match item.kind {
230241
hir::ItemKind::Mod(..) => {
231-
as_keyword(Res::Def(DefKind::Mod, id.owner_id.to_def_id()))
242+
as_target(Res::Def(DefKind::Mod, id.owner_id.to_def_id()))
232243
}
233244
_ => None,
234245
}
235246
})
236247
.collect()
237248
} else {
238-
tcx.module_children(root).iter().map(|item| item.res).filter_map(as_keyword).collect()
249+
tcx.module_children(root).iter().map(|item| item.res).filter_map(as_target).collect()
239250
}
240251
}
241252

@@ -597,6 +608,9 @@ impl Item {
597608
pub(crate) fn is_keyword(&self) -> bool {
598609
self.type_() == ItemType::Keyword
599610
}
611+
pub(crate) fn is_attribute(&self) -> bool {
612+
self.type_() == ItemType::Attribute
613+
}
600614
pub(crate) fn is_stripped(&self) -> bool {
601615
match self.kind {
602616
StrippedItem(..) => true,
@@ -727,7 +741,9 @@ impl Item {
727741
// Primitives and Keywords are written in the source code as private modules.
728742
// The modules need to be private so that nobody actually uses them, but the
729743
// keywords and primitives that they are documenting are public.
730-
ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) => return Some(Visibility::Public),
744+
ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) | ItemKind::AttributeItem => {
745+
return Some(Visibility::Public);
746+
}
731747
// Variant fields inherit their enum's visibility.
732748
StructFieldItem(..) if is_field_vis_inherited(tcx, def_id) => {
733749
return None;
@@ -943,7 +959,12 @@ pub(crate) enum ItemKind {
943959
AssocTypeItem(Box<TypeAlias>, Vec<GenericBound>),
944960
/// An item that has been stripped by a rustdoc pass
945961
StrippedItem(Box<ItemKind>),
962+
/// This item represents a module with a `#[doc(keyword = "...")]` attribute which is used
963+
/// to generate documentation for Rust keywords.
946964
KeywordItem,
965+
/// This item represents a module with a `#[doc(attribute = "...")]` attribute which is used
966+
/// to generate documentation for Rust builtin attributes.
967+
AttributeItem,
947968
}
948969

949970
impl ItemKind {
@@ -984,7 +1005,8 @@ impl ItemKind {
9841005
| RequiredAssocTypeItem(..)
9851006
| AssocTypeItem(..)
9861007
| StrippedItem(_)
987-
| KeywordItem => [].iter(),
1008+
| KeywordItem
1009+
| AttributeItem => [].iter(),
9881010
}
9891011
}
9901012

src/librustdoc/clean/utils.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
5959
let local_crate = ExternalCrate { crate_num: LOCAL_CRATE };
6060
let primitives = local_crate.primitives(cx.tcx);
6161
let keywords = local_crate.keywords(cx.tcx);
62+
let documented_attributes = local_crate.documented_attributes(cx.tcx);
6263
{
6364
let ItemKind::ModuleItem(m) = &mut module.inner.kind else { unreachable!() };
6465
m.items.extend(primitives.iter().map(|&(def_id, prim)| {
@@ -72,6 +73,9 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
7273
m.items.extend(keywords.into_iter().map(|(def_id, kw)| {
7374
Item::from_def_id_and_parts(def_id, Some(kw), ItemKind::KeywordItem, cx)
7475
}));
76+
m.items.extend(documented_attributes.into_iter().map(|(def_id, kw)| {
77+
Item::from_def_id_and_parts(def_id, Some(kw), ItemKind::AttributeItem, cx)
78+
}));
7579
}
7680

7781
Crate { module, external_traits: Box::new(mem::take(&mut cx.external_traits)) }

src/librustdoc/fold.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ pub(crate) trait DocFolder: Sized {
9696
| ImplAssocConstItem(..)
9797
| RequiredAssocTypeItem(..)
9898
| AssocTypeItem(..)
99-
| KeywordItem => kind,
99+
| KeywordItem
100+
| AttributeItem => kind,
100101
}
101102
}
102103

src/librustdoc/formats/cache.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,8 @@ impl DocFolder for CacheBuilder<'_, '_> {
357357
| clean::RequiredAssocTypeItem(..)
358358
| clean::AssocTypeItem(..)
359359
| clean::StrippedItem(..)
360-
| clean::KeywordItem => {
360+
| clean::KeywordItem
361+
| clean::AttributeItem => {
361362
// FIXME: Do these need handling?
362363
// The person writing this comment doesn't know.
363364
// So would rather leave them to an expert,

src/librustdoc/formats/item_type.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ pub(crate) enum ItemType {
5757
TraitAlias = 25,
5858
// This number is reserved for use in JavaScript
5959
// Generic = 26,
60+
Attribute = 27,
6061
}
6162

6263
impl Serialize for ItemType {
@@ -102,6 +103,7 @@ impl<'a> From<&'a clean::Item> for ItemType {
102103
clean::RequiredAssocTypeItem(..) | clean::AssocTypeItem(..) => ItemType::AssocType,
103104
clean::ForeignTypeItem => ItemType::ForeignType,
104105
clean::KeywordItem => ItemType::Keyword,
106+
clean::AttributeItem => ItemType::Attribute,
105107
clean::TraitAliasItem(..) => ItemType::TraitAlias,
106108
clean::ProcMacroItem(mac) => match mac.kind {
107109
MacroKind::Bang => ItemType::Macro,
@@ -189,6 +191,7 @@ impl ItemType {
189191
ItemType::ProcAttribute => "attr",
190192
ItemType::ProcDerive => "derive",
191193
ItemType::TraitAlias => "traitalias",
194+
ItemType::Attribute => "attribute",
192195
}
193196
}
194197
pub(crate) fn is_method(&self) -> bool {

src/librustdoc/html/render/context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ impl<'tcx> Context<'tcx> {
206206
if !is_module {
207207
title.push_str(it.name.unwrap().as_str());
208208
}
209-
if !it.is_primitive() && !it.is_keyword() {
209+
if !it.is_primitive() && !it.is_keyword() && !it.is_attribute() {
210210
if !is_module {
211211
title.push_str(" in ");
212212
}

0 commit comments

Comments
 (0)