Skip to content

Commit 4b7ce0e

Browse files
committed
Recover more expressions in patterns
1 parent e9d13db commit 4b7ce0e

20 files changed

+640
-287
lines changed

compiler/rustc_parse/messages.ftl

+2-8
Original file line numberDiff line numberDiff line change
@@ -787,15 +787,9 @@ parse_unexpected_expr_in_pat =
787787
expected {$is_bound ->
788788
[true] a pattern range bound
789789
*[false] a pattern
790-
}, found {$is_method_call ->
791-
[true] a method call
792-
*[false] an expression
793-
}
790+
}, found an expression
794791
795-
.label = {$is_method_call ->
796-
[true] method calls
797-
*[false] arbitrary expressions
798-
} are not allowed in patterns
792+
.label = arbitrary expressions are not allowed in patterns
799793
800794
parse_unexpected_if_with_if = unexpected `if` in the condition expression
801795
.suggestion = remove the `if`

compiler/rustc_parse/src/errors.rs

-2
Original file line numberDiff line numberDiff line change
@@ -2601,8 +2601,6 @@ pub(crate) struct UnexpectedExpressionInPattern {
26012601
pub span: Span,
26022602
/// Was a `RangePatternBound` expected?
26032603
pub is_bound: bool,
2604-
/// Was the unexpected expression a `MethodCallExpression`?
2605-
pub is_method_call: bool,
26062604
}
26072605

26082606
#[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.
@@ -1023,15 +1023,17 @@ impl<'a> Parser<'a> {
10231023
self.dcx().emit_err(errors::UnexpectedTokenAfterDot { span, actual })
10241024
}
10251025

1026-
// We need an identifier or integer, but the next token is a float.
1027-
// Break the float into components to extract the identifier or integer.
1026+
/// We need an identifier or integer, but the next token is a float.
1027+
/// Break the float into components to extract the identifier or integer.
1028+
///
1029+
/// See also [`TokenKind::break_two_token_op`] which does similar splitting of `>>` into `>`.
1030+
//
10281031
// FIXME: With current `TokenCursor` it's hard to break tokens into more than 2
10291032
// parts unless those parts are processed immediately. `TokenCursor` should either
10301033
// support pushing "future tokens" (would be also helpful to `break_and_eat`), or
10311034
// we should break everything including floats into more basic proc-macro style
10321035
// tokens in the lexer (probably preferable).
1033-
// See also `TokenKind::break_two_token_op` which does similar splitting of `>>` into `>`.
1034-
fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
1036+
pub(super) fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
10351037
#[derive(Debug)]
10361038
enum FloatComponent {
10371039
IdentLike(String),

compiler/rustc_parse/src/parser/pat.rs

+86-57
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::errors::{
1010
UnexpectedParenInRangePat, UnexpectedParenInRangePatSugg,
1111
UnexpectedVertVertBeforeFunctionParam, UnexpectedVertVertInPattern, WrapInParens,
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;
@@ -341,46 +341,72 @@ impl<'a> Parser<'a> {
341341
}
342342
}
343343

344-
/// Ensures that the last parsed pattern (or pattern range bound) is not followed by a method call or an operator.
344+
/// Ensures that the last parsed pattern (or pattern range bound) is not followed by an expression.
345345
///
346346
/// `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))
347347
/// in order to say "expected a pattern range bound" instead of "expected a pattern";
348348
/// ```text
349349
/// 0..=1 + 2
350350
/// ^^^^^
351351
/// ```
352-
/// Only the end bound is spanned, and this function have no idea if there were a `..=` before `pat_span`, hence the parameter.
352+
/// Only the end bound is spanned in this case, and this function has no idea if there was a `..=` before `pat_span`, hence the parameter.
353+
///
354+
/// This function returns `Some` if a trailing expression was recovered, and said expression's span.
353355
#[must_use = "the pattern must be discarded as `PatKind::Err` if this function returns Some"]
354356
fn maybe_recover_trailing_expr(
355357
&mut self,
356358
pat_span: Span,
357359
is_end_bound: bool,
358-
) -> Option<ErrorGuaranteed> {
360+
) -> Option<(ErrorGuaranteed, Span)> {
359361
if self.prev_token.is_keyword(kw::Underscore) || !self.may_recover() {
360362
// Don't recover anything after an `_` or if recovery is disabled.
361363
return None;
362364
}
363365

364-
// Check for `.hello()`, but allow `.Hello()` to be recovered as `, Hello()` in `parse_seq_to_before_tokens()`.
365-
let has_trailing_method = self.check_noexpect(&token::Dot)
366+
// Returns `true` iff `token` is an unsuffixed integer.
367+
let is_one_tuple_index = |_: &Self, token: &Token| -> bool {
368+
use token::{Lit, LitKind};
369+
370+
matches!(
371+
token.kind,
372+
token::Literal(Lit { kind: LitKind::Integer, symbol: _, suffix: None })
373+
)
374+
};
375+
376+
// Returns `true` iff `token` is an unsuffixed `x.y` float.
377+
let is_two_tuple_indexes = |this: &Self, token: &Token| -> bool {
378+
use token::{Lit, LitKind};
379+
380+
if let token::Literal(Lit { kind: LitKind::Float, symbol, suffix: None }) = token.kind
381+
&& let DestructuredFloat::MiddleDot(..) = this.break_up_float(symbol, token.span)
382+
{
383+
true
384+
} else {
385+
false
386+
}
387+
};
388+
389+
// Check for `.hello` or `.0`.
390+
let has_dot_expr = self.check_noexpect(&token::Dot) // `.`
366391
&& self.look_ahead(1, |tok| {
367-
tok.ident()
368-
.and_then(|(ident, _)| ident.name.as_str().chars().next())
369-
.is_some_and(char::is_lowercase)
370-
})
371-
&& self.look_ahead(2, |tok| tok.kind == token::OpenDelim(Delimiter::Parenthesis));
392+
tok.is_ident() // `hello`
393+
|| is_one_tuple_index(&self, &tok) // `0`
394+
|| is_two_tuple_indexes(&self, &tok) // `0.0`
395+
});
372396

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

