From cf8753e4f9c3597f04cd5d3aa261e4561d5378a6 Mon Sep 17 00:00:00 2001 From: Matthew Maurer Date: Tue, 10 Jun 2025 23:37:05 +0000 Subject: [PATCH 01/13] Make `lto` and `linker-plugin-lto` work the same for `compiler_builtins` Fix #142284 by ensuring that `#![no_builtins]` crates can still emit bitcode when proper (i.e., non-rustc) LTO (i.e., -Clinker-plugin-lto) is used. --- compiler/rustc_codegen_ssa/src/back/write.rs | 13 +----------- tests/ui/sanitizer/cfi/no_builtins.rs | 22 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 tests/ui/sanitizer/cfi/no_builtins.rs diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index aa29afb7f5b11..f460c900c08e7 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -143,23 +143,12 @@ impl ModuleConfig { let emit_obj = if !should_emit_obj { EmitObj::None - } else if sess.target.obj_is_bitcode - || (sess.opts.cg.linker_plugin_lto.enabled() && !no_builtins) - { + } else if sess.target.obj_is_bitcode || sess.opts.cg.linker_plugin_lto.enabled() { // This case is selected if the target uses objects as bitcode, or // if linker plugin LTO is enabled. In the linker plugin LTO case // the assumption is that the final link-step will read the bitcode // and convert it to object code. This may be done by either the // native linker or rustc itself. - // - // Note, however, that the linker-plugin-lto requested here is - // explicitly ignored for `#![no_builtins]` crates. These crates are - // specifically ignored by rustc's LTO passes and wouldn't work if - // loaded into the linker. These crates define symbols that LLVM - // lowers intrinsics to, and these symbol dependencies aren't known - // until after codegen. As a result any crate marked - // `#![no_builtins]` is assumed to not participate in LTO and - // instead goes on to generate object code. EmitObj::Bitcode } else if need_bitcode_in_object(tcx) { EmitObj::ObjectCode(BitcodeSection::Full) diff --git a/tests/ui/sanitizer/cfi/no_builtins.rs b/tests/ui/sanitizer/cfi/no_builtins.rs new file mode 100644 index 0000000000000..949057689aba1 --- /dev/null +++ b/tests/ui/sanitizer/cfi/no_builtins.rs @@ -0,0 +1,22 @@ +// Verifies that `#![no_builtins]` crates can be built with linker-plugin-lto and CFI. +// See Issue #142284 +// +//@ needs-sanitizer-cfi +//@ compile-flags: -Clinker-plugin-lto -Copt-level=0 -Zsanitizer=cfi -Ctarget-feature=-crt-static +//@ compile-flags: --crate-type rlib +//@ build-pass + +#![no_builtins] +#![no_std] + +pub static FUNC: fn() = initializer; + +pub fn initializer() { + call(fma_with_fma); +} + +pub fn call(fn_ptr: fn()) { + fn_ptr(); +} + +pub fn fma_with_fma() {} From 21de27c88a87a141003198ec0d9df789ca9c75cc Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 27 Aug 2025 17:30:59 +1000 Subject: [PATCH 02/13] Explicitly mark the end of a failed test's captured output --- src/bootstrap/src/utils/render_tests.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/utils/render_tests.rs b/src/bootstrap/src/utils/render_tests.rs index 40006aca5c53f..90fd57d976d3f 100644 --- a/src/bootstrap/src/utils/render_tests.rs +++ b/src/bootstrap/src/utils/render_tests.rs @@ -250,8 +250,14 @@ impl<'a> Renderer<'a> { if failure.stdout.is_some() || failure.message.is_some() { println!("---- {} stdout ----", failure.name); if let Some(stdout) = &failure.stdout { - println!("{stdout}"); + // Captured test output normally ends with a newline, + // so only use `println!` if it doesn't. + print!("{stdout}"); + if !stdout.ends_with('\n') { + println!("\n\\ (no newline at end of output)"); + } } + println!("---- {} stdout end ----", failure.name); if let Some(message) = &failure.message { println!("NOTE: {message}"); } From bf6e99d7348270a3f43fb180c7de1c6e15bcba2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Wed, 27 Aug 2025 17:40:23 +0200 Subject: [PATCH 03/13] add doc-hidden to exports in attribute prelude --- .../rustc_attr_parsing/src/attributes/prelude.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/prelude.rs b/compiler/rustc_attr_parsing/src/attributes/prelude.rs index 2bcdee55c7560..6aef7e7a67be4 100644 --- a/compiler/rustc_attr_parsing/src/attributes/prelude.rs +++ b/compiler/rustc_attr_parsing/src/attributes/prelude.rs @@ -1,20 +1,30 @@ -// parsing // templates +#[doc(hidden)] pub(super) use rustc_feature::{AttributeTemplate, template}; // data structures +#[doc(hidden)] pub(super) use rustc_hir::attrs::AttributeKind; +#[doc(hidden)] pub(super) use rustc_hir::lints::AttributeLintKind; +#[doc(hidden)] pub(super) use rustc_hir::{MethodKind, Target}; +#[doc(hidden)] pub(super) use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym}; +#[doc(hidden)] pub(super) use thin_vec::ThinVec; +#[doc(hidden)] pub(super) use crate::attributes::{ AcceptMapping, AttributeOrder, AttributeParser, CombineAttributeParser, ConvertFn, NoArgsAttributeParser, OnDuplicate, SingleAttributeParser, }; // contexts +#[doc(hidden)] pub(super) use crate::context::{AcceptContext, FinalizeContext, Stage}; +#[doc(hidden)] pub(super) use crate::parser::*; // target checking +#[doc(hidden)] pub(super) use crate::target_checking::Policy::{Allow, Error, Warn}; +#[doc(hidden)] pub(super) use crate::target_checking::{ALL_TARGETS, AllowedTargets}; From ab0ee84eac9732e4e81e559c688846b4c1bd400a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 13 Jun 2025 21:01:59 +0200 Subject: [PATCH 04/13] Add new `doc(attribute = "...")` attribute --- compiler/rustc_ast_passes/src/feature_gate.rs | 1 + compiler/rustc_passes/messages.ftl | 14 ++++-- compiler/rustc_passes/src/check_attr.rs | 50 +++++++++++++++---- compiler/rustc_passes/src/errors.rs | 19 +++++-- compiler/rustc_resolve/src/late.rs | 2 +- compiler/rustc_resolve/src/rustdoc.rs | 6 +-- compiler/rustc_span/src/symbol.rs | 1 + src/librustdoc/clean/types.rs | 38 +++++++++++--- src/librustdoc/clean/utils.rs | 4 ++ src/librustdoc/fold.rs | 3 +- src/librustdoc/formats/cache.rs | 3 +- src/librustdoc/formats/item_type.rs | 3 ++ src/librustdoc/html/render/context.rs | 2 +- src/librustdoc/html/render/mod.rs | 5 ++ src/librustdoc/html/render/print_item.rs | 9 ++-- src/librustdoc/html/static/css/noscript.css | 2 + src/librustdoc/html/static/css/rustdoc.css | 7 +++ src/librustdoc/html/static/js/main.js | 1 + src/librustdoc/html/static/js/search.js | 5 +- src/librustdoc/json/conversions.rs | 14 ++++-- .../passes/check_doc_test_visibility.rs | 1 + .../passes/collect_intra_doc_links.rs | 4 +- src/librustdoc/passes/propagate_stability.rs | 3 +- src/librustdoc/passes/stripper.rs | 2 + src/librustdoc/visit.rs | 3 +- src/rustdoc-json-types/lib.rs | 9 +++- src/tools/jsondoclint/src/item_kind.rs | 4 ++ 27 files changed, 168 insertions(+), 47 deletions(-) diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index e763c9d69fc25..9ab5b0b354726 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -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 diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 3be06a3704ce2..498d9a405fa2d 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -145,6 +145,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 @@ -173,16 +177,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 diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 3a79176f914f3..fa7591b511362 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 0197206797895..680e2a26d8401 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)] diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 679e663f88614..d4f7fb276a979 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -5016,7 +5016,7 @@ impl<'a, 'ast, 'ra, '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; } diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index f9f2f84bc50f6..804792c6f286d 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -373,8 +373,8 @@ pub fn inner_docs(attrs: &[impl AttributeExt]) -> bool { true } -/// 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; @@ -382,7 +382,7 @@ pub fn has_primitive_or_keyword_docs(attrs: &[impl AttributeExt]) -> bool { && 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; } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 585968044bf24..db20f201a8a35 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -540,6 +540,7 @@ symbols! { att_syntax, attr, attr_literals, + attribute, attributes, audit_that, augmented_assignments, diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 92bd4a498ca37..1502ec9bd78a9 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -226,15 +226,28 @@ impl ExternalCrate { } pub(crate) fn keywords(&self, tcx: TyCtxt<'_>) -> impl Iterator { - fn as_keyword(did: DefId, tcx: TyCtxt<'_>) -> Option<(DefId, Symbol)> { + self.retrieve_keywords_or_documented_attributes(tcx, sym::keyword) + } + pub(crate) fn documented_attributes( + &self, + tcx: TyCtxt<'_>, + ) -> impl Iterator { + self.retrieve_keywords_or_documented_attributes(tcx, sym::attribute) + } + + fn retrieve_keywords_or_documented_attributes( + &self, + tcx: TyCtxt<'_>, + name: Symbol, + ) -> impl Iterator { + let as_target = move |did: DefId, tcx: TyCtxt<'_>| -> Option<(DefId, Symbol)> { tcx.get_attrs(did, sym::doc) .flat_map(|attr| attr.meta_item_list().unwrap_or_default()) - .filter(|meta| meta.has_name(sym::keyword)) + .filter(|meta| meta.has_name(name)) .find_map(|meta| meta.value_str()) .map(|value| (did, value)) - } - - self.mapped_root_modules(tcx, as_keyword) + }; + self.mapped_root_modules(tcx, as_target) } pub(crate) fn primitives( @@ -592,6 +605,9 @@ impl Item { pub(crate) fn is_keyword(&self) -> bool { self.type_() == ItemType::Keyword } + pub(crate) fn is_attribute(&self) -> bool { + self.type_() == ItemType::Attribute + } pub(crate) fn is_stripped(&self) -> bool { match self.kind { StrippedItem(..) => true, @@ -735,7 +751,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; @@ -942,7 +960,12 @@ pub(crate) enum ItemKind { AssocTypeItem(Box, Vec), /// An item that has been stripped by a rustdoc pass StrippedItem(Box), + /// 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 { @@ -983,7 +1006,8 @@ impl ItemKind { | RequiredAssocTypeItem(..) | AssocTypeItem(..) | StrippedItem(_) - | KeywordItem => [].iter(), + | KeywordItem + | AttributeItem => [].iter(), } } diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 813fdee57e17a..6fc1e43c72495 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -60,6 +60,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.map(|(def_id, prim)| { @@ -73,6 +74,9 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { m.items.extend(keywords.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)) } diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs index c03d16ad081bf..ee5f260615db5 100644 --- a/src/librustdoc/fold.rs +++ b/src/librustdoc/fold.rs @@ -96,7 +96,8 @@ pub(crate) trait DocFolder: Sized { | ImplAssocConstItem(..) | RequiredAssocTypeItem(..) | AssocTypeItem(..) - | KeywordItem => kind, + | KeywordItem + | AttributeItem => kind, } } diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index cb6837dd6140d..29b4c4caaf86d 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -372,7 +372,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, diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index 142a9d7d8af25..e94ef51730993 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -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 { @@ -148,6 +149,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, @@ -236,6 +238,7 @@ impl ItemType { ItemType::ProcAttribute => "attr", ItemType::ProcDerive => "derive", ItemType::TraitAlias => "traitalias", + ItemType::Attribute => "attribute", } } pub(crate) fn is_method(&self) -> bool { diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index faaecaf0cbb82..cafbcf7e8dd94 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() { + if !it.is_primitive() && !it.is_keyword() && !it.is_attribute() { if !is_module { title.push_str(" in "); } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 673947ad30832..6db90c9bf2a45 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -2535,6 +2535,7 @@ pub(crate) enum ItemSection { AssociatedConstants, ForeignTypes, Keywords, + Attributes, AttributeMacros, DeriveMacros, TraitAliases, @@ -2567,6 +2568,7 @@ impl ItemSection { AssociatedConstants, ForeignTypes, Keywords, + Attributes, AttributeMacros, DeriveMacros, TraitAliases, @@ -2596,6 +2598,7 @@ impl ItemSection { Self::AssociatedConstants => "associated-consts", Self::ForeignTypes => "foreign-types", Self::Keywords => "keywords", + Self::Attributes => "attributes", Self::AttributeMacros => "attributes", Self::DeriveMacros => "derives", Self::TraitAliases => "trait-aliases", @@ -2625,6 +2628,7 @@ impl ItemSection { Self::AssociatedConstants => "Associated Constants", Self::ForeignTypes => "Foreign Types", Self::Keywords => "Keywords", + Self::Attributes => "Attributes", Self::AttributeMacros => "Attribute Macros", Self::DeriveMacros => "Derive Macros", Self::TraitAliases => "Trait Aliases", @@ -2655,6 +2659,7 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection { ItemType::AssocConst => ItemSection::AssociatedConstants, ItemType::ForeignType => ItemSection::ForeignTypes, ItemType::Keyword => ItemSection::Keywords, + ItemType::Attribute => ItemSection::Attributes, ItemType::ProcAttribute => ItemSection::AttributeMacros, ItemType::ProcDerive => ItemSection::DeriveMacros, ItemType::TraitAlias => ItemSection::TraitAliases, diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index b86a2c94697f8..530e6da4ee303 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -173,6 +173,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp clean::ConstantItem(..) => "Constant ", clean::ForeignTypeItem => "Foreign Type ", clean::KeywordItem => "Keyword ", + clean::AttributeItem => "Attribute ", clean::TraitAliasItem(..) => "Trait Alias ", _ => { // We don't generate pages for any other type. @@ -193,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() { + let path_components = if item.is_primitive() || item.is_keyword() || item.is_attribute() { vec![] } else { let cur = &cx.current; @@ -252,7 +253,9 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp clean::ForeignTypeItem => { write!(buf, "{}", item_foreign_type(cx, item)) } - clean::KeywordItem => write!(buf, "{}", item_keyword(cx, item)), + clean::KeywordItem | clean::AttributeItem => { + write!(buf, "{}", item_keyword_or_attribute(cx, item)) + } clean::TraitAliasItem(ta) => { write!(buf, "{}", item_trait_alias(cx, item, ta)) } @@ -2151,7 +2154,7 @@ fn item_foreign_type(cx: &Context<'_>, it: &clean::Item) -> impl fmt::Display { }) } -fn item_keyword(cx: &Context<'_>, it: &clean::Item) -> impl fmt::Display { +fn item_keyword_or_attribute(cx: &Context<'_>, it: &clean::Item) -> impl fmt::Display { document(cx, it, None, HeadingOffset::H2) } diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css index a3c6bf9816169..5c02e2eb26a3e 100644 --- a/src/librustdoc/html/static/css/noscript.css +++ b/src/librustdoc/html/static/css/noscript.css @@ -75,6 +75,7 @@ nav.sub { --function-link-color: #ad7c37; --macro-link-color: #068000; --keyword-link-color: #3873ad; + --attribute-link-color: #3873ad; --mod-link-color: #3873ad; --link-color: #3873ad; --sidebar-link-color: #356da4; @@ -180,6 +181,7 @@ nav.sub { --function-link-color: #2bab63; --macro-link-color: #09bd00; --keyword-link-color: #d2991d; + --attribute-link-color: #d2991d; --mod-link-color: #d2991d; --link-color: #d2991d; --sidebar-link-color: #fdbf35; diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 86f1a42bc0149..09d289d570c06 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -400,6 +400,10 @@ span.keyword, a.keyword { color: var(--keyword-link-color); } +span.attribute, a.attribute { + color: var(--attribute-link-color); +} + a { color: var(--link-color); text-decoration: none; @@ -3190,6 +3194,7 @@ by default. --function-link-color: #ad7c37; --macro-link-color: #068000; --keyword-link-color: #3873ad; + --attribute-link-color: #3873ad; --mod-link-color: #3873ad; --link-color: #3873ad; --sidebar-link-color: #356da4; @@ -3294,6 +3299,7 @@ by default. --function-link-color: #2bab63; --macro-link-color: #09bd00; --keyword-link-color: #d2991d; + --attribute-link-color: #d2991d; --mod-link-color: #d2991d; --link-color: #d2991d; --sidebar-link-color: #fdbf35; @@ -3407,6 +3413,7 @@ Original by Dempfi (https://github.com/dempfi/ayu) --function-link-color: #fdd687; --macro-link-color: #a37acc; --keyword-link-color: #39afd7; + --attribute-link-color: #39afd7; --mod-link-color: #39afd7; --link-color: #39afd7; --sidebar-link-color: #53b1db; diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 4fcba5f120ba2..75febd6f737e6 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -790,6 +790,7 @@ function preLoadCss(cssUrl) { //block("associatedconstant", "associated-consts", "Associated Constants"); block("foreigntype", "foreign-types", "Foreign Types"); block("keyword", "keywords", "Keywords"); + block("attribute", "attributes", "Attributes"); block("attr", "attributes", "Attribute Macros"); block("derive", "derives", "Derive Macros"); block("traitalias", "trait-aliases", "Trait Aliases"); diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 3fb4db3a89c5c..b003bcc7bf9c8 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -119,6 +119,7 @@ const itemTypes = [ "derive", "traitalias", // 25 "generic", + "attribute", ]; // used for special search precedence @@ -2058,7 +2059,7 @@ class DocSearch { displayPath = item.modulePath + "::"; href = this.rootPath + item.modulePath.replace(/::/g, "/") + "/index.html#reexport." + name; - } else if (type === "primitive" || type === "keyword") { + } else if (type === "primitive" || type === "keyword" || type === "attribute") { displayPath = ""; exactPath = ""; href = this.rootPath + path.replace(/::/g, "/") + @@ -4560,6 +4561,8 @@ const longItemTypes = [ "attribute macro", "derive macro", "trait alias", + "", + "attribute", ]; // @ts-expect-error let currentResults; diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 5fab8ad2a4b1e..f0520716228a5 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -52,7 +52,7 @@ impl JsonRenderer<'_> { let clean::ItemInner { name, item_id, .. } = *item.inner; let id = self.id_from_item(item); let inner = match item.kind { - clean::KeywordItem => return None, + clean::KeywordItem | clean::AttributeItem => return None, clean::StrippedItem(ref inner) => { match &**inner { // We document stripped modules as with `Module::is_stripped` set to @@ -85,7 +85,7 @@ impl JsonRenderer<'_> { fn ids(&self, items: &[clean::Item]) -> Vec { items .iter() - .filter(|i| !i.is_stripped() && !i.is_keyword()) + .filter(|i| !i.is_stripped() && !i.is_keyword() && !i.is_attribute()) .map(|i| self.id_from_item(i)) .collect() } @@ -93,7 +93,10 @@ impl JsonRenderer<'_> { fn ids_keeping_stripped(&self, items: &[clean::Item]) -> Vec> { items .iter() - .map(|i| (!i.is_stripped() && !i.is_keyword()).then(|| self.id_from_item(i))) + .map(|i| { + (!i.is_stripped() && !i.is_keyword() && !i.is_attribute()) + .then(|| self.id_from_item(i)) + }) .collect() } } @@ -332,8 +335,8 @@ fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum bounds: b.into_json(renderer), type_: Some(t.item_type.as_ref().unwrap_or(&t.type_).into_json(renderer)), }, - // `convert_item` early returns `None` for stripped items and keywords. - KeywordItem => unreachable!(), + // `convert_item` early returns `None` for stripped items, keywords and attributes. + KeywordItem | AttributeItem => unreachable!(), StrippedItem(inner) => { match inner.as_ref() { ModuleItem(m) => ItemEnum::Module(Module { @@ -887,6 +890,7 @@ impl FromClean for ItemKind { AssocType => ItemKind::AssocType, ForeignType => ItemKind::ExternType, Keyword => ItemKind::Keyword, + Attribute => ItemKind::Attribute, TraitAlias => ItemKind::TraitAlias, ProcAttribute => ItemKind::ProcAttribute, ProcDerive => ItemKind::ProcDerive, diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs index 8028afea363d5..e0ea760cf3ba5 100644 --- a/src/librustdoc/passes/check_doc_test_visibility.rs +++ b/src/librustdoc/passes/check_doc_test_visibility.rs @@ -67,6 +67,7 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) - | clean::ImportItem(_) | clean::PrimitiveItem(_) | clean::KeywordItem + | clean::AttributeItem | clean::ModuleItem(_) | clean::TraitAliasItem(_) | clean::ForeignFunctionItem(..) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index bad51d7f5b2ee..719b7c6ab89c4 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -19,7 +19,7 @@ use rustc_hir::{Mutability, Safety}; use rustc_middle::ty::{Ty, TyCtxt}; use rustc_middle::{bug, span_bug, ty}; use rustc_resolve::rustdoc::{ - MalformedGenerics, has_primitive_or_keyword_docs, prepare_to_doc_link_resolution, + MalformedGenerics, has_primitive_or_keyword_or_attribute_docs, prepare_to_doc_link_resolution, source_span_for_markdown_range, strip_generics_from_path, }; use rustc_session::config::CrateType; @@ -1073,7 +1073,7 @@ impl LinkCollector<'_, '_> { && let Some(def_id) = item.item_id.as_def_id() && let Some(def_id) = def_id.as_local() && !self.cx.tcx.effective_visibilities(()).is_exported(def_id) - && !has_primitive_or_keyword_docs(&item.attrs.other_attrs) + && !has_primitive_or_keyword_or_attribute_docs(&item.attrs.other_attrs) { // Skip link resolution for non-exported items. return; diff --git a/src/librustdoc/passes/propagate_stability.rs b/src/librustdoc/passes/propagate_stability.rs index 14ec58702e356..5139ca301dd3d 100644 --- a/src/librustdoc/passes/propagate_stability.rs +++ b/src/librustdoc/passes/propagate_stability.rs @@ -106,7 +106,8 @@ impl DocFolder for StabilityPropagator<'_, '_> { | ItemKind::RequiredAssocTypeItem(..) | ItemKind::AssocTypeItem(..) | ItemKind::PrimitiveItem(..) - | ItemKind::KeywordItem => own_stability, + | ItemKind::KeywordItem + | ItemKind::AttributeItem => own_stability, ItemKind::StrippedItem(..) => unreachable!(), } diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index eedbbca0f8dfc..99d22526f85b7 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -133,6 +133,8 @@ impl DocFolder for Stripper<'_, '_> { // Keywords are never stripped clean::KeywordItem => {} + // Attributes are never stripped + clean::AttributeItem => {} } let fastreturn = match i.kind { diff --git a/src/librustdoc/visit.rs b/src/librustdoc/visit.rs index b8b619514aad9..4d31409afe825 100644 --- a/src/librustdoc/visit.rs +++ b/src/librustdoc/visit.rs @@ -49,7 +49,8 @@ pub(crate) trait DocVisitor<'a>: Sized { | ImplAssocConstItem(..) | RequiredAssocTypeItem(..) | AssocTypeItem(..) - | KeywordItem => {} + | KeywordItem + | AttributeItem => {} } } diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index 40f89009a431b..658d3791d2578 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -37,8 +37,8 @@ pub type FxHashMap = HashMap; // re-export for use in src/librustdoc // will instead cause conflicts. See #94591 for more. (This paragraph and the "Latest feature" line // are deliberately not in a doc comment, because they need not be in public docs.) // -// Latest feature: Add Attribute::MacroUse -pub const FORMAT_VERSION: u32 = 55; +// Latest feature: Add `ItemKind::Attribute`. +pub const FORMAT_VERSION: u32 = 56; /// The root of the emitted JSON blob. /// @@ -552,6 +552,11 @@ pub enum ItemKind { /// [`Item`]s of this kind only come from the come library and exist solely /// to carry documentation for the respective keywords. Keyword, + /// An attribute declaration. + /// + /// [`Item`]s of this kind only come from the core library and exist solely + /// to carry documentation for the respective builtin attributes. + Attribute, } /// Specific fields of an item. diff --git a/src/tools/jsondoclint/src/item_kind.rs b/src/tools/jsondoclint/src/item_kind.rs index 51146831efa40..e2738636a1458 100644 --- a/src/tools/jsondoclint/src/item_kind.rs +++ b/src/tools/jsondoclint/src/item_kind.rs @@ -26,6 +26,7 @@ pub(crate) enum Kind { AssocType, Primitive, Keyword, + Attribute, // Not in ItemKind ProcMacro, } @@ -53,6 +54,7 @@ impl Kind { ExternType => true, // FIXME(adotinthevoid): I'm not sure if these are correct + Attribute => false, Keyword => false, ProcAttribute => false, ProcDerive => false, @@ -109,6 +111,7 @@ impl Kind { Kind::Primitive => false, Kind::Keyword => false, Kind::ProcMacro => false, + Kind::Attribute => false, } } @@ -163,6 +166,7 @@ impl Kind { match s.kind { ItemKind::AssocConst => AssocConst, ItemKind::AssocType => AssocType, + ItemKind::Attribute => Attribute, ItemKind::Constant => Constant, ItemKind::Enum => Enum, ItemKind::ExternCrate => ExternCrate, From 75cbd05d19a7cfb0819f15a57f2561cd28a956a7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 13 Jun 2025 21:02:26 +0200 Subject: [PATCH 05/13] Add tests for `doc(attribute = "...")` attribute --- tests/rustdoc-gui/links-color.goml | 6 ++++- tests/rustdoc-gui/module-items-font.goml | 9 +++++++ tests/rustdoc-gui/search-result-color.goml | 11 ++++++++- tests/rustdoc-gui/sidebar-links-color.goml | 20 ++++++++++++++++ tests/rustdoc-gui/src/test_docs/lib.rs | 4 ++++ tests/rustdoc-json/doc_attribute.rs | 18 ++++++++++++++ tests/rustdoc-ui/invalid-attribute.rs | 10 ++++++++ tests/rustdoc-ui/invalid-attribute.stderr | 18 ++++++++++++++ tests/rustdoc/doc-attribute.rs | 24 +++++++++++++++++++ .../feature-gate-rustdoc_internals.rs | 4 ++++ .../feature-gate-rustdoc_internals.stderr | 16 ++++++++++--- 11 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 tests/rustdoc-json/doc_attribute.rs create mode 100644 tests/rustdoc-ui/invalid-attribute.rs create mode 100644 tests/rustdoc-ui/invalid-attribute.stderr create mode 100644 tests/rustdoc/doc-attribute.rs diff --git a/tests/rustdoc-gui/links-color.goml b/tests/rustdoc-gui/links-color.goml index f11920cdd8c2d..a363175c1ddbb 100644 --- a/tests/rustdoc-gui/links-color.goml +++ b/tests/rustdoc-gui/links-color.goml @@ -9,7 +9,7 @@ show-text: true define-function: ( "check-colors", [theme, mod, macro, struct, enum, trait, fn, type, union, keyword, - sidebar, sidebar_current, sidebar_current_background], + attribute, sidebar, sidebar_current, sidebar_current_background], block { call-function: ("switch-theme", {"theme": |theme|}) // Checking results colors. @@ -22,6 +22,7 @@ define-function: ( assert-css: (".item-table .type", {"color": |type|}, ALL) assert-css: (".item-table .union", {"color": |union|}, ALL) assert-css: (".item-table .keyword", {"color": |keyword|}, ALL) + assert-css: (".item-table .attribute", {"color": |attribute|}, ALL) // Checking sidebar elements. assert-css: ( ".sidebar-elems li:not(.current) a", @@ -58,6 +59,7 @@ call-function: ( "type": "#ffa0a5", "union": "#ffa0a5", "keyword": "#39afd7", + "attribute": "#39afd7", "sidebar": "#53b1db", "sidebar_current": "#ffb44c", "sidebar_current_background": "transparent", @@ -76,6 +78,7 @@ call-function: ( "type": "#2dbfb8", "union": "#2dbfb8", "keyword": "#d2991d", + "attribute": "#d2991d", "sidebar": "#fdbf35", "sidebar_current": "#fdbf35", "sidebar_current_background": "#444", @@ -94,6 +97,7 @@ call-function: ( "type": "#ad378a", "union": "#ad378a", "keyword": "#3873ad", + "attribute": "#3873ad", "sidebar": "#356da4", "sidebar_current": "#356da4", "sidebar_current_background": "#fff", diff --git a/tests/rustdoc-gui/module-items-font.goml b/tests/rustdoc-gui/module-items-font.goml index 0e6dd81c05b8d..bed95b378c6ab 100644 --- a/tests/rustdoc-gui/module-items-font.goml +++ b/tests/rustdoc-gui/module-items-font.goml @@ -65,3 +65,12 @@ assert-css: ( "#keywords + .item-table dd", {"font-family": '"Source Serif 4", NanumBarunGothic, serif'}, ) +// attributes +assert-css: ( + "#attributes + .item-table dt a", + {"font-family": '"Fira Sans", Arial, NanumBarunGothic, sans-serif'}, +) +assert-css: ( + "#attributes + .item-table dd", + {"font-family": '"Source Serif 4", NanumBarunGothic, serif'}, +) diff --git a/tests/rustdoc-gui/search-result-color.goml b/tests/rustdoc-gui/search-result-color.goml index e136eab6a7d49..fe0f64010895c 100644 --- a/tests/rustdoc-gui/search-result-color.goml +++ b/tests/rustdoc-gui/search-result-color.goml @@ -7,7 +7,8 @@ define-function: ( [ theme, count_color, desc_color, path_color, bottom_border_color, keyword_color, struct_color, associatedtype_color, tymethod_color, method_color, structfield_color, - structfield_hover_color, macro_color, fn_color, hover_path_color, hover_background, grey + structfield_hover_color, macro_color, fn_color, hover_path_color, hover_background, + attribute_color, grey ], block { call-function: ("switch-theme", {"theme": |theme|}) @@ -46,6 +47,11 @@ define-function: ( "color": |keyword_color|, "hover_color": |keyword_color|, }) + call-function: ("check-result-color", { + "result_kind": "attribute", + "color": |attribute_color|, + "hover_color": |attribute_color|, + }) call-function: ("check-result-color", { "result_kind": "struct", "color": |struct_color|, @@ -155,6 +161,7 @@ call-function: ("check-search-color", { "path_color": "#0096cf", "bottom_border_color": "#aaa3", "keyword_color": "#39afd7", + "attribute_color": "#39afd7", "struct_color": "#ffa0a5", "associatedtype_color": "#39afd7", "tymethod_color": "#fdd687", @@ -176,6 +183,7 @@ call-function: ("check-search-color", { "path_color": "#ddd", "bottom_border_color": "#aaa3", "keyword_color": "#d2991d", + "attribute_color": "#d2991d", "struct_color": "#2dbfb8", "associatedtype_color": "#d2991d", "tymethod_color": "#2bab63", @@ -197,6 +205,7 @@ call-function: ("check-search-color", { "path_color": "#000", "bottom_border_color": "#aaa3", "keyword_color": "#3873ad", + "attribute_color": "#3873ad", "struct_color": "#ad378a", "associatedtype_color": "#3873ad", "tymethod_color": "#ad7c37", diff --git a/tests/rustdoc-gui/sidebar-links-color.goml b/tests/rustdoc-gui/sidebar-links-color.goml index 57c45555a76bd..9a398655f8fa0 100644 --- a/tests/rustdoc-gui/sidebar-links-color.goml +++ b/tests/rustdoc-gui/sidebar-links-color.goml @@ -12,6 +12,7 @@ define-function: ( enum_hover_background, union, union_hover, union_hover_background, trait, trait_hover, trait_hover_background, fn, fn_hover, fn_hover_background, type, type_hover, type_hover_background, keyword, keyword_hover, keyword_hover_background, + attribute, attribute_hover, attribute_hover_background, ], block { call-function: ("switch-theme", {"theme": |theme|}) @@ -85,6 +86,16 @@ define-function: ( ".sidebar .block.keyword a:hover", {"color": |keyword_hover|, "background-color": |keyword_hover_background|}, ) + // Attribute + assert-css: ( + ".sidebar .block.attribute a", + {"color": |attribute|, "background-color": "rgba(0, 0, 0, 0)"}, + ) + move-cursor-to: ".sidebar .block.attribute a" + assert-css: ( + ".sidebar .block.attribute a:hover", + {"color": |attribute_hover|, "background-color": |attribute_hover_background|}, + ) } ) @@ -113,6 +124,9 @@ call-function: ( "keyword": "#53b1db", "keyword_hover": "#ffb44c", "keyword_hover_background": "transparent", + "attribute": "#53b1db", + "attribute_hover": "#ffb44c", + "attribute_hover_background": "transparent", } ) call-function: ( @@ -140,6 +154,9 @@ call-function: ( "keyword": "#fdbf35", "keyword_hover": "#fdbf35", "keyword_hover_background": "#444", + "attribute": "#fdbf35", + "attribute_hover": "#fdbf35", + "attribute_hover_background": "#444", } ) call-function: ( @@ -167,5 +184,8 @@ call-function: ( "keyword": "#356da4", "keyword_hover": "#356da4", "keyword_hover_background": "#fff", + "attribute": "#356da4", + "attribute_hover": "#356da4", + "attribute_hover_background": "#fff", } ) diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs index 623f5b33e9bc9..42f2fbd93b1ee 100644 --- a/tests/rustdoc-gui/src/test_docs/lib.rs +++ b/tests/rustdoc-gui/src/test_docs/lib.rs @@ -161,6 +161,10 @@ pub enum AnEnum { /// Some keyword. pub mod keyword {} +#[doc(attribute = "forbid")] +/// Some attribute. +pub mod repr {} + /// Just some type alias. pub type SomeType = u32; diff --git a/tests/rustdoc-json/doc_attribute.rs b/tests/rustdoc-json/doc_attribute.rs new file mode 100644 index 0000000000000..9e1a711f0b7b5 --- /dev/null +++ b/tests/rustdoc-json/doc_attribute.rs @@ -0,0 +1,18 @@ +// Doc attributes (`#[doc(attribute = "...")]` should not be generated in rustdoc JSON output +// and this test ensures it. + +#![feature(rustdoc_internals)] +#![no_std] + +//@ !has "$.index[?(@.name=='repr')]" +//@ has "$.index[?(@.name=='foo')]" + +#[doc(attribute = "repr")] +/// this is a test! +pub mod foo {} + +//@ !has "$.index[?(@.name=='forbid')]" +//@ !has "$.index[?(@.name=='bar')]" +#[doc(attribute = "forbid")] +/// hello +mod bar {} diff --git a/tests/rustdoc-ui/invalid-attribute.rs b/tests/rustdoc-ui/invalid-attribute.rs new file mode 100644 index 0000000000000..2fc7be2cf341d --- /dev/null +++ b/tests/rustdoc-ui/invalid-attribute.rs @@ -0,0 +1,10 @@ +// Testing the output when an invalid builtin attribute is passed as value +// to `doc(attribute = "...")`. + +#![feature(rustdoc_internals)] + +#[doc(attribute = "foo df")] //~ ERROR +mod foo {} + +#[doc(attribute = "fooyi")] //~ ERROR +mod foo2 {} diff --git a/tests/rustdoc-ui/invalid-attribute.stderr b/tests/rustdoc-ui/invalid-attribute.stderr new file mode 100644 index 0000000000000..66e68ce44b406 --- /dev/null +++ b/tests/rustdoc-ui/invalid-attribute.stderr @@ -0,0 +1,18 @@ +error: nonexistent builtin attribute `foo df` used in `#[doc(attribute = "...")]` + --> $DIR/invalid-attribute.rs:6:19 + | +LL | #[doc(attribute = "foo df")] + | ^^^^^^^^ + | + = help: only existing builtin attributes are allowed in core/std + +error: nonexistent builtin attribute `fooyi` used in `#[doc(attribute = "...")]` + --> $DIR/invalid-attribute.rs:9:19 + | +LL | #[doc(attribute = "fooyi")] + | ^^^^^^^ + | + = help: only existing builtin attributes are allowed in core/std + +error: aborting due to 2 previous errors + diff --git a/tests/rustdoc/doc-attribute.rs b/tests/rustdoc/doc-attribute.rs new file mode 100644 index 0000000000000..92e603ae6e551 --- /dev/null +++ b/tests/rustdoc/doc-attribute.rs @@ -0,0 +1,24 @@ +// Test checking the `#[doc(attribute = "...")]` attribute. + +#![crate_name = "foo"] + +#![feature(rustdoc_internals)] + +//@ has foo/index.html '//h2[@id="attributes"]' 'Attributes' +//@ has foo/index.html '//a[@href="attribute.no_mangle.html"]' 'no_mangle' +//@ has foo/index.html '//div[@class="sidebar-elems"]//li/a' 'Attributes' +//@ has foo/index.html '//div[@class="sidebar-elems"]//li/a/@href' '#attributes' +//@ has foo/attribute.no_mangle.html '//h1' 'Attribute no_mangle' +//@ has foo/attribute.no_mangle.html '//section[@id="main-content"]//div[@class="docblock"]//p' 'this is a test!' +//@ has foo/index.html '//a/@href' '../foo/index.html' +//@ !has foo/foo/index.html +//@ !has-dir foo/foo +//@ !has foo/index.html '//span' '🔒' +#[doc(attribute = "no_mangle")] +/// this is a test! +mod foo{} + +//@ has foo/attribute.repr.html '//section[@id="main-content"]//div[@class="docblock"]//p' 'hello' +#[doc(attribute = "repr")] +/// hello +mod bar {} diff --git a/tests/ui/feature-gates/feature-gate-rustdoc_internals.rs b/tests/ui/feature-gates/feature-gate-rustdoc_internals.rs index 57d6b59128703..0ad3b2aead481 100644 --- a/tests/ui/feature-gates/feature-gate-rustdoc_internals.rs +++ b/tests/ui/feature-gates/feature-gate-rustdoc_internals.rs @@ -2,6 +2,10 @@ /// wonderful mod foo {} +#[doc(attribute = "repr")] //~ ERROR: `#[doc(attribute)]` is meant for internal use only +/// wonderful +mod foo2 {} + trait Mine {} #[doc(fake_variadic)] //~ ERROR: `#[doc(fake_variadic)]` is meant for internal use only diff --git a/tests/ui/feature-gates/feature-gate-rustdoc_internals.stderr b/tests/ui/feature-gates/feature-gate-rustdoc_internals.stderr index f3c00a2156bf9..5a6d4d3b45e0f 100644 --- a/tests/ui/feature-gates/feature-gate-rustdoc_internals.stderr +++ b/tests/ui/feature-gates/feature-gate-rustdoc_internals.stderr @@ -8,8 +8,18 @@ LL | #[doc(keyword = "match")] = help: add `#![feature(rustdoc_internals)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +error[E0658]: `#[doc(attribute)]` is meant for internal use only + --> $DIR/feature-gate-rustdoc_internals.rs:5:1 + | +LL | #[doc(attribute = "repr")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #90418 for more information + = help: add `#![feature(rustdoc_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error[E0658]: `#[doc(fake_variadic)]` is meant for internal use only - --> $DIR/feature-gate-rustdoc_internals.rs:7:1 + --> $DIR/feature-gate-rustdoc_internals.rs:11:1 | LL | #[doc(fake_variadic)] | ^^^^^^^^^^^^^^^^^^^^^ @@ -19,7 +29,7 @@ LL | #[doc(fake_variadic)] = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[doc(search_unbox)]` is meant for internal use only - --> $DIR/feature-gate-rustdoc_internals.rs:10:1 + --> $DIR/feature-gate-rustdoc_internals.rs:14:1 | LL | #[doc(search_unbox)] | ^^^^^^^^^^^^^^^^^^^^ @@ -28,6 +38,6 @@ LL | #[doc(search_unbox)] = help: add `#![feature(rustdoc_internals)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 3 previous errors +error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0658`. From 38e8963b148fef9fb9c4263cc49d448985bb9982 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 13 Jun 2025 21:04:07 +0200 Subject: [PATCH 06/13] Add documentation for `doc(attribute = "...")` attribute --- src/doc/rustdoc/src/unstable-features.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index bb28e3abbf315..25c929a1dbada 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -196,7 +196,7 @@ to enable. ### Document keywords -This is for Rust compiler internal use only. +This is for internal use in the std library. Rust keywords are documented in the standard library (look for `match` for example). @@ -211,6 +211,23 @@ To do so, the `#[doc(keyword = "...")]` attribute is used. Example: mod empty_mod {} ``` +### Document builtin attributes + +This is for internal use in the std library. + +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. +#[doc(attribute = "repr")] +mod empty_mod {} +``` + ### Use the Rust logo as the crate logo This is for official Rust project use only. From 10bd61dcf2e5a7dbe95b4b0588a432a444f907a9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 14 Jun 2025 12:41:30 +0200 Subject: [PATCH 07/13] 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(-) diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index fa7591b511362..410c39fbb41ac 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 1502ec9bd78a9..fcff15650ce2e 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 cafbcf7e8dd94..5f92ab2fada9c 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 530e6da4ee303..afa438f259690 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; From f3c023433f6e44be208f9e3bb74528449da730c1 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 28 Aug 2025 16:27:54 +0200 Subject: [PATCH 08/13] 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 diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 410c39fbb41ac..962555ada2788 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 0000000000000..3bd153117a918 --- /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 0000000000000..e2480a548ac16 --- /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 + From 2c7dfa91b5b4dcde136880894a9fd4dae70114db Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Mon, 25 Aug 2025 17:53:01 +0200 Subject: [PATCH 09/13] Add another case to the bad-lit-suffixes test Signed-off-by: Jonathan Brouwer --- tests/ui/parser/bad-lit-suffixes.rs | 7 +++++++ tests/ui/parser/bad-lit-suffixes.stderr | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/ui/parser/bad-lit-suffixes.rs b/tests/ui/parser/bad-lit-suffixes.rs index 91b32fd453910..792d4a140fcb2 100644 --- a/tests/ui/parser/bad-lit-suffixes.rs +++ b/tests/ui/parser/bad-lit-suffixes.rs @@ -45,3 +45,10 @@ extern "C" {} //~^ ERROR invalid suffix `suffix` for number literal //~| ERROR malformed `rustc_layout_scalar_valid_range_start` attribute input struct S; + +impl S { + #[rustc_confusables("blah"suffix)] + //~^ ERROR suffixes on string literals are invalid + //~| ERROR malformed `rustc_confusables` attribute input + fn woof() { } +} \ No newline at end of file diff --git a/tests/ui/parser/bad-lit-suffixes.stderr b/tests/ui/parser/bad-lit-suffixes.stderr index 217cc74b8eb3b..952695ed0a416 100644 --- a/tests/ui/parser/bad-lit-suffixes.stderr +++ b/tests/ui/parser/bad-lit-suffixes.stderr @@ -202,6 +202,21 @@ LL | #[rustc_layout_scalar_valid_range_start(0suffix)] | | expected an integer literal here | help: must be of the form: `#[rustc_layout_scalar_valid_range_start(start)]` -error: aborting due to 23 previous errors; 2 warnings emitted +error: suffixes on string literals are invalid + --> $DIR/bad-lit-suffixes.rs:50:25 + | +LL | #[rustc_confusables("blah"suffix)] + | ^^^^^^^^^^^^ invalid suffix `suffix` + +error[E0539]: malformed `rustc_confusables` attribute input + --> $DIR/bad-lit-suffixes.rs:50:5 + | +LL | #[rustc_confusables("blah"suffix)] + | ^^^^^^^^^^^^^^^^^^^^------------^^ + | | | + | | expected a string literal here + | help: must be of the form: `#[rustc_confusables("name1", "name2", ...)]` + +error: aborting due to 25 previous errors; 2 warnings emitted For more information about this error, try `rustc --explain E0539`. From f32870927604ab4dbf12ff84681b77e65a096940 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Thu, 28 Aug 2025 20:03:58 +0200 Subject: [PATCH 10/13] Improve error messages around invalid literals in attribute arguments Signed-off-by: Jonathan Brouwer --- compiler/rustc_attr_parsing/src/parser.rs | 59 ++++++++++++++--------- compiler/rustc_parse/src/parser/expr.rs | 4 +- compiler/rustc_session/src/errors.rs | 24 +++++---- 3 files changed, 52 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index 6d3cf684296bc..4f903594225e0 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -10,11 +10,11 @@ use rustc_ast::token::{self, Delimiter, MetaVarKind}; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AttrArgs, DelimArgs, Expr, ExprKind, LitKind, MetaItemLit, NormalAttr, Path}; use rustc_ast_pretty::pprust; -use rustc_errors::PResult; +use rustc_errors::{Diag, PResult}; use rustc_hir::{self as hir, AttrPath}; use rustc_parse::exp; use rustc_parse::parser::{Parser, PathStyle, token_descr}; -use rustc_session::errors::report_lit_error; +use rustc_session::errors::{create_lit_error, report_lit_error}; use rustc_session::parse::ParseSess; use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, sym}; use thin_vec::ThinVec; @@ -379,22 +379,23 @@ struct MetaItemListParserContext<'a, 'sess> { impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { fn parse_unsuffixed_meta_item_lit(&mut self) -> PResult<'sess, MetaItemLit> { - let uninterpolated_span = self.parser.token_uninterpolated_span(); - let Some(token_lit) = self.parser.eat_token_lit() else { - return self.parser.handle_missing_lit(Parser::mk_meta_item_lit_char); - }; + let Some(token_lit) = self.parser.eat_token_lit() else { return Err(self.expected_lit()) }; + self.unsuffixed_meta_item_from_lit(token_lit) + } + fn unsuffixed_meta_item_from_lit( + &mut self, + token_lit: token::Lit, + ) -> PResult<'sess, MetaItemLit> { let lit = match MetaItemLit::from_token_lit(token_lit, self.parser.prev_token.span) { Ok(lit) => lit, Err(err) => { - let guar = - report_lit_error(&self.parser.psess, err, token_lit, uninterpolated_span); - // Pack possible quotes and prefixes from the original literal into - // the error literal's symbol so they can be pretty-printed faithfully. - let suffixless_lit = token::Lit::new(token_lit.kind, token_lit.symbol, None); - let symbol = Symbol::intern(&suffixless_lit.to_string()); - let token_lit = token::Lit::new(token::Err(guar), symbol, token_lit.suffix); - MetaItemLit::from_token_lit(token_lit, uninterpolated_span).unwrap() + return Err(create_lit_error( + &self.parser.psess, + err, + token_lit, + self.parser.prev_token_uninterpolated_span(), + )); } }; @@ -448,16 +449,28 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { } fn parse_meta_item_inner(&mut self) -> PResult<'sess, MetaItemOrLitParser<'static>> { - match self.parse_unsuffixed_meta_item_lit() { - Ok(lit) => return Ok(MetaItemOrLitParser::Lit(lit)), - Err(err) => err.cancel(), // we provide a better error below - } - - match self.parse_attr_item() { - Ok(mi) => return Ok(MetaItemOrLitParser::MetaItemParser(mi)), - Err(err) => err.cancel(), // we provide a better error below + if let Some(token_lit) = self.parser.eat_token_lit() { + // If a literal token is parsed, we commit to parsing a MetaItemLit for better errors + Ok(MetaItemOrLitParser::Lit(self.unsuffixed_meta_item_from_lit(token_lit)?)) + } else { + let prev_pros = self.parser.approx_token_stream_pos(); + match self.parse_attr_item() { + Ok(item) => Ok(MetaItemOrLitParser::MetaItemParser(item)), + Err(err) => { + // If `parse_attr_item` made any progress, it likely has a more precise error we should prefer + // If it didn't make progress we use the `expected_lit` from below + if self.parser.approx_token_stream_pos() != prev_pros { + Err(err) + } else { + err.cancel(); + Err(self.expected_lit()) + } + } + } } + } + fn expected_lit(&mut self) -> Diag<'sess> { let mut err = InvalidMetaItem { span: self.parser.token.span, descr: token_descr(&self.parser.token), @@ -492,7 +505,7 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { self.parser.bump(); } - Err(self.parser.dcx().create_err(err)) + self.parser.dcx().create_err(err) } fn parse( diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index f0f58e901a9b0..7de4f6efd0b0f 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -2077,7 +2077,7 @@ impl<'a> Parser<'a> { (token::Lit { symbol: name, suffix: None, kind: token::Char }, span) } - pub fn mk_meta_item_lit_char(name: Symbol, span: Span) -> MetaItemLit { + fn mk_meta_item_lit_char(name: Symbol, span: Span) -> MetaItemLit { ast::MetaItemLit { symbol: name, suffix: None, @@ -2086,7 +2086,7 @@ impl<'a> Parser<'a> { } } - pub fn handle_missing_lit( + fn handle_missing_lit( &mut self, mk_lit_char: impl FnOnce(Symbol, Span) -> L, ) -> PResult<'a, L> { diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs index 34da54a20bf0a..50bc7348dc9ef 100644 --- a/compiler/rustc_session/src/errors.rs +++ b/compiler/rustc_session/src/errors.rs @@ -384,6 +384,10 @@ pub fn report_lit_error( lit: token::Lit, span: Span, ) -> ErrorGuaranteed { + create_lit_error(psess, err, lit, span).emit() +} + +pub fn create_lit_error(psess: &ParseSess, err: LitError, lit: token::Lit, span: Span) -> Diag<'_> { // Checks if `s` looks like i32 or u1234 etc. fn looks_like_width_suffix(first_chars: &[char], s: &str) -> bool { s.len() > 1 && s.starts_with(first_chars) && s[1..].chars().all(|c| c.is_ascii_digit()) @@ -414,32 +418,32 @@ pub fn report_lit_error( let dcx = psess.dcx(); match err { LitError::InvalidSuffix(suffix) => { - dcx.emit_err(InvalidLiteralSuffix { span, kind: lit.kind.descr(), suffix }) + dcx.create_err(InvalidLiteralSuffix { span, kind: lit.kind.descr(), suffix }) } LitError::InvalidIntSuffix(suffix) => { let suf = suffix.as_str(); if looks_like_width_suffix(&['i', 'u'], suf) { // If it looks like a width, try to be helpful. - dcx.emit_err(InvalidIntLiteralWidth { span, width: suf[1..].into() }) + dcx.create_err(InvalidIntLiteralWidth { span, width: suf[1..].into() }) } else if let Some(fixed) = fix_base_capitalisation(lit.symbol.as_str(), suf) { - dcx.emit_err(InvalidNumLiteralBasePrefix { span, fixed }) + dcx.create_err(InvalidNumLiteralBasePrefix { span, fixed }) } else { - dcx.emit_err(InvalidNumLiteralSuffix { span, suffix: suf.to_string() }) + dcx.create_err(InvalidNumLiteralSuffix { span, suffix: suf.to_string() }) } } LitError::InvalidFloatSuffix(suffix) => { let suf = suffix.as_str(); if looks_like_width_suffix(&['f'], suf) { // If it looks like a width, try to be helpful. - dcx.emit_err(InvalidFloatLiteralWidth { span, width: suf[1..].to_string() }) + dcx.create_err(InvalidFloatLiteralWidth { span, width: suf[1..].to_string() }) } else { - dcx.emit_err(InvalidFloatLiteralSuffix { span, suffix: suf.to_string() }) + dcx.create_err(InvalidFloatLiteralSuffix { span, suffix: suf.to_string() }) } } LitError::NonDecimalFloat(base) => match base { - 16 => dcx.emit_err(HexadecimalFloatLiteralNotSupported { span }), - 8 => dcx.emit_err(OctalFloatLiteralNotSupported { span }), - 2 => dcx.emit_err(BinaryFloatLiteralNotSupported { span }), + 16 => dcx.create_err(HexadecimalFloatLiteralNotSupported { span }), + 8 => dcx.create_err(OctalFloatLiteralNotSupported { span }), + 2 => dcx.create_err(BinaryFloatLiteralNotSupported { span }), _ => unreachable!(), }, LitError::IntTooLarge(base) => { @@ -450,7 +454,7 @@ pub fn report_lit_error( 16 => format!("{max:#x}"), _ => format!("{max}"), }; - dcx.emit_err(IntLiteralTooLarge { span, limit }) + dcx.create_err(IntLiteralTooLarge { span, limit }) } } } From e0bdc46d949b80f6eebc42a844671b23d7120e38 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Thu, 28 Aug 2025 20:04:07 +0200 Subject: [PATCH 11/13] Update uitest stderr Signed-off-by: Jonathan Brouwer --- tests/ui/parser/bad-lit-suffixes.rs | 5 +-- tests/ui/parser/bad-lit-suffixes.stderr | 49 ++----------------------- 2 files changed, 4 insertions(+), 50 deletions(-) diff --git a/tests/ui/parser/bad-lit-suffixes.rs b/tests/ui/parser/bad-lit-suffixes.rs index 792d4a140fcb2..0a1ee12073023 100644 --- a/tests/ui/parser/bad-lit-suffixes.rs +++ b/tests/ui/parser/bad-lit-suffixes.rs @@ -38,17 +38,14 @@ fn g() {} #[link(name = "string"suffix)] //~^ ERROR suffixes on string literals are invalid -//~| ERROR malformed `link` attribute input extern "C" {} #[rustc_layout_scalar_valid_range_start(0suffix)] //~^ ERROR invalid suffix `suffix` for number literal -//~| ERROR malformed `rustc_layout_scalar_valid_range_start` attribute input struct S; impl S { #[rustc_confusables("blah"suffix)] //~^ ERROR suffixes on string literals are invalid - //~| ERROR malformed `rustc_confusables` attribute input fn woof() { } -} \ No newline at end of file +} diff --git a/tests/ui/parser/bad-lit-suffixes.stderr b/tests/ui/parser/bad-lit-suffixes.stderr index 952695ed0a416..6c3dbbcec6453 100644 --- a/tests/ui/parser/bad-lit-suffixes.stderr +++ b/tests/ui/parser/bad-lit-suffixes.stderr @@ -160,63 +160,20 @@ error: suffixes on string literals are invalid LL | #[link(name = "string"suffix)] | ^^^^^^^^^^^^^^ invalid suffix `suffix` -error[E0539]: malformed `link` attribute input - --> $DIR/bad-lit-suffixes.rs:39:1 - | -LL | #[link(name = "string"suffix)] - | ^^^^^^^---------------------^^ - | | - | expected this to be of the form `name = "..."` - | - = note: for more information, visit -help: try changing it to one of the following valid forms of the attribute - | -LL - #[link(name = "string"suffix)] -LL + #[link(name = "...")] - | -LL - #[link(name = "string"suffix)] -LL + #[link(name = "...", import_name_type = "decorated|noprefix|undecorated")] - | -LL - #[link(name = "string"suffix)] -LL + #[link(name = "...", kind = "dylib|static|...")] - | -LL - #[link(name = "string"suffix)] -LL + #[link(name = "...", kind = "dylib|static|...", wasm_import_module = "...", import_name_type = "decorated|noprefix|undecorated")] - | - = and 1 other candidate - error: invalid suffix `suffix` for number literal - --> $DIR/bad-lit-suffixes.rs:44:41 + --> $DIR/bad-lit-suffixes.rs:43:41 | LL | #[rustc_layout_scalar_valid_range_start(0suffix)] | ^^^^^^^ invalid suffix `suffix` | = help: the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.) -error[E0539]: malformed `rustc_layout_scalar_valid_range_start` attribute input - --> $DIR/bad-lit-suffixes.rs:44:1 - | -LL | #[rustc_layout_scalar_valid_range_start(0suffix)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-------^^ - | | | - | | expected an integer literal here - | help: must be of the form: `#[rustc_layout_scalar_valid_range_start(start)]` - error: suffixes on string literals are invalid - --> $DIR/bad-lit-suffixes.rs:50:25 + --> $DIR/bad-lit-suffixes.rs:48:25 | LL | #[rustc_confusables("blah"suffix)] | ^^^^^^^^^^^^ invalid suffix `suffix` -error[E0539]: malformed `rustc_confusables` attribute input - --> $DIR/bad-lit-suffixes.rs:50:5 - | -LL | #[rustc_confusables("blah"suffix)] - | ^^^^^^^^^^^^^^^^^^^^------------^^ - | | | - | | expected a string literal here - | help: must be of the form: `#[rustc_confusables("name1", "name2", ...)]` - -error: aborting due to 25 previous errors; 2 warnings emitted +error: aborting due to 22 previous errors; 2 warnings emitted For more information about this error, try `rustc --explain E0539`. From d76cff3f06f4de2e760eee3130c8a393fa4a124f Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 28 Aug 2025 17:36:46 +0000 Subject: [PATCH 12/13] Only export the sanitizer symbols for LTO and move export code to cg_llvm Don't export them from cdylibs. There is no need to do so and it complicates exported_non_generic_symbols. In addition the GCC backend likely uses different symbols and may potentially not even need us to explicitly tell it to export the symbols it needs. --- compiler/rustc_codegen_llvm/src/back/lto.rs | 28 +++++++++++ .../src/back/symbol_export.rs | 49 +------------------ 2 files changed, 29 insertions(+), 48 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs index d85f432702cc5..551a448d97cc1 100644 --- a/compiler/rustc_codegen_llvm/src/back/lto.rs +++ b/compiler/rustc_codegen_llvm/src/back/lto.rs @@ -15,6 +15,7 @@ use rustc_codegen_ssa::{ModuleCodegen, ModuleKind, looks_like_rust_object_file}; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::memmap::Mmap; use rustc_errors::DiagCtxtHandle; +use rustc_hir::attrs::SanitizerSet; use rustc_middle::bug; use rustc_middle::dep_graph::WorkProduct; use rustc_session::config::{self, Lto}; @@ -42,6 +43,33 @@ fn prepare_lto( .map(|symbol| CString::new(symbol.to_owned()).unwrap()) .collect::>(); + if cgcx.regular_module_config.instrument_coverage + || cgcx.regular_module_config.pgo_gen.enabled() + { + // These are weak symbols that point to the profile version and the + // profile name, which need to be treated as exported so LTO doesn't nix + // them. + const PROFILER_WEAK_SYMBOLS: [&CStr; 2] = + [c"__llvm_profile_raw_version", c"__llvm_profile_filename"]; + + symbols_below_threshold.extend(PROFILER_WEAK_SYMBOLS.iter().map(|&sym| sym.to_owned())); + } + + if cgcx.regular_module_config.sanitizer.contains(SanitizerSet::MEMORY) { + let mut msan_weak_symbols = Vec::new(); + + // Similar to profiling, preserve weak msan symbol during LTO. + if cgcx.regular_module_config.sanitizer_recover.contains(SanitizerSet::MEMORY) { + msan_weak_symbols.push(c"__msan_keep_going"); + } + + if cgcx.regular_module_config.sanitizer_memory_track_origins != 0 { + msan_weak_symbols.push(c"__msan_track_origins"); + } + + symbols_below_threshold.extend(msan_weak_symbols.into_iter().map(|sym| sym.to_owned())); + } + // __llvm_profile_counter_bias is pulled in at link time by an undefined reference to // __llvm_profile_runtime, therefore we won't know until link time if this symbol // should have default visibility. diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index 13419bcb22c73..d8a1480e911fe 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -15,7 +15,7 @@ use rustc_middle::ty::{self, GenericArgKind, GenericArgsRef, Instance, SymbolNam use rustc_middle::util::Providers; use rustc_session::config::{CrateType, OomStrategy}; use rustc_symbol_mangling::mangle_internal_symbol; -use rustc_target::spec::{SanitizerSet, TlsModel}; +use rustc_target::spec::TlsModel; use tracing::debug; use crate::base::allocator_kind_for_codegen; @@ -242,53 +242,6 @@ fn exported_non_generic_symbols_provider_local<'tcx>( } } - if tcx.sess.instrument_coverage() || tcx.sess.opts.cg.profile_generate.enabled() { - // These are weak symbols that point to the profile version and the - // profile name, which need to be treated as exported so LTO doesn't nix - // them. - const PROFILER_WEAK_SYMBOLS: [&str; 2] = - ["__llvm_profile_raw_version", "__llvm_profile_filename"]; - - symbols.extend(PROFILER_WEAK_SYMBOLS.iter().map(|sym| { - let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(tcx, sym)); - ( - exported_symbol, - SymbolExportInfo { - level: SymbolExportLevel::C, - kind: SymbolExportKind::Data, - used: false, - rustc_std_internal_symbol: false, - }, - ) - })); - } - - if tcx.sess.opts.unstable_opts.sanitizer.contains(SanitizerSet::MEMORY) { - let mut msan_weak_symbols = Vec::new(); - - // Similar to profiling, preserve weak msan symbol during LTO. - if tcx.sess.opts.unstable_opts.sanitizer_recover.contains(SanitizerSet::MEMORY) { - msan_weak_symbols.push("__msan_keep_going"); - } - - if tcx.sess.opts.unstable_opts.sanitizer_memory_track_origins != 0 { - msan_weak_symbols.push("__msan_track_origins"); - } - - symbols.extend(msan_weak_symbols.into_iter().map(|sym| { - let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(tcx, sym)); - ( - exported_symbol, - SymbolExportInfo { - level: SymbolExportLevel::C, - kind: SymbolExportKind::Data, - used: false, - rustc_std_internal_symbol: false, - }, - ) - })); - } - // Sort so we get a stable incr. comp. hash. symbols.sort_by_cached_key(|s| s.0.symbol_name_for_local_instance(tcx)); From 23e72ab41e27842eae0d60a6f4750c5151aabcbc Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 28 Aug 2025 18:05:47 +0000 Subject: [PATCH 13/13] Move ___asan_globals_registered export All other sanitizer symbols are handled in prepare_lto already. --- compiler/rustc_codegen_llvm/src/back/lto.rs | 4 ++++ compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp | 6 ------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs index 551a448d97cc1..fc38c4f3e5138 100644 --- a/compiler/rustc_codegen_llvm/src/back/lto.rs +++ b/compiler/rustc_codegen_llvm/src/back/lto.rs @@ -70,6 +70,10 @@ fn prepare_lto( symbols_below_threshold.extend(msan_weak_symbols.into_iter().map(|sym| sym.to_owned())); } + // Preserve LLVM-injected, ASAN-related symbols. + // See also https://github.com/rust-lang/rust/issues/113404. + symbols_below_threshold.push(c"___asan_globals_registered".to_owned()); + // __llvm_profile_counter_bias is pulled in at link time by an undefined reference to // __llvm_profile_runtime, therefore we won't know until link time if this symbol // should have default visibility. diff --git a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp index 8c34052770e61..dd49232581497 100644 --- a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp @@ -1226,12 +1226,6 @@ extern "C" void LLVMRustPrintPasses() { extern "C" void LLVMRustRunRestrictionPass(LLVMModuleRef M, char **Symbols, size_t Len) { auto PreserveFunctions = [=](const GlobalValue &GV) { - // Preserve LLVM-injected, ASAN-related symbols. - // See also https://github.com/rust-lang/rust/issues/113404. - if (GV.getName() == "___asan_globals_registered") { - return true; - } - // Preserve symbols exported from Rust modules. for (size_t I = 0; I < Len; I++) { if (GV.getName() == Symbols[I]) {