diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index 1c3c433d8b731..273d18b3a3211 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -768,12 +768,18 @@ parse_unexpected_const_param_declaration = unexpected `const` parameter declarat parse_unexpected_default_value_for_lifetime_in_generic_parameters = unexpected default lifetime parameter .label = lifetime parameters cannot have default values +parse_unexpected_expr_in_pat = expected pattern, found expression + .label = expressions are not allowed in patterns + parse_unexpected_if_with_if = unexpected `if` in the condition expression .suggestion = remove the `if` parse_unexpected_lifetime_in_pattern = unexpected lifetime `{$symbol}` in pattern .suggestion = remove the lifetime +parse_unexpected_methodcall_in_pat = expected pattern, found method call + .label = method calls are not allowed in patterns + parse_unexpected_parentheses_in_for_head = unexpected parentheses surrounding `for` loop head .suggestion = remove parentheses in `for` loop diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 03e047b297dc5..013ab6cdf45ab 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -2372,6 +2372,22 @@ pub(crate) struct ExpectedCommaAfterPatternField { pub span: Span, } +#[derive(Diagnostic)] +#[diag(parse_unexpected_methodcall_in_pat)] +pub(crate) struct MethodCallInPattern { + #[primary_span] + #[label] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(parse_unexpected_expr_in_pat)] +pub(crate) struct ExpressionInPattern { + #[primary_span] + #[label] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(parse_return_types_use_thin_arrow)] pub(crate) struct ReturnTypesUseThinArrow { diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index bd1bf2c78598b..f1fb73591ca88 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -9,10 +9,11 @@ use crate::errors::{ TrailingVertNotAllowed, UnexpectedLifetimeInPattern, UnexpectedVertVertBeforeFunctionParam, UnexpectedVertVertInPattern, }; +use crate::parser::diagnostics::SnapshotParser; use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole}; use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor}; use rustc_ast::ptr::P; -use rustc_ast::token::{self, Delimiter}; +use rustc_ast::token::{self, BinOpToken, Delimiter, TokenKind}; use rustc_ast::{ self as ast, AttrVec, BindingAnnotation, ByRef, Expr, ExprKind, MacCall, Mutability, Pat, PatField, PatKind, Path, QSelf, RangeEnd, RangeSyntax, @@ -338,6 +339,59 @@ impl<'a> Parser<'a> { } } + /// Ensures that the last parsed pattern is not followed by a method call or an binary operator. + /// Returns `pat` if so, else emit an error, consume the `.methodCall()` or the expression and returns [`PatKind::Wild`] + /// (thus discarding the pattern). + fn maybe_recover_methodcall_or_operator( + &mut self, + mut snapshot: SnapshotParser<'a>, + pat: PatKind, + ) -> PatKind { + // check for `.hello()`, but allow `.Hello()` to be recovered as `, Hello()` in `parse_seq_to_before_tokens()`. + if self.check_noexpect(&token::Dot) + && self.look_ahead(1, |tok| { + tok.ident() + .and_then(|(ident, _)| ident.name.to_string().chars().next()) + .is_some_and(char::is_lowercase) + }) + && self.look_ahead(2, |tok| tok.kind == TokenKind::OpenDelim(Delimiter::Parenthesis)) + { + let span = snapshot.token.span; + + if let Ok(expr) = snapshot.parse_expr().map_err(|err| err.cancel()) { + // we could have `.hello() + something`, so let's parse only the methodcall + self.bump(); // . + self.bump(); // hello + self.parse_paren_comma_seq(|f| f.parse_expr()).unwrap(); // (arg0, arg1, ...) + + let span = span.to(self.prev_token.span); + + if span != expr.span { + // we got something after the methodcall + self.sess.emit_err(errors::ExpressionInPattern { span: expr.span }); + } else { + // we only have a methodcall + self.sess.emit_err(errors::MethodCallInPattern { span }); + } + + self.restore_snapshot(snapshot); + return PatKind::Wild; + } + } + + // `|` may be used in pattern alternatives and lambdas + if self.look_ahead(0, |tok| matches!(tok.kind, token::BinOp(op) if op != BinOpToken::Or)) { + if let Ok(expr) = snapshot.parse_expr().map_err(|err| err.cancel()) { + self.sess.emit_err(errors::ExpressionInPattern { span: expr.span }); + + self.restore_snapshot(snapshot); + return PatKind::Wild; + } + } + + pat + } + /// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are /// allowed). fn parse_pat_with_range_pat( @@ -422,6 +476,8 @@ impl<'a> Parser<'a> { // they are dealt with later in resolve. self.parse_pat_ident(BindingAnnotation::NONE, syntax_loc)? } else if self.is_start_of_pat_with_path() { + let snapshot = self.create_snapshot_for_diagnostic(); + // Parse pattern starting with a path let (qself, path) = if self.eat_lt() { // Parse a qualified path @@ -443,7 +499,7 @@ impl<'a> Parser<'a> { } else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) { self.parse_pat_tuple_struct(qself, path)? } else { - PatKind::Path(qself, path) + self.maybe_recover_methodcall_or_operator(snapshot, PatKind::Path(qself, path)) } } else if matches!(self.token.kind, token::Lifetime(_)) // In pattern position, we're totally fine with using "next token isn't colon" @@ -469,14 +525,18 @@ impl<'a> Parser<'a> { }); PatKind::Lit(self.mk_expr(lo, ExprKind::Lit(lit))) } else { + let snapshot = self.create_snapshot_for_diagnostic(); + // Try to parse everything else as literal with optional minus - match self.parse_literal_maybe_minus() { + let lit = match self.parse_literal_maybe_minus() { Ok(begin) => match self.parse_range_end() { Some(form) => self.parse_pat_range_begin_with(begin, form)?, None => PatKind::Lit(begin), }, Err(err) => return self.fatal_unexpected_non_pat(err, expected), - } + }; + + self.maybe_recover_methodcall_or_operator(snapshot, lit) }; let pat = self.mk_pat(lo.to(self.prev_token.span), pat); @@ -851,6 +911,7 @@ impl<'a> Parser<'a> { binding_annotation: BindingAnnotation, syntax_loc: Option, ) -> PResult<'a, PatKind> { + let snapshot = self.create_snapshot_for_diagnostic(); let ident = self.parse_ident_common(false)?; if self.may_recover() @@ -880,7 +941,11 @@ impl<'a> Parser<'a> { .into_diagnostic(self.diagnostic())); } - Ok(PatKind::Ident(binding_annotation, ident, sub)) + let has_subpat = sub.is_some(); + let pat = PatKind::Ident(binding_annotation, ident, sub); + + // check for methodcall after the `ident`, but not `ident @ pat` as `pat` was already checked + Ok(if !has_subpat { self.maybe_recover_methodcall_or_operator(snapshot, pat) } else { pat }) } /// Parse a struct ("record") pattern (e.g. `Foo { ... }` or `Foo::Bar { ... }`). diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions1.rs b/tests/ui/half-open-range-patterns/range_pat_interactions1.rs index 55353999b6788..6e536cfe8595e 100644 --- a/tests/ui/half-open-range-patterns/range_pat_interactions1.rs +++ b/tests/ui/half-open-range-patterns/range_pat_interactions1.rs @@ -17,12 +17,17 @@ fn main() { } match x as i32 { 0..5+1 => errors_only.push(x), - //~^ error: expected one of `=>`, `if`, or `|`, found `+` + //~^ error: expected pattern, found expression 1 | -3..0 => first_or.push(x), + //~^ error: exclusive range pattern syntax is experimental y @ (0..5 | 6) => or_two.push(y), + //~^ error: exclusive range pattern syntax is experimental y @ 0..const { 5 + 1 } => assert_eq!(y, 5), + //~^ error: exclusive range pattern syntax is experimental + //~| error: inline-const in pattern position is experimental y @ -5.. => range_from.push(y), y @ ..-7 => assert_eq!(y, -8), + //~^ error: exclusive range pattern syntax is experimental y => bottom.push(y), } } diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr b/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr index 19ebcaf0f3699..2a2f28ac48541 100644 --- a/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr +++ b/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr @@ -1,8 +1,8 @@ -error: expected one of `=>`, `if`, or `|`, found `+` - --> $DIR/range_pat_interactions1.rs:19:17 +error: expected pattern, found expression + --> $DIR/range_pat_interactions1.rs:19:13 | LL | 0..5+1 => errors_only.push(x), - | ^ expected one of `=>`, `if`, or `|` + | ^^^^^^ expressions are not allowed in patterns error[E0408]: variable `n` is not bound in all patterns --> $DIR/range_pat_interactions1.rs:10:25 @@ -12,6 +12,15 @@ LL | if let n @ 2..3|4 = x { | | | variable not in all patterns +error[E0658]: inline-const in pattern position is experimental + --> $DIR/range_pat_interactions1.rs:25:20 + | +LL | y @ 0..const { 5 + 1 } => assert_eq!(y, 5), + | ^^^^^ + | + = note: see issue #76001 for more information + = help: add `#![feature(inline_const_pat)]` to the crate attributes to enable + error[E0658]: exclusive range pattern syntax is experimental --> $DIR/range_pat_interactions1.rs:10:20 | @@ -30,7 +39,43 @@ LL | } else if let 2..3 | 4 = x { = note: see issue #37854 for more information = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable -error: aborting due to 4 previous errors +error[E0658]: exclusive range pattern syntax is experimental + --> $DIR/range_pat_interactions1.rs:21:17 + | +LL | 1 | -3..0 => first_or.push(x), + | ^^^^^ + | + = note: see issue #37854 for more information + = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable + +error[E0658]: exclusive range pattern syntax is experimental + --> $DIR/range_pat_interactions1.rs:23:18 + | +LL | y @ (0..5 | 6) => or_two.push(y), + | ^^^^ + | + = note: see issue #37854 for more information + = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable + +error[E0658]: exclusive range pattern syntax is experimental + --> $DIR/range_pat_interactions1.rs:25:17 + | +LL | y @ 0..const { 5 + 1 } => assert_eq!(y, 5), + | ^^^^^^^^^^^^^^^^^^ + | + = note: see issue #37854 for more information + = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable + +error[E0658]: exclusive range pattern syntax is experimental + --> $DIR/range_pat_interactions1.rs:29:17 + | +LL | y @ ..-7 => assert_eq!(y, -8), + | ^^^^ + | + = note: see issue #37854 for more information + = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable + +error: aborting due to 9 previous errors Some errors have detailed explanations: E0408, E0658. For more information about an error, try `rustc --explain E0408`. diff --git a/tests/ui/parser/pat-ranges-3.rs b/tests/ui/parser/pat-ranges-3.rs index 8976dcf0d90f9..26fe7ab60953d 100644 --- a/tests/ui/parser/pat-ranges-3.rs +++ b/tests/ui/parser/pat-ranges-3.rs @@ -1,5 +1,5 @@ // Parsing of range patterns fn main() { - let 10 ..= 10 + 3 = 12; //~ expected one of `:`, `;`, `=`, or `|`, found `+` + let 10 ..= 10 + 3 = 12; //~ expected pattern, found expression } diff --git a/tests/ui/parser/pat-ranges-3.stderr b/tests/ui/parser/pat-ranges-3.stderr index 611b35a650280..271b8fcfafc38 100644 --- a/tests/ui/parser/pat-ranges-3.stderr +++ b/tests/ui/parser/pat-ranges-3.stderr @@ -1,8 +1,8 @@ -error: expected one of `:`, `;`, `=`, or `|`, found `+` - --> $DIR/pat-ranges-3.rs:4:19 +error: expected pattern, found expression + --> $DIR/pat-ranges-3.rs:4:9 | LL | let 10 ..= 10 + 3 = 12; - | ^ expected one of `:`, `;`, `=`, or `|` + | ^^^^^^^^^^^^^ expressions are not allowed in patterns error: aborting due to 1 previous error diff --git a/tests/ui/parser/pat-ranges-4.rs b/tests/ui/parser/pat-ranges-4.rs index 61188976b028c..4d800da8daecd 100644 --- a/tests/ui/parser/pat-ranges-4.rs +++ b/tests/ui/parser/pat-ranges-4.rs @@ -2,5 +2,5 @@ fn main() { let 10 - 3 ..= 10 = 8; - //~^ error: expected one of `...`, `..=`, `..`, `:`, `;`, `=`, or `|`, found `-` + //~^ error: expected pattern, found expression } diff --git a/tests/ui/parser/pat-ranges-4.stderr b/tests/ui/parser/pat-ranges-4.stderr index c30160291d608..7883343331ee2 100644 --- a/tests/ui/parser/pat-ranges-4.stderr +++ b/tests/ui/parser/pat-ranges-4.stderr @@ -1,8 +1,8 @@ -error: expected one of `...`, `..=`, `..`, `:`, `;`, `=`, or `|`, found `-` - --> $DIR/pat-ranges-4.rs:4:12 +error: expected pattern, found expression + --> $DIR/pat-ranges-4.rs:4:9 | LL | let 10 - 3 ..= 10 = 8; - | ^ expected one of 7 possible tokens + | ^^^^^^^^^^^^^ expressions are not allowed in patterns error: aborting due to 1 previous error diff --git a/tests/ui/pattern/methodcall.rs b/tests/ui/pattern/methodcall.rs new file mode 100644 index 0000000000000..74ffe0f99791b --- /dev/null +++ b/tests/ui/pattern/methodcall.rs @@ -0,0 +1,56 @@ +struct Foo(String); +struct Bar { baz: String } + +fn foo(foo: Foo) -> bool { + match foo { + Foo("hi".to_owned()) => true, + //~^ ERROR expected pattern, found method call + _ => false + } +} + +fn bar(bar: Bar) -> bool { + match bar { + Bar { baz: "hi".to_owned() } => true, + //~^ ERROR expected pattern, found method call + _ => false + } +} + +fn qux() { + match u8::MAX { + u8::MAX.abs() => (), + //~^ ERROR expected pattern, found method call + x.sqrt() @ .. => (), + //~^ ERROR expected pattern, found method call + //~| ERROR left-hand side of `@` must be a binding + z @ w @ v.u() => (), + //~^ ERROR expected pattern, found method call + y.ilog(3) => (), + //~^ ERROR expected pattern, found method call + n + 1 => (), + //~^ ERROR expected pattern, found expression + "".f() + 14 * 8 => (), + //~^ ERROR expected pattern, found expression + _ => () + } +} + +fn quux() { + let foo = vec!["foo".to_string()]; + + match foo.as_slice() { + &["foo".to_string()] => {} + //~^ ERROR expected pattern, found method call + _ => {} + }; +} + +fn main() { + if let (-1.some(4)) = (0, Some(4)) {} + //~^ ERROR expected pattern, found method call + + if let (-1.Some(4)) = (0, Some(4)) {} + //~^ ERROR expected one of `)`, `,`, `...`, `..=`, `..`, or `|`, found `.` + //~| HELP missing `,` +} diff --git a/tests/ui/pattern/methodcall.stderr b/tests/ui/pattern/methodcall.stderr new file mode 100644 index 0000000000000..76708596afee3 --- /dev/null +++ b/tests/ui/pattern/methodcall.stderr @@ -0,0 +1,82 @@ +error: expected pattern, found method call + --> $DIR/methodcall.rs:6:13 + | +LL | Foo("hi".to_owned()) => true, + | ^^^^^^^^^^^^^^^ method calls are not allowed in patterns + +error: expected pattern, found method call + --> $DIR/methodcall.rs:14:20 + | +LL | Bar { baz: "hi".to_owned() } => true, + | ^^^^^^^^^^^^^^^ method calls are not allowed in patterns + +error: expected pattern, found method call + --> $DIR/methodcall.rs:22:9 + | +LL | u8::MAX.abs() => (), + | ^^^^^^^^^^^^^ method calls are not allowed in patterns + +error: expected pattern, found method call + --> $DIR/methodcall.rs:24:9 + | +LL | x.sqrt() @ .. => (), + | ^^^^^^^^ method calls are not allowed in patterns + +error: left-hand side of `@` must be a binding + --> $DIR/methodcall.rs:24:9 + | +LL | x.sqrt() @ .. => (), + | --------^^^-- + | | | + | | also a pattern + | interpreted as a pattern, not a binding + | + = note: bindings are `x`, `mut x`, `ref x`, and `ref mut x` + +error: expected pattern, found method call + --> $DIR/methodcall.rs:27:17 + | +LL | z @ w @ v.u() => (), + | ^^^^^ method calls are not allowed in patterns + +error: expected pattern, found method call + --> $DIR/methodcall.rs:29:9 + | +LL | y.ilog(3) => (), + | ^^^^^^^^^ method calls are not allowed in patterns + +error: expected pattern, found expression + --> $DIR/methodcall.rs:31:9 + | +LL | n + 1 => (), + | ^^^^^ expressions are not allowed in patterns + +error: expected pattern, found expression + --> $DIR/methodcall.rs:33:9 + | +LL | "".f() + 14 * 8 => (), + | ^^^^^^^^^^^^^^^ expressions are not allowed in patterns + +error: expected pattern, found method call + --> $DIR/methodcall.rs:43:11 + | +LL | &["foo".to_string()] => {} + | ^^^^^^^^^^^^^^^^^ method calls are not allowed in patterns + +error: expected pattern, found method call + --> $DIR/methodcall.rs:50:13 + | +LL | if let (-1.some(4)) = (0, Some(4)) {} + | ^^^^^^^^^^ method calls are not allowed in patterns + +error: expected one of `)`, `,`, `...`, `..=`, `..`, or `|`, found `.` + --> $DIR/methodcall.rs:53:15 + | +LL | if let (-1.Some(4)) = (0, Some(4)) {} + | ^ + | | + | expected one of `)`, `,`, `...`, `..=`, `..`, or `|` + | help: missing `,` + +error: aborting due to 12 previous errors +