Skip to content

Commit 89bbba4

Browse files
authored
Unrolled build for #142472
Rollup merge of #142472 - GuillaumeGomez:doc-attribute-attribute, r=fmease Add new `doc(attribute = "...")` attribute Fixes #141123. The implementation and purpose of this new `#[doc(attribute = "...")]` attribute is very close to `#[doc(keyword = "...")]`. Which means that luckily for us, most of the code needed was already in place and `@Noratrieb` nicely wrote a first draft that helped me implement this new attribute very fast. Now with all this said, there is one thing I didn't do yet: adding a `rustdoc-js-std` test. I added GUI tests with search results for attributes so should be fine but I still plan on adding one for it once documentation for builtin attributes will be written into the core/std libs. You can test it [here](https://rustdoc.crud.net/imperio/doc-attribute-attribute/foo/index.html). cc `@Noratrieb` `@Veykril`
2 parents 35d55b3 + f3c0234 commit 89bbba4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+376
-55
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: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,21 @@ impl IntoDiagArg for ProcMacroKind {
9999
}
100100
}
101101

102+
#[derive(Clone, Copy)]
103+
enum DocFakeItemKind {
104+
Attribute,
105+
Keyword,
106+
}
107+
108+
impl DocFakeItemKind {
109+
fn name(self) -> &'static str {
110+
match self {
111+
Self::Attribute => "attribute",
112+
Self::Keyword => "keyword",
113+
}
114+
}
115+
}
116+
102117
struct CheckAttrVisitor<'tcx> {
103118
tcx: TyCtxt<'tcx>,
104119

@@ -853,17 +868,27 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
853868
}
854869
}
855870

