From 70e26c1b7bd4e9f8ba610de6dca2d74ab233cb64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 11 Aug 2025 11:46:30 +0200 Subject: [PATCH] take attr style into account in attr diagnostics --- .../src/attributes/inline.rs | 4 ++-- .../src/attributes/macro_attrs.rs | 2 +- .../src/attributes/must_use.rs | 4 ++-- .../src/attributes/test_attrs.rs | 6 +++--- compiler/rustc_attr_parsing/src/context.rs | 18 ++++++++++++++++- .../src/session_diagnostics.rs | 6 ++++-- compiler/rustc_feature/src/builtin_attrs.rs | 8 ++++++-- tests/ui/attributes/lint_on_root.rs | 2 +- tests/ui/attributes/lint_on_root.stderr | 4 ++-- tests/ui/attributes/malformed-reprs.stderr | 20 ++++++++----------- tests/ui/coverage-attr/name-value.stderr | 4 ++-- tests/ui/coverage-attr/word-only.stderr | 10 ++++------ .../resolve/path-attr-in-const-block.stderr | 2 +- 13 files changed, 53 insertions(+), 37 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/inline.rs b/compiler/rustc_attr_parsing/src/attributes/inline.rs index 6a659a95b856f..33c21bad2401b 100644 --- a/compiler/rustc_attr_parsing/src/attributes/inline.rs +++ b/compiler/rustc_attr_parsing/src/attributes/inline.rs @@ -62,8 +62,8 @@ impl SingleAttributeParser for InlineParser { } } ArgParser::NameValue(_) => { - let suggestions = - >::TEMPLATE.suggestions(false, "inline"); + let suggestions = >::TEMPLATE + .suggestions(cx.attr_style, "inline"); let span = cx.attr_span; cx.emit_lint(AttributeLintKind::IllFormedAttributeInput { suggestions }, span); return None; diff --git a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs index c9b5dd35fa1d9..8928129c2013d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs @@ -107,7 +107,7 @@ impl AttributeParser for MacroUseParser { } } ArgParser::NameValue(_) => { - let suggestions = MACRO_USE_TEMPLATE.suggestions(false, sym::macro_use); + let suggestions = MACRO_USE_TEMPLATE.suggestions(cx.attr_style, sym::macro_use); cx.emit_err(session_diagnostics::IllFormedAttributeInputLint { num_suggestions: suggestions.len(), suggestions: DiagArgValue::StrListSepByAnd( diff --git a/compiler/rustc_attr_parsing/src/attributes/must_use.rs b/compiler/rustc_attr_parsing/src/attributes/must_use.rs index b6cfc78059066..b0ee3d1ba6e5c 100644 --- a/compiler/rustc_attr_parsing/src/attributes/must_use.rs +++ b/compiler/rustc_attr_parsing/src/attributes/must_use.rs @@ -35,8 +35,8 @@ impl SingleAttributeParser for MustUseParser { Some(value_str) } ArgParser::List(_) => { - let suggestions = - >::TEMPLATE.suggestions(false, "must_use"); + let suggestions = >::TEMPLATE + .suggestions(cx.attr_style, "must_use"); cx.emit_err(session_diagnostics::IllFormedAttributeInputLint { num_suggestions: suggestions.len(), suggestions: DiagArgValue::StrListSepByAnd( diff --git a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs index 8b666c3868baf..164c680b8a8de 100644 --- a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs @@ -29,7 +29,7 @@ impl SingleAttributeParser for IgnoreParser { ArgParser::NameValue(name_value) => { let Some(str_value) = name_value.value_as_str() else { let suggestions = >::TEMPLATE - .suggestions(false, "ignore"); + .suggestions(cx.attr_style, "ignore"); let span = cx.attr_span; cx.emit_lint( AttributeLintKind::IllFormedAttributeInput { suggestions }, @@ -40,8 +40,8 @@ impl SingleAttributeParser for IgnoreParser { Some(str_value) } ArgParser::List(_) => { - let suggestions = - >::TEMPLATE.suggestions(false, "ignore"); + let suggestions = >::TEMPLATE + .suggestions(cx.attr_style, "ignore"); let span = cx.attr_span; cx.emit_lint(AttributeLintKind::IllFormedAttributeInput { suggestions }, span); return None; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index bebe3350c4e0d..13b7621001d30 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -5,7 +5,7 @@ use std::sync::LazyLock; use itertools::Itertools; use private::Sealed; -use rustc_ast::{self as ast, LitKind, MetaItemLit, NodeId}; +use rustc_ast::{self as ast, AttrStyle, LitKind, MetaItemLit, NodeId}; use rustc_errors::{DiagCtxtHandle, Diagnostic}; use rustc_feature::{AttributeTemplate, Features}; use rustc_hir::attrs::AttributeKind; @@ -313,6 +313,7 @@ pub struct AcceptContext<'f, 'sess, S: Stage> { /// The span of the attribute currently being parsed pub(crate) attr_span: Span, + pub(crate) attr_style: AttrStyle, /// The expected structure of the attribute. /// /// Used in reporting errors to give a hint to users what the attribute *should* look like. @@ -394,6 +395,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { i.kind.is_bytestr().then(|| self.sess().source_map().start_point(i.span)) }), }, + attr_style: self.attr_style, }) } @@ -404,6 +406,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedIntegerLiteral, + attr_style: self.attr_style, }) } @@ -414,6 +417,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedList, + attr_style: self.attr_style, }) } @@ -424,6 +428,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedNoArgs, + attr_style: self.attr_style, }) } @@ -435,6 +440,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedIdentifier, + attr_style: self.attr_style, }) } @@ -447,6 +453,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedNameValue(name), + attr_style: self.attr_style, }) } @@ -458,6 +465,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::DuplicateKey(key), + attr_style: self.attr_style, }) } @@ -470,6 +478,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::UnexpectedLiteral, + attr_style: self.attr_style, }) } @@ -480,6 +489,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedSingleArgument, + attr_style: self.attr_style, }) } @@ -490,6 +500,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedAtLeastOneArgument, + attr_style: self.attr_style, }) } @@ -508,6 +519,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { strings: false, list: false, }, + attr_style: self.attr_style, }) } @@ -526,6 +538,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { strings: false, list: true, }, + attr_style: self.attr_style, }) } @@ -544,6 +557,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { strings: true, list: false, }, + attr_style: self.attr_style, }) } @@ -802,6 +816,7 @@ impl<'sess> AttributeParser<'sess, Early> { }, }, attr_span: attr.span, + attr_style: attr.style, template, attr_path: path.get_attribute_path(), }; @@ -912,6 +927,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { emit_lint: &mut emit_lint, }, attr_span: lower_span(attr.span), + attr_style: attr.style, template: &accept.template, attr_path: path.get_attribute_path(), }; diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 95e85667cd662..c65937b35b3ec 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -1,6 +1,6 @@ use std::num::IntErrorKind; -use rustc_ast as ast; +use rustc_ast::{self as ast, AttrStyle}; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, @@ -579,6 +579,7 @@ pub(crate) enum AttributeParseErrorReason { pub(crate) struct AttributeParseError { pub(crate) span: Span, pub(crate) attr_span: Span, + pub(crate) attr_style: AttrStyle, pub(crate) template: AttributeTemplate, pub(crate) attribute: AttrPath, pub(crate) reason: AttributeParseErrorReason, @@ -717,7 +718,8 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError { if let Some(link) = self.template.docs { diag.note(format!("for more information, visit <{link}>")); } - let suggestions = self.template.suggestions(false, &name); + let suggestions = self.template.suggestions(self.attr_style, &name); + diag.span_suggestions( self.attr_span, if suggestions.len() == 1 { diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index ab6b8f9280217..1f648edaa7d19 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -6,6 +6,7 @@ use AttributeDuplicates::*; use AttributeGate::*; use AttributeType::*; use rustc_data_structures::fx::FxHashMap; +use rustc_hir::AttrStyle; use rustc_hir::attrs::EncodeCrossCrate; use rustc_span::edition::Edition; use rustc_span::{Symbol, sym}; @@ -132,9 +133,12 @@ pub struct AttributeTemplate { } impl AttributeTemplate { - pub fn suggestions(&self, inner: bool, name: impl std::fmt::Display) -> Vec { + pub fn suggestions(&self, style: AttrStyle, name: impl std::fmt::Display) -> Vec { let mut suggestions = vec![]; - let inner = if inner { "!" } else { "" }; + let inner = match style { + AttrStyle::Outer => "", + AttrStyle::Inner => "!", + }; if self.word { suggestions.push(format!("#{inner}[{name}]")); } diff --git a/tests/ui/attributes/lint_on_root.rs b/tests/ui/attributes/lint_on_root.rs index bafdb46883ff5..6cec7508560a5 100644 --- a/tests/ui/attributes/lint_on_root.rs +++ b/tests/ui/attributes/lint_on_root.rs @@ -1,7 +1,7 @@ // NOTE: this used to panic in debug builds (by a sanity assertion) // and not emit any lint on release builds. See https://github.com/rust-lang/rust/issues/142891. #![inline = ""] -//~^ ERROR: valid forms for the attribute are `#[inline(always)]`, `#[inline(never)]`, and `#[inline]` [ill_formed_attribute_input] +//~^ ERROR: valid forms for the attribute are `#![inline(always)]`, `#![inline(never)]`, and `#![inline]` [ill_formed_attribute_input] //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! //~| ERROR attribute cannot be used on diff --git a/tests/ui/attributes/lint_on_root.stderr b/tests/ui/attributes/lint_on_root.stderr index 9d8d1495c1bfe..f6eafc33d6986 100644 --- a/tests/ui/attributes/lint_on_root.stderr +++ b/tests/ui/attributes/lint_on_root.stderr @@ -6,7 +6,7 @@ LL | #![inline = ""] | = help: `#[inline]` can only be applied to functions -error: valid forms for the attribute are `#[inline(always)]`, `#[inline(never)]`, and `#[inline]` +error: valid forms for the attribute are `#![inline(always)]`, `#![inline(never)]`, and `#![inline]` --> $DIR/lint_on_root.rs:3:1 | LL | #![inline = ""] @@ -19,7 +19,7 @@ LL | #![inline = ""] error: aborting due to 2 previous errors Future incompatibility report: Future breakage diagnostic: -error: valid forms for the attribute are `#[inline(always)]`, `#[inline(never)]`, and `#[inline]` +error: valid forms for the attribute are `#![inline(always)]`, `#![inline(never)]`, and `#![inline]` --> $DIR/lint_on_root.rs:3:1 | LL | #![inline = ""] diff --git a/tests/ui/attributes/malformed-reprs.stderr b/tests/ui/attributes/malformed-reprs.stderr index 43085b9c3415c..3a788999542b4 100644 --- a/tests/ui/attributes/malformed-reprs.stderr +++ b/tests/ui/attributes/malformed-reprs.stderr @@ -7,18 +7,14 @@ LL | #![repr] = note: for more information, visit help: try changing it to one of the following valid forms of the attribute | -LL - #![repr] -LL + #[repr()] - | -LL - #![repr] -LL + #[repr(C)] - | -LL - #![repr] -LL + #[repr(Rust)] - | -LL - #![repr] -LL + #[repr(align(...))] - | +LL | #![repr()] + | ++++++++++++++++ +LL | #![repr(C)] + | +++ +LL | #![repr(Rust)] + | ++++++ +LL | #![repr(align(...))] + | ++++++++++++ = and 2 other candidates error[E0589]: invalid `repr(align)` attribute: not a power of two diff --git a/tests/ui/coverage-attr/name-value.stderr b/tests/ui/coverage-attr/name-value.stderr index 2dac2401e3cd9..d1527ec810c50 100644 --- a/tests/ui/coverage-attr/name-value.stderr +++ b/tests/ui/coverage-attr/name-value.stderr @@ -22,10 +22,10 @@ LL | #![coverage = "off"] help: try changing it to one of the following valid forms of the attribute | LL - #![coverage = "off"] -LL + #[coverage(off)] +LL + #![coverage(off)] | LL - #![coverage = "off"] -LL + #[coverage(on)] +LL + #![coverage(on)] | error[E0539]: malformed `coverage` attribute input diff --git a/tests/ui/coverage-attr/word-only.stderr b/tests/ui/coverage-attr/word-only.stderr index e916a817e3678..880ad0809536e 100644 --- a/tests/ui/coverage-attr/word-only.stderr +++ b/tests/ui/coverage-attr/word-only.stderr @@ -19,12 +19,10 @@ LL | #![coverage] | help: try changing it to one of the following valid forms of the attribute | -LL - #![coverage] -LL + #[coverage(off)] - | -LL - #![coverage] -LL + #[coverage(on)] - | +LL | #![coverage(off)] + | +++++ +LL | #![coverage(on)] + | ++++ error[E0539]: malformed `coverage` attribute input --> $DIR/word-only.rs:21:1 diff --git a/tests/ui/resolve/path-attr-in-const-block.stderr b/tests/ui/resolve/path-attr-in-const-block.stderr index f3ae5b60c4fe8..23f4e319c6d4d 100644 --- a/tests/ui/resolve/path-attr-in-const-block.stderr +++ b/tests/ui/resolve/path-attr-in-const-block.stderr @@ -11,7 +11,7 @@ LL | #![path = foo!()] | ^^^^^^^^^^------^ | | | | | expected a string literal here - | help: must be of the form: `#[path = "file"]` + | help: must be of the form: `#![path = "file"]` | = note: for more information, visit