@@ -8,21 +8,21 @@ use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, pe
88use clippy_utils:: visitors:: LocalUsedVisitor ;
99use clippy_utils:: {
1010 get_parent_expr, in_macro, is_allowed, is_expn_of, is_refutable, is_wild, match_qpath, meets_msrv, path_to_local,
11- path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, remove_blocks, strip_pat_refs,
11+ path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns , remove_blocks, strip_pat_refs,
1212} ;
1313use clippy_utils:: { paths, search_same, SpanlessEq , SpanlessHash } ;
1414use if_chain:: if_chain;
1515use rustc_ast:: ast:: LitKind ;
1616use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
1717use rustc_errors:: Applicability ;
18- use rustc_hir:: def:: CtorKind ;
18+ use rustc_hir:: def:: { CtorKind , DefKind , Res } ;
1919use rustc_hir:: {
20- Arm , BindingAnnotation , Block , BorrowKind , Expr , ExprKind , Guard , HirId , Local , MatchSource , Mutability , Node , Pat ,
21- PatKind , QPath , RangeEnd ,
20+ self as hir , Arm , BindingAnnotation , Block , BorrowKind , Expr , ExprKind , Guard , HirId , Local , MatchSource ,
21+ Mutability , Node , Pat , PatKind , PathSegment , QPath , RangeEnd , TyKind ,
2222} ;
2323use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
2424use rustc_middle:: lint:: in_external_macro;
25- use rustc_middle:: ty:: { self , Ty , TyS } ;
25+ use rustc_middle:: ty:: { self , Ty , TyS , VariantDef } ;
2626use rustc_semver:: RustcVersion ;
2727use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
2828use rustc_span:: source_map:: { Span , Spanned } ;
@@ -956,114 +956,181 @@ fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm
956956 }
957957}
958958
959- fn check_wild_enum_match ( cx : & LateContext < ' _ > , ex : & Expr < ' _ > , arms : & [ Arm < ' _ > ] ) {
960- let ty = cx. typeck_results ( ) . expr_ty ( ex) ;
961- if !ty. is_enum ( ) {
962- // If there isn't a nice closed set of possible values that can be conveniently enumerated,
963- // don't complain about not enumerating the mall.
964- return ;
959+ enum CommonPrefixSearcher < ' a > {
960+ None ,
961+ Path ( & ' a [ PathSegment < ' a > ] ) ,
962+ Mixed ,
963+ }
964+ impl CommonPrefixSearcher < ' a > {
965+ fn with_path ( & mut self , path : & ' a [ PathSegment < ' a > ] ) {
966+ match path {
967+ [ path @ .., _] => self . with_prefix ( path) ,
968+ [ ] => ( ) ,
969+ }
965970 }
966971
972+ fn with_prefix ( & mut self , path : & ' a [ PathSegment < ' a > ] ) {
973+ match self {
974+ Self :: None => * self = Self :: Path ( path) ,
975+ Self :: Path ( self_path)
976+ if path
977+ . iter ( )
978+ . map ( |p| p. ident . name )
979+ . eq ( self_path. iter ( ) . map ( |p| p. ident . name ) ) => { } ,
980+ Self :: Path ( _) => * self = Self :: Mixed ,
981+ Self :: Mixed => ( ) ,
982+ }
983+ }
984+ }
985+
986+ #[ allow( clippy:: too_many_lines) ]
987+ fn check_wild_enum_match ( cx : & LateContext < ' _ > , ex : & Expr < ' _ > , arms : & [ Arm < ' _ > ] ) {
988+ let ty = cx. typeck_results ( ) . expr_ty ( ex) . peel_refs ( ) ;
989+ let adt_def = match ty. kind ( ) {
990+ ty:: Adt ( adt_def, _)
991+ if adt_def. is_enum ( )
992+ && !( is_type_diagnostic_item ( cx, ty, sym:: option_type)
993+ || is_type_diagnostic_item ( cx, ty, sym:: result_type) ) =>
994+ {
995+ adt_def
996+ } ,
997+ _ => return ,
998+ } ;
999+
9671000 // First pass - check for violation, but don't do much book-keeping because this is hopefully
9681001 // the uncommon case, and the book-keeping is slightly expensive.
9691002 let mut wildcard_span = None ;
9701003 let mut wildcard_ident = None ;
1004+ let mut has_non_wild = false ;
9711005 for arm in arms {
972- if let PatKind :: Wild = arm. pat . kind {
973- wildcard_span = Some ( arm. pat . span ) ;
974- } else if let PatKind :: Binding ( _, _, ident, None ) = arm. pat . kind {
975- wildcard_span = Some ( arm. pat . span ) ;
976- wildcard_ident = Some ( ident) ;
1006+ match peel_hir_pat_refs ( arm. pat ) . 0 . kind {
1007+ PatKind :: Wild => wildcard_span = Some ( arm. pat . span ) ,
1008+ PatKind :: Binding ( _, _, ident, None ) => {
1009+ wildcard_span = Some ( arm. pat . span ) ;
1010+ wildcard_ident = Some ( ident) ;
1011+ } ,
1012+ _ => has_non_wild = true ,
9771013 }
9781014 }
1015+ let wildcard_span = match wildcard_span {
1016+ Some ( x) if has_non_wild => x,
1017+ _ => return ,
1018+ } ;
9791019
980- if let Some ( wildcard_span ) = wildcard_span {
981- // Accumulate the variants which should be put in place of the wildcard because they're not
982- // already covered.
1020+ // Accumulate the variants which should be put in place of the wildcard because they're not
1021+ // already covered.
1022+ let mut missing_variants : Vec < _ > = adt_def . variants . iter ( ) . collect ( ) ;
9831023
984- let mut missing_variants = vec ! [ ] ;
985- if let ty:: Adt ( def, _) = ty. kind ( ) {
986- for variant in & def. variants {
987- missing_variants. push ( variant) ;
1024+ let mut path_prefix = CommonPrefixSearcher :: None ;
1025+ for arm in arms {
1026+ // Guards mean that this case probably isn't exhaustively covered. Technically
1027+ // this is incorrect, as we should really check whether each variant is exhaustively
1028+ // covered by the set of guards that cover it, but that's really hard to do.
1029+ recurse_or_patterns ( arm. pat , |pat| {
1030+ let path = match & peel_hir_pat_refs ( pat) . 0 . kind {
1031+ PatKind :: Path ( path) => {
1032+ #[ allow( clippy:: match_same_arms) ]
1033+ let id = match cx. qpath_res ( path, pat. hir_id ) {
1034+ Res :: Def ( DefKind :: Const | DefKind :: ConstParam | DefKind :: AnonConst , _) => return ,
1035+ Res :: Def ( _, id) => id,
1036+ _ => return ,
1037+ } ;
1038+ if arm. guard . is_none ( ) {
1039+ missing_variants. retain ( |e| e. ctor_def_id != Some ( id) ) ;
1040+ }
1041+ path
1042+ } ,
1043+ PatKind :: TupleStruct ( path, patterns, ..) => {
1044+ if arm. guard . is_none ( ) && patterns. iter ( ) . all ( |p| !is_refutable ( cx, p) ) {
1045+ let id = cx. qpath_res ( path, pat. hir_id ) . def_id ( ) ;
1046+ missing_variants. retain ( |e| e. ctor_def_id != Some ( id) ) ;
1047+ }
1048+ path
1049+ } ,
1050+ PatKind :: Struct ( path, patterns, ..) => {
1051+ if arm. guard . is_none ( ) && patterns. iter ( ) . all ( |p| !is_refutable ( cx, p. pat ) ) {
1052+ let id = cx. qpath_res ( path, pat. hir_id ) . def_id ( ) ;
1053+ missing_variants. retain ( |e| e. def_id != id) ;
1054+ }
1055+ path
1056+ } ,
1057+ _ => return ,
1058+ } ;
1059+ match path {
1060+ QPath :: Resolved ( _, path) => path_prefix. with_path ( path. segments ) ,
1061+ QPath :: TypeRelative (
1062+ hir:: Ty {
1063+ kind : TyKind :: Path ( QPath :: Resolved ( _, path) ) ,
1064+ ..
1065+ } ,
1066+ _,
1067+ ) => path_prefix. with_prefix ( path. segments ) ,
1068+ _ => ( ) ,
9881069 }
989- }
1070+ } ) ;
1071+ }
9901072
991- for arm in arms {
992- if arm. guard . is_some ( ) {
993- // Guards mean that this case probably isn't exhaustively covered. Technically
994- // this is incorrect, as we should really check whether each variant is exhaustively
995- // covered by the set of guards that cover it, but that's really hard to do.
996- continue ;
997- }
998- if let PatKind :: Path ( ref path) = arm. pat . kind {
999- if let QPath :: Resolved ( _, p) = path {
1000- missing_variants. retain ( |e| e. ctor_def_id != Some ( p. res . def_id ( ) ) ) ;
1001- }
1002- } else if let PatKind :: TupleStruct ( QPath :: Resolved ( _, p) , ref patterns, ..) = arm. pat . kind {
1003- // Some simple checks for exhaustive patterns.
1004- // There is a room for improvements to detect more cases,
1005- // but it can be more expensive to do so.
1006- let is_pattern_exhaustive =
1007- |pat : & & Pat < ' _ > | matches ! ( pat. kind, PatKind :: Wild | PatKind :: Binding ( .., None ) ) ;
1008- if patterns. iter ( ) . all ( is_pattern_exhaustive) {
1009- missing_variants. retain ( |e| e. ctor_def_id != Some ( p. res . def_id ( ) ) ) ;
1073+ let format_suggestion = |variant : & VariantDef | {
1074+ format ! (
1075+ "{}{}{}{}" ,
1076+ if let Some ( ident) = wildcard_ident {
1077+ format!( "{} @ " , ident. name)
1078+ } else {
1079+ String :: new( )
1080+ } ,
1081+ if let CommonPrefixSearcher :: Path ( path_prefix) = path_prefix {
1082+ let mut s = String :: new( ) ;
1083+ for seg in path_prefix {
1084+ s. push_str( & seg. ident. as_str( ) ) ;
1085+ s. push_str( "::" ) ;
10101086 }
1087+ s
1088+ } else {
1089+ let mut s = cx. tcx. def_path_str( adt_def. did) ;
1090+ s. push_str( "::" ) ;
1091+ s
1092+ } ,
1093+ variant. ident. name,
1094+ match variant. ctor_kind {
1095+ CtorKind :: Fn if variant. fields. len( ) == 1 => "(_)" ,
1096+ CtorKind :: Fn => "(..)" ,
1097+ CtorKind :: Const => "" ,
1098+ CtorKind :: Fictive => "{ .. }" ,
10111099 }
1012- }
1013-
1014- let mut suggestion: Vec < String > = missing_variants
1015- . iter ( )
1016- . map ( |v| {
1017- let suffix = match v. ctor_kind {
1018- CtorKind :: Fn => "(..)" ,
1019- CtorKind :: Const | CtorKind :: Fictive => "" ,
1020- } ;
1021- let ident_str = if let Some ( ident) = wildcard_ident {
1022- format ! ( "{} @ " , ident. name)
1023- } else {
1024- String :: new ( )
1025- } ;
1026- // This path assumes that the enum type is imported into scope.
1027- format ! ( "{}{}{}" , ident_str, cx. tcx. def_path_str( v. def_id) , suffix)
1028- } )
1029- . collect ( ) ;
1030-
1031- if suggestion. is_empty ( ) {
1032- return ;
1033- }
1034-
1035- let mut message = "wildcard match will miss any future added variants" ;
1100+ )
1101+ } ;
10361102
1037- if let ty:: Adt ( def, _) = ty. kind ( ) {
1038- if def. is_variant_list_non_exhaustive ( ) {
1039- message = "match on non-exhaustive enum doesn't explicitly match all known variants" ;
1040- suggestion. push ( String :: from ( "_" ) ) ;
1041- }
1042- }
1103+ match missing_variants. as_slice ( ) {
1104+ [ ] => ( ) ,
1105+ [ x] if !adt_def. is_variant_list_non_exhaustive ( ) => span_lint_and_sugg (
1106+ cx,
1107+ MATCH_WILDCARD_FOR_SINGLE_VARIANTS ,
1108+ wildcard_span,
1109+ "wildcard matches only a single variant and will also match any future added variants" ,
1110+ "try this" ,
1111+ format_suggestion ( x) ,
1112+ Applicability :: MaybeIncorrect ,
1113+ ) ,
1114+ variants => {
1115+ let mut suggestions: Vec < _ > = variants. iter ( ) . cloned ( ) . map ( format_suggestion) . collect ( ) ;
1116+ let message = if adt_def. is_variant_list_non_exhaustive ( ) {
1117+ suggestions. push ( "_" . into ( ) ) ;
1118+ "wildcard matches known variants and will also match future added variants"
1119+ } else {
1120+ "wildcard match will also match any future added variants"
1121+ } ;
10431122
1044- if suggestion. len ( ) == 1 {
1045- // No need to check for non-exhaustive enum as in that case len would be greater than 1
10461123 span_lint_and_sugg (
10471124 cx,
1048- MATCH_WILDCARD_FOR_SINGLE_VARIANTS ,
1125+ WILDCARD_ENUM_MATCH_ARM ,
10491126 wildcard_span,
10501127 message,
10511128 "try this" ,
1052- suggestion [ 0 ] . clone ( ) ,
1129+ suggestions . join ( " | " ) ,
10531130 Applicability :: MaybeIncorrect ,
10541131 )
1055- } ;
1056-
1057- span_lint_and_sugg (
1058- cx,
1059- WILDCARD_ENUM_MATCH_ARM ,
1060- wildcard_span,
1061- message,
1062- "try this" ,
1063- suggestion. join ( " | " ) ,
1064- Applicability :: MaybeIncorrect ,
1065- )
1066- }
1132+ } ,
1133+ } ;
10671134}
10681135
10691136// If the block contains only a `panic!` macro (as expression or statement)
0 commit comments