1- use rustc_ast:: mut_visit:: { walk_pat , MutVisitor } ;
1+ use rustc_ast:: mut_visit:: { self , MutVisitor } ;
22use rustc_ast:: ptr:: P ;
33use rustc_ast:: token:: { self , BinOpToken , Delimiter , IdentIsRaw , Token } ;
4+ use rustc_ast:: visit:: { self , Visitor } ;
45use rustc_ast:: {
5- self as ast, AttrVec , BindingMode , ByRef , Expr , ExprKind , MacCall , Mutability , Pat , PatField ,
6- PatFieldsRest , PatKind , Path , QSelf , RangeEnd , RangeSyntax ,
6+ self as ast, Arm , AttrVec , BinOpKind , BindingMode , ByRef , Expr , ExprKind , ExprPrecedence ,
7+ LocalKind , MacCall , Mutability , Pat , PatField , PatFieldsRest , PatKind , Path , QSelf , RangeEnd ,
8+ RangeSyntax , Stmt , StmtKind ,
79} ;
810use rustc_ast_pretty:: pprust;
9- use rustc_errors:: { Applicability , Diag , PResult } ;
11+ use rustc_errors:: { Applicability , Diag , DiagArgValue , PResult , StashKey } ;
1012use rustc_session:: errors:: ExprParenthesesNeeded ;
1113use rustc_span:: source_map:: { respan, Spanned } ;
1214use rustc_span:: symbol:: { kw, sym, Ident } ;
@@ -21,8 +23,8 @@ use crate::errors::{
2123 InclusiveRangeExtraEquals , InclusiveRangeMatchArrow , InclusiveRangeNoEnd , InvalidMutInPattern ,
2224 ParenRangeSuggestion , PatternOnWrongSideOfAt , RemoveLet , RepeatedMutInPattern ,
2325 SwitchRefBoxOrder , TopLevelOrPatternNotAllowed , TopLevelOrPatternNotAllowedSugg ,
24- TrailingVertNotAllowed , UnexpectedExpressionInPattern , UnexpectedLifetimeInPattern ,
25- UnexpectedParenInRangePat , UnexpectedParenInRangePatSugg ,
26+ TrailingVertNotAllowed , UnexpectedExpressionInPattern , UnexpectedExpressionInPatternSugg ,
27+ UnexpectedLifetimeInPattern , UnexpectedParenInRangePat , UnexpectedParenInRangePatSugg ,
2628 UnexpectedVertVertBeforeFunctionParam , UnexpectedVertVertInPattern , WrapInParens ,
2729} ;
2830use crate :: parser:: expr:: { could_be_unclosed_char_literal, DestructuredFloat } ;
@@ -448,12 +450,219 @@ impl<'a> Parser<'a> {
448450 || self . token == token:: CloseDelim ( Delimiter :: Parenthesis )
449451 && self . look_ahead ( 1 , Token :: is_range_separator) ;
450452
453+ let span = expr. span ;
454+
451455 Some ( (
452- self . dcx ( ) . emit_err ( UnexpectedExpressionInPattern { span : expr. span , is_bound } ) ,
453- expr. span ,
456+ self . dcx ( )
457+ . create_err ( UnexpectedExpressionInPattern {
458+ span,
459+ is_bound,
460+ expr_precedence : expr. precedence ( ) . order ( ) ,
461+ } )
462+ . stash ( span, StashKey :: ExprInPat )
463+ . unwrap ( ) ,
464+ span,
454465 ) )
455466 }
456467
468+ /// Called by [`Parser::parse_stmt_without_recovery`], used to add statement-aware subdiagnostics to the errors stashed
469+ /// by [`Parser::maybe_recover_trailing_expr`].
470+ pub ( super ) fn maybe_augment_stashed_expr_in_pats_with_suggestions ( & mut self , stmt : & Stmt ) {
471+ if self . dcx ( ) . has_errors ( ) . is_none ( ) {
472+ // No need to walk the statement if there's no stashed errors.
473+ return ;
474+ }
475+
476+ struct PatVisitor < ' a > {
477+ /// `self`
478+ parser : & ' a Parser < ' a > ,
479+ /// The freshly-parsed statement.
480+ stmt : & ' a Stmt ,
481+ /// The current match arm (for arm guard suggestions).
482+ arm : Option < & ' a Arm > ,
483+ /// The current struct field (for variable name suggestions).
484+ field : Option < & ' a PatField > ,
485+ }
486+
487+ impl < ' a > PatVisitor < ' a > {
488+ /// Looks for stashed [`StashKey::ExprInPat`] errors in `stash_span`, and emit them with suggestions.
489+ /// `stash_span` is contained in `expr_span`, the latter being larger in borrow patterns;
490+ /// ```txt
491+ /// &mut x.y
492+ /// -----^^^ `stash_span`
493+ /// |
494+ /// `expr_span`
495+ /// ```
496+ /// `is_range_bound` is used to exclude arm guard suggestions in range pattern bounds.
497+ fn maybe_add_suggestions_then_emit (
498+ & self ,
499+ stash_span : Span ,
500+ expr_span : Span ,
501+ is_range_bound : bool ,
502+ ) {
503+ self . parser . dcx ( ) . try_steal_modify_and_emit_err (
504+ stash_span,
505+ StashKey :: ExprInPat ,
506+ |err| {
507+ // Includes pre-pats (e.g. `&mut <err>`) in the diagnostic.
508+ err. span . replace ( stash_span, expr_span) ;
509+
510+ let sm = self . parser . psess . source_map ( ) ;
511+ let stmt = self . stmt ;
512+ let line_lo = sm. span_extend_to_line ( stmt. span ) . shrink_to_lo ( ) ;
513+ let indentation = sm. indentation_before ( stmt. span ) . unwrap_or_default ( ) ;
514+ let Ok ( expr) = self . parser . span_to_snippet ( expr_span) else {
515+ // FIXME: some suggestions don't actually need the snippet; see PR #123877's unresolved conversations.
516+ return ;
517+ } ;
518+
519+ if let StmtKind :: Let ( local) = & stmt. kind {
520+ match & local. kind {
521+ LocalKind :: Decl | LocalKind :: Init ( _) => {
522+ // It's kinda hard to guess what the user intended, so don't make suggestions.
523+ return ;
524+ }
525+
526+ LocalKind :: InitElse ( _, _) => { }
527+ }
528+ }
529+
530+ // help: use an arm guard `if val == expr`
531+ // FIXME(guard_patterns): suggest this regardless of a match arm.
532+ if let Some ( arm) = & self . arm
533+ && !is_range_bound
534+ {
535+ let ( ident, ident_span) = match self . field {
536+ Some ( field) => {
537+ ( field. ident . to_string ( ) , field. ident . span . to ( expr_span) )
538+ }
539+ None => ( "val" . to_owned ( ) , expr_span) ,
540+ } ;
541+
542+ // Are parentheses required around `expr`?
543+ // HACK: a neater way would be preferable.
544+ let expr = match & err. args [ "expr_precedence" ] {
545+ DiagArgValue :: Number ( expr_precedence) => {
546+ if * expr_precedence
547+ <= ExprPrecedence :: Binary ( BinOpKind :: Eq ) . order ( ) as i32
548+ {
549+ format ! ( "({expr})" )
550+ } else {
551+ format ! ( "{expr}" )
552+ }
553+ }
554+ _ => unreachable ! ( ) ,
555+ } ;
556+
557+ match & arm. guard {
558+ None => {
559+ err. subdiagnostic (
560+ UnexpectedExpressionInPatternSugg :: CreateGuard {
561+ ident_span,
562+ pat_hi : arm. pat . span . shrink_to_hi ( ) ,
563+ ident,
564+ expr,
565+ } ,
566+ ) ;
567+ }
568+ Some ( guard) => {
569+ // Are parentheses required around the old guard?
570+ let wrap_guard = guard. precedence ( ) . order ( )
571+ <= ExprPrecedence :: Binary ( BinOpKind :: And ) . order ( ) ;
572+
573+ err. subdiagnostic (
574+ UnexpectedExpressionInPatternSugg :: UpdateGuard {
575+ ident_span,
576+ guard_lo : if wrap_guard {
577+ Some ( guard. span . shrink_to_lo ( ) )
578+ } else {
579+ None
580+ } ,
581+ guard_hi : guard. span . shrink_to_hi ( ) ,
582+ guard_hi_paren : if wrap_guard { ")" } else { "" } ,
583+ ident,
584+ expr,
585+ } ,
586+ ) ;
587+ }
588+ }
589+ }
590+
591+ // help: extract the expr into a `const VAL: _ = expr`
592+ let ident = match self . field {
593+ Some ( field) => field. ident . as_str ( ) . to_uppercase ( ) ,
594+ None => "VAL" . to_owned ( ) ,
595+ } ;
596+ err. subdiagnostic ( UnexpectedExpressionInPatternSugg :: Const {
597+ stmt_lo : line_lo,
598+ ident_span : expr_span,
599+ expr,
600+ ident,
601+ indentation,
602+ } ) ;
603+
604+ // help: wrap the expr in a `const { expr }`
605+ // FIXME(inline_const_pat): once stabilized, remove this check and remove the `(requires #[feature(inline_const_pat)])` note from the message
606+ if self . parser . psess . unstable_features . is_nightly_build ( ) {
607+ err. subdiagnostic ( UnexpectedExpressionInPatternSugg :: InlineConst {
608+ start_span : expr_span. shrink_to_lo ( ) ,
609+ end_span : expr_span. shrink_to_hi ( ) ,
610+ } ) ;
611+ }
612+ } ,
613+ ) ;
614+ }
615+ }
616+
617+ impl < ' a > Visitor < ' a > for PatVisitor < ' a > {
618+ fn visit_arm ( & mut self , a : & ' a Arm ) -> Self :: Result {
619+ self . arm = Some ( a) ;
620+ visit:: walk_arm ( self , a) ;
621+ self . arm = None ;
622+ }
623+
624+ fn visit_pat_field ( & mut self , fp : & ' a PatField ) -> Self :: Result {
625+ self . field = Some ( fp) ;
626+ visit:: walk_pat_field ( self , fp) ;
627+ self . field = None ;
628+ }
629+
630+ fn visit_pat ( & mut self , p : & ' a Pat ) -> Self :: Result {
631+ match & p. kind {
632+ // Base expression
633+ PatKind :: Err ( _) | PatKind :: Lit ( _) => {
634+ self . maybe_add_suggestions_then_emit ( p. span , p. span , false )
635+ }
636+
637+ // Sub-patterns
638+ // FIXME: this doesn't work with recursive subpats (`&mut &mut <err>`)
639+ PatKind :: Box ( subpat) | PatKind :: Ref ( subpat, _)
640+ if matches ! ( subpat. kind, PatKind :: Err ( _) | PatKind :: Lit ( _) ) =>
641+ {
642+ self . maybe_add_suggestions_then_emit ( subpat. span , p. span , false )
643+ }
644+
645+ // Sub-expressions
646+ PatKind :: Range ( start, end, _) => {
647+ if let Some ( start) = start {
648+ self . maybe_add_suggestions_then_emit ( start. span , start. span , true ) ;
649+ }
650+
651+ if let Some ( end) = end {
652+ self . maybe_add_suggestions_then_emit ( end. span , end. span , true ) ;
653+ }
654+ }
655+
656+ // Walk continuation
657+ _ => visit:: walk_pat ( self , p) ,
658+ }
659+ }
660+ }
661+
662+ // Starts the visit.
663+ PatVisitor { parser : self , stmt, arm : None , field : None } . visit_stmt ( stmt) ;
664+ }
665+
457666 /// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
458667 /// allowed).
459668 fn parse_pat_with_range_pat (
@@ -845,7 +1054,7 @@ impl<'a> Parser<'a> {
8451054 self . 0 = true ;
8461055 * m = Mutability :: Mut ;
8471056 }
848- walk_pat ( self , pat) ;
1057+ mut_visit :: walk_pat ( self , pat) ;
8491058 }
8501059 }
8511060
0 commit comments