diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index a569483ea7c39..99f856684abd5 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -665,7 +665,11 @@ impl DocParser { let span = cx.attr_span; cx.emit_lint( rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, - AttributeLintKind::IllFormedAttributeInput { suggestions, docs: None }, + AttributeLintKind::IllFormedAttributeInput { + suggestions, + docs: None, + help: None, + }, span, ); } diff --git a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs index e8d2bedc37802..d48e697204b77 100644 --- a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs @@ -27,8 +27,12 @@ impl SingleAttributeParser for IgnoreParser { }; Some(str_value) } - ArgParser::List(_) => { - cx.adcx().warn_ill_formed_attribute_input(ILL_FORMED_ATTRIBUTE_INPUT); + ArgParser::List(list) => { + let help = list.single().and_then(|item| item.meta_item()).and_then(|item| { + item.args().no_args().ok()?; + Some(item.path().to_string()) + }); + cx.warn_ill_formed_attribute_input_with_help(ILL_FORMED_ATTRIBUTE_INPUT, help); return None; } }, diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 7bac0e90532ca..9320c1f3c86ec 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -495,8 +495,216 @@ impl<'f, 'sess: 'f, S: Stage> SharedContext<'f, 'sess, S> { } impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { - pub(crate) fn adcx(&mut self) -> AttributeDiagnosticContext<'_, 'f, 'sess, S> { - AttributeDiagnosticContext { ctx: self, custom_suggestions: Vec::new() } + fn emit_parse_error( + &self, + span: Span, + reason: AttributeParseErrorReason<'_>, + ) -> ErrorGuaranteed { + self.emit_err(AttributeParseError { + span, + attr_span: self.attr_span, + template: self.template.clone(), + path: self.attr_path.clone(), + description: self.parsed_description, + reason, + suggestions: self.suggestions(), + }) + } + + /// error that a string literal was expected. + /// You can optionally give the literal you did find (which you found not to be a string literal) + /// which can make better errors. For example, if the literal was a byte string it will suggest + /// removing the `b` prefix. + pub(crate) fn expected_string_literal( + &self, + span: Span, + actual_literal: Option<&MetaItemLit>, + ) -> ErrorGuaranteed { + self.emit_parse_error( + span, + AttributeParseErrorReason::ExpectedStringLiteral { + byte_string: actual_literal.and_then(|i| { + i.kind.is_bytestr().then(|| self.sess().source_map().start_point(i.span)) + }), + }, + ) + } + + /// Error that a filename string literal was expected. + pub(crate) fn expected_filename_literal(&self, span: Span) { + self.emit_parse_error(span, AttributeParseErrorReason::ExpectedFilenameLiteral); + } + + pub(crate) fn expected_integer_literal(&self, span: Span) -> ErrorGuaranteed { + self.emit_parse_error(span, AttributeParseErrorReason::ExpectedIntegerLiteral) + } + + pub(crate) fn expected_integer_literal_in_range( + &self, + span: Span, + lower_bound: isize, + upper_bound: isize, + ) -> ErrorGuaranteed { + self.emit_parse_error( + span, + AttributeParseErrorReason::ExpectedIntegerLiteralInRange { lower_bound, upper_bound }, + ) + } + + pub(crate) fn expected_list(&self, span: Span, args: &ArgParser) -> ErrorGuaranteed { + let span = match args { + ArgParser::NoArgs => span, + ArgParser::List(list) => list.span, + ArgParser::NameValue(nv) => nv.args_span(), + }; + self.emit_parse_error(span, AttributeParseErrorReason::ExpectedList) + } + + pub(crate) fn expected_list_with_num_args_or_more( + &self, + args: usize, + span: Span, + ) -> ErrorGuaranteed { + self.emit_parse_error( + span, + AttributeParseErrorReason::ExpectedListWithNumArgsOrMore { args }, + ) + } + + pub(crate) fn expected_list_or_no_args(&self, span: Span) -> ErrorGuaranteed { + self.emit_parse_error(span, AttributeParseErrorReason::ExpectedListOrNoArgs) + } + + pub(crate) fn expected_nv_or_no_args(&self, span: Span) -> ErrorGuaranteed { + self.emit_parse_error(span, AttributeParseErrorReason::ExpectedNameValueOrNoArgs) + } + + pub(crate) fn expected_non_empty_string_literal(&self, span: Span) -> ErrorGuaranteed { + self.emit_parse_error(span, AttributeParseErrorReason::ExpectedNonEmptyStringLiteral) + } + + pub(crate) fn expected_no_args(&self, span: Span) -> ErrorGuaranteed { + self.emit_parse_error(span, AttributeParseErrorReason::ExpectedNoArgs) + } + + /// emit an error that a `name` was expected here + pub(crate) fn expected_identifier(&self, span: Span) -> ErrorGuaranteed { + self.emit_parse_error(span, AttributeParseErrorReason::ExpectedIdentifier) + } + + /// emit an error that a `name = value` pair was expected at this span. The symbol can be given for + /// a nicer error message talking about the specific name that was found lacking a value. + pub(crate) fn expected_name_value(&self, span: Span, name: Option) -> ErrorGuaranteed { + self.emit_parse_error(span, AttributeParseErrorReason::ExpectedNameValue(name)) + } + + /// emit an error that a `name = value` pair was found where that name was already seen. + pub(crate) fn duplicate_key(&self, span: Span, key: Symbol) -> ErrorGuaranteed { + self.emit_parse_error(span, AttributeParseErrorReason::DuplicateKey(key)) + } + + /// an error that should be emitted when a [`MetaItemOrLitParser`](crate::parser::MetaItemOrLitParser) + /// was expected *not* to be a literal, but instead a meta item. + pub(crate) fn unexpected_literal(&self, span: Span) -> ErrorGuaranteed { + self.emit_parse_error(span, AttributeParseErrorReason::UnexpectedLiteral) + } + + pub(crate) fn expected_single_argument(&self, span: Span) -> ErrorGuaranteed { + self.emit_parse_error(span, AttributeParseErrorReason::ExpectedSingleArgument) + } + + pub(crate) fn expected_at_least_one_argument(&self, span: Span) -> ErrorGuaranteed { + self.emit_parse_error(span, AttributeParseErrorReason::ExpectedAtLeastOneArgument) + } + + /// produces an error along the lines of `expected one of [foo, meow]` + pub(crate) fn expected_specific_argument( + &self, + span: Span, + possibilities: &[Symbol], + ) -> ErrorGuaranteed { + self.emit_parse_error( + span, + AttributeParseErrorReason::ExpectedSpecificArgument { + possibilities, + strings: false, + list: false, + }, + ) + } + + /// produces an error along the lines of `expected one of [foo, meow] as an argument`. + /// i.e. slightly different wording to [`expected_specific_argument`](Self::expected_specific_argument). + pub(crate) fn expected_specific_argument_and_list( + &self, + span: Span, + possibilities: &[Symbol], + ) -> ErrorGuaranteed { + self.emit_parse_error( + span, + AttributeParseErrorReason::ExpectedSpecificArgument { + possibilities, + strings: false, + list: true, + }, + ) + } + + /// produces an error along the lines of `expected one of ["foo", "meow"]` + pub(crate) fn expected_specific_argument_strings( + &self, + span: Span, + possibilities: &[Symbol], + ) -> ErrorGuaranteed { + self.emit_parse_error( + span, + AttributeParseErrorReason::ExpectedSpecificArgument { + possibilities, + strings: true, + list: false, + }, + ) + } + + pub(crate) fn warn_empty_attribute(&mut self, span: Span) { + let attr_path = self.attr_path.clone().to_string(); + let valid_without_list = self.template.word; + self.emit_lint( + rustc_session::lint::builtin::UNUSED_ATTRIBUTES, + AttributeLintKind::EmptyAttribute { first_span: span, attr_path, valid_without_list }, + span, + ); + } + + pub(crate) fn warn_ill_formed_attribute_input(&mut self, lint: &'static Lint) { + self.warn_ill_formed_attribute_input_with_help(lint, None); + } + + pub(crate) fn warn_ill_formed_attribute_input_with_help( + &mut self, + lint: &'static Lint, + help: Option, + ) { + let suggestions = self.suggestions(); + let span = self.attr_span; + self.emit_lint( + lint, + AttributeLintKind::IllFormedAttributeInput { suggestions, docs: None, help }, + span, + ); + } + + pub(crate) fn suggestions(&self) -> Vec { + let style = match self.parsed_description { + // If the outer and inner spans are equal, we are parsing an embedded attribute + ParsedDescription::Attribute if self.attr_span == self.inner_span => { + AttrSuggestionStyle::EmbeddedAttribute + } + ParsedDescription::Attribute => AttrSuggestionStyle::Attribute(self.attr_style), + ParsedDescription::Macro => AttrSuggestionStyle::Macro, + }; + + self.template.suggestions(style, &self.attr_path) } } diff --git a/compiler/rustc_attr_parsing/src/validate_attr.rs b/compiler/rustc_attr_parsing/src/validate_attr.rs index f56e85b110610..e2521c9abf5ff 100644 --- a/compiler/rustc_attr_parsing/src/validate_attr.rs +++ b/compiler/rustc_attr_parsing/src/validate_attr.rs @@ -211,6 +211,7 @@ fn emit_malformed_attribute( BuiltinLintDiag::AttributeLint(AttributeLintKind::IllFormedAttributeInput { suggestions: suggestions.clone(), docs: template.docs, + help: None, }), ); } else { diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 1cb0f906f730b..62d5c810c4792 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -175,7 +175,7 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { &AttributeLintKind::UnusedDuplicate { this, other, warning } => { lints::UnusedDuplicate { this, other, warning }.into_diag(dcx, level) } - AttributeLintKind::IllFormedAttributeInput { suggestions, docs } => { + AttributeLintKind::IllFormedAttributeInput { suggestions, docs, help } => { lints::IllFormedAttributeInput { num_suggestions: suggestions.len(), suggestions: DiagArgValue::StrListSepByAnd( @@ -183,6 +183,7 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { ), has_docs: docs.is_some(), docs: docs.unwrap_or(""), + help: help.clone().map(|h| lints::IllFormedAttributeInputHelp { lint: h }), } .into_diag(dcx, level) } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index f89d9e51af917..bfec75cac3f4b 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3009,6 +3009,16 @@ pub(crate) struct IllFormedAttributeInput { #[note("for more information, visit <{$docs}>")] pub has_docs: bool, pub docs: &'static str, + #[subdiagnostic] + pub help: Option, +} + +#[derive(Subdiagnostic)] +#[help( + "if you meant to silence a warning, consider using #![allow({$lint})] or #![expect({$lint})]" +)] +pub(crate) struct IllFormedAttributeInputHelp { + pub lint: String, } #[derive(Diagnostic)] diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 1c86d553f9b6a..132e9dd9ca596 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -703,6 +703,7 @@ pub enum AttributeLintKind { IllFormedAttributeInput { suggestions: Vec, docs: Option<&'static str>, + help: Option, }, EmptyAttribute { first_span: Span, diff --git a/tests/ui/malformed/ignore-with-lint-name.rs b/tests/ui/malformed/ignore-with-lint-name.rs new file mode 100644 index 0000000000000..eb023ea81d572 --- /dev/null +++ b/tests/ui/malformed/ignore-with-lint-name.rs @@ -0,0 +1,6 @@ +#[ignore(clippy::single_match)] +//~^ ERROR valid forms for the attribute are `#[ignore = "reason"]` and `#[ignore]` +//~| HELP if you meant to silence a warning, consider using #![allow(clippy::single_match)] or #![expect(clippy::single_match)] +//~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + +fn main() {} diff --git a/tests/ui/malformed/ignore-with-lint-name.stderr b/tests/ui/malformed/ignore-with-lint-name.stderr new file mode 100644 index 0000000000000..a2f251de6fcc3 --- /dev/null +++ b/tests/ui/malformed/ignore-with-lint-name.stderr @@ -0,0 +1,25 @@ +error: valid forms for the attribute are `#[ignore = "reason"]` and `#[ignore]` + --> $DIR/ignore-with-lint-name.rs:1:1 + | +LL | #[ignore(clippy::single_match)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you meant to silence a warning, consider using #![allow(clippy::single_match)] or #![expect(clippy::single_match)] + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #57571 + = note: `#[deny(ill_formed_attribute_input)]` (part of `#[deny(future_incompatible)]`) on by default + +error: aborting due to 1 previous error + +Future incompatibility report: Future breakage diagnostic: +error: valid forms for the attribute are `#[ignore = "reason"]` and `#[ignore]` + --> $DIR/ignore-with-lint-name.rs:1:1 + | +LL | #[ignore(clippy::single_match)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you meant to silence a warning, consider using #![allow(clippy::single_match)] or #![expect(clippy::single_match)] + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #57571 + = note: `#[deny(ill_formed_attribute_input)]` (part of `#[deny(future_incompatible)]`) on by default +