@@ -6,13 +6,15 @@ use crate::utils::{
6
6
snippet_with_applicability, span_lint_and_sugg, span_lint_and_then, span_note_and_lint, walk_ptrs_ty,
7
7
} ;
8
8
use if_chain:: if_chain;
9
+ use rustc:: hir:: def:: CtorKind ;
9
10
use rustc:: hir:: * ;
10
11
use rustc:: lint:: { in_external_macro, LateContext , LateLintPass , LintArray , LintContext , LintPass } ;
11
- use rustc:: ty:: { self , Ty } ;
12
+ use rustc:: ty:: { self , Ty , TyKind } ;
12
13
use rustc:: { declare_tool_lint, lint_array} ;
13
14
use rustc_errors:: Applicability ;
14
15
use std:: cmp:: Ordering ;
15
16
use std:: collections:: Bound ;
17
+ use std:: ops:: Deref ;
16
18
use syntax:: ast:: LitKind ;
17
19
use syntax:: source_map:: Span ;
18
20
@@ -191,7 +193,8 @@ declare_clippy_lint! {
191
193
///
192
194
/// **Why is this bad?** New enum variants added by library updates can be missed.
193
195
///
194
- /// **Known problems:** Nested wildcards a la `Foo(_)` are currently not detected.
196
+ /// **Known problems:** Suggested replacements may be incorrect if guards exhaustively cover some
197
+ /// variants, and also may not use correct path to enum if it's not present in the current scope.
195
198
///
196
199
/// **Example:**
197
200
/// ```rust
@@ -464,19 +467,89 @@ fn check_wild_err_arm(cx: &LateContext<'_, '_>, ex: &Expr, arms: &[Arm]) {
464
467
}
465
468
466
469
fn check_wild_enum_match ( cx : & LateContext < ' _ , ' _ > , ex : & Expr , arms : & [ Arm ] ) {
467
- if cx. tables . expr_ty ( ex) . is_enum ( ) {
470
+ let ty = cx. tables . expr_ty ( ex) ;
471
+ if !ty. is_enum ( ) {
472
+ // If there isn't a nice closed set of possible values that can be conveniently enumerated,
473
+ // don't complain about not enumerating the mall.
474
+ return ;
475
+ }
476
+
477
+ // First pass - check for violation, but don't do much book-keeping because this is hopefully
478
+ // the uncommon case, and the book-keeping is slightly expensive.
479
+ let mut wildcard_span = None ;
480
+ let mut wildcard_ident = None ;
481
+ for arm in arms {
482
+ for pat in & arm. pats {
483
+ if let PatKind :: Wild = pat. node {
484
+ wildcard_span = Some ( pat. span ) ;
485
+ } else if let PatKind :: Binding ( _, _, _, ident, None ) = pat. node {
486
+ wildcard_span = Some ( pat. span ) ;
487
+ wildcard_ident = Some ( ident) ;
488
+ }
489
+ }
490
+ }
491
+
492
+ if let Some ( wildcard_span) = wildcard_span {
493
+ // Accumulate the variants which should be put in place of the wildcard because they're not
494
+ // already covered.
495
+
496
+ let mut missing_variants = vec ! [ ] ;
497
+ if let TyKind :: Adt ( def, _) = ty. sty {
498
+ for variant in & def. variants {
499
+ missing_variants. push ( variant) ;
500
+ }
501
+ }
502
+
468
503
for arm in arms {
469
- if is_wild ( & arm. pats [ 0 ] ) {
470
- span_note_and_lint (
471
- cx,
472
- WILDCARD_ENUM_MATCH_ARM ,
473
- arm. pats [ 0 ] . span ,
474
- "wildcard match will miss any future added variants." ,
475
- arm. pats [ 0 ] . span ,
476
- "to resolve, match each variant explicitly" ,
477
- ) ;
504
+ if arm. guard . is_some ( ) {
505
+ // Guards mean that this case probably isn't exhaustively covered. Technically
506
+ // this is incorrect, as we should really check whether each variant is exhaustively
507
+ // covered by the set of guards that cover it, but that's really hard to do.
508
+ continue ;
478
509
}
510
+ for pat in & arm. pats {
511
+ if let PatKind :: Path ( ref path) = pat. deref ( ) . node {
512
+ if let QPath :: Resolved ( _, p) = path {
513
+ missing_variants. retain ( |e| e. did != p. def . def_id ( ) ) ;
514
+ }
515
+ } else if let PatKind :: TupleStruct ( ref path, ..) = pat. deref ( ) . node {
516
+ if let QPath :: Resolved ( _, p) = path {
517
+ missing_variants. retain ( |e| e. did != p. def . def_id ( ) ) ;
518
+ }
519
+ }
520
+ }
521
+ }
522
+
523
+ let suggestion: Vec < String > = missing_variants
524
+ . iter ( )
525
+ . map ( |v| {
526
+ let suffix = match v. ctor_kind {
527
+ CtorKind :: Fn => "(..)" ,
528
+ CtorKind :: Const | CtorKind :: Fictive => "" ,
529
+ } ;
530
+ let ident_str = if let Some ( ident) = wildcard_ident {
531
+ format ! ( "{} @ " , ident. name)
532
+ } else {
533
+ String :: new ( )
534
+ } ;
535
+ // This path assumes that the enum type is imported into scope.
536
+ format ! ( "{}{}{}" , ident_str, cx. tcx. item_path_str( v. did) , suffix)
537
+ } )
538
+ . collect ( ) ;
539
+
540
+ if suggestion. is_empty ( ) {
541
+ return ;
479
542
}
543
+
544
+ span_lint_and_sugg (
545
+ cx,
546
+ WILDCARD_ENUM_MATCH_ARM ,
547
+ wildcard_span,
548
+ "wildcard match will miss any future added variants." ,
549
+ "try this" ,
550
+ suggestion. join ( " | " ) ,
551
+ Applicability :: MachineApplicable ,
552
+ )
480
553
}
481
554
}
482
555
0 commit comments