@@ -20,7 +20,7 @@ use rustc_session::lint::builtin::{
20
20
} ;
21
21
use rustc_session:: Session ;
22
22
use rustc_span:: source_map:: Spanned ;
23
- use rustc_span:: { DesugaringKind , ExpnKind , Span } ;
23
+ use rustc_span:: { DesugaringKind , ExpnKind , MultiSpan , Span } ;
24
24
25
25
crate fn check_match ( tcx : TyCtxt < ' _ > , def_id : DefId ) {
26
26
let body_id = match def_id. as_local ( ) {
@@ -64,7 +64,9 @@ impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, '_, 'tcx> {
64
64
fn visit_expr ( & mut self , ex : & ' tcx hir:: Expr < ' tcx > ) {
65
65
intravisit:: walk_expr ( self , ex) ;
66
66
match & ex. kind {
67
- hir:: ExprKind :: Match ( scrut, arms, source) => self . check_match ( scrut, arms, * source) ,
67
+ hir:: ExprKind :: Match ( scrut, arms, source) => {
68
+ self . check_match ( scrut, arms, * source, ex. span )
69
+ }
68
70
hir:: ExprKind :: Let ( hir:: Let { pat, init, span, .. } ) => {
69
71
self . check_let ( pat, init, * span)
70
72
}
@@ -163,6 +165,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
163
165
scrut : & hir:: Expr < ' _ > ,
164
166
hir_arms : & ' tcx [ hir:: Arm < ' tcx > ] ,
165
167
source : hir:: MatchSource ,
168
+ expr_span : Span ,
166
169
) {
167
170
let mut cx = self . new_cx ( scrut. hir_id ) ;
168
171
@@ -208,15 +211,14 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
208
211
}
209
212
210
213
// Check if the match is exhaustive.
211
- let is_empty_match = arms. is_empty ( ) ;
212
214
let witnesses = report. non_exhaustiveness_witnesses ;
213
215
if !witnesses. is_empty ( ) {
214
216
if source == hir:: MatchSource :: ForLoopDesugar && hir_arms. len ( ) == 2 {
215
217
// the for loop pattern is not irrefutable
216
218
let pat = hir_arms[ 1 ] . pat . for_loop_some ( ) . unwrap ( ) ;
217
219
self . check_irrefutable ( pat, "`for` loop binding" , None ) ;
218
220
} else {
219
- non_exhaustive_match ( & cx, scrut_ty, scrut. span , witnesses, is_empty_match ) ;
221
+ non_exhaustive_match ( & cx, scrut_ty, scrut. span , witnesses, hir_arms , expr_span ) ;
220
222
}
221
223
}
222
224
}
@@ -334,7 +336,7 @@ fn check_for_bindings_named_same_as_variants(
334
336
let ty_path = cx. tcx . def_path_str ( edef. did ) ;
335
337
let mut err = lint. build ( & format ! (
336
338
"pattern binding `{}` is named the same as one \
337
- of the variants of the type `{}`",
339
+ of the variants of the type `{}`",
338
340
ident, ty_path
339
341
) ) ;
340
342
err. code ( error_code ! ( E0170 ) ) ;
@@ -494,21 +496,26 @@ fn non_exhaustive_match<'p, 'tcx>(
494
496
scrut_ty : Ty < ' tcx > ,
495
497
sp : Span ,
496
498
witnesses : Vec < DeconstructedPat < ' p , ' tcx > > ,
497
- is_empty_match : bool ,
499
+ arms : & [ hir:: Arm < ' tcx > ] ,
500
+ expr_span : Span ,
498
501
) {
502
+ let is_empty_match = arms. is_empty ( ) ;
499
503
let non_empty_enum = match scrut_ty. kind ( ) {
500
504
ty:: Adt ( def, _) => def. is_enum ( ) && !def. variants . is_empty ( ) ,
501
505
_ => false ,
502
506
} ;
503
507
// In the case of an empty match, replace the '`_` not covered' diagnostic with something more
504
508
// informative.
505
509
let mut err;
510
+ let pattern;
511
+ let mut patterns_len = 0 ;
506
512
if is_empty_match && !non_empty_enum {
507
513
err = create_e0004 (
508
514
cx. tcx . sess ,
509
515
sp,
510
516
format ! ( "non-exhaustive patterns: type `{}` is non-empty" , scrut_ty) ,
511
517
) ;
518
+ pattern = "_" . to_string ( ) ;
512
519
} else {
513
520
let joined_patterns = joined_uncovered_patterns ( cx, & witnesses) ;
514
521
err = create_e0004 (
@@ -517,6 +524,16 @@ fn non_exhaustive_match<'p, 'tcx>(
517
524
format ! ( "non-exhaustive patterns: {} not covered" , joined_patterns) ,
518
525
) ;
519
526
err. span_label ( sp, pattern_not_covered_label ( & witnesses, & joined_patterns) ) ;
527
+ patterns_len = witnesses. len ( ) ;
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
+ } ;
520
537
} ;
521
538
522
539
let is_variant_list_non_exhaustive = match scrut_ty. kind ( ) {
@@ -525,10 +542,6 @@ fn non_exhaustive_match<'p, 'tcx>(
525
542
} ;
526
543
527
544
adt_defined_here ( cx, & mut err, scrut_ty, & witnesses) ;
528
- err. help (
529
- "ensure that all possible cases are being handled, \
530
- possibly by adding wildcards or more match arms",
531
- ) ;
532
545
err. note ( & format ! (
533
546
"the matched value is of type `{}`{}" ,
534
547
scrut_ty,
@@ -540,14 +553,14 @@ fn non_exhaustive_match<'p, 'tcx>(
540
553
&& matches ! ( witnesses[ 0 ] . ctor( ) , Constructor :: NonExhaustive )
541
554
{
542
555
err. note ( & format ! (
543
- "`{}` does not have a fixed maximum value, \
544
- 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",
545
558
scrut_ty,
546
559
) ) ;
547
560
if cx. tcx . sess . is_nightly_build ( ) {
548
561
err. help ( & format ! (
549
- "add `#![feature(precise_pointer_size_matching)]` \
550
- to the crate attributes to enable precise `{}` matching",
562
+ "add `#![feature(precise_pointer_size_matching)]` to the crate attributes to \
563
+ enable precise `{}` matching",
551
564
scrut_ty,
552
565
) ) ;
553
566
}
@@ -557,6 +570,84 @@ fn non_exhaustive_match<'p, 'tcx>(
557
570
err. note ( "references are always considered inhabited" ) ;
558
571
}
559
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 = format ! (
628
+ "ensure that all possible cases are being handled by adding a match arm with a wildcard \
629
+ pattern{}{}",
630
+ if patterns_len > 1 && patterns_len < 4 && suggestion. is_some( ) {
631
+ ", a match arm with multiple or-patterns"
632
+ } else {
633
+ // we are either not suggesting anything, or suggesting `_`
634
+ ""
635
+ } ,
636
+ match patterns_len {
637
+ // non-exhaustive enum case
638
+ 0 if suggestion. is_some( ) => " as shown" ,
639
+ 0 => "" ,
640
+ 1 if suggestion. is_some( ) => " or an explicit pattern as shown" ,
641
+ 1 => " or an explicit pattern" ,
642
+ _ if suggestion. is_some( ) => " as shown, or multiple match arms" ,
643
+ _ => " or multiple match arms" ,
644
+ } ,
645
+ ) ;
646
+ if let Some ( ( span, sugg) ) = suggestion {
647
+ err. span_suggestion_verbose ( span, & msg, sugg, Applicability :: HasPlaceholders ) ;
648
+ } else {
649
+ err. help ( & msg) ;
650
+ }
560
651
err. emit ( ) ;
561
652
}
562
653
@@ -597,15 +688,27 @@ fn adt_defined_here<'p, 'tcx>(
597
688
) {
598
689
let ty = ty. peel_refs ( ) ;
599
690
if let ty:: Adt ( def, _) = ty. kind ( ) {
600
- if let Some ( sp) = cx. tcx . hir ( ) . span_if_local ( def. did ) {
601
- err. span_label ( sp, format ! ( "`{}` defined here" , ty) ) ;
602
- }
603
-
604
- if witnesses. len ( ) < 4 {
691
+ let mut spans = vec ! [ ] ;
692
+ if witnesses. len ( ) < 5 {
605
693
for sp in maybe_point_at_variant ( cx, def, witnesses. iter ( ) ) {
606
- err . span_label ( sp, "not covered" ) ;
694
+ spans . push ( sp) ;
607
695
}
608
696
}
697
+ let def_span = cx
698
+ . tcx
699
+ . hir ( )
700
+ . get_if_local ( def. did )
701
+ . and_then ( |node| node. ident ( ) )
702
+ . map ( |ident| ident. span )
703
+ . unwrap_or_else ( || cx. tcx . def_span ( def. did ) ) ;
704
+ let mut span: MultiSpan =
705
+ if spans. is_empty ( ) { def_span. into ( ) } else { spans. clone ( ) . into ( ) } ;
706
+
707
+ span. push_span_label ( def_span, String :: new ( ) ) ;
708
+ for pat in spans {
709
+ span. push_span_label ( pat, "not covered" . to_string ( ) ) ;
710
+ }
711
+ err. span_note ( span, & format ! ( "`{}` defined here" , ty) ) ;
609
712
}
610
713
}
611
714
0 commit comments