Skip to content

Commit 49f43c6

Browse files
committed
Recover more expressions in patterns
1 parent 85bcc00 commit 49f43c6

11 files changed

+104
-89
lines changed

compiler/rustc_parse/messages.ftl

+2-8
Original file line numberDiff line numberDiff line change
@@ -776,15 +776,9 @@ parse_unexpected_expr_in_pat =
776776
expected {$is_bound ->
777777
[true] a pattern range bound
778778
*[false] a pattern
779-
}, found {$is_method_call ->
780-
[true] a method call
781-
*[false] an expression
782-
}
779+
}, found an expression
783780
784-
.label = {$is_method_call ->
785-
[true] method calls
786-
*[false] arbitrary expressions
787-
} are not allowed in patterns
781+
.label = arbitrary expressions are not allowed in patterns
788782
789783
parse_unexpected_if_with_if = unexpected `if` in the condition expression
790784
.suggestion = remove the `if`

compiler/rustc_parse/src/errors.rs

-2
Original file line numberDiff line numberDiff line change
@@ -2439,8 +2439,6 @@ pub(crate) struct UnexpectedExpressionInPattern {
24392439
pub span: Span,
24402440
/// Was a `RangePatternBound` expected?
24412441
pub is_bound: bool,
2442-
/// Was the unexpected expression a `MethodCallExpression`?
2443-
pub is_method_call: bool,
24442442
}
24452443

24462444
#[derive(Diagnostic)]

compiler/rustc_parse/src/parser/expr.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ impl From<P<Expr>> for LhsExpr {
9494
}
9595

