From ab0ee84eac9732e4e81e559c688846b4c1bd400a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 13 Jun 2025 21:01:59 +0200 Subject: Add new `doc(attribute = "...")` attribute --- compiler/rustc_passes/src/check_attr.rs | 50 +++++++++++++++++++++++++++------ compiler/rustc_passes/src/errors.rs | 19 ++++++++++--- 2 files changed, 56 insertions(+), 13 deletions(-) (limited to 'compiler/rustc_passes/src') diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 3a79176f914..fa7591b5113 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -851,7 +851,12 @@ 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 @@ -859,9 +864,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> { 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() { 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) { @@ -871,19 +884,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, }); } } @@ -1144,7 +1170,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); } } diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 01972067978..680e2a26d84 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -195,10 +195,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)] @@ -211,10 +212,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)] -- cgit 1.4.1-3-g733a5 From 10bd61dcf2e5a7dbe95b4b0588a432a444f907a9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 14 Jun 2025 12:41:30 +0200 Subject: Create new `Item::is_fake_item` method as equivalent to check for `is_primitive`, `is_keyword` and `is_attribute` methods --- compiler/rustc_passes/src/check_attr.rs | 66 ++++++++++++++++++++++---------- src/librustdoc/clean/types.rs | 11 ++++++ src/librustdoc/html/render/context.rs | 2 +- src/librustdoc/html/render/print_item.rs | 2 +- 4 files changed, 58 insertions(+), 23 deletions(-) (limited to 'compiler/rustc_passes/src') diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index fa7591b5113..410c39fbb41 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -99,6 +99,21 @@ impl IntoDiagArg for ProcMacroKind { } } +#[derive(Clone, Copy)] +enum DocFakeItemKind { + Attribute, + Keyword, +} + +impl DocFakeItemKind { + fn name(self) -> &'static str { + match self { + Self::Attribute => "attribute", + Self::Keyword => "keyword", + } + } +} + struct CheckAttrVisitor<'tcx> { tcx: TyCtxt<'tcx>, @@ -855,7 +870,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { &self, meta: &MetaItemInner, hir_id: HirId, - is_keyword: bool, + attr_kind: DocFakeItemKind, ) { fn is_doc_keyword(s: Symbol) -> bool { // FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we @@ -868,13 +883,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> { 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() { Some(value) if value != sym::empty => value, - _ => return self.doc_attr_str_error(meta, get_attr_name(is_keyword)), + _ => return self.doc_attr_str_error(meta, attr_kind.name()), }; let item_kind = match self.tcx.hir_node(hir_id) { @@ -886,7 +897,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { if !module.item_ids.is_empty() { self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod { span: meta.span(), - attr_name: get_attr_name(is_keyword), + attr_name: attr_kind.name(), }); return; } @@ -894,23 +905,28 @@ impl<'tcx> CheckAttrVisitor<'tcx> { _ => { self.dcx().emit_err(errors::DocKeywordAttributeNotMod { span: meta.span(), - attr_name: get_attr_name(is_keyword), + attr_name: attr_kind.name(), }); return; } } - 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, - }); + match attr_kind { + DocFakeItemKind::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, + }); + } + } + DocFakeItemKind::Attribute => { + if !is_builtin_attr(value) { + self.dcx().emit_err(errors::DocAttributeNotAttribute { + span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()), + attribute: value, + }); + } } - } else if !is_builtin_attr(value) { - self.dcx().emit_err(errors::DocAttributeNotAttribute { - span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()), - attribute: value, - }); } } @@ -1170,13 +1186,21 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Some(sym::keyword) => { if self.check_attr_not_crate_level(meta, hir_id, "keyword") { - self.check_doc_keyword_and_attribute(meta, hir_id, true); + self.check_doc_keyword_and_attribute( + meta, + hir_id, + DocFakeItemKind::Keyword, + ); } } Some(sym::attribute) => { if self.check_attr_not_crate_level(meta, hir_id, "attribute") { - self.check_doc_keyword_and_attribute(meta, hir_id, false); + self.check_doc_keyword_and_attribute( + meta, + hir_id, + DocFakeItemKind::Attribute, + ); } } diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 1502ec9bd78..fcff15650ce 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -608,6 +608,17 @@ impl Item { 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, diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index cafbcf7e8dd..5f92ab2fada 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -218,7 +218,7 @@ impl<'tcx> Context<'tcx> { } else { it.name.as_ref().unwrap().as_str() }; - if !it.is_primitive() && !it.is_keyword() && !it.is_attribute() { + if !it.is_fake_item() { if !is_module { title.push_str(" in "); } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 530e6da4ee3..afa438f2596 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -194,7 +194,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp let src_href = if cx.info.include_sources && !item.is_primitive() { cx.src_href(item) } else { None }; - let path_components = if item.is_primitive() || item.is_keyword() || item.is_attribute() { + let path_components = if item.is_fake_item() { vec![] } else { let cur = &cx.current; -- cgit 1.4.1-3-g733a5 From f3c023433f6e44be208f9e3bb74528449da730c1 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 28 Aug 2025 16:27:54 +0200 Subject: Add ui test for unsupported `doc(attribute = "...")` case for attributes with namespace --- compiler/rustc_passes/src/check_attr.rs | 1 + tests/rustdoc-ui/doc-attribute-unsupported.rs | 7 +++++++ tests/rustdoc-ui/doc-attribute-unsupported.stderr | 10 ++++++++++ 3 files changed, 18 insertions(+) create mode 100644 tests/rustdoc-ui/doc-attribute-unsupported.rs create mode 100644 tests/rustdoc-ui/doc-attribute-unsupported.stderr (limited to 'compiler/rustc_passes/src') diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 410c39fbb41..962555ada27 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -879,6 +879,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy } + // FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`. fn is_builtin_attr(s: Symbol) -> bool { rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&s) } diff --git a/tests/rustdoc-ui/doc-attribute-unsupported.rs b/tests/rustdoc-ui/doc-attribute-unsupported.rs new file mode 100644 index 00000000000..3bd153117a9 --- /dev/null +++ b/tests/rustdoc-ui/doc-attribute-unsupported.rs @@ -0,0 +1,7 @@ +// This is currently not supported but should be! + +#![feature(rustdoc_internals)] + +#[doc(attribute = "diagnostic::do_not_recommend")] //~ ERROR +/// bla +mod yup {} diff --git a/tests/rustdoc-ui/doc-attribute-unsupported.stderr b/tests/rustdoc-ui/doc-attribute-unsupported.stderr new file mode 100644 index 00000000000..e2480a548ac --- /dev/null +++ b/tests/rustdoc-ui/doc-attribute-unsupported.stderr @@ -0,0 +1,10 @@ +error: nonexistent builtin attribute `diagnostic::do_not_recommend` used in `#[doc(attribute = "...")]` + --> $DIR/doc-attribute-unsupported.rs:5:19 + | +LL | #[doc(attribute = "diagnostic::do_not_recommend")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: only existing builtin attributes are allowed in core/std + +error: aborting due to 1 previous error + -- cgit 1.4.1-3-g733a5