1
- use super::{ForceCollect, Parser, PathStyle, TrailingToken};
1
+ use super::{ForceCollect, Parser, PathStyle, Restrictions, TrailingToken};
2
2
use crate::errors::{
3
3
self, AmbiguousRangePattern, DotDotDotForRemainingFields, DotDotDotRangeToPatternNotAllowed,
4
4
DotDotDotRestPattern, EnumPatternInsteadOfIdentifier, ExpectedBindingLeftOfAt,
5
5
ExpectedCommaAfterPatternField, GenericArgsInPatRequireTurbofishSyntax,
6
6
InclusiveRangeExtraEquals, InclusiveRangeMatchArrow, InclusiveRangeNoEnd, InvalidMutInPattern,
7
7
PatternOnWrongSideOfAt, RefMutOrderIncorrect, RemoveLet, RepeatedMutInPattern,
8
8
SwitchRefBoxOrder, TopLevelOrPatternNotAllowed, TopLevelOrPatternNotAllowedSugg,
9
- TrailingVertNotAllowed, UnexpectedLifetimeInPattern, UnexpectedParenInRangePat ,
10
- UnexpectedParenInRangePatSugg, UnexpectedVertVertBeforeFunctionParam ,
11
- UnexpectedVertVertInPattern,
9
+ TrailingVertNotAllowed, UnexpectedExpressionInPattern, UnexpectedLifetimeInPattern ,
10
+ UnexpectedParenInRangePat, UnexpectedParenInRangePatSugg ,
11
+ UnexpectedVertVertBeforeFunctionParam, UnexpectedVertVertInPattern,
12
12
};
13
13
use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
14
14
use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor};
15
15
use rustc_ast::ptr::P;
16
- use rustc_ast::token::{self, Delimiter};
16
+ use rustc_ast::token::{self, BinOpToken, Delimiter, Token };
17
17
use rustc_ast::{
18
18
self as ast, AttrVec, BindingAnnotation, ByRef, Expr, ExprKind, MacCall, Mutability, Pat,
19
19
PatField, PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax,
@@ -23,7 +23,7 @@ use rustc_errors::{Applicability, DiagnosticBuilder, PResult};
23
23
use rustc_session::errors::ExprParenthesesNeeded;
24
24
use rustc_span::source_map::{respan, Spanned};
25
25
use rustc_span::symbol::{kw, sym, Ident};
26
- use rustc_span::Span;
26
+ use rustc_span::{ErrorGuaranteed, Span} ;
27
27
use thin_vec::{thin_vec, ThinVec};
28
28
29
29
#[derive(PartialEq, Copy, Clone)]
@@ -336,6 +336,95 @@ impl<'a> Parser<'a> {
336
336
}
337
337
}
338
338
339
+ /// Ensures that the last parsed pattern (or pattern range bound) is not followed by a method call or an operator.
340
+ ///
341
+ /// `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))
342
+ /// in order to say "expected a pattern range bound" instead of "expected a pattern";
343
+ /// ```text
344
+ /// 0..=1 + 2
345
+ /// ^^^^^
346
+ /// ```
347
+ /// Only the end bound is spanned, and this function have no idea if there were a `..=` before `pat_span`, hence the parameter.
348
+ #[must_use = "the pattern must be discarded as `PatKind::Err` if this function returns Some"]
349
+ fn maybe_recover_trailing_expr(
350
+ &mut self,
351
+ pat_span: Span,
352
+ is_end_bound: bool,
353
+ ) -> Option<ErrorGuaranteed> {
354
+ if self.prev_token.is_keyword(kw::Underscore) || !self.may_recover() {
355
+ // Don't recover anything after an `_` or if recovery is disabled.
356
+ return None;
357
+ }
358
+
359
+ // Check for `.hello()`, but allow `.Hello()` to be recovered as `, Hello()` in `parse_seq_to_before_tokens()`.
360
+ let has_trailing_method = self.check_noexpect(&token::Dot)
361
+ && self.look_ahead(1, |tok| {
362
+ tok.ident()
363
+ .and_then(|(ident, _)| ident.name.as_str().chars().next())
364
+ .is_some_and(char::is_lowercase)
365
+ })
366
+ && self.look_ahead(2, |tok| tok.kind == token::OpenDelim(Delimiter::Parenthesis));
367
+
368
+ // Check for operators.
369
+ // `|` is excluded as it is used in pattern alternatives and lambdas,
370
+ // `?` is included for error propagation,
371
+ // `[` is included for indexing operations,
372
+ // `[]` is excluded as `a[]` isn't an expression and should be recovered as `a, []` (cf. `tests/ui/parser/pat-lt-bracket-7.rs`)
373
+ let has_trailing_operator = matches!(self.token.kind, token::BinOp(op) if op != BinOpToken::Or)
374
+ || self.token.kind == token::Question
375
+ || (self.token.kind == token::OpenDelim(Delimiter::Bracket)
376
+ && self.look_ahead(1, |tok| tok.kind != token::CloseDelim(Delimiter::Bracket)));
377
+
378
+ if !has_trailing_method && !has_trailing_operator {
379
+ // Nothing to recover here.
380
+ return None;
381
+ }
382
+
383
+ // Let's try to parse an expression to emit a better diagnostic.
384
+ let mut snapshot = self.create_snapshot_for_diagnostic();
385
+ snapshot.restrictions.insert(Restrictions::IS_PAT);
386
+
387
+ // Parse `?`, `.f`, `(arg0, arg1, ...)` or `[expr]` until they've all been eaten.
388
+ if let Ok(expr) = snapshot
389
+ .parse_expr_dot_or_call_with(
390
+ self.mk_expr_err(pat_span), // equivalent to transforming the parsed pattern into an `Expr`
391
+ pat_span,
392
+ AttrVec::new(),
393
+ )
394
+ .map_err(|err| err.cancel())
395
+ {
396
+ let non_assoc_span = expr.span;
397
+
398
+ // Parse an associative expression such as `+ expr`, `% expr`, ...
399
+ // Assignements, ranges and `|` are disabled by [`Restrictions::IS_PAT`].
400
+ if let Ok(expr) =
401
+ snapshot.parse_expr_assoc_with(0, expr.into()).map_err(|err| err.cancel())
402
+ {
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
+ }
423
+
424
+ // We got a trailing method/operator, but we couldn't parse an expression.
425
+ None
426
+ }
427
+
339
428
/// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
340
429
/// allowed).
341
430
fn parse_pat_with_range_pat(
@@ -441,7 +530,10 @@ impl<'a> Parser<'a> {
441
530
} else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {
442
531
self.parse_pat_tuple_struct(qself, path)?
443
532
} else {
444
- PatKind::Path(qself, path)
533
+ match self.maybe_recover_trailing_expr(span, false) {
534
+ Some(guar) => PatKind::Err(guar),
535
+ None => PatKind::Path(qself, path),
536
+ }
445
537
}
446
538
} else if matches!(self.token.kind, token::Lifetime(_))
447
539
// In pattern position, we're totally fine with using "next token isn't colon"
@@ -470,10 +562,17 @@ impl<'a> Parser<'a> {
470
562
} else {
471
563
// Try to parse everything else as literal with optional minus
472
564
match self.parse_literal_maybe_minus() {
473
- Ok(begin) => match self.parse_range_end() {
474
- Some(form) => self.parse_pat_range_begin_with(begin, form)?,
475
- None => PatKind::Lit(begin),
476
- },
565
+ Ok(begin) => {
566
+ let begin = match self.maybe_recover_trailing_expr(begin.span, false) {
567
+ Some(_) => self.mk_expr_err(begin.span),
568
+ None => begin,
569
+ };
570
+
571
+ match self.parse_range_end() {
572
+ Some(form) => self.parse_pat_range_begin_with(begin, form)?,
573
+ None => PatKind::Lit(begin),
574
+ }
575
+ }
477
576
Err(err) => return self.fatal_unexpected_non_pat(err, expected),
478
577
}
479
578
};
@@ -615,6 +714,21 @@ impl<'a> Parser<'a> {
615
714
616
715
self.parse_pat_range_begin_with(begin.clone(), form)?
617
716
}
717
+ // recover ranges with parentheses around the `(start)..`
718
+ PatKind::Err(_)
719
+ if self.may_recover()
720
+ && let Some(form) = self.parse_range_end() =>
721
+ {
722
+ self.dcx().emit_err(UnexpectedParenInRangePat {
723
+ span: vec![open_paren, close_paren],
724
+ sugg: UnexpectedParenInRangePatSugg {
725
+ start_span: open_paren,
726
+ end_span: close_paren,
727
+ },
728
+ });
729
+
730
+ self.parse_pat_range_begin_with(self.mk_expr(pat.span, ExprKind::Err), form)?
731
+ }
618
732
619
733
// (pat) with optional parentheses
620
734
_ => PatKind::Paren(pat),
@@ -853,6 +967,8 @@ impl<'a> Parser<'a> {
853
967
self.parse_literal_maybe_minus()
854
968
}?;
855
969
970
+ let recovered = self.maybe_recover_trailing_expr(bound.span, true);
971
+
856
972
// recover trailing `)`
857
973
if let Some(open_paren) = open_paren {
858
974
self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
@@ -866,7 +982,10 @@ impl<'a> Parser<'a> {
866
982
});
867
983
}
868
984
869
- Ok(bound)
985
+ Ok(match recovered {
986
+ Some(_) => self.mk_expr_err(bound.span),
987
+ None => bound,
988
+ })
870
989
}
871
990
872
991
/// Is this the start of a pattern beginning with a path?
@@ -929,7 +1048,17 @@ impl<'a> Parser<'a> {
929
1048
.create_err(EnumPatternInsteadOfIdentifier { span: self.prev_token.span }));
930
1049
}
931
1050
932
- Ok(PatKind::Ident(binding_annotation, ident, sub))
1051
+ // Check for method calls after the `ident`,
1052
+ // but not `ident @ subpat` as `subpat` was already checked and `ident` continues with `@`.
1053
+
1054
+ let pat = if sub.is_none()
1055
+ && let Some(guar) = self.maybe_recover_trailing_expr(ident.span, false)
1056
+ {
1057
+ PatKind::Err(guar)
1058
+ } else {
1059
+ PatKind::Ident(binding_annotation, ident, sub)
1060
+ };
1061
+ Ok(pat)
933
1062
}
934
1063
935
1064
/// Parse a struct ("record") pattern (e.g. `Foo { ... }` or `Foo::Bar { ... }`).
0 commit comments