9696
#[derive(Debug)]
97-
enum DestructuredFloat {
97+
pub(crate) enum DestructuredFloat {
9898
/// 1e2
9999
Single(Symbol, Span),
100100
/// 1.
@@ -1101,7 +1101,7 @@ impl<'a> Parser<'a> {
11011101
// we should break everything including floats into more basic proc-macro style
11021102
// tokens in the lexer (probably preferable).
11031103
// See also `TokenKind::break_two_token_op` which does similar splitting of `>>` into `>`.
1104-
fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
1104+
pub(crate) fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
11051105
#[derive(Debug)]
11061106
enum FloatComponent {
11071107
IdentLike(String),

compiler/rustc_parse/src/parser/pat.rs

+50-35
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::errors::{
1010
UnexpectedParenInRangePatSugg, UnexpectedVertVertBeforeFunctionParam,
1111
UnexpectedVertVertInPattern,
1212
};
13-
use crate::parser::expr::could_be_unclosed_char_literal;
13+
use crate::parser::expr::{could_be_unclosed_char_literal, DestructuredFloat};
1414
use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
1515
use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor};
1616
use rustc_ast::ptr::P;
@@ -346,37 +346,51 @@ impl<'a> Parser<'a> {
346346
/// ^^^^^
347347
/// ```
348348
/// Only the end bound is spanned, and this function have no idea if there were a `..=` before `pat_span`, hence the parameter.
349+
/// This function returns `Some` if a trailing expression was recovered, and said expression's span.
349350
#[must_use = "the pattern must be discarded as `PatKind::Err` if this function returns Some"]
350351
fn maybe_recover_trailing_expr(
351352
&mut self,
352353
pat_span: Span,
353354
is_end_bound: bool,
354-
) -> Option<ErrorGuaranteed> {
355+
) -> Option<(ErrorGuaranteed, Span)> {
355356
if self.prev_token.is_keyword(kw::Underscore) || !self.may_recover() {
356357
// Don't recover anything after an `_` or if recovery is disabled.
357358
return None;
358359
}
359360

360-
// Check for `.hello()`, but allow `.Hello()` to be recovered as `, Hello()` in `parse_seq_to_before_tokens()`.
361-
let has_trailing_method = self.check_noexpect(&token::Dot)
361+
// Returns `true` iff `token` is a `x.y` float
362+
let is_float_literal = |that: &Self, token: &Token| -> bool {
363+
use token::{Lit, LitKind};
364+
365+
let token::Literal(Lit { kind: LitKind::Float, symbol, suffix: None }) = token.kind
366+
else {
367+
return false;
368+
};
369+
370+
matches!(that.break_up_float(symbol, token.span), DestructuredFloat::MiddleDot(..))
371+
};
372+
373+
// Check for `.hello` or `.0`.
374+
let has_dot_expr = self.check_noexpect(&token::Dot) // `.`
362375
&& self.look_ahead(1, |tok| {
363-
tok.ident()
364-
.and_then(|(ident, _)| ident.name.as_str().chars().next())
365-
.is_some_and(char::is_lowercase)
366-
})
367-
&& self.look_ahead(2, |tok| tok.kind == token::OpenDelim(Delimiter::Parenthesis));
376+
tok.is_ident() // `hello`
377+
|| tok.is_integer_lit() // `0`
378+
|| is_float_literal(&self, &tok) // `0.0`
379+
});
368380

369381
// Check for operators.
370382
// `|` is excluded as it is used in pattern alternatives and lambdas,
371383
// `?` is included for error propagation,
372384
// `[` is included for indexing operations,
373-
// `[]` is excluded as `a[]` isn't an expression and should be recovered as `a, []` (cf. `tests/ui/parser/pat-lt-bracket-7.rs`)
385+
// `[]` is excluded as `a[]` isn't an expression and should be recovered as `a, []` (cf. `tests/ui/parser/pat-lt-bracket-7.rs`),
386+
// `as` is included for type casts
374387
let has_trailing_operator = matches!(self.token.kind, token::BinOp(op) if op != BinOpToken::Or)
375388
|| self.token.kind == token::Question
376389
|| (self.token.kind == token::OpenDelim(Delimiter::Bracket)
377-
&& self.look_ahead(1, |tok| tok.kind != token::CloseDelim(Delimiter::Bracket)));
390+
&& self.look_ahead(1, |tok| tok.kind != token::CloseDelim(Delimiter::Bracket))) // excludes `[]`
391+
|| self.token.is_keyword(kw::As);
378392

379-
if !has_trailing_method && !has_trailing_operator {
393+
if !has_dot_expr && !has_trailing_operator {
380394
// Nothing to recover here.
381395
return None;
382396
}
@@ -394,8 +408,6 @@ impl<'a> Parser<'a> {
394408
)
395409
.map_err(|err| err.cancel())
396410
{
397-
let non_assoc_span = expr.span;
398-
399411
// Parse an associative expression such as `+ expr`, `% expr`, ...
400412
// Assignements, ranges and `|` are disabled by [`Restrictions::IS_PAT`].
401413
if let Ok(expr) =
@@ -411,14 +423,11 @@ impl<'a> Parser<'a> {
411423
|| self.token.kind == token::CloseDelim(Delimiter::Parenthesis)
412424
&& self.look_ahead(1, Token::is_range_separator);
413425

414-
// Check that `parse_expr_assoc_with` didn't eat a rhs.
415-
let is_method_call = has_trailing_method && non_assoc_span == expr.span;
416-
417-
return Some(self.dcx().emit_err(UnexpectedExpressionInPattern {
418-
span: expr.span,
419-
is_bound,
420-
is_method_call,
421-
}));
426+
return Some((
427+
self.dcx()
428+
.emit_err(UnexpectedExpressionInPattern { span: expr.span, is_bound }),
429+
expr.span,
430+
));
422431
}
423432
}
424433

@@ -535,7 +544,7 @@ impl<'a> Parser<'a> {
535544
self.parse_pat_tuple_struct(qself, path)?
536545
} else {
537546
match self.maybe_recover_trailing_expr(span, false) {
538-
Some(guar) => PatKind::Err(guar),
547+
Some((guar, _)) => PatKind::Err(guar),
539548
None => PatKind::Path(qself, path),
540549
}
541550
}
@@ -568,10 +577,10 @@ impl<'a> Parser<'a> {
568577
// Try to parse everything else as literal with optional minus
569578
match self.parse_literal_maybe_minus() {
570579
Ok(begin) => {
571-
let begin = match self.maybe_recover_trailing_expr(begin.span, false) {
572-
Some(guar) => self.mk_expr_err(begin.span, guar),
573-
None => begin,
574-
};
580+
let begin = self
581+
.maybe_recover_trailing_expr(begin.span, false)
582+
.map(|(guar, sp)| self.mk_expr_err(sp, guar))
583+
.unwrap_or(begin);
575584

576585
match self.parse_range_end() {
577586
Some(form) => self.parse_pat_range_begin_with(begin, form)?,
@@ -701,7 +710,8 @@ impl<'a> Parser<'a> {
701710
// For backward compatibility, `(..)` is a tuple pattern as well.
702711
let paren_pattern =
703712
fields.len() == 1 && !(matches!(trailing_comma, Trailing::Yes) || fields[0].is_rest());
704-
if paren_pattern {
713+
714+
let pat = if paren_pattern {
705715
let pat = fields.into_iter().next().unwrap();
706716
let close_paren = self.prev_token.span;
707717

@@ -719,7 +729,7 @@ impl<'a> Parser<'a> {
719729
},
720730
});
721731

722-
self.parse_pat_range_begin_with(begin.clone(), form)
732+
self.parse_pat_range_begin_with(begin.clone(), form)?
723733
}
724734
// recover ranges with parentheses around the `(start)..`
725735
PatKind::Err(guar)
@@ -734,15 +744,20 @@ impl<'a> Parser<'a> {
734744
},
735745
});
736746

737-
self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)
747+
self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)?
738748
}
739749

740750
// (pat) with optional parentheses
741-
_ => Ok(PatKind::Paren(pat)),
751+
_ => PatKind::Paren(pat),
742752
}
743753
} else {
744-
Ok(PatKind::Tuple(fields))
745-
}
754+
PatKind::Tuple(fields)
755+
};
756+
757+
Ok(match self.maybe_recover_trailing_expr(open_paren.to(self.prev_token.span), false) {
758+
None => pat,
759+
Some((guar, _)) => PatKind::Err(guar),
760+
})
746761
}
747762

