@@ -17,7 +17,7 @@ use rustc_session::lint::builtin::{
17
17
BINDINGS_WITH_VARIANT_NAME , IRREFUTABLE_LET_PATTERNS , UNREACHABLE_PATTERNS ,
18
18
} ;
19
19
use rustc_session:: Session ;
20
- use rustc_span:: { DesugaringKind , ExpnKind , Span } ;
20
+ use rustc_span:: { DesugaringKind , ExpnKind , MultiSpan , Span } ;
21
21
22
22
crate fn check_match ( tcx : TyCtxt < ' _ > , def_id : DefId ) {
23
23
let body_id = match def_id. as_local ( ) {
@@ -63,7 +63,9 @@ impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, '_, 'tcx> {
63
63
fn visit_expr ( & mut self , ex : & ' tcx hir:: Expr < ' tcx > ) {
64
64
intravisit:: walk_expr ( self , ex) ;
65
65
match & ex. kind {
66
- hir:: ExprKind :: Match ( scrut, arms, source) => self . check_match ( scrut, arms, * source) ,
66
+ hir:: ExprKind :: Match ( scrut, arms, source) => {
67
+ self . check_match ( scrut, arms, * source, ex. span )
68
+ }
67
69
hir:: ExprKind :: Let ( pat, scrut, span) => self . check_let ( pat, scrut, * span) ,
68
70
_ => { }
69
71
}
@@ -160,6 +162,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
160
162
scrut : & hir:: Expr < ' _ > ,
161
163
hir_arms : & ' tcx [ hir:: Arm < ' tcx > ] ,
162
164
source : hir:: MatchSource ,
165
+ expr_span : Span ,
163
166
) {
164
167
let mut cx = self . new_cx ( scrut. hir_id ) ;
165
168
@@ -205,15 +208,14 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
205
208
}
206
209
207
210
// Check if the match is exhaustive.
208
- let is_empty_match = arms. is_empty ( ) ;
209
211
let witnesses = report. non_exhaustiveness_witnesses ;
210
212
if !witnesses. is_empty ( ) {
211
213
if source == hir:: MatchSource :: ForLoopDesugar && hir_arms. len ( ) == 2 {
212
214
// the for loop pattern is not irrefutable
213
215
let pat = hir_arms[ 1 ] . pat . for_loop_some ( ) . unwrap ( ) ;
214
216
self . check_irrefutable ( pat, "`for` loop binding" , None ) ;
215
217
} else {
216
- non_exhaustive_match ( & cx, scrut_ty, scrut. span , witnesses, is_empty_match ) ;
218
+ non_exhaustive_match ( & cx, scrut_ty, scrut. span , witnesses, hir_arms , expr_span ) ;
217
219
}
218
220
}
219
221
}
@@ -496,21 +498,25 @@ fn non_exhaustive_match<'p, 'tcx>(
496
498
scrut_ty : Ty < ' tcx > ,
497
499
sp : Span ,
498
500
witnesses : Vec < DeconstructedPat < ' p , ' tcx > > ,
499
- is_empty_match : bool ,
501
+ arms : & [ hir:: Arm < ' tcx > ] ,
502
+ expr_span : Span ,
500
503
) {
504
+ let is_empty_match = arms. is_empty ( ) ;
501
505
let non_empty_enum = match scrut_ty. kind ( ) {
502
506
ty:: Adt ( def, _) => def. is_enum ( ) && !def. variants . is_empty ( ) ,
503
507
_ => false ,
504
508
} ;
505
509
// In the case of an empty match, replace the '`_` not covered' diagnostic with something more
506
510
// informative.
507
511
let mut err;
512
+ let pattern;
508
513
if is_empty_match && !non_empty_enum {
509
514
err = create_e0004 (
510
515
cx. tcx . sess ,
511
516
sp,
512
517
format ! ( "non-exhaustive patterns: type `{}` is non-empty" , scrut_ty) ,
513
518
) ;
519
+ pattern = "_" . to_string ( ) ;
514
520
} else {
515
521
let joined_patterns = joined_uncovered_patterns ( cx, & witnesses) ;
516
522
err = create_e0004 (
@@ -519,6 +525,15 @@ fn non_exhaustive_match<'p, 'tcx>(
519
525
format ! ( "non-exhaustive patterns: {} not covered" , joined_patterns) ,
520
526
) ;
521
527
err. span_label ( sp, pattern_not_covered_label ( & witnesses, & joined_patterns) ) ;
528
+ pattern = if witnesses. len ( ) < 4 {
529
+ witnesses
530
+ . iter ( )
531
+ . map ( |witness| witness. to_pat ( cx) . to_string ( ) )
532
+ . collect :: < Vec < String > > ( )
533
+ . join ( " | " )
534
+ } else {
535
+ "_" . to_string ( )
536
+ } ;
522
537
} ;
523
538
524
539
let is_variant_list_non_exhaustive = match scrut_ty. kind ( ) {
@@ -527,10 +542,6 @@ fn non_exhaustive_match<'p, 'tcx>(
527
542
} ;
528
543
529
544
adt_defined_here ( cx, & mut err, scrut_ty, & witnesses) ;
530
- err. help (
531
- "ensure that all possible cases are being handled, \
532
- possibly by adding wildcards or more match arms",
533
- ) ;
534
545
err. note ( & format ! (
535
546
"the matched value is of type `{}`{}" ,
536
547
scrut_ty,
@@ -542,14 +553,14 @@ fn non_exhaustive_match<'p, 'tcx>(
542
553
&& matches ! ( witnesses[ 0 ] . ctor( ) , Constructor :: NonExhaustive )
543
554
{
544
555
err. note ( & format ! (
545
- "`{}` does not have a fixed maximum value, \
546
- so a wildcard `_` is necessary to match exhaustively",
556
+ "`{}` does not have a fixed maximum value, so a wildcard `_` is necessary to match \
557
+ exhaustively",
547
558
scrut_ty,
548
559
) ) ;
549
560
if cx. tcx . sess . is_nightly_build ( ) {
550
561
err. help ( & format ! (
551
- "add `#![feature(precise_pointer_size_matching)]` \
552
- to the crate attributes to enable precise `{}` matching",
562
+ "add `#![feature(precise_pointer_size_matching)]` to the crate attributes to \
563
+ enable precise `{}` matching",
553
564
scrut_ty,
554
565
) ) ;
555
566
}
@@ -559,6 +570,67 @@ fn non_exhaustive_match<'p, 'tcx>(
559
570
err. note ( "references are always considered inhabited" ) ;
560
571
}
561
572
}
573
+
574
+ let mut suggestion = None ;
575
+ let sm = cx. tcx . sess . source_map ( ) ;
576
+ match arms {
577
+ [ ] if sp. ctxt ( ) == expr_span. ctxt ( ) => {
578
+ // Get the span for the empty match body `{}`.
579
+ let ( indentation, more) = if let Some ( snippet) = sm. indentation_before ( sp) {
580
+ ( format ! ( "\n {}" , snippet) , " " )
581
+ } else {
582
+ ( " " . to_string ( ) , "" )
583
+ } ;
584
+ suggestion = Some ( (
585
+ sp. shrink_to_hi ( ) . with_hi ( expr_span. hi ( ) ) ,
586
+ format ! (
587
+ " {{{indentation}{more}{pattern} => todo!(),{indentation}}}" ,
588
+ indentation = indentation,
589
+ more = more,
590
+ pattern = pattern,
591
+ ) ,
592
+ ) ) ;
593
+ }
594
+ [ only] => {
595
+ let pre_indentation = if let ( Some ( snippet) , true ) = (
596
+ sm. indentation_before ( only. span ) ,
597
+ sm. is_multiline ( sp. shrink_to_hi ( ) . with_hi ( only. span . lo ( ) ) ) ,
598
+ ) {
599
+ format ! ( "\n {}" , snippet)
600
+ } else {
601
+ " " . to_string ( )
602
+ } ;
603
+ let comma = if matches ! ( only. body. kind, hir:: ExprKind :: Block ( ..) ) { "" } else { "," } ;
604
+ suggestion = Some ( (
605
+ only. span . shrink_to_hi ( ) ,
606
+ format ! ( "{}{}{} => todo!()" , comma, pre_indentation, pattern) ,
607
+ ) ) ;
608
+ }
609
+ [ .., prev, last] if prev. span . ctxt ( ) == last. span . ctxt ( ) => {
610
+ if let Ok ( snippet) = sm. span_to_snippet ( prev. span . between ( last. span ) ) {
611
+ let comma =
612
+ if matches ! ( last. body. kind, hir:: ExprKind :: Block ( ..) ) { "" } else { "," } ;
613
+ suggestion = Some ( (
614
+ last. span . shrink_to_hi ( ) ,
615
+ format ! (
616
+ "{}{}{} => todo!()" ,
617
+ comma,
618
+ snippet. strip_prefix( "," ) . unwrap_or( & snippet) ,
619
+ pattern
620
+ ) ,
621
+ ) ) ;
622
+ }
623
+ }
624
+ _ => { }
625
+ }
626
+
627
+ let msg = "ensure that all possible cases are being handled, possibly by adding wildcards \
628
+ or more match arms";
629
+ if let Some ( ( span, sugg) ) = suggestion {
630
+ err. span_suggestion_verbose ( span, msg, sugg, Applicability :: HasPlaceholders ) ;
631
+ } else {
632
+ err. help ( msg) ;
633
+ }
562
634
err. emit ( ) ;
563
635
}
564
636
@@ -599,15 +671,27 @@ fn adt_defined_here<'p, 'tcx>(
599
671
) {
600
672
let ty = ty. peel_refs ( ) ;
601
673
if let ty:: Adt ( def, _) = ty. kind ( ) {
602
- if let Some ( sp) = cx. tcx . hir ( ) . span_if_local ( def. did ) {
603
- err. span_label ( sp, format ! ( "`{}` defined here" , ty) ) ;
604
- }
605
-
606
- if witnesses. len ( ) < 4 {
674
+ let mut spans = vec ! [ ] ;
675
+ if witnesses. len ( ) < 5 {
607
676
for sp in maybe_point_at_variant ( cx, def, witnesses. iter ( ) ) {
608
- err . span_label ( sp, "not covered" ) ;
677
+ spans . push ( sp) ;
609
678
}
610
679
}
680
+ let def_span = cx
681
+ . tcx
682
+ . hir ( )
683
+ . get_if_local ( def. did )
684
+ . and_then ( |node| node. ident ( ) )
685
+ . map ( |ident| ident. span )
686
+ . unwrap_or_else ( || cx. tcx . def_span ( def. did ) ) ;
687
+ let mut span: MultiSpan =
688
+ if spans. is_empty ( ) { def_span. into ( ) } else { spans. clone ( ) . into ( ) } ;
689
+
690
+ span. push_span_label ( def_span, String :: new ( ) ) ;
691
+ for pat in spans {
692
+ span. push_span_label ( pat, "not covered" . to_string ( ) ) ;
693
+ }
694
+ err. span_note ( span, & format ! ( "`{}` defined here" , ty) ) ;
611
695
}
612
696
}
613
697
0 commit comments