Skip to content

Add new doc(attribute = "...") attribute #142472

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
notable_trait => doc_notable_trait
}
"meant for internal use only" {
attribute => rustdoc_internals
keyword => rustdoc_internals
fake_variadic => rustdoc_internals
search_unbox => rustdoc_internals
Expand Down
14 changes: 9 additions & 5 deletions compiler/rustc_passes/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ passes_doc_alias_start_end =
passes_doc_attr_not_crate_level =
`#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute

passes_doc_attribute_not_attribute =
nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = "...")]`
.help = only existing builtin attributes are allowed in core/std

passes_doc_cfg_hide_takes_list =
`#[doc(cfg_hide(...))]` takes a list of attributes

Expand Down Expand Up @@ -189,16 +193,16 @@ passes_doc_inline_only_use =
passes_doc_invalid =
invalid `doc` attribute

passes_doc_keyword_empty_mod =
`#[doc(keyword = "...")]` should be used on empty modules
passes_doc_keyword_attribute_empty_mod =
`#[doc({$attr_name} = "...")]` should be used on empty modules

passes_doc_keyword_attribute_not_mod =
`#[doc({$attr_name} = "...")]` should be used on modules

passes_doc_keyword_not_keyword =
nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]`
.help = only existing keywords are allowed in core/std

passes_doc_keyword_not_mod =
`#[doc(keyword = "...")]` should be used on modules

passes_doc_keyword_only_impl =
`#[doc(keyword = "...")]` should be used on impl blocks

Expand Down
50 changes: 41 additions & 9 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1069,17 +1069,30 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
}

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

let doc_keyword = match meta.value_str() {
fn is_builtin_attr(s: Symbol) -> bool {
rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&s)
}

fn get_attr_name(is_keyword: bool) -> &'static str {
if is_keyword { "keyword" } else { "attribute " }
}

let value = match meta.value_str() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sigh, I'll put the doc attribute high up on my todo list to get a proper parser... (this is fine for now)

Some(value) if value != sym::empty => value,
_ => return self.doc_attr_str_error(meta, "keyword"),
_ => return self.doc_attr_str_error(meta, get_attr_name(is_keyword)),
};