748763
/// Parse a mutable binding with the `mut` token already eaten.
@@ -991,7 +1006,7 @@ impl<'a> Parser<'a> {
9911006
}
9921007

9931008
Ok(match recovered {
994-
Some(guar) => self.mk_expr_err(bound.span, guar),
1009+
Some((guar, sp)) => self.mk_expr_err(sp, guar),
9951010
None => bound,
9961011
})
9971012
}
@@ -1060,7 +1075,7 @@ impl<'a> Parser<'a> {
10601075
// but not `ident @ subpat` as `subpat` was already checked and `ident` continues with `@`.
10611076

10621077
let pat = if sub.is_none()
1063-
&& let Some(guar) = self.maybe_recover_trailing_expr(ident.span, false)
1078+
&& let Some((guar, _)) = self.maybe_recover_trailing_expr(ident.span, false)
10641079
{
10651080
PatKind::Err(guar)
10661081
} else {

tests/ui/parser/bad-name.stderr

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1-
error: expected one of `:`, `;`, `=`, `@`, or `|`, found `.`
2-
--> $DIR/bad-name.rs:4:8
1+
error: field expressions cannot have generic arguments
2+
--> $DIR/bad-name.rs:4:12
33
|
44
LL | let x.y::<isize>.z foo;
5-
| ^ expected one of `:`, `;`, `=`, `@`, or `|`
5+
| ^^^^^^^
66

7-
error: aborting due to 1 previous error
7+
error: expected a pattern, found an expression
8+
--> $DIR/bad-name.rs:4:7
9+
|
10+
LL | let x.y::<isize>.z foo;
11+
| ^^^^^^^^^^^^^^ arbitrary expressions are not allowed in patterns
12+
13+
error: expected one of `(`, `.`, `::`, `:`, `;`, `=`, `?`, `|`, or an operator, found `foo`
14+
--> $DIR/bad-name.rs:4:22
15+
|
16+
LL | let x.y::<isize>.z foo;
17+
| ^^^ expected one of 9 possible tokens
18+
19+
error: aborting due to 3 previous errors
820

tests/ui/parser/pat-recover-exprs.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
fn main() {
22
match u8::MAX {
33
u8::MAX.abs() => (),
4-
//~^ error: expected a pattern, found a method call
4+
//~^ error: expected a pattern, found an expression
55
x.sqrt() @ .. => (),
6-
//~^ error: expected a pattern, found a method call
6+
//~^ error: expected a pattern, found an expression
77
//~| error: left-hand side of `@` must be a binding
88
z @ w @ v.u() => (),
9-
//~^ error: expected a pattern, found a method call
9+
//~^ error: expected a pattern, found an expression
1010
y.ilog(3) => (),
11-
//~^ error: expected a pattern, found a method call
11+
//~^ error: expected a pattern, found an expression
1212
n + 1 => (),
1313
//~^ error: expected a pattern, found an expression
1414
("".f() + 14 * 8) => (),

tests/ui/parser/pat-recover-exprs.stderr

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
error: expected a pattern, found a method call
1+
error: expected a pattern, found an expression
22
--> $DIR/pat-recover-exprs.rs:3:9
33
|
44
LL | u8::MAX.abs() => (),
5-
| ^^^^^^^^^^^^^ method calls are not allowed in patterns
5+
| ^^^^^^^^^^^^^ arbitrary expressions are not allowed in patterns
66

7-
error: expected a pattern, found a method call
7+
error: expected a pattern, found an expression
88
--> $DIR/pat-recover-exprs.rs:5:9
99
|
1010
LL | x.sqrt() @ .. => (),
11-
| ^^^^^^^^ method calls are not allowed in patterns
11+
| ^^^^^^^^ arbitrary expressions are not allowed in patterns
1212

1313
error: left-hand side of `@` must be a binding
1414
--> $DIR/pat-recover-exprs.rs:5:9
@@ -21,17 +21,17 @@ LL | x.sqrt() @ .. => (),
2121
|
2222
= note: bindings are `x`, `mut x`, `ref x`, and `ref mut x`
2323

24-
error: expected a pattern, found a method call
24+
error: expected a pattern, found an expression
2525
--> $DIR/pat-recover-exprs.rs:8:17
2626
|
2727
LL | z @ w @ v.u() => (),
28-
| ^^^^^ method calls are not allowed in patterns
28+
| ^^^^^ arbitrary expressions are not allowed in patterns
2929

30-
error: expected a pattern, found a method call
30+
error: expected a pattern, found an expression
3131
--> $DIR/pat-recover-exprs.rs:10:9
3232
|
3333
LL | y.ilog(3) => (),
34-
| ^^^^^^^^^ method calls are not allowed in patterns
34+
| ^^^^^^^^^ arbitrary expressions are not allowed in patterns
3535

3636
error: expected a pattern, found an expression
3737
--> $DIR/pat-recover-exprs.rs:12:9

tests/ui/parser/pat-recover-methodcalls.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ struct Bar { baz: String }
44
fn foo(foo: Foo) -> bool {
55
match foo {
66
Foo("hi".to_owned()) => true,
7-
//~^ error: expected a pattern, found a method call
7+
//~^ error: expected a pattern, found an expression
88
_ => false
99
}
1010
}
1111

1212
fn bar(bar: Bar) -> bool {
1313
match bar {
1414
Bar { baz: "hi".to_owned() } => true,
15-
//~^ error: expected a pattern, found a method call
15+
//~^ error: expected a pattern, found an expression
1616
_ => false
1717
}
1818
}
@@ -22,16 +22,15 @@ fn baz() { // issue #90121
2222

2323
match foo.as_slice() {
2424
&["foo".to_string()] => {}
25-
//~^ error: expected a pattern, found a method call
25+
//~^ error: expected a pattern, found an expression
2626
_ => {}
2727
};
2828
}
2929

3030
fn main() {
3131
if let (-1.some(4)) = (0, Some(4)) {}
32-
//~^ error: expected a pattern, found a method call
32+
//~^ error: expected a pattern, found an expression
3333

3434
if let (-1.Some(4)) = (0, Some(4)) {}
35-
//~^ error: expected one of `)`, `,`, `...`, `..=`, `..`, or `|`, found `.`
36-
//~| help: missing `,`
35+
//~^ error: expected a pattern, found an expression
3736
}

0 commit comments

Comments
 (0)