Skip to content

Commit b317f9f

Browse files
committed
Recover more expressions in patterns
1 parent 5f8987f commit b317f9f

20 files changed

+561
-287
lines changed

compiler/rustc_parse/messages.ftl

+2-8
Original file line numberDiff line numberDiff line change
@@ -774,15 +774,9 @@ parse_unexpected_expr_in_pat =
774774
expected {$is_bound ->
775775
[true] a pattern range bound
776776
*[false] a pattern
777-
}, found {$is_method_call ->
778-
[true] a method call
779-
*[false] an expression
780-
}
777+
}, found an expression
781778
782-
.label = {$is_method_call ->
783-
[true] method calls
784-
*[false] arbitrary expressions
785-
} are not allowed in patterns
779+
.label = arbitrary expressions are not allowed in patterns
786780
787781
parse_unexpected_if_with_if = unexpected `if` in the condition expression
788782
.suggestion = remove the `if`

compiler/rustc_parse/src/errors.rs

-2
Original file line numberDiff line numberDiff line change
@@ -2422,8 +2422,6 @@ pub(crate) struct UnexpectedExpressionInPattern {
24222422
pub span: Span,
24232423
/// Was a `RangePatternBound` expected?
24242424
pub is_bound: bool,
2425-
/// Was the unexpected expression a `MethodCallExpression`?
2426-
pub is_method_call: bool,
24272425
}
24282426

24292427
#[derive(Diagnostic)]

compiler/rustc_parse/src/parser/expr.rs

+9-7
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ use tracing::instrument;
4141

4242
#[derive(Debug)]
4343
pub(super) enum LhsExpr {
44-
// Already parsed just the outer attributes.
44+
/// Already parsed just the outer attributes.
4545
Unparsed { attrs: AttrWrapper },
46-
// Already parsed the expression.
46+
/// Already parsed the expression.
4747
Parsed { expr: P<Expr>, starts_statement: bool },
4848
}
4949

5050
#[derive(Debug)]
51-
enum DestructuredFloat {
51+
pub(super) enum DestructuredFloat {
5252
/// 1e2
5353
Single(Symbol, Span),
5454
/// 1.
@@ -1036,15 +1036,17 @@ impl<'a> Parser<'a> {
10361036
self.dcx().emit_err(errors::UnexpectedTokenAfterDot { span, actual })
10371037
}
10381038

1039-
// We need an identifier or integer, but the next token is a float.
1040-
// Break the float into components to extract the identifier or integer.
1039+
/// We need an identifier or integer, but the next token is a float.
1040+
/// Break the float into components to extract the identifier or integer.
1041+
///
1042+
/// See also [`TokenKind::break_two_token_op`] which does similar splitting of `>>` into `>`.
1043+
//
10411044
// FIXME: With current `TokenCursor` it's hard to break tokens into more than 2
10421045
// parts unless those parts are processed immediately. `TokenCursor` should either
10431046
// support pushing "future tokens" (would be also helpful to `break_and_eat`), or
10441047
// we should break everything including floats into more basic proc-macro style
10451048
// tokens in the lexer (probably preferable).
1046-
// See also `TokenKind::break_two_token_op` which does similar splitting of `>>` into `>`.
1047-
fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
1049+
pub(super) fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
10481050
#[derive(Debug)]
10491051
enum FloatComponent {
10501052
IdentLike(String),

compiler/rustc_parse/src/parser/pat.rs

+75-57
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, LhsExpr};
13+
use crate::parser::expr::{could_be_unclosed_char_literal, DestructuredFloat, LhsExpr};
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;
@@ -337,46 +337,61 @@ impl<'a> Parser<'a> {
337337
}
338338
}
339339

340-
/// Ensures that the last parsed pattern (or pattern range bound) is not followed by a method call or an operator.
340+
/// Ensures that the last parsed pattern (or pattern range bound) is not followed by an expression.
341341
///
342342
/// `is_end_bound` indicates whether the last parsed thing was the end bound of a range pattern (see [`parse_pat_range_end`](Self::parse_pat_range_end))
343343
/// in order to say "expected a pattern range bound" instead of "expected a pattern";
344344
/// ```text
345345
/// 0..=1 + 2
346346
/// ^^^^^
347347
/// ```
348-
/// Only the end bound is spanned, and this function have no idea if there were a `..=` before `pat_span`, hence the parameter.
348+
/// Only the end bound is spanned in this case, and this function have no idea if there were a `..=` before `pat_span`, hence the parameter.
349+
///
350+
/// This function returns `Some` if a trailing expression was recovered, and said expression's span.
349351
#[must_use = "the pattern must be discarded as `PatKind::Err` if this function returns Some"]
350352
fn maybe_recover_trailing_expr(
351353
&mut self,
352354
pat_span: Span,
353355
is_end_bound: bool,
354-
) -> Option<ErrorGuaranteed> {
356+
) -> Option<(ErrorGuaranteed, Span)> {
355357
if self.prev_token.is_keyword(kw::Underscore) || !self.may_recover() {
356358
// Don't recover anything after an `_` or if recovery is disabled.
357359
return None;
358360
}
359361

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)
362+
// Returns `true` iff `token` is a `x.y` float.
363+
let is_two_tuple_indexes = |that: &Self, token: &Token| -> bool {
364+
use token::{Lit, LitKind};
365+
366+
let token::Literal(Lit { kind: LitKind::Float, symbol, suffix: None }) = token.kind
367+
else {
368+
return false;
369+
};
370+
371+
matches!(that.break_up_float(symbol, token.span), DestructuredFloat::MiddleDot(..))
372+
};
373+
374+
// Check for `.hello` or `.0`.
375+
let has_dot_expr = self.check_noexpect(&token::Dot) // `.`
362376
&& 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));
377+
tok.is_ident() // `hello`
378+
|| tok.is_integer_lit() // `0`
379+
|| is_two_tuple_indexes(&self, &tok) // `0.0`
380+
});
368381

