Skip to content

Commit aa09e0f

Browse files
committed
Recover more expressions in patterns
1 parent f8b3046 commit aa09e0f

20 files changed

+638
-285
lines changed

compiler/rustc_parse/messages.ftl

+2-8
Original file line numberDiff line numberDiff line change
@@ -797,15 +797,9 @@ parse_unexpected_expr_in_pat =
797797
expected {$is_bound ->
798798
[true] a pattern range bound
799799
*[false] a pattern
800-
}, found {$is_method_call ->
801-
[true] a method call
802-
*[false] an expression
803-
}
800+
}, found an expression
804801
805-
.label = {$is_method_call ->
806-
[true] method calls
807-
*[false] arbitrary expressions
808-
} are not allowed in patterns
802+
.label = arbitrary expressions are not allowed in patterns
809803
810804
parse_unexpected_if_with_if = unexpected `if` in the condition expression
811805
.suggestion = remove the `if`

compiler/rustc_parse/src/errors.rs

-2
Original file line numberDiff line numberDiff line change
@@ -2597,8 +2597,6 @@ pub(crate) struct UnexpectedExpressionInPattern {
25972597
pub span: Span,
25982598
/// Was a `RangePatternBound` expected?
25992599
pub is_bound: bool,
2600-
/// Was the unexpected expression a `MethodCallExpression`?
2601-
pub is_method_call: bool,
26022600
}
26032601

26042602
#[derive(Diagnostic)]

