Rework attribute recovery logic

This commit is contained in:
Esteban Küber 2025-09-28 20:52:57 +00:00
parent 01e2cf8f44
commit 0b0e826c0f
16 changed files with 116 additions and 64 deletions

View file

@ -1259,6 +1259,19 @@ pub enum StmtKind {
MacCall(Box<MacCallStmt>),
}
impl StmtKind {
pub fn descr(&self) -> &'static str {
match self {
StmtKind::Let(_) => "local",
StmtKind::Item(_) => "item",
StmtKind::Expr(_) => "expression",
StmtKind::Semi(_) => "statement",
StmtKind::Empty => "semicolon",
StmtKind::MacCall(_) => "macro",
}
}
}
#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
pub struct MacCallStmt {
pub mac: Box<MacCall>,

View file

@ -87,7 +87,7 @@ attr_parsing_invalid_link_modifier =
attr_parsing_invalid_meta_item = expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found {$descr}
.remove_neg_sugg = negative numbers are not literals, try removing the `-` sign
.quote_ident_sugg = surround the identifier with quotation marks to make it into a string literal
.label = macros are not allowed here
.label = {$descr}s are not allowed here
attr_parsing_invalid_predicate =
invalid predicate `{$predicate}`

View file

@ -8,7 +8,7 @@ use std::fmt::{Debug, Display};
use rustc_ast::token::{self, Delimiter, MetaVarKind};
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::{AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, NormalAttr, Path};
use rustc_ast::{AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, NormalAttr, Path, StmtKind, UnOp};
use rustc_ast_pretty::pprust;
use rustc_errors::{Diag, PResult};
use rustc_hir::{self as hir, AttrPath};
@ -488,51 +488,55 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
descr: token_descr(&self.parser.token),
quote_ident_sugg: None,
remove_neg_sugg: None,
macro_call: None,
label: None,
};
if let token::OpenInvisible(_) = self.parser.token.kind {
// Do not attempt to suggest anything when encountered as part of a macro expansion.
return self.parser.dcx().create_err(err);
}
// Suggest quoting idents, e.g. in `#[cfg(key = value)]`. We don't use `Token::ident` and
// don't `uninterpolate` the token to avoid suggesting anything butchered or questionable
// when macro metavariables are involved.
if self.parser.prev_token == token::Eq
&& let token::Ident(..) = self.parser.token.kind
{
if self.parser.look_ahead(1, |t| matches!(t.kind, token::TokenKind::Bang)) {
let snapshot = self.parser.create_snapshot_for_diagnostic();
let stmt = self.parser.parse_stmt_without_recovery(false, ForceCollect::No, false);
match stmt {
Ok(Some(stmt)) => {
// The user tried to write something like
// `#[deprecated(note = concat!("a", "b"))]`.
err.descr = format!("macro {}", err.descr);
err.macro_call = Some(stmt.span);
err.span = stmt.span;
}
Ok(None) => {}
Err(err) => {
err.cancel();
self.parser.restore_snapshot(snapshot);
let snapshot = self.parser.create_snapshot_for_diagnostic();
let stmt = self.parser.parse_stmt_without_recovery(false, ForceCollect::No, false);
match stmt {
Ok(Some(stmt)) => {
// The user tried to write something like
// `#[deprecated(note = concat!("a", "b"))]`.
err.descr = stmt.kind.descr().to_string();
err.label = Some(stmt.span);
err.span = stmt.span;
if let StmtKind::Expr(expr) = &stmt.kind
&& let ExprKind::Unary(UnOp::Neg, val) = &expr.kind
&& let ExprKind::Lit(_) = val.kind
{
err.remove_neg_sugg = Some(InvalidMetaItemRemoveNegSugg {
negative_sign: expr.span.until(val.span),
});
} else if let StmtKind::Expr(expr) = &stmt.kind
&& let ExprKind::Path(None, Path { segments, .. }) = &expr.kind
&& segments.len() == 1
{
while let token::Ident(..) | token::Literal(_) | token::Dot =
self.parser.token.kind
{
// We've got a word, so we try to consume the rest of a potential sentence.
// We include `.` to correctly handle things like `A sentence here.`.
self.parser.bump();
}
err.quote_ident_sugg = Some(InvalidMetaItemQuoteIdentSugg {
before: expr.span.shrink_to_lo(),
after: self.parser.prev_token.span.shrink_to_hi(),
});
}
} else {
let before = self.parser.token.span.shrink_to_lo();
while let token::Ident(..) = self.parser.token.kind {
self.parser.bump();
}
err.quote_ident_sugg = Some(InvalidMetaItemQuoteIdentSugg {
before,
after: self.parser.prev_token.span.shrink_to_hi(),
});
}
}
if self.parser.token == token::Minus
&& self.parser.look_ahead(1, |t| matches!(t.kind, token::TokenKind::Literal { .. }))
{
err.remove_neg_sugg =
Some(InvalidMetaItemRemoveNegSugg { negative_sign: self.parser.token.span });
self.parser.bump();
self.parser.bump();
Ok(None) => {}
Err(e) => {
e.cancel();
self.parser.restore_snapshot(snapshot);
}
}
self.parser.dcx().create_err(err)

View file

@ -805,7 +805,7 @@ pub(crate) struct InvalidMetaItem {
#[subdiagnostic]
pub remove_neg_sugg: Option<InvalidMetaItemRemoveNegSugg>,
#[label]
pub macro_call: Option<Span>,
pub label: Option<Span>,
}
#[derive(Subdiagnostic)]

View file

@ -26,7 +26,7 @@ fn f3() {}
#[repr(align(16))] //~ ERROR `#[repr(align(...))]` is not supported on functions
fn f4() {}
#[rustc_align(-1)] //~ ERROR expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `-`
#[rustc_align(-1)] //~ ERROR expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found
fn f5() {}
#[rustc_align(3)] //~ ERROR invalid alignment value: not a power of two

View file

@ -37,11 +37,11 @@ error[E0589]: invalid alignment value: not a power of two
LL | #[rustc_align(0)]
| ^
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `-`
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression
--> $DIR/malformed-fn-align.rs:29:15
|
LL | #[rustc_align(-1)]
| ^
| ^^ expressions are not allowed here
|
help: negative numbers are not literals, try removing the `-` sign
|

View file

@ -7,5 +7,5 @@ fn main() {
}
#[deprecated(note = test)]
//~^ ERROR expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `test`
//~^ ERROR expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found
fn foo() {}

View file

@ -1,8 +1,8 @@
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `test`
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression
--> $DIR/issue-66340-deprecated-attr-non-meta-grammar.rs:9:21
|
LL | #[deprecated(note = test)]
| ^^^^
| ^^^^ expressions are not allowed here
|
help: surround the identifier with quotation marks to make it into a string literal
|

View file

@ -9,7 +9,7 @@ macro_rules! mac {
mac!(an(arbitrary token stream));
#[cfg(feature = -1)]
//~^ ERROR expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `-`
//~^ ERROR expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found
fn handler() {}
fn main() {}

View file

@ -1,8 +1,8 @@
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `-`
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression
--> $DIR/attr-bad-meta-4.rs:11:17
|
LL | #[cfg(feature = -1)]
| ^
| ^^ expressions are not allowed here
|
help: negative numbers are not literals, try removing the `-` sign
|

View file

@ -4,14 +4,21 @@
fn main() {
#[cfg(key=foo)]
//~^ ERROR expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `foo`
//~| HELP surround the identifier with quotation marks to make it into a string literal
//~^ ERROR: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found
//~| HELP: surround the identifier with quotation marks to make it into a string literal
//~| NOTE: expressions are not allowed here
println!();
#[cfg(key="bar")]
println!();
#[cfg(key=foo bar baz)]
//~^ ERROR expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `foo`
//~| HELP surround the identifier with quotation marks to make it into a string literal
//~^ ERROR: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found
//~| HELP: surround the identifier with quotation marks to make it into a string literal
//~| NOTE: expressions are not allowed here
println!();
#[cfg(key=foo 1 bar 2.0 baz.)]
//~^ ERROR: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found
//~| HELP: surround the identifier with quotation marks to make it into a string literal
//~| NOTE: expressions are not allowed here
println!();
}
@ -19,7 +26,7 @@ fn main() {
macro_rules! make {
($name:ident) => { #[doc(alias = $name)] pub struct S; }
//~^ ERROR expected unsuffixed literal, found identifier `nickname`
//~^ ERROR: expected unsuffixed literal, found identifier `nickname`
}
make!(nickname); //~ NOTE in this expansion
make!(nickname); //~ NOTE: in this expansion

View file

@ -1,27 +1,38 @@
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `foo`
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression
--> $DIR/attr-unquoted-ident.rs:6:15
|
LL | #[cfg(key=foo)]
| ^^^
| ^^^ expressions are not allowed here
|
help: surround the identifier with quotation marks to make it into a string literal
|
LL | #[cfg(key="foo")]
| + +
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `foo`
--> $DIR/attr-unquoted-ident.rs:12:15
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression
--> $DIR/attr-unquoted-ident.rs:13:15
|
LL | #[cfg(key=foo bar baz)]
| ^^^
| ^^^ expressions are not allowed here
|
help: surround the identifier with quotation marks to make it into a string literal
|
LL | #[cfg(key="foo bar baz")]
| + +
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression
--> $DIR/attr-unquoted-ident.rs:18:15
|
LL | #[cfg(key=foo 1 bar 2.0 baz.)]
| ^^^ expressions are not allowed here
|
help: surround the identifier with quotation marks to make it into a string literal
|
LL | #[cfg(key="foo 1 bar 2.0 baz.")]
| + +
error: expected unsuffixed literal, found identifier `nickname`
--> $DIR/attr-unquoted-ident.rs:21:38
--> $DIR/attr-unquoted-ident.rs:28:38
|
LL | ($name:ident) => { #[doc(alias = $name)] pub struct S; }
| ^^^^^
@ -31,5 +42,5 @@ LL | make!(nickname);
|
= note: this error originates in the macro `make` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 3 previous errors
error: aborting due to 4 previous errors

View file

@ -0,0 +1,9 @@
// Test for #146325.
// Ensure that when we encounter an expr invocation in an attribute, we don't suggest nonsense.
#[deprecated(note = a!=b)]
struct X;
//~^^ ERROR: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression
//~| NOTE: expressions are not allowed here
fn main() {}

View file

@ -0,0 +1,8 @@
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression
--> $DIR/expr-in-attribute.rs:4:21
|
LL | #[deprecated(note = a!=b)]
| ^^^^ expressions are not allowed here
error: aborting due to 1 previous error

View file

@ -3,7 +3,7 @@
#[deprecated(note = concat!("a", "b"))]
struct X;
//~^^ ERROR: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found macro `concat`
//~^^ ERROR: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found
//~| NOTE: macros are not allowed here
fn main() {}

View file

@ -1,4 +1,4 @@
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found macro `concat`
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found macro
--> $DIR/macro-in-attribute.rs:4:21
|
LL | #[deprecated(note = concat!("a", "b"))]