Skip to content

Commit b67c7dc

Browse files
Add new doc(attribute = "...") attribute
1 parent 22be76b commit b67c7dc

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
@@ -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
@@ -1112,17 +1112,30 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
11121112
}
11131113
}
11141114

1115-
fn check_doc_keyword(&self, meta: &MetaItemInner, hir_id: HirId) {
1115+
fn check_doc_keyword_and_attribute(
1116+
&self,
1117+
meta: &MetaItemInner,
1118+
hir_id: HirId,
1119+
is_keyword: bool,
1120+
) {
11161121
fn is_doc_keyword(s: Symbol) -> bool {
11171122
// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
11181123
// can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the
11191124
// `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.
11201125
s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy
11211126
}
11221127

1123-
let doc_keyword = match meta.value_str() {
1128+
fn is_builtin_attr(s: Symbol) -> bool {
1129+
rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&s)
1130+
}
1131+
1132+
fn get_attr_name(is_keyword: bool) -> &'static str {
1133+
if is_keyword { "keyword" } else { "attribute " }
1134+
}
1135+
1136+
let value = match meta.value_str() {
11241137
Some(value) if value != sym::empty => value,
1125-
_ => return self.doc_attr_str_error(meta, "keyword"),
1138+
_ => return self.doc_attr_str_error(meta, get_attr_name(is_keyword)),
11261139
};
11271140

11281141
let item_kind = match self.tcx.hir_node(hir_id) {
@@ -1132,19 +1145,32 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
11321145
match item_kind {
11331146
Some(ItemKind::Mod(_, module)) => {
11341147
if !module.item_ids.is_empty() {
1135-
self.dcx().emit_err(errors::DocKeywordEmptyMod { span: meta.span() });
1148+
self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod {
1149+
span: meta.span(),
1150+
attr_name: get_attr_name(is_keyword),
1151+
});
11361152
return;
11371153
}
11381154
}
11391155
_ => {
1140-
self.dcx().emit_err(errors::DocKeywordNotMod { span: meta.span() });
1156+
self.dcx().emit_err(errors::DocKeywordAttributeNotMod {
1157+
span: meta.span(),
1158+
attr_name: get_attr_name(is_keyword),
1159+
});
11411160
return;
11421161
}
11431162
}
1144-
if !is_doc_keyword(doc_keyword) {
1145-
self.dcx().emit_err(errors::DocKeywordNotKeyword {
1163+
if is_keyword {
1164+
if !is_doc_keyword(value) {
1165+
self.dcx().emit_err(errors::DocKeywordNotKeyword {
1166+
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
1167+
keyword: value,
1168+
});
1169+
}
1170+
} else if !is_builtin_attr(value) {
1171+
self.dcx().emit_err(errors::DocAttributeNotAttribute {
11461172
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
1147-
keyword: doc_keyword,
1173+
attribute: value,
11481174
});
11491175
}
11501176
}
@@ -1404,7 +1430,13 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
14041430

14051431
Some(sym::keyword) => {
14061432
if self.check_attr_not_crate_level(meta, hir_id, "keyword") {
1407-
self.check_doc_keyword(meta, hir_id);
1433+
self.check_doc_keyword_and_attribute(meta, hir_id, true);
1434+
}
1435+
}
1436+
1437+
Some(sym::attribute) => {
1438+
if self.check_attr_not_crate_level(meta, hir_id, "attribute") {
1439+
self.check_doc_keyword_and_attribute(meta, hir_id, false);
14081440
}
14091441
}
14101442

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
@@ -5033,7 +5033,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
50335033
}
50345034
ResolveDocLinks::Exported
50355035
if !maybe_exported.eval(self.r)
5036-
&& !rustdoc::has_primitive_or_keyword_docs(attrs) =>
5036+
&& !rustdoc::has_primitive_or_keyword_or_attribute_docs(attrs) =>
50375037
{
50385038
return;
50395039
}

compiler/rustc_resolve/src/rustdoc.rs

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

367-
/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]`.
368-
pub fn has_primitive_or_keyword_docs(attrs: &[impl AttributeExt]) -> bool {
367+
/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]` or `#[doc(attribute)]`.
368+
pub fn has_primitive_or_keyword_or_attribute_docs(attrs: &[impl AttributeExt]) -> bool {
369369
for attr in attrs {
370370
if attr.has_name(sym::rustc_doc_primitive) {
371371
return true;
372372
} else if attr.has_name(sym::doc)
373373
&& let Some(items) = attr.meta_item_list()
374374
{
375375
for item in items {
376-
if item.has_name(sym::keyword) {
376+
if item.has_name(sym::keyword) || item.has_name(sym::attribute) {
377377
return true;
378378
}
379379
}

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ symbols! {
539539
att_syntax,
540540
attr,
541541
attr_literals,
542+
attribute,
542543
attributes,
543544
audit_that,
544545
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,
@@ -709,7 +725,9 @@ impl Item {
709725
// Primitives and Keywords are written in the source code as private modules.
710726
// The modules need to be private so that nobody actually uses them, but the
711727
// keywords and primitives that they are documenting are public.
712-
ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) => return Some(Visibility::Public),
728+
ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) | ItemKind::AttributeItem => {
729+
return Some(Visibility::Public);
730+
}
713731
// Variant fields inherit their enum's visibility.
714732
StructFieldItem(..) if is_field_vis_inherited(tcx, def_id) => {
715733
return None;
@@ -923,7 +941,12 @@ pub(crate) enum ItemKind {
923941
AssocTypeItem(Box<TypeAlias>, Vec<GenericBound>),
924942
/// An item that has been stripped by a rustdoc pass
925943
StrippedItem(Box<ItemKind>),
944+
/// This item represents a module with a `#[doc(keyword = "...")]` attribute which is used
945+
/// to generate documentation for Rust keywords.
926946
KeywordItem,
947+
/// This item represents a module with a `#[doc(attribute = "...")]` attribute which is used
948+
/// to generate documentation for Rust builtin attributes.
949+
AttributeItem,
927950
}
928951

929952
impl ItemKind {
@@ -964,7 +987,8 @@ impl ItemKind {
964987
| RequiredAssocTypeItem(..)
965988
| AssocTypeItem(..)
966989
| StrippedItem(_)
967-
| KeywordItem => [].iter(),
990+
| KeywordItem
991+
| AttributeItem => [].iter(),
968992
}
969993
}
970994

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)