369382
// Check for operators.
370383
// `|` is excluded as it is used in pattern alternatives and lambdas,
371384
// `?` is included for error propagation,
372385
// `[` 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`)
386+
// `[]` is excluded as `a[]` isn't an expression and should be recovered as `a, []` (cf. `tests/ui/parser/pat-lt-bracket-7.rs`),
387+
// `as` is included for type casts
374388
let has_trailing_operator = matches!(self.token.kind, token::BinOp(op) if op != BinOpToken::Or)
375389
|| self.token.kind == token::Question
376390
|| (self.token.kind == token::OpenDelim(Delimiter::Bracket)
377-
&& self.look_ahead(1, |tok| tok.kind != token::CloseDelim(Delimiter::Bracket)));
391+
&& self.look_ahead(1, |tok| tok.kind != token::CloseDelim(Delimiter::Bracket))) // excludes `[]`
392+
|| self.token.is_keyword(kw::As);
378393

379-
if !has_trailing_method && !has_trailing_operator {
394+
if !has_dot_expr && !has_trailing_operator {
380395
// Nothing to recover here.
381396
return None;
382397
}
@@ -386,43 +401,40 @@ impl<'a> Parser<'a> {
386401
snapshot.restrictions.insert(Restrictions::IS_PAT);
387402

388403
// Parse `?`, `.f`, `(arg0, arg1, ...)` or `[expr]` until they've all been eaten.
389-
if let Ok(expr) = snapshot
404+
let Ok(expr) = snapshot
390405
.parse_expr_dot_or_call_with(
391406
self.mk_expr(pat_span, ExprKind::Dummy), // equivalent to transforming the parsed pattern into an `Expr`
392407
pat_span,
393408
AttrVec::new(),
394409
)
395410
.map_err(|err| err.cancel())
396-
{
397-
let non_assoc_span = expr.span;
398-
399-
// Parse an associative expression such as `+ expr`, `% expr`, ...
400-
// Assignements, ranges and `|` are disabled by [`Restrictions::IS_PAT`].
401-
let lhs = LhsExpr::Parsed { expr, starts_statement: false };
402-
if let Ok(expr) = snapshot.parse_expr_assoc_with(0, lhs).map_err(|err| err.cancel()) {
403-
// We got a valid expression.
404-
self.restore_snapshot(snapshot);
405-
self.restrictions.remove(Restrictions::IS_PAT);
406-
407-
let is_bound = is_end_bound
408-
// is_start_bound: either `..` or `)..`
409-
|| self.token.is_range_separator()
410-
|| self.token.kind == token::CloseDelim(Delimiter::Parenthesis)
411-
&& self.look_ahead(1, Token::is_range_separator);
412-
413-
// Check that `parse_expr_assoc_with` didn't eat a rhs.
414-
let is_method_call = has_trailing_method && non_assoc_span == expr.span;
415-
416-
return Some(self.dcx().emit_err(UnexpectedExpressionInPattern {
417-
span: expr.span,
418-
is_bound,
419-
is_method_call,
420-
}));
421-
}
422-
}
411+
else {
412+
// We got a trailing method/operator, but that wasn't an expression.
413+
return None;
414+
};
415+
416+
// Parse an associative expression such as `+ expr`, `% expr`, ...
417+
// Assignements, ranges and `|` are disabled by [`Restrictions::IS_PAT`].
418+
let lhs = LhsExpr::Parsed { expr, starts_statement: false };
419+
let Ok(expr) = snapshot.parse_expr_assoc_with(0, lhs).map_err(|err| err.cancel()) else {
420+
// We got a trailing method/operator, but that wasn't an expression.
421+
return None;
422+
};
423+
424+
// We got a valid expression.
425+
self.restore_snapshot(snapshot);
426+
self.restrictions.remove(Restrictions::IS_PAT);
427+
428+
let is_bound = is_end_bound
429+
// is_start_bound: either `..` or `)..`
430+
|| self.token.is_range_separator()
431+
|| self.token.kind == token::CloseDelim(Delimiter::Parenthesis)
432+
&& self.look_ahead(1, Token::is_range_separator);
423433

424-
// We got a trailing method/operator, but we couldn't parse an expression.
425-
None
434+
Some((
435+
self.dcx().emit_err(UnexpectedExpressionInPattern { span: expr.span, is_bound }),
436+
expr.span,
437+
))
426438
}
427439

428440
/// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
@@ -534,7 +546,7 @@ impl<'a> Parser<'a> {
534546
self.parse_pat_tuple_struct(qself, path)?
535547
} else {
536548
match self.maybe_recover_trailing_expr(span, false) {
537-
Some(guar) => PatKind::Err(guar),
549+
Some((guar, _)) => PatKind::Err(guar),
538550
None => PatKind::Path(qself, path),
539551
}
540552
}
@@ -567,10 +579,10 @@ impl<'a> Parser<'a> {
567579
// Try to parse everything else as literal with optional minus
568580
match self.parse_literal_maybe_minus() {
569581
Ok(begin) => {
570-
let begin = match self.maybe_recover_trailing_expr(begin.span, false) {
571-
Some(guar) => self.mk_expr_err(begin.span, guar),
572-
None => begin,
573-
};
582+
let begin = self
583+
.maybe_recover_trailing_expr(begin.span, false)
584+
.map(|(guar, sp)| self.mk_expr_err(sp, guar))
585+
.unwrap_or(begin);
574586

575587
match self.parse_range_end() {
576588
Some(form) => self.parse_pat_range_begin_with(begin, form)?,
@@ -700,7 +712,8 @@ impl<'a> Parser<'a> {
700712
// For backward compatibility, `(..)` is a tuple pattern as well.
701713
let paren_pattern =
702714
fields.len() == 1 && !(matches!(trailing_comma, Trailing::Yes) || fields[0].is_rest());
703-
if paren_pattern {
715+
716+
let pat = if paren_pattern {
704717
let pat = fields.into_iter().next().unwrap();
705718
let close_paren = self.prev_token.span;
706719

@@ -718,7 +731,7 @@ impl<'a> Parser<'a> {
718731
},
719732
});
720733

721-
self.parse_pat_range_begin_with(begin.clone(), form)
734+
self.parse_pat_range_begin_with(begin.clone(), form)?
722735
}
723736
// recover ranges with parentheses around the `(start)..`
724737
PatKind::Err(guar)
@@ -733,15 +746,20 @@ impl<'a> Parser<'a> {
733746
},
734747
});
735748

736-
self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)
749+
self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)?
737750
}
738751

