@@ -6,13 +6,15 @@ use crate::utils::{
66 snippet_with_applicability, span_lint_and_sugg, span_lint_and_then, span_note_and_lint, walk_ptrs_ty,
77} ;
88use if_chain:: if_chain;
9+ use rustc:: hir:: def:: CtorKind ;
910use rustc:: hir:: * ;
1011use rustc:: lint:: { in_external_macro, LateContext , LateLintPass , LintArray , LintContext , LintPass } ;
11- use rustc:: ty:: { self , Ty } ;
12+ use rustc:: ty:: { self , Ty , TyKind } ;
1213use rustc:: { declare_tool_lint, lint_array} ;
1314use rustc_errors:: Applicability ;
1415use std:: cmp:: Ordering ;
1516use std:: collections:: Bound ;
17+ use std:: ops:: Deref ;
1618use syntax:: ast:: LitKind ;
1719use syntax:: source_map:: Span ;
1820
@@ -191,7 +193,8 @@ declare_clippy_lint! {
191193///
192194/// **Why is this bad?** New enum variants added by library updates can be missed.
193195///
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.
195198///
196199/// **Example:**
197200/// ```rust
@@ -464,19 +467,89 @@ fn check_wild_err_arm(cx: &LateContext<'_, '_>, ex: &Expr, arms: &[Arm]) {
464467}
465468
466469fn 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+
468503 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 ;
478509 }
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 ;
479542 }
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+ )
480553 }
481554}
482555
0 commit comments