Skip to content

Commit 52ee3eb

Browse files
committed
Handle methodcalls & operators in patterns
1 parent b3d6e3f commit 52ee3eb

File tree

11 files changed

+295
-18
lines changed

11 files changed

+295
-18
lines changed

compiler/rustc_parse/messages.ftl

+6
Original file line numberDiff line numberDiff line change
@@ -768,12 +768,18 @@ parse_unexpected_const_param_declaration = unexpected `const` parameter declarat
768768
parse_unexpected_default_value_for_lifetime_in_generic_parameters = unexpected default lifetime parameter
769769
.label = lifetime parameters cannot have default values
770770
771+
parse_unexpected_expr_in_pat = expected pattern, found expression
772+
.label = expressions are not allowed in patterns
773+
771774
parse_unexpected_if_with_if = unexpected `if` in the condition expression
772775
.suggestion = remove the `if`
773776
774777
parse_unexpected_lifetime_in_pattern = unexpected lifetime `{$symbol}` in pattern
775778
.suggestion = remove the lifetime
776779
780+
parse_unexpected_methodcall_in_pat = expected pattern, found method call
781+
.label = method calls are not allowed in patterns
782+
777783
parse_unexpected_parentheses_in_for_head = unexpected parentheses surrounding `for` loop head
778784
.suggestion = remove parentheses in `for` loop
779785

compiler/rustc_parse/src/errors.rs

+16
Original file line numberDiff line numberDiff line change
@@ -2372,6 +2372,22 @@ pub(crate) struct ExpectedCommaAfterPatternField {
23722372
pub span: Span,
23732373
}
23742374

2375+
#[derive(Diagnostic)]
2376+
#[diag(parse_unexpected_methodcall_in_pat)]
2377+
pub(crate) struct MethodCallInPattern {
2378+
#[primary_span]
2379+
#[label]
2380+
pub span: Span,
2381+
}
2382+
2383+
#[derive(Diagnostic)]
2384+
#[diag(parse_unexpected_expr_in_pat)]
2385+
pub(crate) struct ExpressionInPattern {
2386+
#[primary_span]
2387+
#[label]
2388+
pub span: Span,
2389+
}
2390+
23752391
#[derive(Diagnostic)]
23762392
#[diag(parse_return_types_use_thin_arrow)]
23772393
pub(crate) struct ReturnTypesUseThinArrow {

compiler/rustc_parse/src/parser/pat.rs

+72-5
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ use crate::errors::{
99
TrailingVertNotAllowed, UnexpectedLifetimeInPattern, UnexpectedVertVertBeforeFunctionParam,
1010
UnexpectedVertVertInPattern,
1111
};
12+
use crate::parser::diagnostics::SnapshotParser;
1213
use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
1314
use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor};
1415
use rustc_ast::ptr::P;
15-
use rustc_ast::token::{self, Delimiter};
16+
use rustc_ast::token::{self, BinOpToken, Delimiter, TokenKind};
1617
use rustc_ast::{
1718
self as ast, AttrVec, BindingAnnotation, ByRef, Expr, ExprKind, MacCall, Mutability, Pat,
1819
PatField, PatKind, Path, QSelf, RangeEnd, RangeSyntax,
@@ -338,6 +339,61 @@ impl<'a> Parser<'a> {
338339
}
339340
}
340341

