From 43057698c12e8ceab1f49eff8a3e5a38cc05f7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 24 Sep 2025 21:06:54 +0000 Subject: [PATCH] Tweak handling of "struct like start" where a struct isn't supported This improves the case where someone tries to write a `match` expr where the patterns have type ascription syntax. Makes them less verbose, by giving up on the first encounter in the block, and makes them more accurate by only treating them as a struct literal if successfuly parsed as such. --- .../rustc_parse/src/parser/diagnostics.rs | 40 ++++------ compiler/rustc_parse/src/parser/expr.rs | 61 +++++++++----- .../issues/issue-87086-colon-path-sep.rs | 3 +- .../issues/issue-87086-colon-path-sep.stderr | 20 ++--- tests/ui/parser/type-ascription-in-pattern.rs | 17 ++-- .../parser/type-ascription-in-pattern.stderr | 80 +++++-------------- 6 files changed, 93 insertions(+), 128 deletions(-) diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index a28af7833c38..a9fbd0fa33d5 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -2748,28 +2748,7 @@ impl<'a> Parser<'a> { if token::Colon != self.token.kind { return first_pat; } - if !matches!(first_pat.kind, PatKind::Ident(_, _, None) | PatKind::Path(..)) - || !self.look_ahead(1, |token| token.is_non_reserved_ident()) - { - let mut snapshot_type = self.create_snapshot_for_diagnostic(); - snapshot_type.bump(); // `:` - match snapshot_type.parse_ty() { - Err(inner_err) => { - inner_err.cancel(); - } - Ok(ty) => { - let Err(mut err) = self.expected_one_of_not_found(&[], &[]) else { - return first_pat; - }; - err.span_label(ty.span, "specifying the type of a pattern isn't supported"); - self.restore_snapshot(snapshot_type); - let span = first_pat.span.to(ty.span); - first_pat = self.mk_pat(span, PatKind::Wild); - err.emit(); - } - } - return first_pat; - } + // The pattern looks like it might be a path with a `::` -> `:` typo: // `match foo { bar:baz => {} }` let colon_span = self.token.span; @@ -2857,7 +2836,13 @@ impl<'a> Parser<'a> { Applicability::MaybeIncorrect, ); } else { - first_pat = self.mk_pat(new_span, PatKind::Wild); + first_pat = self.mk_pat( + new_span, + PatKind::Err( + self.dcx() + .span_delayed_bug(colon_span, "recovered bad path pattern"), + ), + ); } self.restore_snapshot(snapshot_pat); } @@ -2870,7 +2855,14 @@ impl<'a> Parser<'a> { err.span_label(ty.span, "specifying the type of a pattern isn't supported"); self.restore_snapshot(snapshot_type); let new_span = first_pat.span.to(ty.span); - first_pat = self.mk_pat(new_span, PatKind::Wild); + first_pat = + self.mk_pat( + new_span, + PatKind::Err(self.dcx().span_delayed_bug( + colon_span, + "recovered bad pattern with type", + )), + ); } } err.emit(); diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 81a5d48d94e8..2f4158450d83 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -3612,36 +3612,55 @@ impl<'a> Parser<'a> { self.token.is_keyword(kw::Async) && self.is_gen_block(kw::Gen, 1) } - fn is_certainly_not_a_block(&self) -> bool { - // `{ ident, ` and `{ ident: ` cannot start a block. - self.look_ahead(1, |t| t.is_ident()) - && self.look_ahead(2, |t| t == &token::Comma || t == &token::Colon) - } - fn maybe_parse_struct_expr( &mut self, qself: &Option>, path: &ast::Path, ) -> Option>> { let struct_allowed = !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL); - if struct_allowed || self.is_certainly_not_a_block() { - if let Err(err) = self.expect(exp!(OpenBrace)) { - return Some(Err(err)); + let is_ident = self.look_ahead(1, |t| t.is_ident()); + let is_comma = self.look_ahead(2, |t| t == &token::Comma); + let is_colon = self.look_ahead(2, |t| t == &token::Colon); + match (struct_allowed, is_ident, is_comma, is_colon) { + (false, true, true, _) | (false, true, _, true) => { + // We have something like `match foo { bar,` or `match foo { bar:`, which means the + // user might have meant to write a struct literal as part of the `match` + // discriminant. + let snapshot = self.create_snapshot_for_diagnostic(); + if let Err(err) = self.expect(exp!(OpenBrace)) { + return Some(Err(err)); + } + match self.parse_expr_struct(qself.clone(), path.clone(), false) { + Ok(expr) => { + // This is a struct literal, but we don't accept them here. + self.dcx().emit_err(errors::StructLiteralNotAllowedHere { + span: expr.span, + sub: errors::StructLiteralNotAllowedHereSugg { + left: path.span.shrink_to_lo(), + right: expr.span.shrink_to_hi(), + }, + }); + Some(Ok(expr)) + } + Err(err) => { + // We couldn't parse a valid struct, rollback and let the parser emit an + // error elsewhere. + err.cancel(); + self.restore_snapshot(snapshot); + None + } + } } - let expr = self.parse_expr_struct(qself.clone(), path.clone(), true); - if let (Ok(expr), false) = (&expr, struct_allowed) { - // This is a struct literal, but we don't can't accept them here. - self.dcx().emit_err(errors::StructLiteralNotAllowedHere { - span: expr.span, - sub: errors::StructLiteralNotAllowedHereSugg { - left: path.span.shrink_to_lo(), - right: expr.span.shrink_to_hi(), - }, - }); + (true, _, _, _) => { + // A struct is accepted here, try to parse it and rely on `parse_expr_struct` for + // any kind of recovery. + if let Err(err) = self.expect(exp!(OpenBrace)) { + return Some(Err(err)); + } + Some(self.parse_expr_struct(qself.clone(), path.clone(), true)) } - return Some(expr); + (false, _, _, _) => None, } - None } pub(super) fn parse_struct_fields( diff --git a/tests/ui/parser/issues/issue-87086-colon-path-sep.rs b/tests/ui/parser/issues/issue-87086-colon-path-sep.rs index d081c06044f1..e1ea38f2795d 100644 --- a/tests/ui/parser/issues/issue-87086-colon-path-sep.rs +++ b/tests/ui/parser/issues/issue-87086-colon-path-sep.rs @@ -37,10 +37,9 @@ fn g1() { //~| HELP: maybe write a path separator here _ => {} } - if let Foo:Bar = f() { //~ WARN: irrefutable `if let` pattern + if let Foo:Bar = f() { //~^ ERROR: expected one of //~| HELP: maybe write a path separator here - //~| HELP: consider replacing the `if let` with a `let` } } diff --git a/tests/ui/parser/issues/issue-87086-colon-path-sep.stderr b/tests/ui/parser/issues/issue-87086-colon-path-sep.stderr index a9bad96f9af7..061586882e0c 100644 --- a/tests/ui/parser/issues/issue-87086-colon-path-sep.stderr +++ b/tests/ui/parser/issues/issue-87086-colon-path-sep.stderr @@ -64,7 +64,7 @@ LL | if let Foo::Bar = f() { | + error: expected one of `@` or `|`, found `:` - --> $DIR/issue-87086-colon-path-sep.rs:49:16 + --> $DIR/issue-87086-colon-path-sep.rs:48:16 | LL | ref qux: Foo::Baz => {} | ^ -------- specifying the type of a pattern isn't supported @@ -77,7 +77,7 @@ LL | ref qux::Foo::Baz => {} | ~~ error: expected one of `@` or `|`, found `:` - --> $DIR/issue-87086-colon-path-sep.rs:58:16 + --> $DIR/issue-87086-colon-path-sep.rs:57:16 | LL | mut qux: Foo::Baz => {} | ^ -------- specifying the type of a pattern isn't supported @@ -90,7 +90,7 @@ LL | mut qux::Foo::Baz => {} | ~~ error: expected one of `@` or `|`, found `:` - --> $DIR/issue-87086-colon-path-sep.rs:69:12 + --> $DIR/issue-87086-colon-path-sep.rs:68:12 | LL | Foo:Bar::Baz => {} | ^-------- specifying the type of a pattern isn't supported @@ -103,7 +103,7 @@ LL | Foo::Bar::Baz => {} | + error: expected one of `@` or `|`, found `:` - --> $DIR/issue-87086-colon-path-sep.rs:75:12 + --> $DIR/issue-87086-colon-path-sep.rs:74:12 | LL | Foo:Bar => {} | ^--- specifying the type of a pattern isn't supported @@ -115,15 +115,5 @@ help: maybe write a path separator here LL | Foo::Bar => {} | + -warning: irrefutable `if let` pattern - --> $DIR/issue-87086-colon-path-sep.rs:40:8 - | -LL | if let Foo:Bar = f() { - | ^^^^^^^^^^^^^^^^^ - | - = note: this pattern will always match, so the `if let` is useless - = help: consider replacing the `if let` with a `let` - = note: `#[warn(irrefutable_let_patterns)]` on by default - -error: aborting due to 9 previous errors; 1 warning emitted +error: aborting due to 9 previous errors diff --git a/tests/ui/parser/type-ascription-in-pattern.rs b/tests/ui/parser/type-ascription-in-pattern.rs index 18d7061d69c8..75059d33db64 100644 --- a/tests/ui/parser/type-ascription-in-pattern.rs +++ b/tests/ui/parser/type-ascription-in-pattern.rs @@ -1,15 +1,16 @@ fn foo(x: bool) -> i32 { - match x { //~ ERROR struct literals are not allowed here - x: i32 => x, //~ ERROR expected - true => 42., //~ ERROR expected identifier - false => 0.333, //~ ERROR expected identifier + match x { + x: i32 => x, //~ ERROR: expected + //~^ ERROR: mismatched types + true => 42., + false => 0.333, } -} //~ ERROR expected one of +} fn main() { match foo(true) { - 42: i32 => (), //~ ERROR expected - _: f64 => (), //~ ERROR expected - x: i32 => (), //~ ERROR expected + 42: i32 => (), //~ ERROR: expected + _: f64 => (), //~ ERROR: expected + x: i32 => (), //~ ERROR: expected } } diff --git a/tests/ui/parser/type-ascription-in-pattern.stderr b/tests/ui/parser/type-ascription-in-pattern.stderr index 135879f208b2..091907549936 100644 --- a/tests/ui/parser/type-ascription-in-pattern.stderr +++ b/tests/ui/parser/type-ascription-in-pattern.stderr @@ -1,64 +1,18 @@ -error: expected one of `!`, `,`, `.`, `::`, `?`, `{`, `}`, or an operator, found `=>` - --> $DIR/type-ascription-in-pattern.rs:3:16 +error: expected one of `@` or `|`, found `:` + --> $DIR/type-ascription-in-pattern.rs:3:10 | -LL | match x { - | - while parsing this struct LL | x: i32 => x, - | -^^ expected one of 8 possible tokens - | | - | help: try adding a comma: `,` - -error: expected identifier, found keyword `true` - --> $DIR/type-ascription-in-pattern.rs:4:9 + | ^ --- specifying the type of a pattern isn't supported + | | + | expected one of `@` or `|` | -LL | match x { - | - while parsing this struct -LL | x: i32 => x, -LL | true => 42., - | ^^^^ expected identifier, found keyword - -error: expected identifier, found keyword `false` - --> $DIR/type-ascription-in-pattern.rs:5:9 +help: maybe write a path separator here | -LL | match x { - | - while parsing this struct -... -LL | false => 0.333, - | ^^^^^ expected identifier, found keyword - -error: struct literals are not allowed here - --> $DIR/type-ascription-in-pattern.rs:2:11 - | -LL | match x { - | ___________^ -LL | | x: i32 => x, -LL | | true => 42., -LL | | false => 0.333, -LL | | } - | |_____^ - | -help: surround the struct literal with parentheses - | -LL ~ match (x { -LL | x: i32 => x, -LL | true => 42., -LL | false => 0.333, -LL ~ }) - | - -error: expected one of `.`, `?`, `{`, or an operator, found `}` - --> $DIR/type-ascription-in-pattern.rs:7:1 - | -LL | match x { - | ----- while parsing this `match` expression -... -LL | } - | - expected one of `.`, `?`, `{`, or an operator -LL | } - | ^ unexpected token +LL | x::i32 => x, + | ~~ error: expected one of `...`, `..=`, `..`, or `|`, found `:` - --> $DIR/type-ascription-in-pattern.rs:11:11 + --> $DIR/type-ascription-in-pattern.rs:12:11 | LL | 42: i32 => (), | ^ --- specifying the type of a pattern isn't supported @@ -66,7 +20,7 @@ LL | 42: i32 => (), | expected one of `...`, `..=`, `..`, or `|` error: expected `|`, found `:` - --> $DIR/type-ascription-in-pattern.rs:12:10 + --> $DIR/type-ascription-in-pattern.rs:13:10 | LL | _: f64 => (), | ^ --- specifying the type of a pattern isn't supported @@ -74,7 +28,7 @@ LL | _: f64 => (), | expected `|` error: expected one of `@` or `|`, found `:` - --> $DIR/type-ascription-in-pattern.rs:13:10 + --> $DIR/type-ascription-in-pattern.rs:14:10 | LL | x: i32 => (), | ^ --- specifying the type of a pattern isn't supported @@ -86,5 +40,15 @@ help: maybe write a path separator here LL | x::i32 => (), | ~~ -error: aborting due to 8 previous errors +error[E0308]: mismatched types + --> $DIR/type-ascription-in-pattern.rs:3:19 + | +LL | fn foo(x: bool) -> i32 { + | --- expected `i32` because of return type +LL | match x { +LL | x: i32 => x, + | ^ expected `i32`, found `bool` +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0308`.