Skip to content

Commit 80882d7

Browse files
Add new doc(attribute = "...") attribute
1 parent aa51a9b commit 80882d7

File tree

27 files changed

+168
-47
lines changed

27 files changed

+168
-47
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
@@ -172,6 +172,10 @@ passes_doc_alias_start_end =
172172
passes_doc_attr_not_crate_level =
173173
`#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute
174174
175+
passes_doc_attribute_not_attribute =
176+
nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = "...")]`
177+
.help = only existing builtin attributes are allowed in core/std
178+
175179
passes_doc_cfg_hide_takes_list =
176180
`#[doc(cfg_hide(...))]` takes a list of attributes
177181
@@ -200,16 +204,16 @@ passes_doc_inline_only_use =
200204
passes_doc_invalid =
201205
invalid `doc` attribute
202206
203-
passes_doc_keyword_empty_mod =
204-
`#[doc(keyword = "...")]` should be used on empty modules
207+
passes_doc_keyword_attribute_empty_mod =
208+
`#[doc({$attr_name} = "...")]` should be used on empty modules
209+
210+
passes_doc_keyword_attribute_not_mod =
211+
`#[doc({$attr_name} = "...")]` should be used on modules
205212
206213
passes_doc_keyword_not_keyword =
207214
nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]`
208215
.help = only existing keywords are allowed in core/std
209216
210-
passes_doc_keyword_not_mod =
211-
`#[doc(keyword = "...")]` should be used on modules
212-
213217
passes_doc_keyword_only_impl =
214218
`#[doc(keyword = "...")]` should be used on impl blocks
215219

compiler/rustc_passes/src/check_attr.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,17 +1086,30 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
10861086
}
10871087
}
10881088

