1- use super :: { ForceCollect , Parser , PathStyle , TrailingToken } ;
1+ use super :: { ForceCollect , Parser , PathStyle , Restrictions , TrailingToken } ;
22use crate :: errors:: {
33 self , AmbiguousRangePattern , DotDotDotForRemainingFields , DotDotDotRangeToPatternNotAllowed ,
44 DotDotDotRestPattern , EnumPatternInsteadOfIdentifier , ExpectedBindingLeftOfAt ,
55 ExpectedCommaAfterPatternField , GenericArgsInPatRequireTurbofishSyntax ,
66 InclusiveRangeExtraEquals , InclusiveRangeMatchArrow , InclusiveRangeNoEnd , InvalidMutInPattern ,
77 PatternOnWrongSideOfAt , RefMutOrderIncorrect , RemoveLet , RepeatedMutInPattern ,
88 SwitchRefBoxOrder , TopLevelOrPatternNotAllowed , TopLevelOrPatternNotAllowedSugg ,
9- TrailingVertNotAllowed , UnexpectedLifetimeInPattern , UnexpectedParenInRangePat ,
10- UnexpectedParenInRangePatSugg , UnexpectedVertVertBeforeFunctionParam ,
11- UnexpectedVertVertInPattern ,
9+ TrailingVertNotAllowed , UnexpectedExpressionInPattern , UnexpectedLifetimeInPattern ,
10+ UnexpectedParenInRangePat , UnexpectedParenInRangePatSugg ,
11+ UnexpectedVertVertBeforeFunctionParam , UnexpectedVertVertInPattern ,
1212} ;
1313use crate :: { maybe_recover_from_interpolated_ty_qpath, maybe_whole} ;
1414use rustc_ast:: mut_visit:: { noop_visit_pat, MutVisitor } ;
1515use rustc_ast:: ptr:: P ;
16- use rustc_ast:: token:: { self , Delimiter } ;
16+ use rustc_ast:: token:: { self , BinOpToken , Delimiter , Token } ;
1717use rustc_ast:: {
1818 self as ast, AttrVec , BindingAnnotation , ByRef , Expr , ExprKind , MacCall , Mutability , Pat ,
1919 PatField , PatFieldsRest , PatKind , Path , QSelf , RangeEnd , RangeSyntax ,
@@ -23,7 +23,7 @@ use rustc_errors::{Applicability, DiagnosticBuilder, PResult};
2323use rustc_session:: errors:: ExprParenthesesNeeded ;
2424use rustc_span:: source_map:: { respan, Spanned } ;
2525use rustc_span:: symbol:: { kw, sym, Ident } ;
26- use rustc_span:: Span ;
26+ use rustc_span:: { ErrorGuaranteed , Span } ;
2727use thin_vec:: { thin_vec, ThinVec } ;
2828
2929#[ derive( PartialEq , Copy , Clone ) ]
@@ -336,6 +336,95 @@ impl<'a> Parser<'a> {
336336 }
337337 }
338338
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+
339428 /// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
340429 /// allowed).
341430 fn parse_pat_with_range_pat (
@@ -441,7 +530,10 @@ impl<'a> Parser<'a> {
441530 } else if self . check ( & token:: OpenDelim ( Delimiter :: Parenthesis ) ) {
442531 self . parse_pat_tuple_struct ( qself, path) ?
443532 } 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+ }
445537 }
446538 } else if matches ! ( self . token. kind, token:: Lifetime ( _) )
447539 // In pattern position, we're totally fine with using "next token isn't colon"
@@ -470,10 +562,17 @@ impl<'a> Parser<'a> {
470562 } else {
471563 // Try to parse everything else as literal with optional minus
472564 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+ }
477576 Err ( err) => return self . fatal_unexpected_non_pat ( err, expected) ,
478577 }
479578 } ;
@@ -615,6 +714,21 @@ impl<'a> Parser<'a> {
615714
616715 self . parse_pat_range_begin_with ( begin. clone ( ) , form) ?
617716 }
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+ }
618732
619733 // (pat) with optional parentheses
620734 _ => PatKind :: Paren ( pat) ,
@@ -853,6 +967,8 @@ impl<'a> Parser<'a> {
853967 self . parse_literal_maybe_minus ( )
854968 } ?;
855969
970+ let recovered = self . maybe_recover_trailing_expr ( bound. span , true ) ;
971+
856972 // recover trailing `)`
857973 if let Some ( open_paren) = open_paren {
858974 self . expect ( & token:: CloseDelim ( Delimiter :: Parenthesis ) ) ?;
@@ -866,7 +982,10 @@ impl<'a> Parser<'a> {
866982 } ) ;
867983 }
868984
869- Ok ( bound)
985+ Ok ( match recovered {
986+ Some ( _) => self . mk_expr_err ( bound. span ) ,
987+ None => bound,
988+ } )
870989 }
871990
872991 /// Is this the start of a pattern beginning with a path?
@@ -929,7 +1048,17 @@ impl<'a> Parser<'a> {
9291048 . create_err ( EnumPatternInsteadOfIdentifier { span : self . prev_token . span } ) ) ;
9301049 }
9311050
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)
9331062 }
9341063
9351064 /// Parse a struct ("record") pattern (e.g. `Foo { ... }` or `Foo::Bar { ... }`).
0 commit comments