@@ -8,21 +8,21 @@ use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, pe
8
8
use clippy_utils:: visitors:: LocalUsedVisitor ;
9
9
use clippy_utils:: {
10
10
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,
12
12
} ;
13
13
use clippy_utils:: { paths, search_same, SpanlessEq , SpanlessHash } ;
14
14
use if_chain:: if_chain;
15
15
use rustc_ast:: ast:: LitKind ;
16
16
use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
17
17
use rustc_errors:: Applicability ;
18
- use rustc_hir:: def:: CtorKind ;
18
+ use rustc_hir:: def:: { CtorKind , DefKind , Res } ;
19
19
use 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 ,
22
22
} ;
23
23
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
24
24
use rustc_middle:: lint:: in_external_macro;
25
- use rustc_middle:: ty:: { self , Ty , TyS } ;
25
+ use rustc_middle:: ty:: { self , Ty , TyS , VariantDef } ;
26
26
use rustc_semver:: RustcVersion ;
27
27
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
28
28
use rustc_span:: source_map:: { Span , Spanned } ;
@@ -956,114 +956,181 @@ fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm
956
956
}
957
957
}
958
958
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
+ }
965
970
}
966
971
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
+
967
1000
// First pass - check for violation, but don't do much book-keeping because this is hopefully
968
1001
// the uncommon case, and the book-keeping is slightly expensive.
969
1002
let mut wildcard_span = None ;
970
1003
let mut wildcard_ident = None ;
1004
+ let mut has_non_wild = false ;
971
1005
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 ,
977
1013
}
978
1014
}
1015
+ let wildcard_span = match wildcard_span {
1016
+ Some ( x) if has_non_wild => x,
1017
+ _ => return ,
1018
+ } ;
979
1019
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 ( ) ;
983
1023
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
+ _ => ( ) ,
988
1069
}
989
- }
1070
+ } ) ;
1071
+ }
990
1072
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( "::" ) ;
1010
1086
}
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 => "{ .. }" ,
1011
1099
}
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
+ } ;
1036
1102
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
+ } ;
1043
1122
1044
- if suggestion. len ( ) == 1 {
1045
- // No need to check for non-exhaustive enum as in that case len would be greater than 1
1046
1123
span_lint_and_sugg (
1047
1124
cx,
1048
- MATCH_WILDCARD_FOR_SINGLE_VARIANTS ,
1125
+ WILDCARD_ENUM_MATCH_ARM ,
1049
1126
wildcard_span,
1050
1127
message,
1051
1128
"try this" ,
1052
- suggestion [ 0 ] . clone ( ) ,
1129
+ suggestions . join ( " | " ) ,
1053
1130
Applicability :: MaybeIncorrect ,
1054
1131
)
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
+ } ;
1067
1134
}
1068
1135
1069
1136
// If the block contains only a `panic!` macro (as expression or statement)
0 commit comments