342+
/// Ensures that the last parsed pattern is not followed by a method call or an binary operator.
343+
/// Returns `pat` if so, else emit an error, consume the `.methodCall()` or the expression and returns [`PatKind::Wild`]
344+
/// (thus discarding the pattern).
345+
fn maybe_recover_methodcall_or_operator(
346+
&mut self,
347+
mut snapshot: SnapshotParser<'a>,
348+
pat: PatKind,
349+
) -> PatKind {
350+
// check for `.hello()`, but allow `.Hello()` to be recovered as `, Hello()` in `parse_seq_to_before_tokens()`.
351+
if self.check_noexpect(&token::Dot)
352+
&& self.look_ahead(1, |tok| {
353+
tok.ident()
354+
.and_then(|(ident, _)| ident.name.to_string().chars().next())
355+
.is_some_and(char::is_lowercase)
356+
})
357+
&& self.look_ahead(2, |tok| tok.kind == TokenKind::OpenDelim(Delimiter::Parenthesis))
358+
{
359+
let span = snapshot.token.span;
360+
361+
if let Ok(expr) = snapshot.parse_expr().map_err(|err| err.cancel()) {
362+
// we could have `.hello() + something`, so let's parse only the methodcall
363+
self.bump(); // .
364+
self.bump(); // hello
365+
self.parse_paren_comma_seq(|f| f.parse_expr()).unwrap(); // (arg0, arg1, ...)
366+
367+
let span = span.to(self.prev_token.span);
368+
369+
if span != expr.span {
370+
// we got something after the methodcall
371+
self.sess.emit_err(errors::ExpressionInPattern { span: expr.span });
372+
} else {
373+
// we only have a methodcall
374+
self.sess.emit_err(errors::MethodCallInPattern { span });
375+
}
376+
377+
self.restore_snapshot(snapshot);
378+
return PatKind::Wild;
379+
}
380+
}
381+
382+
// `|` may be used in pattern alternatives and lambdas
383+
if self
384+
.look_ahead(0, |tok| matches!(tok.kind, token::BinOp(ref op) if op != &BinOpToken::Or))
385+
{
386+
if let Ok(expr) = snapshot.parse_expr().map_err(|err| err.cancel()) {
387+
self.sess.emit_err(errors::ExpressionInPattern { span: expr.span });
388+
389+
self.restore_snapshot(snapshot);
390+
return PatKind::Wild;
391+
}
392+
}
393+
394+
pat
395+
}
396+
341397
/// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
342398
/// allowed).
343399
fn parse_pat_with_range_pat(
@@ -422,6 +478,8 @@ impl<'a> Parser<'a> {
422478
// they are dealt with later in resolve.
423479
self.parse_pat_ident(BindingAnnotation::NONE, syntax_loc)?
424480
} else if self.is_start_of_pat_with_path() {
481+
let snapshot = self.create_snapshot_for_diagnostic();
482+
425483
// Parse pattern starting with a path
426484
let (qself, path) = if self.eat_lt() {
427485
// Parse a qualified path
@@ -443,7 +501,7 @@ impl<'a> Parser<'a> {
443501
} else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {
444502
self.parse_pat_tuple_struct(qself, path)?
445503
} else {
446-
PatKind::Path(qself, path)
504+
self.maybe_recover_methodcall_or_operator(snapshot, PatKind::Path(qself, path))
447505
}
448506
} else if matches!(self.token.kind, token::Lifetime(_))
449507
// In pattern position, we're totally fine with using "next token isn't colon"
@@ -469,14 +527,18 @@ impl<'a> Parser<'a> {
469527
});
470528
PatKind::Lit(self.mk_expr(lo, ExprKind::Lit(lit)))
471529
} else {
530+
let snapshot = self.create_snapshot_for_diagnostic();
531+
472532
// Try to parse everything else as literal with optional minus
473-
match self.parse_literal_maybe_minus() {
533+
let lit = match self.parse_literal_maybe_minus() {
474534
Ok(begin) => match self.parse_range_end() {
475535
Some(form) => self.parse_pat_range_begin_with(begin, form)?,
476536
None => PatKind::Lit(begin),
477537
},
478538
Err(err) => return self.fatal_unexpected_non_pat(err, expected),
479-
}
539+
};
540+
541+
self.maybe_recover_methodcall_or_operator(snapshot, lit)
480542
};
481543

482544
let pat = self.mk_pat(lo.to(self.prev_token.span), pat);
@@ -851,6 +913,7 @@ impl<'a> Parser<'a> {
851913
binding_annotation: BindingAnnotation,
852914
syntax_loc: Option<PatternLocation>,
853915
) -> PResult<'a, PatKind> {
916+
let snapshot = self.create_snapshot_for_diagnostic();
854917
let ident = self.parse_ident_common(false)?;
855918

856919
if self.may_recover()
@@ -880,7 +943,11 @@ impl<'a> Parser<'a> {
880943
.into_diagnostic(self.diagnostic()));
881944
}
882945

