Skip to content

Commit ab0ee84

Browse files
Add new doc(attribute = "...") attribute
1 parent b416342 commit ab0ee84

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
@@ -145,6 +145,10 @@ passes_doc_alias_start_end =
145145
passes_doc_attr_not_crate_level =
146146
`#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute
147147
148+
passes_doc_attribute_not_attribute =
149+
nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = "...")]`
150+
.help = only existing builtin attributes are allowed in core/std
151+
148152
passes_doc_cfg_hide_takes_list =
149153
`#[doc(cfg_hide(...))]` takes a list of attributes
150154
@@ -173,16 +177,16 @@ passes_doc_inline_only_use =
173177
passes_doc_invalid =
174178
invalid `doc` attribute
175179
176-
passes_doc_keyword_empty_mod =
177-
`#[doc(keyword = "...")]` should be used on empty modules
180+
passes_doc_keyword_attribute_empty_mod =
181+
`#[doc({$attr_name} = "...")]` should be used on empty modules
182+
183+
passes_doc_keyword_attribute_not_mod =
184+
`#[doc({$attr_name} = "...")]` should be used on modules
178185
179186
passes_doc_keyword_not_keyword =
180187
nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]`
181188
.help = only existing keywords are allowed in core/std
182189
183-
passes_doc_keyword_not_mod =
184-
`#[doc(keyword = "...")]` should be used on modules
185-
186190
passes_doc_keyword_only_impl =
187191
`#[doc(keyword = "...")]` should be used on impl blocks
188192

compiler/rustc_passes/src/check_attr.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -851,17 +851,30 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
851851
}
852852
}
853853