383-
if !has_trailing_method && !has_trailing_operator {
409+
if !has_dot_expr && !has_trailing_operator {
384410
// Nothing to recover here.
385411
return None;
386412
}
@@ -390,43 +416,40 @@ impl<'a> Parser<'a> {
390416
snapshot.restrictions.insert(Restrictions::IS_PAT);
391417

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

428-
// We got a trailing method/operator, but we couldn't parse an expression.
429-
None
439+
// We got a valid expression.
440+
self.restore_snapshot(snapshot);
441+
self.restrictions.remove(Restrictions::IS_PAT);
442+
443+
let is_bound = is_end_bound
444+
// is_start_bound: either `..` or `)..`
445+
|| self.token.is_range_separator()
446+
|| self.token.kind == token::CloseDelim(Delimiter::Parenthesis)
447+
&& self.look_ahead(1, Token::is_range_separator);
448+
449+
Some((
450+
self.dcx().emit_err(UnexpectedExpressionInPattern { span: expr.span, is_bound }),
451+
expr.span,
452+
))
430453
}
431454

432455
/// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
@@ -538,7 +561,7 @@ impl<'a> Parser<'a> {
538561
self.parse_pat_tuple_struct(qself, path)?
539562
} else {
540563
match self.maybe_recover_trailing_expr(span, false) {
541-
Some(guar) => PatKind::Err(guar),
564+
Some((guar, _)) => PatKind::Err(guar),
542565
None => PatKind::Path(qself, path),
543566
}
544567
}
@@ -571,10 +594,10 @@ impl<'a> Parser<'a> {
571594
// Try to parse everything else as literal with optional minus
572595
match self.parse_literal_maybe_minus() {
573596
Ok(begin) => {
574-
let begin = match self.maybe_recover_trailing_expr(begin.span, false) {
575-
Some(guar) => self.mk_expr_err(begin.span, guar),
576-
None => begin,
577-
};
597+
let begin = self
598+
.maybe_recover_trailing_expr(begin.span, false)
599+
.map(|(guar, sp)| self.mk_expr_err(sp, guar))
600+
.unwrap_or(begin);
578601

579602
match self.parse_range_end() {
580603
Some(form) => self.parse_pat_range_begin_with(begin, form)?,
@@ -715,7 +738,8 @@ impl<'a> Parser<'a> {
715738
// For backward compatibility, `(..)` is a tuple pattern as well.
716739
let paren_pattern =
717740
fields.len() == 1 && !(matches!(trailing_comma, Trailing::Yes) || fields[0].is_rest());
718-
if paren_pattern {
741+
742+
let pat = if paren_pattern {
719743
let pat = fields.into_iter().next().unwrap();
720744
let close_paren = self.prev_token.span;
721745

@@ -733,7 +757,7 @@ impl<'a> Parser<'a> {
733757
},
734758
});
735759

736-
self.parse_pat_range_begin_with(begin.clone(), form)
760+
self.parse_pat_range_begin_with(begin.clone(), form)?
737761
}
738762
// recover ranges with parentheses around the `(start)..`
739763
PatKind::Err(guar)
@@ -748,15 +772,20 @@ impl<'a> Parser<'a> {
748772
},
749773
});
750774

751-
self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)
775+
self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)?
752776
}
753777

754778
// (pat) with optional parentheses
755-
_ => Ok(PatKind::Paren(pat)),
779+
_ => PatKind::Paren(pat),
756780
}
757781
} else {
758-
Ok(PatKind::Tuple(fields))
759-
}
782+
PatKind::Tuple(fields)
783+
};
784+
785+
Ok(match self.maybe_recover_trailing_expr(open_paren.to(self.prev_token.span), false) {
786+
None => pat,
787+
Some((guar, _)) => PatKind::Err(guar),
788+
})
760789
}
761790

762791
/// Parse a mutable binding with the `mut` token already eaten.
@@ -1009,7 +1038,7 @@ impl<'a> Parser<'a> {
10091038
}
10101039

10111040
Ok(match recovered {
1012-
Some(guar) => self.mk_expr_err(bound.span, guar),
1041+
Some((guar, sp)) => self.mk_expr_err(sp, guar),
10131042
None => bound,
10141043
})
10151044
}
@@ -1078,7 +1107,7 @@ impl<'a> Parser<'a> {
10781107
// but not `ident @ subpat` as `subpat` was already checked and `ident` continues with `@`.
10791108

10801109
let pat = if sub.is_none()
1081-
&& let Some(guar) = self.maybe_recover_trailing_expr(ident.span, false)
1110+
&& let Some((guar, _)) = self.maybe_recover_trailing_expr(ident.span, false)
10821111
{
10831112
PatKind::Err(guar)
10841113
} 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)