883-
Ok(PatKind::Ident(binding_annotation, ident, sub))
946+
let has_subpat = sub.is_some();
947+
let pat = PatKind::Ident(binding_annotation, ident, sub);
948+
949+
// check for methodcall after the `ident`, but not `ident @ pat` as `pat` was already checked
950+
Ok(if !has_subpat { self.maybe_recover_methodcall_or_operator(snapshot, pat) } else { pat })
884951
}
885952

886953
/// Parse a struct ("record") pattern (e.g. `Foo { ... }` or `Foo::Bar { ... }`).

tests/ui/half-open-range-patterns/range_pat_interactions1.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@ fn main() {
1717
}
1818
match x as i32 {
1919
0..5+1 => errors_only.push(x),
20-
//~^ error: expected one of `=>`, `if`, or `|`, found `+`
20+
//~^ error: expected pattern, found expression
2121
1 | -3..0 => first_or.push(x),
22+
//~^ error: exclusive range pattern syntax is experimental
2223
y @ (0..5 | 6) => or_two.push(y),
24+
//~^ error: exclusive range pattern syntax is experimental
2325
y @ 0..const { 5 + 1 } => assert_eq!(y, 5),
26+
//~^ error: exclusive range pattern syntax is experimental
27+
//~| error: inline-const in pattern position is experimental
2428
y @ -5.. => range_from.push(y),
2529
y @ ..-7 => assert_eq!(y, -8),
30+
//~^ error: exclusive range pattern syntax is experimental
2631
y => bottom.push(y),
2732
}
2833
}

tests/ui/half-open-range-patterns/range_pat_interactions1.stderr

+49-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: expected one of `=>`, `if`, or `|`, found `+`
2-
--> $DIR/range_pat_interactions1.rs:19:17
1+
error: expected pattern, found expression
2+
--> $DIR/range_pat_interactions1.rs:19:13
33
|
44
LL | 0..5+1 => errors_only.push(x),
5-
| ^ expected one of `=>`, `if`, or `|`
5+
| ^^^^^^ expressions are not allowed in patterns
66

