Skip to content

Commit 049f7d7

Browse files
Add new doc(attribute = "...") attribute
1 parent 40daf23 commit 049f7d7

File tree

24 files changed

+150
-44
lines changed

24 files changed

+150
-44
lines changed

compiler/rustc_ast_passes/src/feature_gate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
178178
notable_trait => doc_notable_trait
179179
}
180180
"meant for internal use only" {
181+
attribute => rustdoc_internals
181182
keyword => rustdoc_internals
182183
fake_variadic => rustdoc_internals
183184
search_unbox => rustdoc_internals

compiler/rustc_passes/messages.ftl

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ passes_doc_alias_start_end =
182182
passes_doc_attr_not_crate_level =
183183
`#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute
184184
185+
passes_doc_attribute_not_attribute =
186+
nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = "...")]`
187+
.help = only existing builtin attributes are allowed in core/std
188+
185189
passes_doc_cfg_hide_takes_list =
186190
`#[doc(cfg_hide(...))]` takes a list of attributes
187191
@@ -210,16 +214,16 @@ passes_doc_inline_only_use =
210214
passes_doc_invalid =
211215
invalid `doc` attribute
212216
213-
passes_doc_keyword_empty_mod =
214-
`#[doc(keyword = "...")]` should be used on empty modules
217+
passes_doc_keyword_attribute_empty_mod =
218+
`#[doc({$attr_name} = "...")]` should be used on empty modules
219+
220+
passes_doc_keyword_attribute_not_mod =
221+
`#[doc({$attr_name} = "...")]` should be used on modules
215222
216223
passes_doc_keyword_not_keyword =
217224
nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]`
218225
.help = only existing keywords are allowed in core/std
219226
220-
passes_doc_keyword_not_mod =
221-
`#[doc(keyword = "...")]` should be used on modules
222-
223227
passes_doc_keyword_only_impl =
224228
`#[doc(keyword = "...")]` should be used on impl blocks
225229

compiler/rustc_passes/src/check_attr.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,17 +1059,30 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
10591059
}
10601060
}
10611061

1062-
fn check_doc_keyword(&self, meta: &MetaItemInner, hir_id: HirId) {
1062+
fn check_doc_keyword_and_attribute(
1063+
&self,
1064+
meta: &MetaItemInner,
1065+
hir_id: HirId,
1066+
is_keyword: bool,
1067+
) {
10631068
fn is_doc_keyword(s: Symbol) -> bool {
10641069
// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
10651070
// can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the
10661071
// `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.
10671072
s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy
10681073
}
10691074

1070-
let doc_keyword = match meta.value_str() {
1075+
fn is_builtin_attr(s: Symbol) -> bool {
1076+
rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&s)
1077+
}
1078+
1079+
fn get_attr_name(is_keyword: bool) -> &'static str {
1080+
if is_keyword { "keyword" } else { "attribute " }
1081+
}
1082+
1083+
let value = match meta.value_str() {
10711084
Some(value) if value != sym::empty => value,
1072-
_ => return self.doc_attr_str_error(meta, "keyword"),
1085+
_ => return self.doc_attr_str_error(meta, get_attr_name(is_keyword)),
10731086
};
10741087

10751088
let item_kind = match self.tcx.hir_node(hir_id) {
@@ -1079,19 +1092,32 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
10791092
match item_kind {
10801093
Some(ItemKind::Mod(_, module)) => {
10811094
if !module.item_ids.is_empty() {
1082-
self.dcx().emit_err(errors::DocKeywordEmptyMod { span: meta.span() });
1095+
self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod {
1096+
span: meta.span(),
1097+
attr_name: get_attr_name(is_keyword),
1098+
});
10831099
return;
10841100
}
10851101
}
10861102
_ => {
1087-
self.dcx().emit_err(errors::DocKeywordNotMod { span: meta.span() });
1103+
self.dcx().emit_err(errors::DocKeywordAttributeNotMod {
1104+
span: meta.span(),
1105+
attr_name: get_attr_name(is_keyword),
1106+
});
10881107
return;
10891108
}
10901109
}
1091-
if !is_doc_keyword(doc_keyword) {
1092-
self.dcx().emit_err(errors::DocKeywordNotKeyword {
1110+
if is_keyword {
1111+
if !is_doc_keyword(value) {
1112+
self.dcx().emit_err(errors::DocKeywordNotKeyword {
1113+
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
1114+
keyword: value,
1115+
});
1116+
}
1117+
} else if !is_builtin_attr(value) {
1118+
self.dcx().emit_err(errors::DocAttributeNotAttribute {
10931119
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
1094-
keyword: doc_keyword,
1120+
attribute: value,
10951121
});
10961122
}
10971123
}
@@ -1346,7 +1372,13 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
13461372

13471373
Some(sym::keyword) => {
13481374
if self.check_attr_not_crate_level(meta, hir_id, "keyword") {
1349-
self.check_doc_keyword(meta, hir_id);
1375+
self.check_doc_keyword_and_attribute(meta, hir_id, true);
1376+
}
1377+
}
1378+
1379+
Some(sym::attribute) => {
1380+
if self.check_attr_not_crate_level(meta, hir_id, "attribute") {
1381+
self.check_doc_keyword_and_attribute(meta, hir_id, false);
13501382
}
13511383
}
13521384

compiler/rustc_passes/src/errors.rs

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

232232
#[derive(Diagnostic)]
233-
#[diag(passes_doc_keyword_empty_mod)]
234-
pub(crate) struct DocKeywordEmptyMod {
233+
#[diag(passes_doc_keyword_attribute_empty_mod)]
234+
pub(crate) struct DocKeywordAttributeEmptyMod {
235235
#[primary_span]
236236
pub span: Span,
237+
pub attr_name: &'static str,
237238
}
238239

239240
#[derive(Diagnostic)]
@@ -246,10 +247,20 @@ pub(crate) struct DocKeywordNotKeyword {
246247
}
247248

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

255266
#[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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,15 +360,15 @@ pub fn inner_docs(attrs: &[impl AttributeExt]) -> bool {
360360
}
361361

362362
/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]`.
363-
pub fn has_primitive_or_keyword_docs(attrs: &[impl AttributeExt]) -> bool {
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
@@ -537,6 +537,7 @@ symbols! {
537537
att_syntax,
538538
attr,
539539
attr_literals,
540+
attribute,
540541
attributes,
541542
audit_that,
542543
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)