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 , 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,220 @@ 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 ( ) . stash_err (
457+ span,
458+ StashKey :: ExprInPat ,
459+ UnexpectedExpressionInPattern {
460+ span,
461+ is_bound,
462+ expr_precedence : expr. precedence ( ) . order ( ) ,
463+ } ,
464+ ) ,
465+ span,
454466 ) )
455467 }
456468
469+ /// Called by [`Parser::parse_stmt_without_recovery`], used to add statement-aware subdiagnostics to the errors stashed
470+ /// by [`Parser::maybe_recover_trailing_expr`].
471+ pub ( super ) fn maybe_augment_stashed_expr_in_pats_with_suggestions ( & mut self , stmt : & Stmt ) {
472+ if self . dcx ( ) . has_errors ( ) . is_none ( ) {
473+ // No need to walk the statement if there's no stashed errors.
474+ return ;
475+ }
476+
477+ struct PatVisitor < ' a > {
478+ /// `self`
479+ parser : & ' a Parser < ' a > ,
480+ /// The freshly-parsed statement.
481+ stmt : & ' a Stmt ,
482+ /// The current match arm (for arm guard suggestions).
483+ arm : Option < & ' a Arm > ,
484+ /// The current struct field (for variable name suggestions).
485+ field : Option < & ' a PatField > ,
486+ }
487+
488+ impl < ' a > PatVisitor < ' a > {
489+ /// Looks for stashed [`StashKey::ExprInPat`] errors in `stash_span`, and emit them with suggestions.
490+ /// `stash_span` is contained in `expr_span`, the latter being larger in borrow patterns;
491+ /// ```txt
492+ /// &mut x.y
493+ /// -----^^^ `stash_span`
494+ /// |
495+ /// `expr_span`
496+ /// ```
497+ /// `is_range_bound` is used to exclude arm guard suggestions in range pattern bounds.
498+ fn maybe_add_suggestions_then_emit (
499+ & self ,
500+ stash_span : Span ,
501+ expr_span : Span ,
502+ is_range_bound : bool ,
503+ ) {
504+ self . parser . dcx ( ) . try_steal_modify_and_emit_err (
505+ stash_span,
506+ StashKey :: ExprInPat ,
507+ |err| {
508+ // Includes pre-pats (e.g. `&mut <err>`) in the diagnostic.
509+ err. span . replace ( stash_span, expr_span) ;
510+
511+ let sm = self . parser . psess . source_map ( ) ;
512+ let stmt = self . stmt ;
513+ let line_lo = sm. span_extend_to_line ( stmt. span ) . shrink_to_lo ( ) ;
514+ let indentation = sm. indentation_before ( stmt. span ) . unwrap_or_default ( ) ;
515+ let Ok ( expr) = self . parser . span_to_snippet ( expr_span) else {
516+ // FIXME: some suggestions don't actually need the snippet; see PR #123877's unresolved conversations.
517+ return ;
518+ } ;
519+
520+ if let StmtKind :: Let ( local) = & stmt. kind {
521+ match & local. kind {
522+ LocalKind :: Decl | LocalKind :: Init ( _) => {
523+ // It's kinda hard to guess what the user intended, so don't make suggestions.
524+ return ;
525+ }
526+
527+ LocalKind :: InitElse ( _, _) => { }
528+ }
529+ }
530+
531+ // help: use an arm guard `if val == expr`
532+ // FIXME(guard_patterns): suggest this regardless of a match arm.
533+ if let Some ( arm) = & self . arm
534+ && !is_range_bound
535+ {
536+ let ( ident, ident_span) = match self . field {
537+ Some ( field) => {
538+ ( field. ident . to_string ( ) , field. ident . span . to ( expr_span) )
539+ }
540+ None => ( "val" . to_owned ( ) , expr_span) ,
541+ } ;
542+
543+ // Are parentheses required around `expr`?
544+ // HACK: a neater way would be preferable.
545+ let expr = match & err. args [ "expr_precedence" ] {
546+ DiagArgValue :: Number ( expr_precedence) => {
547+ if * expr_precedence
548+ <= ExprPrecedence :: Binary ( BinOpKind :: Eq ) . order ( ) as i32
549+ {
550+ format ! ( "({expr})" )
551+ } else {
552+ format ! ( "{expr}" )
553+ }
554+ }
555+ _ => unreachable ! ( ) ,
556+ } ;
557+
558+ match & arm. guard {
559+ None => {
560+ err. subdiagnostic (
561+ UnexpectedExpressionInPatternSugg :: CreateGuard {
562+ ident_span,
563+ pat_hi : arm. pat . span . shrink_to_hi ( ) ,
564+ ident,
565+ expr,
566+ } ,
567+ ) ;
568+ }
569+ Some ( guard) => {
570+ // Are parentheses required around the old guard?
571+ let wrap_guard = guard. precedence ( ) . order ( )
572+ <= ExprPrecedence :: Binary ( BinOpKind :: And ) . order ( ) ;
573+
574+ err. subdiagnostic (
575+ UnexpectedExpressionInPatternSugg :: UpdateGuard {
576+ ident_span,
577+ guard_lo : if wrap_guard {
578+ Some ( guard. span . shrink_to_lo ( ) )
579+ } else {
580+ None
581+ } ,
582+ guard_hi : guard. span . shrink_to_hi ( ) ,
583+ guard_hi_paren : if wrap_guard { ")" } else { "" } ,
584+ ident,
585+ expr,
586+ } ,
587+ ) ;
588+ }
589+ }
590+ }
591+
592+ // help: extract the expr into a `const VAL: _ = expr`
593+ let ident = match self . field {
594+ Some ( field) => field. ident . as_str ( ) . to_uppercase ( ) ,
595+ None => "VAL" . to_owned ( ) ,
596+ } ;
597+ err. subdiagnostic ( UnexpectedExpressionInPatternSugg :: Const {
598+ stmt_lo : line_lo,
599+ ident_span : expr_span,
600+ expr,
601+ ident,
602+ indentation,
603+ } ) ;
604+
605+ // help: wrap the expr in a `const { expr }`
606+ // FIXME(inline_const_pat): once stabilized, remove this check and remove the `(requires #[feature(inline_const_pat)])` note from the message
607+ if self . parser . psess . unstable_features . is_nightly_build ( ) {
608+ err. subdiagnostic ( UnexpectedExpressionInPatternSugg :: InlineConst {
609+ start_span : expr_span. shrink_to_lo ( ) ,
610+ end_span : expr_span. shrink_to_hi ( ) ,
611+ } ) ;
612+ }
613+ } ,
614+ ) ;
615+ }
616+ }
617+
618+ impl < ' a > Visitor < ' a > for PatVisitor < ' a > {
619+ fn visit_arm ( & mut self , a : & ' a Arm ) -> Self :: Result {
620+ self . arm = Some ( a) ;
621+ visit:: walk_arm ( self , a) ;
622+ self . arm = None ;
623+ }
624+
625+ fn visit_pat_field ( & mut self , fp : & ' a PatField ) -> Self :: Result {
626+ self . field = Some ( fp) ;
627+ visit:: walk_pat_field ( self , fp) ;
628+ self . field = None ;
629+ }
630+
631+ fn visit_pat ( & mut self , p : & ' a Pat ) -> Self :: Result {
632+ match & p. kind {
633+ // Base expression
634+ PatKind :: Err ( _) | PatKind :: Lit ( _) => {
635+ self . maybe_add_suggestions_then_emit ( p. span , p. span , false )
636+ }
637+
638+ // Sub-patterns
639+ // FIXME: this doesn't work with recursive subpats (`&mut &mut <err>`)
640+ PatKind :: Box ( subpat) | PatKind :: Ref ( subpat, _)
641+ if matches ! ( subpat. kind, PatKind :: Err ( _) | PatKind :: Lit ( _) ) =>
642+ {
643+ self . maybe_add_suggestions_then_emit ( subpat. span , p. span , false )
644+ }
645+
646+ // Sub-expressions
647+ PatKind :: Range ( start, end, _) => {
648+ if let Some ( start) = start {
649+ self . maybe_add_suggestions_then_emit ( start. span , start. span , true ) ;
650+ }
651+
652+ if let Some ( end) = end {
653+ self . maybe_add_suggestions_then_emit ( end. span , end. span , true ) ;
654+ }
655+ }
656+
657+ // Walk continuation
658+ _ => visit:: walk_pat ( self , p) ,
659+ }
660+ }
661+ }
662+
663+ // Starts the visit.
664+ PatVisitor { parser : self , stmt, arm : None , field : None } . visit_stmt ( stmt) ;
665+ }
666+
457667 /// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
458668 /// allowed).
459669 fn parse_pat_with_range_pat (
@@ -845,7 +1055,7 @@ impl<'a> Parser<'a> {
8451055 self . 0 = true ;
8461056 * m = Mutability :: Mut ;
8471057 }
848- walk_pat ( self , pat) ;
1058+ mut_visit :: walk_pat ( self , pat) ;
8491059 }
8501060 }
8511061
0 commit comments