compiler/rustc_parse/src/parser/expr.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use super::{
4141
use crate::{errors, maybe_recover_from_interpolated_ty_qpath};
4242

4343
#[derive(Debug)]
44-
enum DestructuredFloat {
44+
pub(super) enum DestructuredFloat {
4545
/// 1e2
4646
Single(Symbol, Span),
4747
/// 1.
@@ -1012,15 +1012,17 @@ impl<'a> Parser<'a> {
10121012
self.dcx().emit_err(errors::UnexpectedTokenAfterDot { span, actual })
10131013
}
10141014

1015-
// We need an identifier or integer, but the next token is a float.
1016-
// Break the float into components to extract the identifier or integer.
1015+
/// We need an identifier or integer, but the next token is a float.
1016+
/// Break the float into components to extract the identifier or integer.
1017+
///
1018+
/// See also [`TokenKind::break_two_token_op`] which does similar splitting of `>>` into `>`.
1019+
//
10171020
// FIXME: With current `TokenCursor` it's hard to break tokens into more than 2
10181021
// parts unless those parts are processed immediately. `TokenCursor` should either
10191022
// support pushing "future tokens" (would be also helpful to `break_and_eat`), or
10201023
// we should break everything including floats into more basic proc-macro style
10211024
// tokens in the lexer (probably preferable).
1022-
// See also `TokenKind::break_two_token_op` which does similar splitting of `>>` into `>`.
1023-
fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
1025+
pub(super) fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
10241026
#[derive(Debug)]
10251027
enum FloatComponent {
10261028
IdentLike(String),

compiler/rustc_parse/src/parser/pat.rs

+86-57
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use crate::errors::{
2525
UnexpectedParenInRangePat, UnexpectedParenInRangePatSugg,
2626
UnexpectedVertVertBeforeFunctionParam, UnexpectedVertVertInPattern, WrapInParens,
2727
};
28-
use crate::parser::expr::could_be_unclosed_char_literal;
28+
use crate::parser::expr::{could_be_unclosed_char_literal, DestructuredFloat};
2929
use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
3030

3131
#[derive(PartialEq, Copy, Clone)]
@@ -342,46 +342,72 @@ impl<'a> Parser<'a> {
342342
}
343343
}
344344

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

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

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

384-
if !has_trailing_method && !has_trailing_operator {
410+
if !has_dot_expr && !has_trailing_operator {
385411
// Nothing to recover here.
386412
return None;
387413
}
@@ -391,44 +417,41 @@ impl<'a> Parser<'a> {
391417
snapshot.restrictions.insert(Restrictions::IS_PAT);
392418

393419
// Parse `?`, `.f`, `(arg0, arg1, ...)` or `[expr]` until they've all been eaten.
394-
if let Ok(expr) = snapshot
420+
let Ok(expr) = snapshot
395421
.parse_expr_dot_or_call_with(
396422
AttrVec::new(),
397423
self.mk_expr(pat_span, ExprKind::Dummy), // equivalent to transforming the parsed pattern into an `Expr`
398424
pat_span,
399425
)
400426
.map_err(|err| err.cancel())
401-
{
402-
let non_assoc_span = expr.span;
427+
else {
428+
// We got a trailing method/operator, but that wasn't an expression.
429+
return None;
430+
};
403431

404-
// Parse an associative expression such as `+ expr`, `% expr`, ...
405-
// Assignements, ranges and `|` are disabled by [`Restrictions::IS_PAT`].
406-
if let Ok(expr) =
407-
snapshot.parse_expr_assoc_rest_with(0, false, expr).map_err(|err| err.cancel())
408-
{
409-
// We got a valid expression.
410-
self.restore_snapshot(snapshot);
411-
self.restrictions.remove(Restrictions::IS_PAT);
412-
413-
let is_bound = is_end_bound
414-
// is_start_bound: either `..` or `)..`
415-
|| self.token.is_range_separator()
416-
|| self.token.kind == token::CloseDelim(Delimiter::Parenthesis)
417-
&& self.look_ahead(1, Token::is_range_separator);
418-
419-
// Check that `parse_expr_assoc_with` didn't eat a rhs.
420-
let is_method_call = has_trailing_method && non_assoc_span == expr.span;
421-
422-
return Some(self.dcx().emit_err(UnexpectedExpressionInPattern {
423-
span: expr.span,
424-
is_bound,
425-
is_method_call,
426-
}));
427-
}
428-
}
432+
// Parse an associative expression such as `+ expr`, `% expr`, ...
433+
// Assignements, ranges and `|` are disabled by [`Restrictions::IS_PAT`].
434+
let Ok(expr) =
435+
snapshot.parse_expr_assoc_rest_with(0, false, expr).map_err(|err| err.cancel())
436+
else {
437+
// We got a trailing method/operator, but that wasn't an expression.
438+
return None;
439+
};
429440

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

434457
/// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
@@ -540,7 +563,7 @@ impl<'a> Parser<'a> {
540563
self.parse_pat_tuple_struct(qself, path)?
541564
} else {
542565
match self.maybe_recover_trailing_expr(span, false) {
543-
Some(guar) => PatKind::Err(guar),
566+
Some((guar, _)) => PatKind::Err(guar),
544567
None => PatKind::Path(qself, path),
545568
}
546569
}
@@ -573,10 +596,10 @@ impl<'a> Parser<'a> {
573596
// Try to parse everything else as literal with optional minus
574597
match self.parse_literal_maybe_minus() {
575598
Ok(begin) => {
576-
let begin = match self.maybe_recover_trailing_expr(begin.span, false) {
577-
Some(guar) => self.mk_expr_err(begin.span, guar),
578-
None => begin,
579-
};
599+
let begin = self
600+
.maybe_recover_trailing_expr(begin.span, false)
601+
.map(|(guar, sp)| self.mk_expr_err(sp, guar))
602+
.unwrap_or(begin);
580603

581604
match self.parse_range_end() {
582605
Some(form) => self.parse_pat_range_begin_with(begin, form)?,
@@ -717,7 +740,8 @@ impl<'a> Parser<'a> {
717740
// For backward compatibility, `(..)` is a tuple pattern as well.
718741
let paren_pattern =
719742
fields.len() == 1 && !(matches!(trailing_comma, Trailing::Yes) || fields[0].is_rest());
720-
if paren_pattern {
743+
744+
let pat = if paren_pattern {
721745
let pat = fields.into_iter().next().unwrap();
722746
let close_paren = self.prev_token.span;
723747

@@ -735,7 +759,7 @@ impl<'a> Parser<'a> {
735759
},
736760
});
737761

738-
self.parse_pat_range_begin_with(begin.clone(), form)
762+
self.parse_pat_range_begin_with(begin.clone(), form)?
739763
}
740764
// recover ranges with parentheses around the `(start)..`
741765
PatKind::Err(guar)
@@ -750,15 +774,20 @@ impl<'a> Parser<'a> {
750774
},
751775
});
752776

753-
self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)
777+
self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)?
754778
}
755779

756780
// (pat) with optional parentheses
757-
_ => Ok(PatKind::Paren(pat)),
781+
_ => PatKind::Paren(pat),
758782
}
759783
} else {
760-
Ok(PatKind::Tuple(fields))
761-
}
784+
PatKind::Tuple(fields)
785+
};
786+
787+
Ok(match self.maybe_recover_trailing_expr(open_paren.to(self.prev_token.span), false) {
788+
None => pat,
789+
Some((guar, _)) => PatKind::Err(guar),
790+
})
762791
}
763792

764793
/// Parse a mutable binding with the `mut` token already eaten.
@@ -1011,7 +1040,7 @@ impl<'a> Parser<'a> {
10111040
}
10121041

10131042
Ok(match recovered {
1014-
Some(guar) => self.mk_expr_err(bound.span, guar),
1043+
Some((guar, sp)) => self.mk_expr_err(sp, guar),
10151044
None => bound,
10161045
})
10171046
}
@@ -1080,7 +1109,7 @@ impl<'a> Parser<'a> {
10801109
// but not `ident @ subpat` as `subpat` was already checked and `ident` continues with `@`.
10811110

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