1
- use rustc_ast:: mut_visit:: { walk_pat , MutVisitor } ;
1
+ use rustc_ast:: mut_visit:: { self , MutVisitor } ;
2
2
use rustc_ast:: ptr:: P ;
3
3
use rustc_ast:: token:: { self , BinOpToken , Delimiter , Token } ;
4
+ use rustc_ast:: visit:: { self , Visitor } ;
4
5
use 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 ,
7
9
} ;
8
10
use rustc_ast_pretty:: pprust;
9
- use rustc_errors:: { Applicability , Diag , PResult } ;
11
+ use rustc_errors:: { Applicability , Diag , DiagArgValue , PResult , StashKey } ;
10
12
use rustc_session:: errors:: ExprParenthesesNeeded ;
11
13
use rustc_span:: source_map:: { respan, Spanned } ;
12
14
use rustc_span:: symbol:: { kw, sym, Ident } ;
@@ -21,8 +23,8 @@ use crate::errors::{
21
23
InclusiveRangeExtraEquals , InclusiveRangeMatchArrow , InclusiveRangeNoEnd , InvalidMutInPattern ,
22
24
ParenRangeSuggestion , PatternOnWrongSideOfAt , RemoveLet , RepeatedMutInPattern ,
23
25
SwitchRefBoxOrder , TopLevelOrPatternNotAllowed , TopLevelOrPatternNotAllowedSugg ,
24
- TrailingVertNotAllowed , UnexpectedExpressionInPattern , UnexpectedLifetimeInPattern ,
25
- UnexpectedParenInRangePat , UnexpectedParenInRangePatSugg ,
26
+ TrailingVertNotAllowed , UnexpectedExpressionInPattern , UnexpectedExpressionInPatternSugg ,
27
+ UnexpectedLifetimeInPattern , UnexpectedParenInRangePat , UnexpectedParenInRangePatSugg ,
26
28
UnexpectedVertVertBeforeFunctionParam , UnexpectedVertVertInPattern , WrapInParens ,
27
29
} ;
28
30
use crate :: parser:: expr:: { could_be_unclosed_char_literal, DestructuredFloat } ;
@@ -448,12 +450,220 @@ impl<'a> Parser<'a> {
448
450
|| self . token == token:: CloseDelim ( Delimiter :: Parenthesis )
449
451
&& self . look_ahead ( 1 , Token :: is_range_separator) ;
450
452
453
+ let span = expr. span ;
454
+
451
455
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,
454
466
) )
455
467
}
456
468
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
+
457
667
/// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
458
668
/// allowed).
459
669
fn parse_pat_with_range_pat (
@@ -845,7 +1055,7 @@ impl<'a> Parser<'a> {
845
1055
self . 0 = true ;
846
1056
* m = Mutability :: Mut ;
847
1057
}
848
- walk_pat ( self , pat) ;
1058
+ mut_visit :: walk_pat ( self , pat) ;
849
1059
}
850
1060
}
851
1061
0 commit comments