77
error[E0408]: variable `n` is not bound in all patterns
88
--> $DIR/range_pat_interactions1.rs:10:25
@@ -12,6 +12,15 @@ LL | if let n @ 2..3|4 = x {
1212
| |
1313
| variable not in all patterns
1414

15+
error[E0658]: inline-const in pattern position is experimental
16+
--> $DIR/range_pat_interactions1.rs:25:20
17+
|
18+
LL | y @ 0..const { 5 + 1 } => assert_eq!(y, 5),
19+
| ^^^^^
20+
|
21+
= note: see issue #76001 <https://github.com/rust-lang/rust/issues/76001> for more information
22+
= help: add `#![feature(inline_const_pat)]` to the crate attributes to enable
23+
1524
error[E0658]: exclusive range pattern syntax is experimental
1625
--> $DIR/range_pat_interactions1.rs:10:20
1726
|
@@ -30,7 +39,43 @@ LL | } else if let 2..3 | 4 = x {
3039
= note: see issue #37854 <https://github.com/rust-lang/rust/issues/37854> for more information
3140
= help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable
3241

33-
error: aborting due to 4 previous errors
42+
error[E0658]: exclusive range pattern syntax is experimental
43+
--> $DIR/range_pat_interactions1.rs:21:17
44+
|
45+
LL | 1 | -3..0 => first_or.push(x),
46+
| ^^^^^
47+
|
48+
= note: see issue #37854 <https://github.com/rust-lang/rust/issues/37854> for more information
49+
= help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable
50+
51+
error[E0658]: exclusive range pattern syntax is experimental
52+
--> $DIR/range_pat_interactions1.rs:23:18
53+
|
54+
LL | y @ (0..5 | 6) => or_two.push(y),
55+
| ^^^^
56+
|
57+
= note: see issue #37854 <https://github.com/rust-lang/rust/issues/37854> for more information
58+
= help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable
59+
60+
error[E0658]: exclusive range pattern syntax is experimental
61+
--> $DIR/range_pat_interactions1.rs:25:17
62+
|
63+
LL | y @ 0..const { 5 + 1 } => assert_eq!(y, 5),
64+
| ^^^^^^^^^^^^^^^^^^
65+
|
66+
= note: see issue #37854 <https://github.com/rust-lang/rust/issues/37854> for more information
67+
= help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable
68+
69+
error[E0658]: exclusive range pattern syntax is experimental
70+
--> $DIR/range_pat_interactions1.rs:29:17
71+
|
72+
LL | y @ ..-7 => assert_eq!(y, -8),
73+
| ^^^^
74+
|
75+
= note: see issue #37854 <https://github.com/rust-lang/rust/issues/37854> for more information
76+
= help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable
77+
78+
error: aborting due to 9 previous errors
3479

3580
Some errors have detailed explanations: E0408, E0658.
3681
For more information about an error, try `rustc --explain E0408`.

tests/ui/parser/pat-ranges-3.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Parsing of range patterns
22

33
fn main() {
4-
let 10 ..= 10 + 3 = 12; //~ expected one of `:`, `;`, `=`, or `|`, found `+`
4+
let 10 ..= 10 + 3 = 12; //~ expected pattern, found expression
55
}

tests/ui/parser/pat-ranges-3.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: expected one of `:`, `;`, `=`, or `|`, found `+`
2-
--> $DIR/pat-ranges-3.rs:4:19
1+
error: expected pattern, found expression
2+
--> $DIR/pat-ranges-3.rs:4:9
33
|
44
LL | let 10 ..= 10 + 3 = 12;
5-
| ^ expected one of `:`, `;`, `=`, or `|`
5+
| ^^^^^^^^^^^^^ expressions are not allowed in patterns
66

77
error: aborting due to 1 previous error
88

tests/ui/parser/pat-ranges-4.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
fn main() {
44
let 10 - 3 ..= 10 = 8;
5-
//~^ error: expected one of `...`, `..=`, `..`, `:`, `;`, `=`, or `|`, found `-`
5+
//~^ error: expected pattern, found expression
66
}

tests/ui/parser/pat-ranges-4.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: expected one of `...`, `..=`, `..`, `:`, `;`, `=`, or `|`, found `-`
2-
--> $DIR/pat-ranges-4.rs:4:12
1+
error: expected pattern, found expression
2+
--> $DIR/pat-ranges-4.rs:4:9
33
|
44
LL | let 10 - 3 ..= 10 = 8;
5-
| ^ expected one of 7 possible tokens
5+
| ^^^^^^^^^^^^^ expressions are not allowed in patterns
66

77
error: aborting due to 1 previous error
88

tests/ui/pattern/methodcall.rs

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
struct Foo(String);
2+
struct Bar { baz: String }
3+
4+
fn foo(foo: Foo) -> bool {
5+
match foo {
6+
Foo("hi".to_owned()) => true,
7+
//~^ ERROR expected pattern, found method call
8+
_ => false
9+
}
10+
}
11+
12+
fn bar(bar: Bar) -> bool {
13+
match bar {
14+
Bar { baz: "hi".to_owned() } => true,
15+
//~^ ERROR expected pattern, found method call
16+
_ => false
17+
}
18+
}
19+
20+
fn qux() {
21+
match u8::MAX {
22+
u8::MAX.abs() => (),
23+
//~^ ERROR expected pattern, found method call
24+
x.sqrt() @ .. => (),
25+
//~^ ERROR expected pattern, found method call
26+
//~| ERROR left-hand side of `@` must be a binding
27+
z @ w @ v.u() => (),
28+
//~^ ERROR expected pattern, found method call
29+
y.ilog(3) => (),
30+
//~^ ERROR expected pattern, found method call
31+
n + 1 => (),
32+
//~^ ERROR expected pattern, found expression
33+
"".f() + 14 * 8 => (),
34+
//~^ ERROR expected pattern, found expression
35+
_ => ()
36+
}
37+
}
38+
39+
fn quux() {
40+
let foo = vec!["foo".to_string()];
41+
42+
match foo.as_slice() {
43+
&["foo".to_string()] => {}
44+
//~^ ERROR expected pattern, found method call
45+
_ => {}
46+
};
47+
}
48+
49+
fn main() {
50+
if let (-1.some(4)) = (0, Some(4)) {}
51+
//~^ ERROR expected pattern, found method call
52+
53+
if let (-1.Some(4)) = (0, Some(4)) {}
54+
//~^ ERROR expected one of `)`, `,`, `...`, `..=`, `..`, or `|`, found `.`
55+
//~| HELP missing `,`
56+
}

0 commit comments

Comments
 (0)