856-
fn check_doc_keyword(&self, meta: &MetaItemInner, hir_id: HirId) {
871+
fn check_doc_keyword_and_attribute(
872+
&self,
873+
meta: &MetaItemInner,
874+
hir_id: HirId,
875+
attr_kind: DocFakeItemKind,
876+
) {
857877
fn is_doc_keyword(s: Symbol) -> bool {
858878
// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
859879
// can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the
860880
// `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.
861881
s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy
862882
}
863883

864-
let doc_keyword = match meta.value_str() {
884+
// FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`.
885+
fn is_builtin_attr(s: Symbol) -> bool {
886+
rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&s)
887+
}
888+
889+
let value = match meta.value_str() {
865890
Some(value) if value != sym::empty => value,
866-
_ => return self.doc_attr_str_error(meta, "keyword"),
891+
_ => return self.doc_attr_str_error(meta, attr_kind.name()),
867892
};
868893

869894
let item_kind = match self.tcx.hir_node(hir_id) {
@@ -873,20 +898,38 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
873898
match item_kind {
874899
Some(ItemKind::Mod(_, module)) => {
875900
if !module.item_ids.is_empty() {
876-
self.dcx().emit_err(errors::DocKeywordEmptyMod { span: meta.span() });
901+
self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod {
902+
span: meta.span(),
903+
attr_name: attr_kind.name(),
904+
});
877905
return;
878906
}
879907
}
880908
_ => {
881-
self.dcx().emit_err(errors::DocKeywordNotMod { span: meta.span() });
909+
self.dcx().emit_err(errors::DocKeywordAttributeNotMod {
910+
span: meta.span(),
911+
attr_name: attr_kind.name(),
912+
});
882913
return;
883914
}
884915
}
885-
if !is_doc_keyword(doc_keyword) {
886-
self.dcx().emit_err(errors::DocKeywordNotKeyword {
887-
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
888-
keyword: doc_keyword,
889-
});
916+
match attr_kind {
917+
DocFakeItemKind::Keyword => {
918+
if !is_doc_keyword(value) {
919+
self.dcx().emit_err(errors::DocKeywordNotKeyword {
920+
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
921+
keyword: value,
922+
});
923+
}
924+
}
925+
DocFakeItemKind::Attribute => {
926+
if !is_builtin_attr(value) {
927+
self.dcx().emit_err(errors::DocAttributeNotAttribute {
928+
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
929+
attribute: value,
930+
});
931+
}
932+
}
890933
}
891934
}
892935

@@ -1146,7 +1189,21 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
11461189

11471190
Some(sym::keyword) => {
11481191
if self.check_attr_not_crate_level(meta, hir_id, "keyword") {
1149-
self.check_doc_keyword(meta, hir_id);
1192+
self.check_doc_keyword_and_attribute(
1193+
meta,
1194+
hir_id,
1195+
DocFakeItemKind::Keyword,
1196+
);
1197+
}
1198+
}
1199+
1200+
Some(sym::attribute) => {
1201+
if self.check_attr_not_crate_level(meta, hir_id, "attribute") {
1202+
self.check_doc_keyword_and_attribute(
1203+
meta,
1204+
hir_id,
1205+
DocFakeItemKind::Attribute,
1206+
);
11501207
}
11511208
}
11521209

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
@@ -541,6 +541,7 @@ symbols! {
541541
att_syntax,
542542
attr,
543543
attr_literals,
544+
attribute,
544545
attributes,
545546
audit_that,
546547
augmented_assignments,

src/doc/rustdoc/src/unstable-features.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ to enable.
196196

197197
### Document keywords
198198

199-
This is for Rust compiler internal use only.
199+
This is for internal use in the std library.
200200

201201
Rust keywords are documented in the standard library (look for `match` for example).
202202

@@ -211,6 +211,23 @@ To do so, the `#[doc(keyword = "...")]` attribute is used. Example:
211211
mod empty_mod {}
212212
```
213213

214+
### Document builtin attributes
215+
216+
This is for internal use in the std library.
217+
218+
Rust builtin attributes are documented in the standard library (look for `repr` for example).
219+
220+
To do so, the `#[doc(attribute = "...")]` attribute is used. Example:
221+
222+
```rust
223+
#![feature(rustdoc_internals)]
224+
#![allow(internal_features)]
225+
226+
/// Some documentation about the attribute.
227+
#[doc(attribute = "repr")]
228+
mod empty_mod {}
229+
```
230+
214231
### Use the Rust logo as the crate logo
215232

216233
This is for official Rust project use only.

src/librustdoc/clean/types.rs

Lines changed: 42 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,20 @@ 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+
}
611+
/// Returns `true` if the item kind is one of the following:
612+
///
613+
/// * `ItemType::Primitive`
614+
/// * `ItemType::Keyword`
615+
/// * `ItemType::Attribute`
616+
///
617+
/// They are considered fake because they only exist thanks to their
618+
/// `#[doc(primitive|keyword|attribute)]` attribute.
619+
pub(crate) fn is_fake_item(&self) -> bool {
620+
matches!(self.type_(), ItemType::Primitive | ItemType::Keyword | ItemType::Attribute)
621+
}
595622
pub(crate) fn is_stripped(&self) -> bool {
596623
match self.kind {
597624
StrippedItem(..) => true,
@@ -735,7 +762,9 @@ impl Item {
735762
// Primitives and Keywords are written in the source code as private modules.
736763
// The modules need to be private so that nobody actually uses them, but the
737764
// keywords and primitives that they are documenting are public.
738-
ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) => return Some(Visibility::Public),
765+
ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) | ItemKind::AttributeItem => {
766+
return Some(Visibility::Public);
767+
}
739768
// Variant fields inherit their enum's visibility.
740769
StructFieldItem(..) if is_field_vis_inherited(tcx, def_id) => {
741770
return None;
@@ -942,7 +971,12 @@ pub(crate) enum ItemKind {
942971
AssocTypeItem(Box<TypeAlias>, Vec<GenericBound>),
943972
/// An item that has been stripped by a rustdoc pass
944973
StrippedItem(Box<ItemKind>),
974+
/// This item represents a module with a `#[doc(keyword = "...")]` attribute which is used
975+
/// to generate documentation for Rust keywords.
945976
KeywordItem,
977+
/// This item represents a module with a `#[doc(attribute = "...")]` attribute which is used
978+
/// to generate documentation for Rust builtin attributes.
979+
AttributeItem,
946980
}
947981

948982
impl ItemKind {
@@ -983,7 +1017,8 @@ impl ItemKind {
9831017
| RequiredAssocTypeItem(..)
9841018
| AssocTypeItem(..)
9851019
| StrippedItem(_)
986-
| KeywordItem => [].iter(),
1020+
| KeywordItem
1021+
| AttributeItem => [].iter(),
9871022
}
9881023
}
9891024

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

0 commit comments

Comments
 (0)