From a8a2ee4e8f5e3c20c826d2cce6d500fb9bedfdd0 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 14 Nov 2023 00:13:30 +0000 Subject: Recover `dyn` and `impl` after `for<...>` --- compiler/rustc_parse/src/parser/ty.rs | 40 ++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index be2cbaf3020..051028cf7f8 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -287,6 +287,7 @@ impl<'a> Parser<'a> { // Function pointer type self.parse_ty_bare_fn(lo, ThinVec::new(), None, recover_return_sign)? } else if self.check_keyword(kw::For) { + let for_span = self.token.span; // Function pointer type or bound list (trait object type) starting with a poly-trait. // `for<'lt> [unsafe] [extern "ABI"] fn (&'lt S) -> T` // `for<'lt> Trait1<'lt> + Trait2 + 'a` @@ -299,9 +300,42 @@ impl<'a> Parser<'a> { recover_return_sign, )? } else { - let path = self.parse_path(PathStyle::Type)?; - let parse_plus = allow_plus == AllowPlus::Yes && self.check_plus(); - self.parse_remaining_bounds_path(lifetime_defs, path, lo, parse_plus)? + // Try to recover `for<'a> dyn Trait` or `for<'a> impl Trait`. + if self.may_recover() + && (self.eat_keyword_noexpect(kw::Impl) || self.eat_keyword_noexpect(kw::Dyn)) + { + let kw = self.prev_token.ident().unwrap().0.name; + let mut err = self.sess.create_err(errors::TransposeDynOrImpl { + span: self.prev_token.span, + kw: kw.as_str(), + sugg: errors::TransposeDynOrImplSugg { + removal_span: self.prev_token.span.with_hi(self.token.span.lo()), + insertion_span: for_span.shrink_to_lo(), + kw: kw.as_str(), + }, + }); + let path = self.parse_path(PathStyle::Type)?; + let parse_plus = allow_plus == AllowPlus::Yes && self.check_plus(); + let kind = + self.parse_remaining_bounds_path(lifetime_defs, path, lo, parse_plus)?; + // Take the parsed bare trait object and turn it either + // into a `dyn` object or an `impl Trait`. + let kind = match (kind, kw) { + (TyKind::TraitObject(bounds, _), kw::Dyn) => { + TyKind::TraitObject(bounds, TraitObjectSyntax::Dyn) + } + (TyKind::TraitObject(bounds, _), kw::Impl) => { + TyKind::ImplTrait(ast::DUMMY_NODE_ID, bounds) + } + _ => return Err(err), + }; + err.emit(); + kind + } else { + let path = self.parse_path(PathStyle::Type)?; + let parse_plus = allow_plus == AllowPlus::Yes && self.check_plus(); + self.parse_remaining_bounds_path(lifetime_defs, path, lo, parse_plus)? + } } } else if self.eat_keyword(kw::Impl) { self.parse_impl_ty(&mut impl_dyn_multi)? -- cgit 1.4.1-3-g733a5 From f830fe313ba8b23fe882589ba8dcbbd5739137e8 Mon Sep 17 00:00:00 2001 From: Esteban Küber Date: Tue, 14 Nov 2023 00:46:37 +0000 Subject: Detect more `=>` typos Handle and recover `match expr { pat >= { arm } }`. --- compiler/rustc_ast/src/token.rs | 3 ++- compiler/rustc_parse/src/parser/expr.rs | 19 ++++++++-------- .../ui/parser/issues/recover-ge-as-fat-arrow.fixed | 7 ++++++ tests/ui/parser/issues/recover-ge-as-fat-arrow.rs | 7 ++++++ .../parser/issues/recover-ge-as-fat-arrow.stderr | 25 ++++++++++++++++++++++ 5 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 tests/ui/parser/issues/recover-ge-as-fat-arrow.fixed create mode 100644 tests/ui/parser/issues/recover-ge-as-fat-arrow.rs create mode 100644 tests/ui/parser/issues/recover-ge-as-fat-arrow.stderr (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index a6ee93e8a6b..707238e6c02 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -388,7 +388,8 @@ impl TokenKind { match *self { Comma => Some(vec![Dot, Lt, Semi]), Semi => Some(vec![Colon, Comma]), - FatArrow => Some(vec![Eq, RArrow]), + Colon => Some(vec![Semi]), + FatArrow => Some(vec![Eq, RArrow, Ge, Gt]), _ => None, } } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 235b28b6e26..bfd7e8ef4d0 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -2904,15 +2904,16 @@ impl<'a> Parser<'a> { "=>", Applicability::MachineApplicable, ); - err.emit(); - this.bump(); - } else if matches!( - (&this.prev_token.kind, &this.token.kind), - (token::DotDotEq, token::Gt) - ) { - // `error_inclusive_range_match_arrow` handles cases like `0..=> {}`, - // so we suppress the error here - err.delay_as_bug(); + if matches!( + (&this.prev_token.kind, &this.token.kind), + (token::DotDotEq, token::Gt) + ) { + // `error_inclusive_range_match_arrow` handles cases like `0..=> {}`, + // so we suppress the error here + err.delay_as_bug(); + } else { + err.emit(); + } this.bump(); } else { return Err(err); diff --git a/tests/ui/parser/issues/recover-ge-as-fat-arrow.fixed b/tests/ui/parser/issues/recover-ge-as-fat-arrow.fixed new file mode 100644 index 00000000000..7b73dfb02df --- /dev/null +++ b/tests/ui/parser/issues/recover-ge-as-fat-arrow.fixed @@ -0,0 +1,7 @@ +// run-rustfix +fn main() { + match 1 { + 1 => {} //~ ERROR + _ => { let _: u16 = 2u16; } //~ ERROR + } +} diff --git a/tests/ui/parser/issues/recover-ge-as-fat-arrow.rs b/tests/ui/parser/issues/recover-ge-as-fat-arrow.rs new file mode 100644 index 00000000000..92143fcf3f7 --- /dev/null +++ b/tests/ui/parser/issues/recover-ge-as-fat-arrow.rs @@ -0,0 +1,7 @@ +// run-rustfix +fn main() { + match 1 { + 1 >= {} //~ ERROR + _ => { let _: u16 = 2u8; } //~ ERROR + } +} diff --git a/tests/ui/parser/issues/recover-ge-as-fat-arrow.stderr b/tests/ui/parser/issues/recover-ge-as-fat-arrow.stderr new file mode 100644 index 00000000000..2df5cca24f0 --- /dev/null +++ b/tests/ui/parser/issues/recover-ge-as-fat-arrow.stderr @@ -0,0 +1,25 @@ +error: expected one of `...`, `..=`, `..`, `=>`, `if`, or `|`, found `>=` + --> $DIR/recover-ge-as-fat-arrow.rs:4:11 + | +LL | 1 >= {} + | ^^ + | | + | expected one of `...`, `..=`, `..`, `=>`, `if`, or `|` + | help: use a fat arrow to start a match arm: `=>` + +error[E0308]: mismatched types + --> $DIR/recover-ge-as-fat-arrow.rs:5:29 + | +LL | _ => { let _: u16 = 2u8; } + | --- ^^^ expected `u16`, found `u8` + | | + | expected due to this + | +help: change the type of the numeric literal from `u8` to `u16` + | +LL | _ => { let _: u16 = 2u16; } + | ~~~ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. -- cgit 1.4.1-3-g733a5 From 4e418805da5867bc48d82ba1cc7eff2ba68be575 Mon Sep 17 00:00:00 2001 From: Esteban Küber Date: Mon, 31 Jul 2023 14:55:47 +0000 Subject: More detail when expecting expression but encountering bad macro argument Partially address #71039. --- compiler/rustc_ast/src/attr/mod.rs | 2 +- compiler/rustc_ast/src/mut_visit.rs | 5 ++- compiler/rustc_ast/src/token.rs | 53 ++++++++++++++-------- compiler/rustc_ast/src/tokenstream.rs | 4 +- compiler/rustc_ast_pretty/src/pprust/state.rs | 2 +- compiler/rustc_expand/src/mbe/diagnostics.rs | 6 +++ compiler/rustc_expand/src/mbe/macro_parser.rs | 4 +- compiler/rustc_expand/src/proc_macro.rs | 2 +- compiler/rustc_expand/src/proc_macro_server.rs | 13 ++++-- compiler/rustc_parse/src/parser/attr.rs | 4 +- compiler/rustc_parse/src/parser/diagnostics.rs | 58 ++++++++++++++++++++++++- compiler/rustc_parse/src/parser/expr.rs | 4 +- compiler/rustc_parse/src/parser/item.rs | 4 +- compiler/rustc_parse/src/parser/mod.rs | 20 +++++---- compiler/rustc_parse/src/parser/nonterminal.rs | 14 +++--- compiler/rustc_parse/src/parser/pat.rs | 2 +- compiler/rustc_parse/src/parser/path.rs | 2 +- compiler/rustc_parse/src/parser/stmt.rs | 2 +- tests/ui/issues/issue-39848.rs | 2 +- tests/ui/issues/issue-39848.stderr | 2 +- tests/ui/macros/nonterminal-matching.stderr | 2 + tests/ui/macros/syntax-error-recovery.rs | 2 +- tests/ui/macros/syntax-error-recovery.stderr | 2 +- tests/ui/macros/trace_faulty_macros.rs | 11 +++++ tests/ui/macros/trace_faulty_macros.stderr | 37 +++++++++++++++- tests/ui/parser/float-field-interpolated.rs | 4 +- tests/ui/parser/float-field-interpolated.stderr | 4 +- 27 files changed, 200 insertions(+), 67 deletions(-) (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index be7d1b207bc..152ef4bcbfd 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -342,7 +342,7 @@ impl MetaItem { let span = span.with_hi(segments.last().unwrap().ident.span.hi()); Path { span, segments, tokens: None } } - Some(TokenTree::Token(Token { kind: token::Interpolated(nt), .. }, _)) => match &**nt { + Some(TokenTree::Token(Token { kind: token::Interpolated(nt), .. }, _)) => match &nt.0 { token::Nonterminal::NtMeta(item) => return item.meta(item.path.span), token::Nonterminal::NtPath(path) => (**path).clone(), _ => return None, diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index 7c0a78253a2..541b9872922 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -764,7 +764,10 @@ pub fn visit_token(t: &mut Token, vis: &mut T) { return; // Avoid visiting the span for the second time. } token::Interpolated(nt) => { - visit_nonterminal(Lrc::make_mut(nt), vis); + let nt = Lrc::make_mut(nt); + let (nt, sp) = (&mut nt.0, &mut nt.1); + vis.visit_span(sp); + visit_nonterminal(nt, vis); } _ => {} } diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index a6ee93e8a6b..791da7d9742 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -110,7 +110,7 @@ impl Lit { Ident(name, false) if name.is_bool_lit() => Some(Lit::new(Bool, name, None)), Literal(token_lit) => Some(token_lit), Interpolated(ref nt) - if let NtExpr(expr) | NtLiteral(expr) = &**nt + if let NtExpr(expr) | NtLiteral(expr) = &nt.0 && let ast::ExprKind::Lit(token_lit) = expr.kind => { Some(token_lit) @@ -314,7 +314,7 @@ pub enum TokenKind { /// - It prevents `Token` from implementing `Copy`. /// It adds complexity and likely slows things down. Please don't add new /// occurrences of this token kind! - Interpolated(Lrc), + Interpolated(Lrc<(Nonterminal, Span)>), /// A doc comment token. /// `Symbol` is the doc comment's data excluding its "quotes" (`///`, `/**`, etc) @@ -421,7 +421,7 @@ impl Token { /// if they keep spans or perform edition checks. pub fn uninterpolated_span(&self) -> Span { match &self.kind { - Interpolated(nt) => nt.span(), + Interpolated(nt) => nt.0.use_span(), _ => self.span, } } @@ -464,7 +464,7 @@ impl Token { ModSep | // global path Lifetime(..) | // labeled loop Pound => true, // expression attributes - Interpolated(ref nt) => matches!(**nt, NtLiteral(..) | + Interpolated(ref nt) => matches!(&nt.0, NtLiteral(..) | NtExpr(..) | NtBlock(..) | NtPath(..)), @@ -488,7 +488,7 @@ impl Token { | DotDot | DotDotDot | DotDotEq // ranges | Lt | BinOp(Shl) // associated path | ModSep => true, // global path - Interpolated(ref nt) => matches!(**nt, NtLiteral(..) | + Interpolated(ref nt) => matches!(&nt.0, NtLiteral(..) | NtPat(..) | NtBlock(..) | NtPath(..)), @@ -511,7 +511,7 @@ impl Token { Lifetime(..) | // lifetime bound in trait object Lt | BinOp(Shl) | // associated path ModSep => true, // global path - Interpolated(ref nt) => matches!(**nt, NtTy(..) | NtPath(..)), + Interpolated(ref nt) => matches!(&nt.0, NtTy(..) | NtPath(..)), // For anonymous structs or unions, which only appear in specific positions // (type of struct fields or union fields), we don't consider them as regular types _ => false, @@ -522,7 +522,7 @@ impl Token { pub fn can_begin_const_arg(&self) -> bool { match self.kind { OpenDelim(Delimiter::Brace) => true, - Interpolated(ref nt) => matches!(**nt, NtExpr(..) | NtBlock(..) | NtLiteral(..)), + Interpolated(ref nt) => matches!(&nt.0, NtExpr(..) | NtBlock(..) | NtLiteral(..)), _ => self.can_begin_literal_maybe_minus(), } } @@ -576,7 +576,7 @@ impl Token { match self.uninterpolate().kind { Literal(..) | BinOp(Minus) => true, Ident(name, false) if name.is_bool_lit() => true, - Interpolated(ref nt) => match &**nt { + Interpolated(ref nt) => match &nt.0 { NtLiteral(_) => true, NtExpr(e) => match &e.kind { ast::ExprKind::Lit(_) => true, @@ -597,9 +597,9 @@ impl Token { /// otherwise returns the original token. pub fn uninterpolate(&self) -> Cow<'_, Token> { match &self.kind { - Interpolated(nt) => match **nt { + Interpolated(nt) => match &nt.0 { NtIdent(ident, is_raw) => { - Cow::Owned(Token::new(Ident(ident.name, is_raw), ident.span)) + Cow::Owned(Token::new(Ident(ident.name, *is_raw), ident.span)) } NtLifetime(ident) => Cow::Owned(Token::new(Lifetime(ident.name), ident.span)), _ => Cow::Borrowed(self), @@ -614,8 +614,8 @@ impl Token { // We avoid using `Token::uninterpolate` here because it's slow. match &self.kind { &Ident(name, is_raw) => Some((Ident::new(name, self.span), is_raw)), - Interpolated(nt) => match **nt { - NtIdent(ident, is_raw) => Some((ident, is_raw)), + Interpolated(nt) => match &nt.0 { + NtIdent(ident, is_raw) => Some((*ident, *is_raw)), _ => None, }, _ => None, @@ -628,8 +628,8 @@ impl Token { // We avoid using `Token::uninterpolate` here because it's slow. match &self.kind { &Lifetime(name) => Some(Ident::new(name, self.span)), - Interpolated(nt) => match **nt { - NtLifetime(ident) => Some(ident), + Interpolated(nt) => match &nt.0 { + NtLifetime(ident) => Some(*ident), _ => None, }, _ => None, @@ -655,7 +655,7 @@ impl Token { /// Returns `true` if the token is an interpolated path. fn is_path(&self) -> bool { if let Interpolated(nt) = &self.kind - && let NtPath(..) = **nt + && let NtPath(..) = &nt.0 { return true; } @@ -668,7 +668,7 @@ impl Token { /// (which happens while parsing the result of macro expansion)? pub fn is_whole_expr(&self) -> bool { if let Interpolated(nt) = &self.kind - && let NtExpr(_) | NtLiteral(_) | NtPath(_) | NtBlock(_) = **nt + && let NtExpr(_) | NtLiteral(_) | NtPath(_) | NtBlock(_) = &nt.0 { return true; } @@ -679,7 +679,7 @@ impl Token { /// Is the token an interpolated block (`$b:block`)? pub fn is_whole_block(&self) -> bool { if let Interpolated(nt) = &self.kind - && let NtBlock(..) = **nt + && let NtBlock(..) = &nt.0 { return true; } @@ -927,7 +927,7 @@ impl fmt::Display for NonterminalKind { } impl Nonterminal { - pub fn span(&self) -> Span { + pub fn use_span(&self) -> Span { match self { NtItem(item) => item.span, NtBlock(block) => block.span, @@ -941,6 +941,23 @@ impl Nonterminal { NtVis(vis) => vis.span, } } + + pub fn descr(&self) -> &'static str { + match self { + NtItem(..) => "item", + NtBlock(..) => "block", + NtStmt(..) => "statement", + NtPat(..) => "pattern", + NtExpr(..) => "expression", + NtLiteral(..) => "literal", + NtTy(..) => "type", + NtIdent(..) => "identifier", + NtLifetime(..) => "lifetime", + NtMeta(..) => "attribute", + NtPath(..) => "path", + NtVis(..) => "visibility", + } + } } impl PartialEq for Nonterminal { diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index 23b8f9c12d8..48854bbae24 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -477,13 +477,13 @@ impl TokenStream { fn flatten_token(token: &Token, spacing: Spacing) -> TokenTree { match &token.kind { - token::Interpolated(nt) if let token::NtIdent(ident, is_raw) = **nt => { + token::Interpolated(nt) if let token::NtIdent(ident, is_raw) = nt.0 => { TokenTree::Token(Token::new(token::Ident(ident.name, is_raw), ident.span), spacing) } token::Interpolated(nt) => TokenTree::Delimited( DelimSpan::from_single(token.span), Delimiter::Invisible, - TokenStream::from_nonterminal_ast(nt).flattened(), + TokenStream::from_nonterminal_ast(&nt.0).flattened(), ), _ => TokenTree::Token(token.clone(), spacing), } diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 48421ff7140..0962cb1b8d4 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -825,7 +825,7 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere } token::Eof => "".into(), - token::Interpolated(ref nt) => self.nonterminal_to_string(nt).into(), + token::Interpolated(ref nt) => self.nonterminal_to_string(&nt.0).into(), } } diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index e060375646c..64cf9ced9c1 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -67,6 +67,12 @@ pub(super) fn failed_to_match_macro<'cx>( && (matches!(expected_token.kind, TokenKind::Interpolated(_)) || matches!(token.kind, TokenKind::Interpolated(_))) { + if let TokenKind::Interpolated(node) = &expected_token.kind { + err.span_label(node.1, ""); + } + if let TokenKind::Interpolated(node) = &token.kind { + err.span_label(node.1, ""); + } err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens"); err.note("see for more information"); diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 7e85beaadcb..1c78232a0f3 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -397,7 +397,7 @@ pub(crate) enum NamedMatch { MatchedTokenTree(rustc_ast::tokenstream::TokenTree), // A metavar match of any type other than `tt`. - MatchedNonterminal(Lrc), + MatchedNonterminal(Lrc<(Nonterminal, rustc_span::Span)>), } /// Performs a token equality check, ignoring syntax context (that is, an unhygienic comparison) @@ -692,7 +692,7 @@ impl TtParser { Ok(nt) => nt, }; let m = match nt { - ParseNtResult::Nt(nt) => MatchedNonterminal(Lrc::new(nt)), + ParseNtResult::Nt(nt) => MatchedNonterminal(Lrc::new((nt, span))), ParseNtResult::Tt(tt) => MatchedTokenTree(tt), }; mp.push_match(next_metavar, seq_depth, m); diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs index c617cd76e3c..39a16259fa6 100644 --- a/compiler/rustc_expand/src/proc_macro.rs +++ b/compiler/rustc_expand/src/proc_macro.rs @@ -126,7 +126,7 @@ impl MultiItemModifier for DeriveProcMacro { Annotatable::Stmt(stmt) => token::NtStmt(stmt), _ => unreachable!(), }; - TokenStream::token_alone(token::Interpolated(Lrc::new(nt)), DUMMY_SP) + TokenStream::token_alone(token::Interpolated(Lrc::new((nt, span))), DUMMY_SP) } else { item.to_tokens() }; diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs index aa4c7a53135..41ec0ed84f7 100644 --- a/compiler/rustc_expand/src/proc_macro_server.rs +++ b/compiler/rustc_expand/src/proc_macro_server.rs @@ -226,18 +226,23 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec trees - .push(TokenTree::Ident(Ident { sym: ident.name, is_raw, span: ident.span })), + Interpolated(ref nt) if let NtIdent(ident, is_raw) = &nt.0 => { + trees.push(TokenTree::Ident(Ident { + sym: ident.name, + is_raw: *is_raw, + span: ident.span, + })) + } Interpolated(nt) => { - let stream = TokenStream::from_nonterminal_ast(&nt); + let stream = TokenStream::from_nonterminal_ast(&nt.0); // A hack used to pass AST fragments to attribute and derive // macros as a single nonterminal token instead of a token // stream. Such token needs to be "unwrapped" and not // represented as a delimited group. // FIXME: It needs to be removed, but there are some // compatibility issues (see #73345). - if crate::base::nt_pretty_printing_compatibility_hack(&nt, rustc.sess()) { + if crate::base::nt_pretty_printing_compatibility_hack(&nt.0, rustc.sess()) { trees.extend(Self::from_internal((stream, rustc))); } else { trees.push(TokenTree::Group(Group { diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index 104de47b97d..bad7c19cc27 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -249,7 +249,7 @@ impl<'a> Parser<'a> { /// The delimiters or `=` are still put into the resulting token stream. pub fn parse_attr_item(&mut self, capture_tokens: bool) -> PResult<'a, ast::AttrItem> { let item = match &self.token.kind { - token::Interpolated(nt) => match &**nt { + token::Interpolated(nt) => match &nt.0 { Nonterminal::NtMeta(item) => Some(item.clone().into_inner()), _ => None, }, @@ -369,7 +369,7 @@ impl<'a> Parser<'a> { /// ``` pub fn parse_meta_item(&mut self) -> PResult<'a, ast::MetaItem> { let nt_meta = match &self.token.kind { - token::Interpolated(nt) => match &**nt { + token::Interpolated(nt) => match &nt.0 { token::NtMeta(e) => Some(e.clone()), _ => None, }, diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index f2acb70ac45..262e7b899ad 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -24,11 +24,12 @@ use crate::parser; use rustc_ast as ast; use rustc_ast::ptr::P; use rustc_ast::token::{self, Delimiter, Lit, LitKind, TokenKind}; +use rustc_ast::tokenstream::AttrTokenTree; use rustc_ast::util::parser::AssocOp; use rustc_ast::{ AngleBracketedArg, AngleBracketedArgs, AnonConst, AttrVec, BinOpKind, BindingAnnotation, Block, - BlockCheckMode, Expr, ExprKind, GenericArg, Generics, Item, ItemKind, Param, Pat, PatKind, - Path, PathSegment, QSelf, Ty, TyKind, + BlockCheckMode, Expr, ExprKind, GenericArg, Generics, HasTokens, Item, ItemKind, Param, Pat, + PatKind, Path, PathSegment, QSelf, Ty, TyKind, }; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashSet; @@ -2252,6 +2253,59 @@ impl<'a> Parser<'a> { err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); } err.span_label(span, "expected expression"); + + // Walk the chain of macro expansions for the current token to point at how the original + // code was interpreted. This helps the user realize when a macro argument of one type is + // later reinterpreted as a different type, like `$x:expr` being reinterpreted as `$x:pat` + // in a subsequent macro invocation (#71039). + let mut tok = self.token.clone(); + let mut labels = vec![]; + while let TokenKind::Interpolated(node) = &tok.kind { + let tokens = node.0.tokens(); + labels.push(node.clone()); + if let Some(tokens) = tokens + && let tokens = tokens.to_attr_token_stream() + && let tokens = tokens.0.deref() + && let [AttrTokenTree::Token(token, _)] = &tokens[..] + { + tok = token.clone(); + } else { + break; + } + } + let mut iter = labels.into_iter().peekable(); + let mut show_link = false; + while let Some(node) = iter.next() { + let descr = node.0.descr(); + if let Some(next) = iter.peek() { + let next_descr = next.0.descr(); + if next_descr != descr { + err.span_label(next.1, format!("this macro fragment matcher is {next_descr}")); + err.span_label(node.1, format!("this macro fragment matcher is {descr}")); + err.span_label( + next.0.use_span(), + format!("this is expected to be {next_descr}"), + ); + err.span_label( + node.0.use_span(), + format!( + "this is interpreted as {}, but it is expected to be {}", + next_descr, descr, + ), + ); + show_link = true; + } else { + err.span_label(node.1, ""); + } + } + } + if show_link { + err.note( + "when forwarding a matched fragment to another macro-by-example, matchers in the \ + second macro will see an opaque AST of the fragment type, not the underlying \ + tokens", + ); + } err } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 235b28b6e26..82adb2b68f4 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -46,7 +46,7 @@ use thin_vec::{thin_vec, ThinVec}; macro_rules! maybe_whole_expr { ($p:expr) => { if let token::Interpolated(nt) = &$p.token.kind { - match &**nt { + match &nt.0 { token::NtExpr(e) | token::NtLiteral(e) => { let e = e.clone(); $p.bump(); @@ -1952,7 +1952,7 @@ impl<'a> Parser<'a> { mk_lit_char: impl FnOnce(Symbol, Span) -> L, ) -> PResult<'a, L> { if let token::Interpolated(nt) = &self.token.kind - && let token::NtExpr(e) | token::NtLiteral(e) = &**nt + && let token::NtExpr(e) | token::NtLiteral(e) = &nt.0 && matches!(e.kind, ExprKind::Err) { let mut err = errors::InvalidInterpolatedExpression { span: self.token.span } diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 801860c2123..d124ea571ab 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -123,7 +123,7 @@ impl<'a> Parser<'a> { // Don't use `maybe_whole` so that we have precise control // over when we bump the parser if let token::Interpolated(nt) = &self.token.kind - && let token::NtItem(item) = &**nt + && let token::NtItem(item) = &nt.0 { let mut item = item.clone(); self.bump(); @@ -2750,7 +2750,7 @@ impl<'a> Parser<'a> { fn is_named_param(&self) -> bool { let offset = match &self.token.kind { - token::Interpolated(nt) => match **nt { + token::Interpolated(nt) => match &nt.0 { token::NtPat(..) => return self.look_ahead(1, |t| t == &token::Colon), _ => 0, }, diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 2fe70694368..c53470ac734 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -93,7 +93,7 @@ pub enum TrailingToken { macro_rules! maybe_whole { ($p:expr, $constructor:ident, |$x:ident| $e:expr) => { if let token::Interpolated(nt) = &$p.token.kind { - if let token::$constructor(x) = &**nt { + if let token::$constructor(x) = &nt.0 { let $x = x.clone(); $p.bump(); return Ok($e); @@ -110,7 +110,7 @@ macro_rules! maybe_recover_from_interpolated_ty_qpath { && $self.may_recover() && $self.look_ahead(1, |t| t == &token::ModSep) && let token::Interpolated(nt) = &$self.token.kind - && let token::NtTy(ty) = &**nt + && let token::NtTy(ty) = &nt.0 { let ty = ty.clone(); $self.bump(); @@ -367,12 +367,14 @@ impl TokenDescription { pub(super) fn token_descr(token: &Token) -> String { let name = pprust::token_to_string(token).to_string(); - let kind = TokenDescription::from_token(token).map(|kind| match kind { - TokenDescription::ReservedIdentifier => "reserved identifier", - TokenDescription::Keyword => "keyword", - TokenDescription::ReservedKeyword => "reserved keyword", - TokenDescription::DocComment => "doc comment", - }); + let kind = match (TokenDescription::from_token(token), &token.kind) { + (Some(TokenDescription::ReservedIdentifier), _) => Some("reserved identifier"), + (Some(TokenDescription::Keyword), _) => Some("keyword"), + (Some(TokenDescription::ReservedKeyword), _) => Some("reserved keyword"), + (Some(TokenDescription::DocComment), _) => Some("doc comment"), + (None, TokenKind::Interpolated(node)) => Some(node.0.descr()), + (None, _) => None, + }; if let Some(kind) = kind { format!("{kind} `{name}`") } else { format!("`{name}`") } } @@ -662,7 +664,7 @@ impl<'a> Parser<'a> { fn check_inline_const(&self, dist: usize) -> bool { self.is_keyword_ahead(dist, &[kw::Const]) && self.look_ahead(dist + 1, |t| match &t.kind { - token::Interpolated(nt) => matches!(**nt, token::NtBlock(..)), + token::Interpolated(nt) => matches!(&nt.0, token::NtBlock(..)), token::OpenDelim(Delimiter::Brace) => true, _ => false, }) diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index 025b0615a7e..06cc39fbb5a 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -50,12 +50,12 @@ impl<'a> Parser<'a> { NonterminalKind::Literal => token.can_begin_literal_maybe_minus(), NonterminalKind::Vis => match token.kind { // The follow-set of :vis + "priv" keyword + interpolated - token::Comma | token::Ident(..) | token::Interpolated(..) => true, + token::Comma | token::Ident(..) | token::Interpolated(_) => true, _ => token.can_begin_type(), }, NonterminalKind::Block => match &token.kind { token::OpenDelim(Delimiter::Brace) => true, - token::Interpolated(nt) => match **nt { + token::Interpolated(nt) => match &nt.0 { NtBlock(_) | NtLifetime(_) | NtStmt(_) | NtExpr(_) | NtLiteral(_) => true, NtItem(_) | NtPat(_) | NtTy(_) | NtIdent(..) | NtMeta(_) | NtPath(_) | NtVis(_) => false, @@ -64,7 +64,7 @@ impl<'a> Parser<'a> { }, NonterminalKind::Path | NonterminalKind::Meta => match &token.kind { token::ModSep | token::Ident(..) => true, - token::Interpolated(nt) => may_be_ident(nt), + token::Interpolated(nt) => may_be_ident(&nt.0), _ => false, }, NonterminalKind::PatParam { .. } | NonterminalKind::PatWithOr => { @@ -75,7 +75,7 @@ impl<'a> Parser<'a> { token::BinOp(token::And) | // reference token::BinOp(token::Minus) | // negative literal token::AndAnd | // double reference - token::Literal(..) | // literal + token::Literal(_) | // literal token::DotDot | // range pattern (future compat) token::DotDotDot | // range pattern (future compat) token::ModSep | // path @@ -83,14 +83,14 @@ impl<'a> Parser<'a> { token::BinOp(token::Shl) => true, // path (double UFCS) // leading vert `|` or-pattern token::BinOp(token::Or) => matches!(kind, NonterminalKind::PatWithOr), - token::Interpolated(nt) => may_be_ident(nt), + token::Interpolated(nt) => may_be_ident(&nt.0), _ => false, } } NonterminalKind::Lifetime => match &token.kind { token::Lifetime(_) => true, token::Interpolated(nt) => { - matches!(**nt, NtLifetime(_)) + matches!(&nt.0, NtLifetime(_)) } _ => false, }, @@ -191,7 +191,7 @@ impl<'a> Parser<'a> { panic!( "Missing tokens for nt {:?} at {:?}: {:?}", nt, - nt.span(), + nt.use_span(), pprust::nonterminal_to_string(&nt) ); } diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index 0a4c7c17d06..b242ba6e94b 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -592,7 +592,7 @@ impl<'a> Parser<'a> { // Make sure we don't allow e.g. `let mut $p;` where `$p:pat`. if let token::Interpolated(nt) = &self.token.kind { - if let token::NtPat(_) = **nt { + if let token::NtPat(..) = &nt.0 { self.expected_ident_found_err().emit(); } } diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index 8626dbe40af..0f4ba9617c6 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -185,7 +185,7 @@ impl<'a> Parser<'a> { }); if let token::Interpolated(nt) = &self.token.kind { - if let token::NtTy(ty) = &**nt { + if let token::NtTy(ty) = &nt.0 { if let ast::TyKind::Path(None, path) = &ty.kind { let path = path.clone(); self.bump(); diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index aa939a71d63..d1c5ca4b93c 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -53,7 +53,7 @@ impl<'a> Parser<'a> { // Don't use `maybe_whole` so that we have precise control // over when we bump the parser if let token::Interpolated(nt) = &self.token.kind - && let token::NtStmt(stmt) = &**nt + && let token::NtStmt(stmt) = &nt.0 { let mut stmt = stmt.clone(); self.bump(); diff --git a/tests/ui/issues/issue-39848.rs b/tests/ui/issues/issue-39848.rs index 1964d739989..2a059120e81 100644 --- a/tests/ui/issues/issue-39848.rs +++ b/tests/ui/issues/issue-39848.rs @@ -1,6 +1,6 @@ macro_rules! get_opt { ($tgt:expr, $field:ident) => { - if $tgt.has_$field() {} //~ ERROR expected `{`, found `foo` + if $tgt.has_$field() {} //~ ERROR expected `{`, found identifier `foo` } } diff --git a/tests/ui/issues/issue-39848.stderr b/tests/ui/issues/issue-39848.stderr index 387ef0776ff..f98fde43784 100644 --- a/tests/ui/issues/issue-39848.stderr +++ b/tests/ui/issues/issue-39848.stderr @@ -1,4 +1,4 @@ -error: expected `{`, found `foo` +error: expected `{`, found identifier `foo` --> $DIR/issue-39848.rs:3:21 | LL | if $tgt.has_$field() {} diff --git a/tests/ui/macros/nonterminal-matching.stderr b/tests/ui/macros/nonterminal-matching.stderr index c2b047022ed..88c2c1c773d 100644 --- a/tests/ui/macros/nonterminal-matching.stderr +++ b/tests/ui/macros/nonterminal-matching.stderr @@ -1,6 +1,8 @@ error: no rules expected the token `enum E {}` --> $DIR/nonterminal-matching.rs:19:10 | +LL | macro complex_nonterminal($nt_item: item) { + | -------------- LL | macro n(a $nt_item b) { | --------------------- when calling this macro ... diff --git a/tests/ui/macros/syntax-error-recovery.rs b/tests/ui/macros/syntax-error-recovery.rs index ae6de3c5046..f6178c137db 100644 --- a/tests/ui/macros/syntax-error-recovery.rs +++ b/tests/ui/macros/syntax-error-recovery.rs @@ -9,7 +9,7 @@ macro_rules! values { } }; } -//~^^^^^ ERROR expected one of `(`, `,`, `=`, `{`, or `}`, found `(String)` +//~^^^^^ ERROR expected one of `(`, `,`, `=`, `{`, or `}`, found type `(String)` //~| ERROR macro expansion ignores token `(String)` and any following values!(STRING(1) as (String) => cfg(test),); diff --git a/tests/ui/macros/syntax-error-recovery.stderr b/tests/ui/macros/syntax-error-recovery.stderr index c42ee9b295e..6218bf43a1e 100644 --- a/tests/ui/macros/syntax-error-recovery.stderr +++ b/tests/ui/macros/syntax-error-recovery.stderr @@ -1,4 +1,4 @@ -error: expected one of `(`, `,`, `=`, `{`, or `}`, found `(String)` +error: expected one of `(`, `,`, `=`, `{`, or `}`, found type `(String)` --> $DIR/syntax-error-recovery.rs:7:26 | LL | $token $($inner)? = $value, diff --git a/tests/ui/macros/trace_faulty_macros.rs b/tests/ui/macros/trace_faulty_macros.rs index b2fdd2e1965..00eb7593799 100644 --- a/tests/ui/macros/trace_faulty_macros.rs +++ b/tests/ui/macros/trace_faulty_macros.rs @@ -41,3 +41,14 @@ fn use_bang_macro_as_attr() {} #[derive(Debug)] //~ ERROR `derive` may only be applied to `struct`s fn use_derive_macro_as_attr() {} + +macro_rules! test { + (let $p:pat = $e:expr) => {test!(($p,$e))}; + // this should be expr + // vvv + (($p:pat, $e:pat)) => {let $p = $e;}; //~ ERROR expected expression, found pattern `1 + 1` +} + +fn foo() { + test!(let x = 1+1); +} diff --git a/tests/ui/macros/trace_faulty_macros.stderr b/tests/ui/macros/trace_faulty_macros.stderr index 21e47da0757..6a89d3bfd09 100644 --- a/tests/ui/macros/trace_faulty_macros.stderr +++ b/tests/ui/macros/trace_faulty_macros.stderr @@ -50,7 +50,7 @@ LL | my_recursive_macro!(); = note: expanding `my_recursive_macro! { }` = note: to `my_recursive_macro! () ;` -error: expected expression, found `A { a: a, b: 0, c: _, .. }` +error: expected expression, found pattern `A { a: a, b: 0, c: _, .. }` --> $DIR/trace_faulty_macros.rs:16:9 | LL | $a @@ -69,6 +69,28 @@ LL | #[derive(Debug)] LL | fn use_derive_macro_as_attr() {} | -------------------------------- not a `struct`, `enum` or `union` +error: expected expression, found pattern `1 + 1` + --> $DIR/trace_faulty_macros.rs:49:37 + | +LL | (let $p:pat = $e:expr) => {test!(($p,$e))}; + | ------- -- this is interpreted as expression, but it is expected to be pattern + | | + | this macro fragment matcher is expression +... +LL | (($p:pat, $e:pat)) => {let $p = $e;}; + | ------ ^^ expected expression + | | + | this macro fragment matcher is pattern +... +LL | test!(let x = 1+1); + | ------------------ + | | | + | | this is expected to be expression + | in this macro invocation + | + = note: when forwarding a matched fragment to another macro-by-example, matchers in the second macro will see an opaque AST of the fragment type, not the underlying tokens + = note: this error originates in the macro `test` (in Nightly builds, run with -Z macro-backtrace for more info) + note: trace_macro --> $DIR/trace_faulty_macros.rs:36:13 | @@ -80,6 +102,17 @@ LL | let a = pat_macro!(); = note: expanding `pat_macro! { A { a : a, b : 0, c : _, .. } }` = note: to `A { a: a, b: 0, c: _, .. }` -error: aborting due to 4 previous errors +note: trace_macro + --> $DIR/trace_faulty_macros.rs:53:5 + | +LL | test!(let x = 1+1); + | ^^^^^^^^^^^^^^^^^^ + | + = note: expanding `test! { let x = 1 + 1 }` + = note: to `test! ((x, 1 + 1))` + = note: expanding `test! { (x, 1 + 1) }` + = note: to `let x = 1 + 1 ;` + +error: aborting due to 5 previous errors For more information about this error, try `rustc --explain E0774`. diff --git a/tests/ui/parser/float-field-interpolated.rs b/tests/ui/parser/float-field-interpolated.rs index a3053203536..990f2926dc8 100644 --- a/tests/ui/parser/float-field-interpolated.rs +++ b/tests/ui/parser/float-field-interpolated.rs @@ -6,9 +6,9 @@ macro_rules! generate_field_accesses { s.$a; // OK { s.$b; } //~ ERROR unexpected token: `1.1` - //~| ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found `1.1` + //~| ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found literal `1.1` { s.$c; } //~ ERROR unexpected token: `1.1` - //~| ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found `1.1` + //~| ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found expression `1.1` }; } diff --git a/tests/ui/parser/float-field-interpolated.stderr b/tests/ui/parser/float-field-interpolated.stderr index 664adb35818..2a1a4926cb3 100644 --- a/tests/ui/parser/float-field-interpolated.stderr +++ b/tests/ui/parser/float-field-interpolated.stderr @@ -9,7 +9,7 @@ LL | generate_field_accesses!(1.1, 1.1, 1.1); | = note: this error originates in the macro `generate_field_accesses` (in Nightly builds, run with -Z macro-backtrace for more info) -error: expected one of `.`, `;`, `?`, `}`, or an operator, found `1.1` +error: expected one of `.`, `;`, `?`, `}`, or an operator, found literal `1.1` --> $DIR/float-field-interpolated.rs:8:13 | LL | { s.$b; } @@ -31,7 +31,7 @@ LL | generate_field_accesses!(1.1, 1.1, 1.1); | = note: this error originates in the macro `generate_field_accesses` (in Nightly builds, run with -Z macro-backtrace for more info) -error: expected one of `.`, `;`, `?`, `}`, or an operator, found `1.1` +error: expected one of `.`, `;`, `?`, `}`, or an operator, found expression `1.1` --> $DIR/float-field-interpolated.rs:10:13 | LL | { s.$c; } -- cgit 1.4.1-3-g733a5 From 1c6bd0b12b7b0017beaa8e39c48f999d20c0ad8e Mon Sep 17 00:00:00 2001 From: Esteban Küber Date: Mon, 6 Nov 2023 19:47:38 +0000 Subject: Smaller span for unnessary `mut` suggestion --- compiler/rustc_parse/src/errors.rs | 3 +-- compiler/rustc_parse/src/parser/pat.rs | 10 +++++----- tests/ui/parser/issues/issue-32501.stderr | 2 +- .../parser/issues/issue-65122-mac-invoc-in-mut-patterns.stderr | 4 ++-- tests/ui/parser/mut-patterns.stderr | 4 ++-- tests/ui/self/self_type_keyword.stderr | 2 +- 6 files changed, 12 insertions(+), 13 deletions(-) (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 8ab1ec298a1..99e66fddc70 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -2278,9 +2278,8 @@ pub(crate) enum InvalidMutInPattern { #[note(parse_note_mut_pattern_usage)] NonIdent { #[primary_span] - #[suggestion(code = "{pat}", applicability = "machine-applicable")] + #[suggestion(code = "", applicability = "machine-applicable")] span: Span, - pat: String, }, } diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index 0a4c7c17d06..f6e1a21bd26 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -638,13 +638,13 @@ impl<'a> Parser<'a> { /// Error on `mut $pat` where `$pat` is not an ident. fn ban_mut_general_pat(&self, lo: Span, pat: &Pat, changed_any_binding: bool) { - let span = lo.to(pat.span); - let pat = pprust::pat_to_string(&pat); - self.sess.emit_err(if changed_any_binding { - InvalidMutInPattern::NestedIdent { span, pat } + InvalidMutInPattern::NestedIdent { + span: lo.to(pat.span), + pat: pprust::pat_to_string(&pat), + } } else { - InvalidMutInPattern::NonIdent { span, pat } + InvalidMutInPattern::NonIdent { span: lo.until(pat.span) } }); } diff --git a/tests/ui/parser/issues/issue-32501.stderr b/tests/ui/parser/issues/issue-32501.stderr index d53302449a8..df12f7768d4 100644 --- a/tests/ui/parser/issues/issue-32501.stderr +++ b/tests/ui/parser/issues/issue-32501.stderr @@ -2,7 +2,7 @@ error: `mut` must be followed by a named binding --> $DIR/issue-32501.rs:7:9 | LL | let mut _ = 0; - | ^^^^^ help: remove the `mut` prefix: `_` + | ^^^^ help: remove the `mut` prefix | = note: `mut` may be followed by `variable` and `variable @ pattern` diff --git a/tests/ui/parser/issues/issue-65122-mac-invoc-in-mut-patterns.stderr b/tests/ui/parser/issues/issue-65122-mac-invoc-in-mut-patterns.stderr index 8c032e588e3..2bd87ee0c38 100644 --- a/tests/ui/parser/issues/issue-65122-mac-invoc-in-mut-patterns.stderr +++ b/tests/ui/parser/issues/issue-65122-mac-invoc-in-mut-patterns.stderr @@ -2,7 +2,7 @@ error: `mut` must be followed by a named binding --> $DIR/issue-65122-mac-invoc-in-mut-patterns.rs:6:13 | LL | let mut $eval = (); - | ^^^^^^^^^ help: remove the `mut` prefix: `does_not_exist!()` + | ^^^^ help: remove the `mut` prefix ... LL | mac1! { does_not_exist!() } | --------------------------- in this macro invocation @@ -25,7 +25,7 @@ error: `mut` must be followed by a named binding --> $DIR/issue-65122-mac-invoc-in-mut-patterns.rs:13:13 | LL | let mut $eval = (); - | ^^^ help: remove the `mut` prefix: `does_not_exist!()` + | ^^^ help: remove the `mut` prefix ... LL | mac2! { does_not_exist!() } | --------------------------- in this macro invocation diff --git a/tests/ui/parser/mut-patterns.stderr b/tests/ui/parser/mut-patterns.stderr index f179d8c9e0a..66985c9f5e4 100644 --- a/tests/ui/parser/mut-patterns.stderr +++ b/tests/ui/parser/mut-patterns.stderr @@ -2,7 +2,7 @@ error: `mut` must be followed by a named binding --> $DIR/mut-patterns.rs:9:9 | LL | let mut _ = 0; - | ^^^^^ help: remove the `mut` prefix: `_` + | ^^^^ help: remove the `mut` prefix | = note: `mut` may be followed by `variable` and `variable @ pattern` @@ -10,7 +10,7 @@ error: `mut` must be followed by a named binding --> $DIR/mut-patterns.rs:10:9 | LL | let mut (_, _) = (0, 0); - | ^^^^^^^^^^ help: remove the `mut` prefix: `(_, _)` + | ^^^^ help: remove the `mut` prefix | = note: `mut` may be followed by `variable` and `variable @ pattern` diff --git a/tests/ui/self/self_type_keyword.stderr b/tests/ui/self/self_type_keyword.stderr index 6e65fae808d..fed853a7e1f 100644 --- a/tests/ui/self/self_type_keyword.stderr +++ b/tests/ui/self/self_type_keyword.stderr @@ -14,7 +14,7 @@ error: `mut` must be followed by a named binding --> $DIR/self_type_keyword.rs:16:9 | LL | mut Self => (), - | ^^^^^^^^ help: remove the `mut` prefix: `Self` + | ^^^^ help: remove the `mut` prefix | = note: `mut` may be followed by `variable` and `variable @ pattern` -- cgit 1.4.1-3-g733a5 From a16722d2210caa5eea941ffa20837859e249fd05 Mon Sep 17 00:00:00 2001 From: Esteban Küber Date: Thu, 16 Nov 2023 21:21:26 +0000 Subject: Handle attempts to have multiple `cfg`d tail expressions When encountering code that seems like it might be trying to have multiple tail expressions depending on `cfg` information, suggest alternatives that will success to parse. ```rust fn foo() -> String { #[cfg(feature = "validation")] [1, 2, 3].iter().map(|c| c.to_string()).collect::() #[cfg(not(feature = "validation"))] String::new() } ``` ``` error: expected `;`, found `#` --> $DIR/multiple-tail-expr-behind-cfg.rs:5:64 | LL | #[cfg(feature = "validation")] | ------------------------------ only `;` terminated statements or tail expressions are allowed after this attribute LL | [1, 2, 3].iter().map(|c| c.to_string()).collect::() | ^ expected `;` here LL | #[cfg(not(feature = "validation"))] | - unexpected token | help: add `;` here | LL | [1, 2, 3].iter().map(|c| c.to_string()).collect::(); | + help: alternatively, consider surrounding the expression with a block | LL | { [1, 2, 3].iter().map(|c| c.to_string()).collect::() } | + + help: it seems like you are trying to provide different expressions depending on `cfg`, consider using `if cfg!(..)` | LL ~ if cfg!(feature = "validation") { LL ~ [1, 2, 3].iter().map(|c| c.to_string()).collect::() LL ~ } else if cfg!(not(feature = "validation")) { LL ~ String::new() LL + } | ``` Fix #106020. --- compiler/rustc_parse/src/parser/diagnostics.rs | 96 ++++++++++++++++++++++ compiler/rustc_parse/src/parser/stmt.rs | 14 ++++ .../attribute/multiple-tail-expr-behind-cfg.rs | 19 +++++ .../attribute/multiple-tail-expr-behind-cfg.stderr | 54 ++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 tests/ui/parser/attribute/multiple-tail-expr-behind-cfg.rs create mode 100644 tests/ui/parser/attribute/multiple-tail-expr-behind-cfg.stderr (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index f2acb70ac45..37183bbc721 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -21,6 +21,7 @@ use crate::errors::{ use crate::fluent_generated as fluent; use crate::parser; +use crate::parser::attr::InnerAttrPolicy; use rustc_ast as ast; use rustc_ast::ptr::P; use rustc_ast::token::{self, Delimiter, Lit, LitKind, TokenKind}; @@ -722,6 +723,101 @@ impl<'a> Parser<'a> { Err(err) } + pub(super) fn attr_on_non_tail_expr(&self, expr: &Expr) { + // Missing semicolon typo error. + let span = self.prev_token.span.shrink_to_hi(); + let mut err = self.sess.create_err(ExpectedSemi { + span, + token: self.token.clone(), + unexpected_token_label: Some(self.token.span), + sugg: ExpectedSemiSugg::AddSemi(span), + }); + let attr_span = match &expr.attrs[..] { + [] => unreachable!(), + [only] => only.span, + [first, rest @ ..] => { + for attr in rest { + err.span_label(attr.span, ""); + } + first.span + } + }; + err.span_label( + attr_span, + format!( + "only `;` terminated statements or tail expressions are allowed after {}", + if expr.attrs.len() == 1 { "this attribute" } else { "these attributes" }, + ), + ); + if self.token == token::Pound + && self.look_ahead(1, |t| t.kind == token::OpenDelim(Delimiter::Bracket)) + { + // We have + // #[attr] + // expr + // #[not_attr] + // other_expr + err.span_label(span, "expected `;` here"); + err.multipart_suggestion( + "alternatively, consider surrounding the expression with a block", + vec![ + (expr.span.shrink_to_lo(), "{ ".to_string()), + (expr.span.shrink_to_hi(), " }".to_string()), + ], + Applicability::MachineApplicable, + ); + let mut snapshot = self.create_snapshot_for_diagnostic(); + if let [attr] = &expr.attrs[..] + && let ast::AttrKind::Normal(attr_kind) = &attr.kind + && let [segment] = &attr_kind.item.path.segments[..] + && segment.ident.name == sym::cfg + && let Ok(next_attr) = snapshot.parse_attribute(InnerAttrPolicy::Forbidden(None)) + && let ast::AttrKind::Normal(next_attr_kind) = next_attr.kind + && let [next_segment] = &next_attr_kind.item.path.segments[..] + && segment.ident.name == sym::cfg + && let Ok(next_expr) = snapshot.parse_expr() + { + // We have for sure + // #[cfg(..)] + // expr + // #[cfg(..)] + // other_expr + // So we suggest using `if cfg!(..) { expr } else if cfg!(..) { other_expr }`. + let margin = self.sess.source_map().span_to_margin(next_expr.span).unwrap_or(0); + let sugg = vec![ + (attr.span.with_hi(segment.span().hi()), "if cfg!".to_string()), + ( + attr_kind.item.args.span().unwrap().shrink_to_hi().with_hi(attr.span.hi()), + " {".to_string(), + ), + (expr.span.shrink_to_lo(), " ".to_string()), + ( + next_attr.span.with_hi(next_segment.span().hi()), + "} else if cfg!".to_string(), + ), + ( + next_attr_kind + .item + .args + .span() + .unwrap() + .shrink_to_hi() + .with_hi(next_attr.span.hi()), + " {".to_string(), + ), + (next_expr.span.shrink_to_lo(), " ".to_string()), + (next_expr.span.shrink_to_hi(), format!("\n{}}}", " ".repeat(margin))), + ]; + err.multipart_suggestion( + "it seems like you are trying to provide different expressions depending on \ + `cfg`, consider using `if cfg!(..)`", + sugg, + Applicability::MachineApplicable, + ); + } + } + err.emit(); + } fn check_too_many_raw_str_terminators(&mut self, err: &mut Diagnostic) -> bool { let sm = self.sess.source_map(); match (&self.prev_token.kind, &self.token.kind) { diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index aa939a71d63..758e9aafb04 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -617,6 +617,20 @@ impl<'a> Parser<'a> { let mut add_semi_to_stmt = false; match &mut stmt.kind { + // Expression without semicolon. + StmtKind::Expr(expr) + if classify::expr_requires_semi_to_be_stmt(expr) + && !expr.attrs.is_empty() + && ![token::Eof, token::Semi, token::CloseDelim(Delimiter::Brace)] + .contains(&self.token.kind) => + { + // The user has written `#[attr] expr` which is unsupported. (#106020) + self.attr_on_non_tail_expr(&expr); + // We already emitted an error, so don't emit another type error + let sp = expr.span.to(self.prev_token.span); + *expr = self.mk_expr_err(sp); + } + // Expression without semicolon. StmtKind::Expr(expr) if self.token != token::Eof && classify::expr_requires_semi_to_be_stmt(expr) => diff --git a/tests/ui/parser/attribute/multiple-tail-expr-behind-cfg.rs b/tests/ui/parser/attribute/multiple-tail-expr-behind-cfg.rs new file mode 100644 index 00000000000..d97f24a3d29 --- /dev/null +++ b/tests/ui/parser/attribute/multiple-tail-expr-behind-cfg.rs @@ -0,0 +1,19 @@ +#![feature(stmt_expr_attributes)] + +fn foo() -> String { + #[cfg(feature = "validation")] + [1, 2, 3].iter().map(|c| c.to_string()).collect::() //~ ERROR expected `;`, found `#` + #[cfg(not(feature = "validation"))] + String::new() +} + +fn bar() -> String { + #[attr] + [1, 2, 3].iter().map(|c| c.to_string()).collect::() //~ ERROR expected `;`, found `#` + #[attr] //~ ERROR cannot find attribute `attr` in this scope + String::new() +} + +fn main() { + println!("{}", foo()); +} diff --git a/tests/ui/parser/attribute/multiple-tail-expr-behind-cfg.stderr b/tests/ui/parser/attribute/multiple-tail-expr-behind-cfg.stderr new file mode 100644 index 00000000000..a71253a5e42 --- /dev/null +++ b/tests/ui/parser/attribute/multiple-tail-expr-behind-cfg.stderr @@ -0,0 +1,54 @@ +error: expected `;`, found `#` + --> $DIR/multiple-tail-expr-behind-cfg.rs:5:64 + | +LL | #[cfg(feature = "validation")] + | ------------------------------ only `;` terminated statements or tail expressions are allowed after this attribute +LL | [1, 2, 3].iter().map(|c| c.to_string()).collect::() + | ^ expected `;` here +LL | #[cfg(not(feature = "validation"))] + | - unexpected token + | +help: add `;` here + | +LL | [1, 2, 3].iter().map(|c| c.to_string()).collect::(); + | + +help: alternatively, consider surrounding the expression with a block + | +LL | { [1, 2, 3].iter().map(|c| c.to_string()).collect::() } + | + + +help: it seems like you are trying to provide different expressions depending on `cfg`, consider using `if cfg!(..)` + | +LL ~ if cfg!(feature = "validation") { +LL ~ [1, 2, 3].iter().map(|c| c.to_string()).collect::() +LL ~ } else if cfg!(not(feature = "validation")) { +LL ~ String::new() +LL + } + | + +error: expected `;`, found `#` + --> $DIR/multiple-tail-expr-behind-cfg.rs:12:64 + | +LL | #[attr] + | ------- only `;` terminated statements or tail expressions are allowed after this attribute +LL | [1, 2, 3].iter().map(|c| c.to_string()).collect::() + | ^ expected `;` here +LL | #[attr] + | - unexpected token + | +help: add `;` here + | +LL | [1, 2, 3].iter().map(|c| c.to_string()).collect::(); + | + +help: alternatively, consider surrounding the expression with a block + | +LL | { [1, 2, 3].iter().map(|c| c.to_string()).collect::() } + | + + + +error: cannot find attribute `attr` in this scope + --> $DIR/multiple-tail-expr-behind-cfg.rs:13:7 + | +LL | #[attr] + | ^^^^ + +error: aborting due to 3 previous errors + -- cgit 1.4.1-3-g733a5 From 099eb409322acdc59fc6af6cf3a935ce6a0217f8 Mon Sep 17 00:00:00 2001 From: Esteban Küber Date: Mon, 6 Nov 2023 19:56:45 +0000 Subject: Fix code indentation --- compiler/rustc_parse/src/parser/mod.rs | 4 ++-- tests/ui/parser/recover/recover-parens-around-match-arm-head.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 2fe70694368..833381a6503 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -830,8 +830,8 @@ impl<'a> Parser<'a> { // https://github.com/rust-lang/rust/issues/72373 if self.prev_token.is_ident() && self.token.kind == token::DotDot { let msg = format!( - "if you meant to bind the contents of \ - the rest of the array pattern into `{}`, use `@`", + "if you meant to bind the contents of the rest of the array \ + pattern into `{}`, use `@`", pprust::token_to_string(&self.prev_token) ); expect_err diff --git a/tests/ui/parser/recover/recover-parens-around-match-arm-head.rs b/tests/ui/parser/recover/recover-parens-around-match-arm-head.rs index 0c348e27e9a..9ed733bf079 100644 --- a/tests/ui/parser/recover/recover-parens-around-match-arm-head.rs +++ b/tests/ui/parser/recover/recover-parens-around-match-arm-head.rs @@ -11,4 +11,4 @@ fn main() { _ => 0u8, }; let _y: u32 = x; //~ ERROR mismatched types -} \ No newline at end of file +} -- cgit 1.4.1-3-g733a5