Skip to content

Commit

Permalink
Recover more expressions in patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
ShE3py committed Apr 29, 2024
1 parent 85bcc00 commit 49f43c6
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 89 deletions.
10 changes: 2 additions & 8 deletions compiler/rustc_parse/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -776,15 +776,9 @@ parse_unexpected_expr_in_pat =
expected {$is_bound ->
[true] a pattern range bound
*[false] a pattern
}, found {$is_method_call ->
[true] a method call
*[false] an expression
}
}, found an expression
.label = {$is_method_call ->
[true] method calls
*[false] arbitrary expressions
} are not allowed in patterns
.label = arbitrary expressions are not allowed in patterns
parse_unexpected_if_with_if = unexpected `if` in the condition expression
.suggestion = remove the `if`
Expand Down
2 changes: 0 additions & 2 deletions compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2439,8 +2439,6 @@ pub(crate) struct UnexpectedExpressionInPattern {
pub span: Span,
/// Was a `RangePatternBound` expected?
pub is_bound: bool,
/// Was the unexpected expression a `MethodCallExpression`?
pub is_method_call: bool,
}

#[derive(Diagnostic)]
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ impl From<P<Expr>> for LhsExpr {
}

#[derive(Debug)]
enum DestructuredFloat {
pub(crate) enum DestructuredFloat {
/// 1e2
Single(Symbol, Span),
/// 1.
Expand Down Expand Up @@ -1101,7 +1101,7 @@ impl<'a> Parser<'a> {
// we should break everything including floats into more basic proc-macro style
// tokens in the lexer (probably preferable).
// See also `TokenKind::break_two_token_op` which does similar splitting of `>>` into `>`.
fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
pub(crate) fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
#[derive(Debug)]
enum FloatComponent {
IdentLike(String),
Expand Down
85 changes: 50 additions & 35 deletions compiler/rustc_parse/src/parser/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::errors::{
UnexpectedParenInRangePatSugg, UnexpectedVertVertBeforeFunctionParam,
UnexpectedVertVertInPattern,
};
use crate::parser::expr::could_be_unclosed_char_literal;
use crate::parser::expr::{could_be_unclosed_char_literal, DestructuredFloat};
use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor};
use rustc_ast::ptr::P;
Expand Down Expand Up @@ -346,37 +346,51 @@ impl<'a> Parser<'a> {
/// ^^^^^
/// ```
/// Only the end bound is spanned, and this function have no idea if there were a `..=` before `pat_span`, hence the parameter.
/// This function returns `Some` if a trailing expression was recovered, and said expression's span.
#[must_use = "the pattern must be discarded as `PatKind::Err` if this function returns Some"]
fn maybe_recover_trailing_expr(
&mut self,
pat_span: Span,
is_end_bound: bool,
) -> Option<ErrorGuaranteed> {
) -> Option<(ErrorGuaranteed, Span)> {
if self.prev_token.is_keyword(kw::Underscore) || !self.may_recover() {
// Don't recover anything after an `_` or if recovery is disabled.
return None;
}

// Check for `.hello()`, but allow `.Hello()` to be recovered as `, Hello()` in `parse_seq_to_before_tokens()`.
let has_trailing_method = self.check_noexpect(&token::Dot)
// Returns `true` iff `token` is a `x.y` float
let is_float_literal = |that: &Self, token: &Token| -> bool {
use token::{Lit, LitKind};

let token::Literal(Lit { kind: LitKind::Float, symbol, suffix: None }) = token.kind
else {
return false;
};

matches!(that.break_up_float(symbol, token.span), DestructuredFloat::MiddleDot(..))
};

// Check for `.hello` or `.0`.
let has_dot_expr = self.check_noexpect(&token::Dot) // `.`
&& self.look_ahead(1, |tok| {
tok.ident()
.and_then(|(ident, _)| ident.name.as_str().chars().next())
.is_some_and(char::is_lowercase)
})
&& self.look_ahead(2, |tok| tok.kind == token::OpenDelim(Delimiter::Parenthesis));
tok.is_ident() // `hello`
|| tok.is_integer_lit() // `0`
|| is_float_literal(&self, &tok) // `0.0`
});

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

if !has_trailing_method && !has_trailing_operator {
if !has_dot_expr && !has_trailing_operator {
// Nothing to recover here.
return None;
}
Expand All @@ -394,8 +408,6 @@ impl<'a> Parser<'a> {
)
.map_err(|err| err.cancel())
{
let non_assoc_span = expr.span;

// Parse an associative expression such as `+ expr`, `% expr`, ...
// Assignements, ranges and `|` are disabled by [`Restrictions::IS_PAT`].
if let Ok(expr) =
Expand All @@ -411,14 +423,11 @@ impl<'a> Parser<'a> {
|| self.token.kind == token::CloseDelim(Delimiter::Parenthesis)
&& self.look_ahead(1, Token::is_range_separator);

// Check that `parse_expr_assoc_with` didn't eat a rhs.
let is_method_call = has_trailing_method && non_assoc_span == expr.span;

return Some(self.dcx().emit_err(UnexpectedExpressionInPattern {
span: expr.span,
is_bound,
is_method_call,
}));
return Some((
self.dcx()
.emit_err(UnexpectedExpressionInPattern { span: expr.span, is_bound }),
expr.span,
));
}
}

Expand Down Expand Up @@ -535,7 +544,7 @@ impl<'a> Parser<'a> {
self.parse_pat_tuple_struct(qself, path)?
} else {
match self.maybe_recover_trailing_expr(span, false) {
Some(guar) => PatKind::Err(guar),
Some((guar, _)) => PatKind::Err(guar),
None => PatKind::Path(qself, path),
}
}
Expand Down Expand Up @@ -568,10 +577,10 @@ impl<'a> Parser<'a> {
// Try to parse everything else as literal with optional minus
match self.parse_literal_maybe_minus() {
Ok(begin) => {
let begin = match self.maybe_recover_trailing_expr(begin.span, false) {
Some(guar) => self.mk_expr_err(begin.span, guar),
None => begin,
};
let begin = self
.maybe_recover_trailing_expr(begin.span, false)
.map(|(guar, sp)| self.mk_expr_err(sp, guar))
.unwrap_or(begin);

match self.parse_range_end() {
Some(form) => self.parse_pat_range_begin_with(begin, form)?,
Expand Down Expand Up @@ -701,7 +710,8 @@ impl<'a> Parser<'a> {
// For backward compatibility, `(..)` is a tuple pattern as well.
let paren_pattern =
fields.len() == 1 && !(matches!(trailing_comma, Trailing::Yes) || fields[0].is_rest());
if paren_pattern {

let pat = if paren_pattern {
let pat = fields.into_iter().next().unwrap();
let close_paren = self.prev_token.span;

Expand All @@ -719,7 +729,7 @@ impl<'a> Parser<'a> {
},
});

self.parse_pat_range_begin_with(begin.clone(), form)
self.parse_pat_range_begin_with(begin.clone(), form)?
}
// recover ranges with parentheses around the `(start)..`
PatKind::Err(guar)
Expand All @@ -734,15 +744,20 @@ impl<'a> Parser<'a> {
},
});

self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)
self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)?
}

// (pat) with optional parentheses
_ => Ok(PatKind::Paren(pat)),
_ => PatKind::Paren(pat),
}
} else {
Ok(PatKind::Tuple(fields))
}
PatKind::Tuple(fields)
};

