Skip to content

Commit

Permalink
Handle methodcalls & operators in patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
ShE3py committed Dec 6, 2023
1 parent b3d6e3f commit 6580704
Show file tree
Hide file tree
Showing 11 changed files with 293 additions and 18 deletions.
6 changes: 6 additions & 0 deletions compiler/rustc_parse/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
75 changes: 70 additions & 5 deletions compiler/rustc_parse/src/parser/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand All @@ -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);
Expand Down Expand Up @@ -851,6 +911,7 @@ impl<'a> Parser<'a> {
binding_annotation: BindingAnnotation,
syntax_loc: Option<PatternLocation>,
) -> PResult<'a, PatKind> {
let snapshot = self.create_snapshot_for_diagnostic();
let ident = self.parse_ident_common(false)?;

if self.may_recover()
Expand Down Expand Up @@ -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 { ... }`).
Expand Down
7 changes: 6 additions & 1 deletion tests/ui/half-open-range-patterns/range_pat_interactions1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}
Expand Down
53 changes: 49 additions & 4 deletions tests/ui/half-open-range-patterns/range_pat_interactions1.stderr
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 <https://github.com/rust-lang/rust/issues/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
|
Expand All @@ -30,7 +39,43 @@ LL | } else if let 2..3 | 4 = x {
= note: see issue #37854 <https://github.com/rust-lang/rust/issues/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 <https://github.com/rust-lang/rust/issues/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 <https://github.com/rust-lang/rust/issues/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 <https://github.com/rust-lang/rust/issues/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 <https://github.com/rust-lang/rust/issues/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`.
2 changes: 1 addition & 1 deletion tests/ui/parser/pat-ranges-3.rs
Original file line number Diff line number Diff line change
@@ -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
}
6 changes: 3 additions & 3 deletions tests/ui/parser/pat-ranges-3.stderr
Original file line number Diff line number Diff line change
@@ -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

2 changes: 1 addition & 1 deletion tests/ui/parser/pat-ranges-4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

fn main() {
let 10 - 3 ..= 10 = 8;
//~^ error: expected one of `...`, `..=`, `..`, `:`, `;`, `=`, or `|`, found `-`
//~^ error: expected pattern, found expression
}
6 changes: 3 additions & 3 deletions tests/ui/parser/pat-ranges-4.stderr
Original file line number Diff line number Diff line change
@@ -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

56 changes: 56 additions & 0 deletions tests/ui/pattern/methodcall.rs
Original file line number Diff line number Diff line change
@@ -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 `,`
}
Loading

0 comments on commit 6580704

Please sign in to comment.