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,8 @@ 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 } ;
27+ use std:: borrow:: Cow ;
2728use thin_vec:: { thin_vec, ThinVec } ;
2829
2930#[ derive( PartialEq , Copy , Clone ) ]
@@ -336,6 +337,88 @@ impl<'a> Parser<'a> {
336337 }
337338 }
338339
340+ /// Ensures that the last parsed pattern is not followed by a method call or an operator.
341+ #[ must_use = "the pattern must be discarded as `PatKind::Err` if this function returns Some" ]
342+ fn maybe_recover_trailing_expr < ' b > (
343+ & ' b mut self ,
344+ pat : Cow < ' b , P < Expr > > ,
345+ is_end_bound : bool ,
346+ ) -> Option < ErrorGuaranteed > {
347+ if self . prev_token . is_keyword ( kw:: Underscore ) || !self . may_recover ( ) {
348+ // Don't recover anything after an `_` or if recovery is disabled.
349+ return None ;
350+ }
351+
352+ // Check for `.hello()`, but allow `.Hello()` to be recovered as `, Hello()` in `parse_seq_to_before_tokens()`.
353+ let has_trailing_method = self . check_noexpect ( & token:: Dot )
354+ && self . look_ahead ( 1 , |tok| {
355+ tok. ident ( )
356+ . and_then ( |( ident, _) | ident. name . to_string ( ) . chars ( ) . next ( ) )
357+ . is_some_and ( char:: is_lowercase)
358+ } )
359+ && self . look_ahead ( 2 , |tok| tok. kind == token:: OpenDelim ( Delimiter :: Parenthesis ) ) ;
360+
361+ // Check for operators.
362+ // `|` is excluded as it is used in pattern alternatives and lambdas,
363+ // `?` is included for error propagation,
364+ // `[` is included for indexing operations,
365+ // `[]` is excluded as `a[]` isn't an expression and should be recovered as `a, []` (cf. `tests/ui/parser/pat-lt-bracket-7.rs`)
366+ let has_trailing_operator = matches ! ( self . token. kind, token:: BinOp ( op) if op != BinOpToken :: Or )
367+ || self . token . kind == token:: Question
368+ || ( self . token . kind == token:: OpenDelim ( Delimiter :: Bracket )
369+ && self . look_ahead ( 1 , |tok| tok. kind != token:: CloseDelim ( Delimiter :: Bracket ) ) ) ;
370+
371+ if !has_trailing_method && !has_trailing_operator {
372+ // Nothing to recover here.
373+ return None ;
374+ }
375+
376+ // Let's try to parse an expression to emit a better diagnostic.
377+ let pat = pat. into_owned ( ) ;
378+ let pat_span = pat. span ;
379+
380+ let mut snapshot = self . create_snapshot_for_diagnostic ( ) ;
381+ snapshot. restrictions . insert ( Restrictions :: IS_PAT ) ;
382+
383+ // Parse `?`, `.f`, `(arg0, arg1, ...)` or `[expr]` until they've all been eaten.
384+ if let Ok ( expr) = snapshot
385+ . parse_expr_dot_or_call_with ( pat, pat_span, AttrVec :: new ( ) )
386+ . map_err ( |err| err. cancel ( ) )
387+ {
388+ let non_assoc_span = expr. span ;
389+
390+ // Parse an associative expression such as `+ expr`, `% expr`, ...
391+ // Assignements, ranges and `|` are disabled by [`Restrictions::IS_PAT`].
392+ if let Ok ( expr) =
393+ snapshot. parse_expr_assoc_with ( 0 , expr. into ( ) ) . map_err ( |err| err. cancel ( ) )
394+ {
395+ // We got a valid expression.
396+ self . restore_snapshot ( snapshot) ;
397+ self . restrictions . remove ( Restrictions :: IS_PAT ) ;
398+
399+ let span = expr. span ;
400+
401+ let is_bound = is_end_bound
402+ // is_start_bound: either `..` or `)..`
403+ || self . token . is_range_separator ( )
404+ || self . token . kind == token:: CloseDelim ( Delimiter :: Parenthesis )
405+ && self . look_ahead ( 1 , Token :: is_range_separator) ;
406+
407+ // Check that `parse_expr_assoc_with` didn't eat a rhs.
408+ let is_method_call = has_trailing_method && non_assoc_span == span;
409+
410+ return Some ( self . dcx ( ) . emit_err ( UnexpectedExpressionInPattern {
411+ span,
412+ is_bound,
413+ is_method_call,
414+ } ) ) ;
415+ }
416+ }
417+
418+ // We got a trailing method/operator, but we couldn't parse an expression.
419+ None
420+ }
421+
339422 /// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
340423 /// allowed).
341424 fn parse_pat_with_range_pat (
@@ -441,7 +524,11 @@ impl<'a> Parser<'a> {
441524 } else if self . check ( & token:: OpenDelim ( Delimiter :: Parenthesis ) ) {
442525 self . parse_pat_tuple_struct ( qself, path) ?
443526 } else {
444- PatKind :: Path ( qself, path)
527+ let pat = self . mk_expr ( span, ExprKind :: Path ( qself. clone ( ) , path. clone ( ) ) ) ;
528+ match self . maybe_recover_trailing_expr ( Cow :: Owned ( pat) , false ) {
529+ Some ( guar) => PatKind :: Err ( guar) ,
530+ None => PatKind :: Path ( qself, path) ,
531+ }
445532 }
446533 } else if matches ! ( self . token. kind, token:: Lifetime ( _) )
447534 // In pattern position, we're totally fine with using "next token isn't colon"
@@ -470,10 +557,18 @@ impl<'a> Parser<'a> {
470557 } else {
471558 // Try to parse everything else as literal with optional minus
472559 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- } ,
560+ Ok ( begin) => {
561+ let begin = match self . maybe_recover_trailing_expr ( Cow :: Borrowed ( & begin) , false )
562+ {
563+ Some ( _) => self . mk_expr_err ( begin. span ) ,
564+ None => begin,
565+ } ;
566+
567+ match self . parse_range_end ( ) {
568+ Some ( form) => self . parse_pat_range_begin_with ( begin, form) ?,
569+ None => PatKind :: Lit ( begin) ,
570+ }
571+ }
477572 Err ( err) => return self . fatal_unexpected_non_pat ( err, expected) ,
478573 }
479574 } ;
@@ -615,6 +710,21 @@ impl<'a> Parser<'a> {
615710
616711 self . parse_pat_range_begin_with ( begin. clone ( ) , form) ?
617712 }
713+ // recover ranges with parentheses around the `(start)..`
714+ PatKind :: Err ( _)
715+ if self . may_recover ( )
716+ && let Some ( form) = self . parse_range_end ( ) =>
717+ {
718+ self . dcx ( ) . emit_err ( UnexpectedParenInRangePat {
719+ span : vec ! [ open_paren, close_paren] ,
720+ sugg : UnexpectedParenInRangePatSugg {
721+ start_span : open_paren,
722+ end_span : close_paren,
723+ } ,
724+ } ) ;
725+
726+ self . parse_pat_range_begin_with ( self . mk_expr ( pat. span , ExprKind :: Err ) , form) ?
727+ }
618728
619729 // (pat) with optional parentheses
620730 _ => PatKind :: Paren ( pat) ,
@@ -853,6 +963,8 @@ impl<'a> Parser<'a> {
853963 self . parse_literal_maybe_minus ( )
854964 } ?;
855965
966+ let recovered = self . maybe_recover_trailing_expr ( Cow :: Borrowed ( & bound) , true ) ;
967+
856968 // recover trailing `)`
857969 if let Some ( open_paren) = open_paren {
858970 self . expect ( & token:: CloseDelim ( Delimiter :: Parenthesis ) ) ?;
@@ -866,7 +978,10 @@ impl<'a> Parser<'a> {
866978 } ) ;
867979 }
868980
869- Ok ( bound)
981+ Ok ( match recovered {
982+ Some ( _) => self . mk_expr_err ( bound. span ) ,
983+ None => bound,
984+ } )
870985 }
871986
872987 /// Is this the start of a pattern beginning with a path?
@@ -929,7 +1044,18 @@ impl<'a> Parser<'a> {
9291044 . create_err ( EnumPatternInsteadOfIdentifier { span : self . prev_token . span } ) ) ;
9301045 }
9311046
932- Ok ( PatKind :: Ident ( binding_annotation, ident, sub) )
1047+ // Check for method calls after the `ident`,
1048+ // but not `ident @ subpat` as `subpat` was already checked and `ident` continues with `@`.
1049+
1050+ let expr = self . mk_expr ( ident. span , ExprKind :: Path ( None , Path :: from_ident ( ident) ) ) ;
1051+ let pat = if sub. is_none ( )
1052+ && let Some ( guar) = self . maybe_recover_trailing_expr ( Cow :: Owned ( expr) , false )
1053+ {
1054+ PatKind :: Err ( guar)
1055+ } else {
1056+ PatKind :: Ident ( binding_annotation, ident, sub)
1057+ } ;
1058+ Ok ( pat)
9331059 }
9341060
9351061 /// Parse a struct ("record") pattern (e.g. `Foo { ... }` or `Foo::Bar { ... }`).
0 commit comments