1089-
fn check_doc_keyword(&self, meta: &MetaItemInner, hir_id: HirId) {
1089+
fn check_doc_keyword_and_attribute(
1090+
&self,
1091+
meta: &MetaItemInner,
1092+
hir_id: HirId,
1093+
is_keyword: bool,
1094+
) {
10901095
fn is_doc_keyword(s: Symbol) -> bool {
10911096
// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
10921097
// can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the
10931098
// `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.
10941099
s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy
10951100
}
10961101

1097-
let doc_keyword = match meta.value_str() {
1102+
fn is_builtin_attr(s: Symbol) -> bool {
1103+
rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&s)
1104+
}
1105+
1106+
fn get_attr_name(is_keyword: bool) -> &'static str {
1107+
if is_keyword { "keyword" } else { "attribute" }
1108+
}
1109+
1110+
let value = match meta.value_str() {
10981111
Some(value) if value != sym::empty => value,
1099-
_ => return self.doc_attr_str_error(meta, "keyword"),
1112+
_ => return self.doc_attr_str_error(meta, get_attr_name(is_keyword)),
11001113
};
11011114

11021115
let item_kind = match self.tcx.hir_node(hir_id) {
@@ -1106,19 +1119,32 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
11061119
match item_kind {
11071120
Some(ItemKind::Mod(_, module)) => {
11081121
if !module.item_ids.is_empty() {
1109-
self.dcx().emit_err(errors::DocKeywordEmptyMod { span: meta.span() });
1122+
self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod {
1123+
span: meta.span(),
1124+
attr_name: get_attr_name(is_keyword),
1125+
});
11101126
return;
11111127
}
11121128
}
11131129
_ => {
1114-
self.dcx().emit_err(errors::DocKeywordNotMod { span: meta.span() });
1130+
self.dcx().emit_err(errors::DocKeywordAttributeNotMod {
1131+
span: meta.span(),
1132+
attr_name: get_attr_name(is_keyword),
1133+
});
11151134
return;
11161135
}
11171136
}
1118-
if !is_doc_keyword(doc_keyword) {
1119-
self.dcx().emit_err(errors::DocKeywordNotKeyword {
1137+
if is_keyword {
1138+
if !is_doc_keyword(value) {
1139+
self.dcx().emit_err(errors::DocKeywordNotKeyword {
1140+
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
1141+
keyword: value,
1142+
});
1143+
}
1144+
} else if !is_builtin_attr(value) {
1145+
self.dcx().emit_err(errors::DocAttributeNotAttribute {
11201146
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
1121-
keyword: doc_keyword,
1147+
attribute: value,
11221148
});
11231149
}
11241150
}
@@ -1380,7 +1406,13 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
13801406

13811407
Some(sym::keyword) => {
13821408
if self.check_attr_not_crate_level(meta, hir_id, "keyword") {
1383-
self.check_doc_keyword(meta, hir_id);
1409+
self.check_doc_keyword_and_attribute(meta, hir_id, true);
1410+
}
1411+
}
1412+
1413+
Some(sym::attribute) => {
1414+
if self.check_attr_not_crate_level(meta, hir_id, "attribute") {
1415+
self.check_doc_keyword_and_attribute(meta, hir_id, false);
13841416
}
13851417
}
13861418

compiler/rustc_passes/src/errors.rs

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

261261
#[derive(Diagnostic)]
262-
#[diag(passes_doc_keyword_empty_mod)]
263-
pub(crate) struct DocKeywordEmptyMod {
262+
#[diag(passes_doc_keyword_attribute_empty_mod)]
263+
pub(crate) struct DocKeywordAttributeEmptyMod {
264264
#[primary_span]
265265
pub span: Span,
266+
pub attr_name: &'static str,
266267
}
267268

268269
#[derive(Diagnostic)]
@@ -275,10 +276,20 @@ pub(crate) struct DocKeywordNotKeyword {
275276
}
276277

277278
#[derive(Diagnostic)]
278-
#[diag(passes_doc_keyword_not_mod)]
279-
pub(crate) struct DocKeywordNotMod {
279+
#[diag(passes_doc_attribute_not_attribute)]
280+
#[help]
281+
pub(crate) struct DocAttributeNotAttribute {
282+
#[primary_span]
283+
pub span: Span,
284+
pub attribute: Symbol,
285+
}
286+
287+
#[derive(Diagnostic)]
288+
#[diag(passes_doc_keyword_attribute_not_mod)]
289+
pub(crate) struct DocKeywordAttributeNotMod {
280290
#[primary_span]
281291
pub span: Span,
292+
pub attr_name: &'static str,
282293
}
283294

284295
#[derive(Diagnostic)]

compiler/rustc_resolve/src/late.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5031,7 +5031,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
50315031
}
50325032
ResolveDocLinks::Exported
50335033
if !maybe_exported.eval(self.r)
5034-
&& !rustdoc::has_primitive_or_keyword_docs(attrs) =>
5034+
&& !rustdoc::has_primitive_or_keyword_or_attribute_docs(attrs) =>
50355035
{
50365036
return;
50375037
}

compiler/rustc_resolve/src/rustdoc.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -368,16 +368,16 @@ pub fn inner_docs(attrs: &[impl AttributeExt]) -> bool {
368368
true
369369
}
370370

371-
/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]`.
372-
pub fn has_primitive_or_keyword_docs(attrs: &[impl AttributeExt]) -> bool {
371+
/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]` or `#[doc(attribute)]`.
372+
pub fn has_primitive_or_keyword_or_attribute_docs(attrs: &[impl AttributeExt]) -> bool {
373373
for attr in attrs {
374374
if attr.has_name(sym::rustc_doc_primitive) {
375375
return true;
376376
} else if attr.has_name(sym::doc)
377377
&& let Some(items) = attr.meta_item_list()
378378
{
379379
for item in items {
380-
if item.has_name(sym::keyword) {
380+
if item.has_name(sym::keyword) || item.has_name(sym::attribute) {
381381
return true;
382382
}
383383
}

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ symbols! {
536536
att_syntax,
537537
attr,
538538
attr_literals,
539+
attribute,
539540
attributes,
540541
audit_that,
541542
augmented_assignments,

src/librustdoc/clean/types.rs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,15 +228,28 @@ impl ExternalCrate {
228228
}
229229

230230
pub(crate) fn keywords(&self, tcx: TyCtxt<'_>) -> impl Iterator<Item = (DefId, Symbol)> {
231-
fn as_keyword(did: DefId, tcx: TyCtxt<'_>) -> Option<(DefId, Symbol)> {
231+
self.retrieve_keywords_or_documented_attributes(tcx, sym::keyword)
232+
}
233+
pub(crate) fn documented_attributes(
234+
&self,
235+
tcx: TyCtxt<'_>,
236+
) -> impl Iterator<Item = (DefId, Symbol)> {
237+
self.retrieve_keywords_or_documented_attributes(tcx, sym::attribute)
238+
}
239+
240+
fn retrieve_keywords_or_documented_attributes(
241+
&self,
242+
tcx: TyCtxt<'_>,
243+
name: Symbol,
244+
) -> impl Iterator<Item = (DefId, Symbol)> {
245+
let as_target = move |did: DefId, tcx: TyCtxt<'_>| -> Option<(DefId, Symbol)> {
232246
tcx.get_attrs(did, sym::doc)
233247
.flat_map(|attr| attr.meta_item_list().unwrap_or_default())
234-
.filter(|meta| meta.has_name(sym::keyword))
248+
.filter(|meta| meta.has_name(name))
235249
.find_map(|meta| meta.value_str())
236250
.map(|value| (did, value))
237-
}
238-
239-
self.mapped_root_modules(tcx, as_keyword)
251+
};
252+
self.mapped_root_modules(tcx, as_target)
240253
}
241254

242255
pub(crate) fn primitives(
@@ -579,6 +592,9 @@ impl Item {
579592
pub(crate) fn is_keyword(&self) -> bool {
580593
self.type_() == ItemType::Keyword
581594
}
595+
pub(crate) fn is_attribute(&self) -> bool {
596+
self.type_() == ItemType::Attribute
597+
}
582598
pub(crate) fn is_stripped(&self) -> bool {
583599
match self.kind {
584600
StrippedItem(..) => true,
@@ -722,7 +738,9 @@ impl Item {
722738
// Primitives and Keywords are written in the source code as private modules.
723739
// The modules need to be private so that nobody actually uses them, but the
724740
// keywords and primitives that they are documenting are public.
725-
ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) => return Some(Visibility::Public),
741+
ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) | ItemKind::AttributeItem => {
742+
return Some(Visibility::Public);
743+
}
726744
// Variant fields inherit their enum's visibility.
727745
StructFieldItem(..) if is_field_vis_inherited(tcx, def_id) => {
728746
return None;
@@ -959,7 +977,12 @@ pub(crate) enum ItemKind {
959977
AssocTypeItem(Box<TypeAlias>, Vec<GenericBound>),
960978
/// An item that has been stripped by a rustdoc pass
961979
StrippedItem(Box<ItemKind>),
980+
/// This item represents a module with a `#[doc(keyword = "...")]` attribute which is used
981+
/// to generate documentation for Rust keywords.
962982
KeywordItem,
983+
/// This item represents a module with a `#[doc(attribute = "...")]` attribute which is used
984+
/// to generate documentation for Rust builtin attributes.
985+
AttributeItem,
963986
}
964987

965988
impl ItemKind {
@@ -1000,7 +1023,8 @@ impl ItemKind {
10001023
| RequiredAssocTypeItem(..)
10011024
| AssocTypeItem(..)
10021025
| StrippedItem(_)
1003-
| KeywordItem => [].iter(),
1026+
| KeywordItem
1027+
| AttributeItem => [].iter(),
10041028
}
10051029
}
10061030

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.map(|(def_id, prim)| {
@@ -72,6 +73,9 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
7273
m.items.extend(keywords.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

0 commit comments

Comments
 (0)