@@ -20,7 +20,7 @@ use rustc_session::lint::builtin::{
2020} ;
2121use rustc_session:: Session ;
2222use rustc_span:: source_map:: Spanned ;
23- use rustc_span:: { DesugaringKind , ExpnKind , Span } ;
23+ use rustc_span:: { DesugaringKind , ExpnKind , MultiSpan , Span } ;
2424
2525crate fn check_match ( tcx : TyCtxt < ' _ > , def_id : DefId ) {
2626 let body_id = match def_id. as_local ( ) {
@@ -64,7 +64,9 @@ impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, '_, 'tcx> {
6464 fn visit_expr ( & mut self , ex : & ' tcx hir:: Expr < ' tcx > ) {
6565 intravisit:: walk_expr ( self , ex) ;
6666 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+ }
6870 hir:: ExprKind :: Let ( hir:: Let { pat, init, span, .. } ) => {
6971 self . check_let ( pat, init, * span)
7072 }
@@ -163,6 +165,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
163165 scrut : & hir:: Expr < ' _ > ,
164166 hir_arms : & ' tcx [ hir:: Arm < ' tcx > ] ,
165167 source : hir:: MatchSource ,
168+ expr_span : Span ,
166169 ) {
167170 let mut cx = self . new_cx ( scrut. hir_id ) ;
168171
@@ -208,15 +211,14 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
208211 }
209212
210213 // Check if the match is exhaustive.
211- let is_empty_match = arms. is_empty ( ) ;
212214 let witnesses = report. non_exhaustiveness_witnesses ;
213215 if !witnesses. is_empty ( ) {
214216 if source == hir:: MatchSource :: ForLoopDesugar && hir_arms. len ( ) == 2 {
215217 // the for loop pattern is not irrefutable
216218 let pat = hir_arms[ 1 ] . pat . for_loop_some ( ) . unwrap ( ) ;
217219 self . check_irrefutable ( pat, "`for` loop binding" , None ) ;
218220 } 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 ) ;
220222 }
221223 }
222224 }
@@ -334,7 +336,7 @@ fn check_for_bindings_named_same_as_variants(
334336 let ty_path = cx. tcx . def_path_str ( edef. did ) ;
335337 let mut err = lint. build ( & format ! (
336338 "pattern binding `{}` is named the same as one \
337- of the variants of the type `{}`",
339+ of the variants of the type `{}`",
338340 ident, ty_path
339341 ) ) ;
340342 err. code ( error_code ! ( E0170 ) ) ;
@@ -494,21 +496,26 @@ fn non_exhaustive_match<'p, 'tcx>(
494496 scrut_ty : Ty < ' tcx > ,
495497 sp : Span ,
496498 witnesses : Vec < DeconstructedPat < ' p , ' tcx > > ,
497- is_empty_match : bool ,
499+ arms : & [ hir:: Arm < ' tcx > ] ,
500+ expr_span : Span ,
498501) {
502+ let is_empty_match = arms. is_empty ( ) ;
499503 let non_empty_enum = match scrut_ty. kind ( ) {
500504 ty:: Adt ( def, _) => def. is_enum ( ) && !def. variants . is_empty ( ) ,
501505 _ => false ,
502506 } ;
503507 // In the case of an empty match, replace the '`_` not covered' diagnostic with something more
504508 // informative.
505509 let mut err;
510+ let pattern;
511+ let mut patterns_len = 0 ;
506512 if is_empty_match && !non_empty_enum {
507513 err = create_e0004 (
508514 cx. tcx . sess ,
509515 sp,
510516 format ! ( "non-exhaustive patterns: type `{}` is non-empty" , scrut_ty) ,
511517 ) ;
518+ pattern = "_" . to_string ( ) ;
512519 } else {
513520 let joined_patterns = joined_uncovered_patterns ( cx, & witnesses) ;
514521 err = create_e0004 (
@@ -517,6 +524,16 @@ fn non_exhaustive_match<'p, 'tcx>(
517524 format ! ( "non-exhaustive patterns: {} not covered" , joined_patterns) ,
518525 ) ;
519526 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+ } ;
520537 } ;
521538
522539 let is_variant_list_non_exhaustive = match scrut_ty. kind ( ) {
@@ -525,10 +542,6 @@ fn non_exhaustive_match<'p, 'tcx>(
525542 } ;
526543
527544 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- ) ;
532545 err. note ( & format ! (
533546 "the matched value is of type `{}`{}" ,
534547 scrut_ty,
@@ -540,14 +553,14 @@ fn non_exhaustive_match<'p, 'tcx>(
540553 && matches ! ( witnesses[ 0 ] . ctor( ) , Constructor :: NonExhaustive )
541554 {
542555 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",
545558 scrut_ty,
546559 ) ) ;
547560 if cx. tcx . sess . is_nightly_build ( ) {
548561 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",
551564 scrut_ty,
552565 ) ) ;
553566 }
@@ -557,6 +570,84 @@ fn non_exhaustive_match<'p, 'tcx>(
557570 err. note ( "references are always considered inhabited" ) ;
558571 }
559572 }
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+ }
560651 err. emit ( ) ;
561652}
562653
@@ -597,15 +688,27 @@ fn adt_defined_here<'p, 'tcx>(
597688) {
598689 let ty = ty. peel_refs ( ) ;
599690 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 {
605693 for sp in maybe_point_at_variant ( cx, def, witnesses. iter ( ) ) {
606- err . span_label ( sp, "not covered" ) ;
694+ spans . push ( sp) ;
607695 }
608696 }
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) ) ;
609712 }
610713}
611714
0 commit comments