let item_kind = match self.tcx.hir_node(hir_id) {
Expand All @@ -1089,19 +1102,32 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
match item_kind {
Some(ItemKind::Mod(_, module)) => {
if !module.item_ids.is_empty() {
self.dcx().emit_err(errors::DocKeywordEmptyMod { span: meta.span() });
self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod {
span: meta.span(),
attr_name: get_attr_name(is_keyword),
});
return;
}
}
_ => {
self.dcx().emit_err(errors::DocKeywordNotMod { span: meta.span() });
self.dcx().emit_err(errors::DocKeywordAttributeNotMod {
span: meta.span(),
attr_name: get_attr_name(is_keyword),
});
return;
}
}
if !is_doc_keyword(doc_keyword) {
self.dcx().emit_err(errors::DocKeywordNotKeyword {
if is_keyword {
if !is_doc_keyword(value) {
self.dcx().emit_err(errors::DocKeywordNotKeyword {
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
keyword: value,
});
}
} else if !is_builtin_attr(value) {
self.dcx().emit_err(errors::DocAttributeNotAttribute {
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
keyword: doc_keyword,
attribute: value,
});
}
}
Expand Down Expand Up @@ -1356,7 +1382,13 @@ impl<'tcx> CheckAttrVisitor<'tcx> {

Some(sym::keyword) => {
if self.check_attr_not_crate_level(meta, hir_id, "keyword") {
self.check_doc_keyword(meta, hir_id);
self.check_doc_keyword_and_attribute(meta, hir_id, true);
}
}

Some(sym::attribute) => {
if self.check_attr_not_crate_level(meta, hir_id, "attribute") {
self.check_doc_keyword_and_attribute(meta, hir_id, false);
}
}

Expand Down
19 changes: 15 additions & 4 deletions compiler/rustc_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,11 @@ pub(crate) struct DocAliasMalformed {
}

#[derive(Diagnostic)]
#[diag(passes_doc_keyword_empty_mod)]
pub(crate) struct DocKeywordEmptyMod {
#[diag(passes_doc_keyword_attribute_empty_mod)]
pub(crate) struct DocKeywordAttributeEmptyMod {
#[primary_span]
pub span: Span,
pub attr_name: &'static str,
}

#[derive(Diagnostic)]
Expand All @@ -245,10 +246,20 @@ pub(crate) struct DocKeywordNotKeyword {
}

#[derive(Diagnostic)]
#[diag(passes_doc_keyword_not_mod)]
pub(crate) struct DocKeywordNotMod {
#[diag(passes_doc_attribute_not_attribute)]
#[help]
pub(crate) struct DocAttributeNotAttribute {
#[primary_span]
pub span: Span,
pub attribute: Symbol,
}

#[derive(Diagnostic)]
#[diag(passes_doc_keyword_attribute_not_mod)]
pub(crate) struct DocKeywordAttributeNotMod {
#[primary_span]
pub span: Span,
pub attr_name: &'static str,
}

#[derive(Diagnostic)]
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_resolve/src/late.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5072,7 +5072,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
}
ResolveDocLinks::Exported
if !maybe_exported.eval(self.r)
&& !rustdoc::has_primitive_or_keyword_docs(attrs) =>
&& !rustdoc::has_primitive_or_keyword_or_attribute_docs(attrs) =>
{
return;
}
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_resolve/src/rustdoc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,16 +359,16 @@ pub fn inner_docs(attrs: &[impl AttributeExt]) -> bool {
attrs.iter().find(|a| a.doc_str().is_some()).is_none_or(|a| a.style() == ast::AttrStyle::Inner)
}

/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]`.
pub fn has_primitive_or_keyword_docs(attrs: &[impl AttributeExt]) -> bool {
/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]` or `#[doc(attribute)]`.
pub fn has_primitive_or_keyword_or_attribute_docs(attrs: &[impl AttributeExt]) -> bool {
for attr in attrs {
if attr.has_name(sym::rustc_doc_primitive) {
return true;
} else if attr.has_name(sym::doc)
&& let Some(items) = attr.meta_item_list()
{
for item in items {
if item.has_name(sym::keyword) {
if item.has_name(sym::keyword) || item.has_name(sym::attribute) {
return true;
}
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ symbols! {
att_syntax,
attr,
attr_literals,
attribute,
attributes,
audit_that,
augmented_assignments,
Expand Down
17 changes: 17 additions & 0 deletions src/doc/rustdoc/src/unstable-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,23 @@ To do so, the `#[doc(keyword = "...")]` attribute is used. Example:
mod empty_mod {}
```

### Document builtin attributes

This is for Rust compiler internal use only.

Rust builtin attributes are documented in the standard library (look for `repr` for example).

To do so, the `#[doc(attribute = "...")]` attribute is used. Example:

```rust
#![feature(rustdoc_internals)]
#![allow(internal_features)]

/// Some documentation about the attribute.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we make sure this stays in sync with the compiler's view of attributes? Is the plan to keep this manual, or could we somehow generate this from for example rustc_attr_data_structures

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just like keywords: we can only check that the attribute is used on an existing one. If new ones are added, there is no check for that.

We could add a tidy check for it though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that'd be nice

#[doc(attribute = "repr")]
mod empty_mod {}
```

### Use the Rust logo as the crate logo

This is for official Rust project use only.
Expand Down
45 changes: 39 additions & 6 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,16 +200,27 @@ impl ExternalCrate {
}

pub(crate) fn keywords(&self, tcx: TyCtxt<'_>) -> ThinVec<(DefId, Symbol)> {
self.retrieve_keywords_or_documented_attributes(tcx, sym::keyword)
}
pub(crate) fn documented_attributes(&self, tcx: TyCtxt<'_>) -> ThinVec<(DefId, Symbol)> {
self.retrieve_keywords_or_documented_attributes(tcx, sym::attribute)
}

fn retrieve_keywords_or_documented_attributes(
&self,
tcx: TyCtxt<'_>,
name: Symbol,
) -> ThinVec<(DefId, Symbol)> {
let root = self.def_id();

let as_keyword = |res: Res<!>| {
let as_target = |res: Res<!>| {
if let Res::Def(DefKind::Mod, def_id) = res {
let mut keyword = None;
let meta_items = tcx
.get_attrs(def_id, sym::doc)
.flat_map(|attr| attr.meta_item_list().unwrap_or_default());
for meta in meta_items {
if meta.has_name(sym::keyword)
if meta.has_name(name)
&& let Some(v) = meta.value_str()
{
keyword = Some(v);
Expand All @@ -228,14 +239,14 @@ impl ExternalCrate {
let item = tcx.hir_item(id);
match item.kind {
hir::ItemKind::Mod(..) => {
as_keyword(Res::Def(DefKind::Mod, id.owner_id.to_def_id()))
as_target(Res::Def(DefKind::Mod, id.owner_id.to_def_id()))
}
_ => None,
}
})
.collect()
} else {
tcx.module_children(root).iter().map(|item| item.res).filter_map(as_keyword).collect()
tcx.module_children(root).iter().map(|item| item.res).filter_map(as_target).collect()
}
}

Expand Down Expand Up @@ -597,6 +608,20 @@ impl Item {
pub(crate) fn is_keyword(&self) -> bool {
self.type_() == ItemType::Keyword
}
pub(crate) fn is_attribute(&self) -> bool {
self.type_() == ItemType::Attribute
}
/// Returns `true` if the item kind is one of the following:
///
/// * `ItemType::Primitive`
/// * `ItemType::Keyword`
/// * `ItemType::Attribute`
///
/// They are considered fake because they only exist thanks to their
/// `#[doc(primitive|keyword|attribute)]` attribute.
pub(crate) fn is_fake_item(&self) -> bool {
matches!(self.type_(), ItemType::Primitive | ItemType::Keyword | ItemType::Attribute)
}
pub(crate) fn is_stripped(&self) -> bool {
match self.kind {
StrippedItem(..) => true,
Expand Down Expand Up @@ -727,7 +752,9 @@ impl Item {
// Primitives and Keywords are written in the source code as private modules.
// The modules need to be private so that nobody actually uses them, but the
// keywords and primitives that they are documenting are public.
ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) => return Some(Visibility::Public),
ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) | ItemKind::AttributeItem => {
return Some(Visibility::Public);
}
// Variant fields inherit their enum's visibility.
StructFieldItem(..) if is_field_vis_inherited(tcx, def_id) => {
return None;
Expand Down Expand Up @@ -943,7 +970,12 @@ pub(crate) enum ItemKind {
AssocTypeItem(Box<TypeAlias>, Vec<GenericBound>),
/// An item that has been stripped by a rustdoc pass
StrippedItem(Box<ItemKind>),
/// This item represents a module with a `#[doc(keyword = "...")]` attribute which is used
/// to generate documentation for Rust keywords.
KeywordItem,
/// This item represents a module with a `#[doc(attribute = "...")]` attribute which is used
/// to generate documentation for Rust builtin attributes.
AttributeItem,
}

impl ItemKind {
Expand Down Expand Up @@ -984,7 +1016,8 @@ impl ItemKind {
| RequiredAssocTypeItem(..)
| AssocTypeItem(..)
| StrippedItem(_)
| KeywordItem => [].iter(),
| KeywordItem
| AttributeItem => [].iter(),
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/librustdoc/clean/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
let local_crate = ExternalCrate { crate_num: LOCAL_CRATE };
let primitives = local_crate.primitives(cx.tcx);
let keywords = local_crate.keywords(cx.tcx);
let documented_attributes = local_crate.documented_attributes(cx.tcx);
{
let ItemKind::ModuleItem(m) = &mut module.inner.kind else { unreachable!() };
m.items.extend(primitives.iter().map(|&(def_id, prim)| {
Expand All @@ -72,6 +73,9 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
m.items.extend(keywords.into_iter().map(|(def_id, kw)| {
Item::from_def_id_and_parts(def_id, Some(kw), ItemKind::KeywordItem, cx)
}));
m.items.extend(documented_attributes.into_iter().map(|(def_id, kw)| {
Item::from_def_id_and_parts(def_id, Some(kw), ItemKind::AttributeItem, cx)
}));
}

Crate { module, external_traits: Box::new(mem::take(&mut cx.external_traits)) }
Expand Down
3 changes: 2 additions & 1 deletion src/librustdoc/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ pub(crate) trait DocFolder: Sized {
| ImplAssocConstItem(..)
| RequiredAssocTypeItem(..)
| AssocTypeItem(..)
| KeywordItem => kind,
| KeywordItem
| AttributeItem => kind,
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/librustdoc/formats/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,8 @@ impl DocFolder for CacheBuilder<'_, '_> {
| clean::RequiredAssocTypeItem(..)
| clean::AssocTypeItem(..)
| clean::StrippedItem(..)
| clean::KeywordItem => {
| clean::KeywordItem
| clean::AttributeItem => {
// FIXME: Do these need handling?
// The person writing this comment doesn't know.
// So would rather leave them to an expert,
Expand Down
3 changes: 3 additions & 0 deletions src/librustdoc/formats/item_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub(crate) enum ItemType {
TraitAlias = 25,
// This number is reserved for use in JavaScript
// Generic = 26,
Attribute = 27,
}

impl Serialize for ItemType {
Expand Down Expand Up @@ -102,6 +103,7 @@ impl<'a> From<&'a clean::Item> for ItemType {
clean::RequiredAssocTypeItem(..) | clean::AssocTypeItem(..) => ItemType::AssocType,
clean::ForeignTypeItem => ItemType::ForeignType,
clean::KeywordItem => ItemType::Keyword,
clean::AttributeItem => ItemType::Attribute,
clean::TraitAliasItem(..) => ItemType::TraitAlias,
clean::ProcMacroItem(mac) => match mac.kind {
MacroKind::Bang => ItemType::Macro,
Expand Down Expand Up @@ -189,6 +191,7 @@ impl ItemType {
ItemType::ProcAttribute => "attr",
ItemType::ProcDerive => "derive",
ItemType::TraitAlias => "traitalias",
ItemType::Attribute => "attribute",
}
}
pub(crate) fn is_method(&self) -> bool {
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/html/render/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ impl<'tcx> Context<'tcx> {
if !is_module {
title.push_str(it.name.unwrap().as_str());
}
if !it.is_primitive() && !it.is_keyword() {
if !it.is_fake_item() {
if !is_module {
title.push_str(" in ");
}
Expand Down
Loading
Loading