Ok(match self.maybe_recover_trailing_expr(open_paren.to(self.prev_token.span), false) {
None => pat,
Some((guar, _)) => PatKind::Err(guar),
})
}

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

Ok(match recovered {
Some(guar) => self.mk_expr_err(bound.span, guar),
Some((guar, sp)) => self.mk_expr_err(sp, guar),
None => bound,
})
}
Expand Down Expand Up @@ -1060,7 +1075,7 @@ impl<'a> Parser<'a> {
// but not `ident @ subpat` as `subpat` was already checked and `ident` continues with `@`.

let pat = if sub.is_none()
&& let Some(guar) = self.maybe_recover_trailing_expr(ident.span, false)
&& let Some((guar, _)) = self.maybe_recover_trailing_expr(ident.span, false)
{
PatKind::Err(guar)
} else {
Expand Down
20 changes: 16 additions & 4 deletions tests/ui/parser/bad-name.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
error: expected one of `:`, `;`, `=`, `@`, or `|`, found `.`
--> $DIR/bad-name.rs:4:8
error: field expressions cannot have generic arguments
--> $DIR/bad-name.rs:4:12
|
LL | let x.y::<isize>.z foo;
| ^ expected one of `:`, `;`, `=`, `@`, or `|`
| ^^^^^^^

error: aborting due to 1 previous error
error: expected a pattern, found an expression
--> $DIR/bad-name.rs:4:7
|
LL | let x.y::<isize>.z foo;
| ^^^^^^^^^^^^^^ arbitrary expressions are not allowed in patterns

error: expected one of `(`, `.`, `::`, `:`, `;`, `=`, `?`, `|`, or an operator, found `foo`
--> $DIR/bad-name.rs:4:22
|
LL | let x.y::<isize>.z foo;
| ^^^ expected one of 9 possible tokens

error: aborting due to 3 previous errors

8 changes: 4 additions & 4 deletions tests/ui/parser/pat-recover-exprs.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
fn main() {
match u8::MAX {
u8::MAX.abs() => (),
//~^ error: expected a pattern, found a method call
//~^ error: expected a pattern, found an expression
x.sqrt() @ .. => (),
//~^ error: expected a pattern, found a method call
//~^ error: expected a pattern, found an expression
//~| error: left-hand side of `@` must be a binding
z @ w @ v.u() => (),
//~^ error: expected a pattern, found a method call
//~^ error: expected a pattern, found an expression
y.ilog(3) => (),
//~^ error: expected a pattern, found a method call
//~^ error: expected a pattern, found an expression
n + 1 => (),
//~^ error: expected a pattern, found an expression
("".f() + 14 * 8) => (),
Expand Down
16 changes: 8 additions & 8 deletions tests/ui/parser/pat-recover-exprs.stderr
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
error: expected a pattern, found a method call
error: expected a pattern, found an expression
--> $DIR/pat-recover-exprs.rs:3:9
|
LL | u8::MAX.abs() => (),
| ^^^^^^^^^^^^^ method calls are not allowed in patterns
| ^^^^^^^^^^^^^ arbitrary expressions are not allowed in patterns

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

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

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

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

error: expected a pattern, found an expression
--> $DIR/pat-recover-exprs.rs:12:9
Expand Down
11 changes: 5 additions & 6 deletions tests/ui/parser/pat-recover-methodcalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ struct Bar { baz: String }
fn foo(foo: Foo) -> bool {
match foo {
Foo("hi".to_owned()) => true,
//~^ error: expected a pattern, found a method call
//~^ error: expected a pattern, found an expression
_ => false
}
}

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

match foo.as_slice() {
&["foo".to_string()] => {}
//~^ error: expected a pattern, found a method call
//~^ error: expected a pattern, found an expression
_ => {}
};
}

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

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

0 comments on commit 49f43c6

Please sign in to comment.