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 , IdentIsRaw , 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,219 @@ 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 ( )
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,
454
465
) )
455
466
}
456
467
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
+
457
666
/// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
458
667
/// allowed).
459
668
fn parse_pat_with_range_pat (
@@ -845,7 +1054,7 @@ impl<'a> Parser<'a> {
845
1054
self . 0 = true ;
846
1055
* m = Mutability :: Mut ;
847
1056
}
848
- walk_pat ( self , pat) ;
1057
+ mut_visit :: walk_pat ( self , pat) ;
849
1058
}
850
1059
}
851
1060
0 commit comments