739752
// (pat) with optional parentheses
740-
_ => Ok(PatKind::Paren(pat)),
753+
_ => PatKind::Paren(pat),
741754
}
742755
} else {
743-
Ok(PatKind::Tuple(fields))
744-
}
756+
PatKind::Tuple(fields)
757+
};
758+
759+
Ok(match self.maybe_recover_trailing_expr(open_paren.to(self.prev_token.span), false) {
760+
None => pat,
761+
Some((guar, _)) => PatKind::Err(guar),
762+
})
745763
}
746764

747765
/// Parse a mutable binding with the `mut` token already eaten.
@@ -991,7 +1009,7 @@ impl<'a> Parser<'a> {
9911009
}
9921010

9931011
Ok(match recovered {
994-
Some(guar) => self.mk_expr_err(bound.span, guar),
1012+
Some((guar, sp)) => self.mk_expr_err(sp, guar),
9951013
None => bound,
9961014
})
9971015
}
@@ -1060,7 +1078,7 @@ impl<'a> Parser<'a> {
10601078
// but not `ident @ subpat` as `subpat` was already checked and `ident` continues with `@`.
10611079

10621080
let pat = if sub.is_none()
1063-
&& let Some(guar) = self.maybe_recover_trailing_expr(ident.span, false)
1081+
&& let Some((guar, _)) = self.maybe_recover_trailing_expr(ident.span, false)
10641082
{
10651083
PatKind::Err(guar)
10661084
} else {

tests/ui/parser/bad-name.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
//@ error-pattern: expected
2-
31
fn main() {
42
let x.y::<isize>.z foo;
3+
//~^ error: field expressions cannot have generic arguments
4+
//~| error: expected a pattern, found an expression
5+
//~| error: expected one of `(`, `.`, `::`, `:`, `;`, `=`, `?`, `|`, or an operator, found `foo`
56
}

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:2: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:2: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:2: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

-28
This file was deleted.

0 commit comments

Comments
 (0)