854-
fn check_doc_keyword(&self, meta: &MetaItemInner, hir_id: HirId) {
854+
fn check_doc_keyword_and_attribute(
855+
&self,
856+
meta: &MetaItemInner,
857+
hir_id: HirId,
858+
is_keyword: bool,
859+
) {
855860
fn is_doc_keyword(s: Symbol) -> bool {
856861
// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
857862
// can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the
858863
// `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.
859864
s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy
860865
}
861866

862-
let doc_keyword = match meta.value_str() {
867+
fn is_builtin_attr(s: Symbol) -> bool {
868+
rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&s)
869+
}
870+
871+
fn get_attr_name(is_keyword: bool) -> &'static str {
872+
if is_keyword { "keyword" } else { "attribute" }
873+
}
874+
875+
let value = match meta.value_str() {
863876
Some(value) if value != sym::empty => value,
864-
_ => return self.doc_attr_str_error(meta, "keyword"),
877+
_ => return self.doc_attr_str_error(meta, get_attr_name(is_keyword)),
865878
};
866879

867880
let item_kind = match self.tcx.hir_node(hir_id) {
@@ -871,19 +884,32 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
871884
match item_kind {
872885
Some(ItemKind::Mod(_, module)) => {
873886
if !module.item_ids.is_empty() {
874-
self.dcx().emit_err(errors::DocKeywordEmptyMod { span: meta.span() });
887+
self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod {
888+
span: meta.span(),
889+
attr_name: get_attr_name(is_keyword),
890+
});
875891
return;
876892
}
877893
}
878894
_ => {
879-
self.dcx().emit_err(errors::DocKeywordNotMod { span: meta.span() });
895+
self.dcx().emit_err(errors::DocKeywordAttributeNotMod {
896+
span: meta.span(),
897+
attr_name: get_attr_name(is_keyword),
898+
});
880899
return;
881900
}
882901
}
883-
if !is_doc_keyword(doc_keyword) {
884-
self.dcx().emit_err(errors::DocKeywordNotKeyword {
902+
if is_keyword {
903+
if !is_doc_keyword(value) {
904+
self.dcx().emit_err(errors::DocKeywordNotKeyword {
905+
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
906+
keyword: value,
907+
});
908+
}
909+
} else if !is_builtin_attr(value) {
910+
self.dcx().emit_err(errors::DocAttributeNotAttribute {
885911
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
886-
keyword: doc_keyword,
912+
attribute: value,
887913
});
888914
}
889915
}
@@ -1144,7 +1170,13 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
11441170

11451171
Some(sym::keyword) => {
11461172
if self.check_attr_not_crate_level(meta, hir_id, "keyword") {
1147-
self.check_doc_keyword(meta, hir_id);
1173+
self.check_doc_keyword_and_attribute(meta, hir_id, true);
1174+
}
1175+
}
1176+
1177+
Some(sym::attribute) => {
1178+
if self.check_attr_not_crate_level(meta, hir_id, "attribute") {
1179+
self.check_doc_keyword_and_attribute(meta, hir_id, false);
11481180
}
11491181
}
11501182

compiler/rustc_passes/src/errors.rs

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

197197
#[derive(Diagnostic)]
198-
#[diag(passes_doc_keyword_empty_mod)]
199-
pub(crate) struct DocKeywordEmptyMod {
198+
#[diag(passes_doc_keyword_attribute_empty_mod)]
199+
pub(crate) struct DocKeywordAttributeEmptyMod {
200200
#[primary_span]
201201
pub span: Span,
202+
pub attr_name: &'static str,
202203
}
203204

204205
#[derive(Diagnostic)]
@@ -211,10 +212,20 @@ pub(crate) struct DocKeywordNotKeyword {
211212
}
212213

213214
#[derive(Diagnostic)]
214-
#[diag(passes_doc_keyword_not_mod)]
215-
pub(crate) struct DocKeywordNotMod {
215+
#[diag(passes_doc_attribute_not_attribute)]
216+
#[help]
217+
pub(crate) struct DocAttributeNotAttribute {
218+
#[primary_span]
219+
pub span: Span,
220+
pub attribute: Symbol,
221+
}
222+
223+
#[derive(Diagnostic)]
224+
#[diag(passes_doc_keyword_attribute_not_mod)]
225+
pub(crate) struct DocKeywordAttributeNotMod {
216226
#[primary_span]
217227
pub span: Span,
228+
pub attr_name: &'static str,
218229
}
219230

220231
#[derive(Diagnostic)]

compiler/rustc_resolve/src/late.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5016,7 +5016,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
50165016
}
50175017
ResolveDocLinks::Exported
50185018
if !maybe_exported.eval(self.r)
5019-
&& !rustdoc::has_primitive_or_keyword_docs(attrs) =>
5019+
&& !rustdoc::has_primitive_or_keyword_or_attribute_docs(attrs) =>
50205020
{
50215021
return;
50225022
}

compiler/rustc_resolve/src/rustdoc.rs

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

376-
/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]`.
377-
pub fn has_primitive_or_keyword_docs(attrs: &[impl AttributeExt]) -> bool {
376+
/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]` or `#[doc(attribute)]`.
377+
pub fn has_primitive_or_keyword_or_attribute_docs(attrs: &[impl AttributeExt]) -> bool {
378378
for attr in attrs {
379379
if attr.has_name(sym::rustc_doc_primitive) {
380380
return true;
381381
} else if attr.has_name(sym::doc)
382382
&& let Some(items) = attr.meta_item_list()
383383
{
384384
for item in items {
385-
if item.has_name(sym::keyword) {
385+
if item.has_name(sym::keyword) || item.has_name(sym::attribute) {
386386
return true;
387387
}
388388
}

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: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -226,15 +226,28 @@ impl ExternalCrate {
226226
}
227227

228228
pub(crate) fn keywords(&self, tcx: TyCtxt<'_>) -> impl Iterator<Item = (DefId, Symbol)> {
229-
fn as_keyword(did: DefId, tcx: TyCtxt<'_>) -> Option<(DefId, Symbol)> {
229+
self.retrieve_keywords_or_documented_attributes(tcx, sym::keyword)
230+
}
231+
pub(crate) fn documented_attributes(
232+
&self,
233+
tcx: TyCtxt<'_>,
234+
) -> impl Iterator<Item = (DefId, Symbol)> {
235+
self.retrieve_keywords_or_documented_attributes(tcx, sym::attribute)
236+
}
237+
238+
fn retrieve_keywords_or_documented_attributes(
239+
&self,
240+
tcx: TyCtxt<'_>,
241+
name: Symbol,
242+
) -> impl Iterator<Item = (DefId, Symbol)> {
243+
let as_target = move |did: DefId, tcx: TyCtxt<'_>| -> Option<(DefId, Symbol)> {
230244
tcx.get_attrs(did, sym::doc)
231245
.flat_map(|attr| attr.meta_item_list().unwrap_or_default())
232-
.filter(|meta| meta.has_name(sym::keyword))
246+
.filter(|meta| meta.has_name(name))
233247
.find_map(|meta| meta.value_str())
234248
.map(|value| (did, value))
235-
}
236-
237-
self.mapped_root_modules(tcx, as_keyword)
249+
};
250+
self.mapped_root_modules(tcx, as_target)
238251
}
239252

240253
pub(crate) fn primitives(
@@ -592,6 +605,9 @@ impl Item {
592605
pub(crate) fn is_keyword(&self) -> bool {
593606
self.type_() == ItemType::Keyword
594607
}
608+
pub(crate) fn is_attribute(&self) -> bool {
609+
self.type_() == ItemType::Attribute
610+
}
595611
pub(crate) fn is_stripped(&self) -> bool {
596612
match self.kind {
597613
StrippedItem(..) => true,
@@ -735,7 +751,9 @@ impl Item {
735751
// Primitives and Keywords are written in the source code as private modules.
736752
// The modules need to be private so that nobody actually uses them, but the
737753
// keywords and primitives that they are documenting are public.
738-
ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) => return Some(Visibility::Public),
754+
ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) | ItemKind::AttributeItem => {
755+
return Some(Visibility::Public);
756+
}
739757
// Variant fields inherit their enum's visibility.
740758
StructFieldItem(..) if is_field_vis_inherited(tcx, def_id) => {
741759
return None;
@@ -942,7 +960,12 @@ pub(crate) enum ItemKind {
942960
AssocTypeItem(Box<TypeAlias>, Vec<GenericBound>),
943961
/// An item that has been stripped by a rustdoc pass
944962
StrippedItem(Box<ItemKind>),
963+
/// This item represents a module with a `#[doc(keyword = "...")]` attribute which is used
964+
/// to generate documentation for Rust keywords.
945965
KeywordItem,
966+
/// This item represents a module with a `#[doc(attribute = "...")]` attribute which is used
967+
/// to generate documentation for Rust builtin attributes.
968+
AttributeItem,
946969
}
947970

948971
impl ItemKind {
@@ -983,7 +1006,8 @@ impl ItemKind {
9831006
| RequiredAssocTypeItem(..)
9841007
| AssocTypeItem(..)
9851008
| StrippedItem(_)
986-
| KeywordItem => [].iter(),
1009+
| KeywordItem
1010+
| AttributeItem => [].iter(),
9871011
}
9881012
}
9891013

src/librustdoc/clean/utils.rs

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

7882
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)