1
- use clippy_utils:: diagnostics:: span_lint_and_sugg;
2
- use clippy_utils:: source:: snippet_with_context;
1
+ use clippy_utils:: source:: { snippet_with_context, snippet_with_applicability} ;
3
2
use clippy_utils:: ty:: implements_trait;
3
+ use clippy_utils:: { diagnostics:: span_lint_and_sugg, higher:: MatchesExpn } ;
4
4
use if_chain:: if_chain;
5
5
use rustc_errors:: Applicability ;
6
- use rustc_hir:: { Expr , ExprKind , Pat , PatKind } ;
7
- use rustc_lint:: { LateContext , LateLintPass } ;
8
- use rustc_middle:: ty:: Ty ;
9
- use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
6
+ use rustc_hir:: {
7
+ def:: { DefKind , Res } ,
8
+ Arm , Expr , ExprKind , Pat , PatKind , QPath ,
9
+ } ;
10
+ use rustc_lint:: { LateContext , LateLintPass , Lint } ;
11
+ use rustc_middle:: ty:: { Adt , Ty } ;
12
+ use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
13
+ use rustc_span:: { Span , SyntaxContext } ;
14
+
15
+ use crate :: utils:: conf:: EquatablePatternLevel ;
10
16
11
17
declare_clippy_lint ! {
12
18
/// ### What it does
13
- /// Checks for pattern matchings that can be expressed using equality.
19
+ /// Checks for `if let <pat> = <expr>` (and `while let` and similars) that can be expressed
20
+ /// using `if <expr> == <pat>`.
14
21
///
15
22
/// ### Why is this bad?
16
23
///
@@ -33,68 +40,212 @@ declare_clippy_lint! {
33
40
/// }
34
41
/// ```
35
42
pub EQUATABLE_IF_LET ,
36
- nursery ,
37
- "using pattern matching instead of equality"
43
+ style ,
44
+ "using if let instead of if with a equality condition "
38
45
}
39
46
40
- declare_lint_pass ! ( PatternEquality => [ EQUATABLE_IF_LET ] ) ;
47
+ declare_clippy_lint ! {
48
+ /// ### What it does
49
+ /// Checks for `matches!(<expr>, <pat>)` that can be expressed
50
+ /// using `<expr> == <pat>`.
51
+ ///
52
+ /// ### Why is this bad?
53
+ ///
54
+ /// It is less concise and less clear.
55
+ ///
56
+ /// ### Example
57
+ /// ```rust,ignore
58
+ /// let condition = matches!(x, Some(2));
59
+ /// ```
60
+ /// Should be written
61
+ /// ```rust,ignore
62
+ /// let condition = x == Some(2);
63
+ /// ```
64
+ pub EQUATABLE_MATCHES ,
65
+ pedantic,
66
+ "using `matches!` instead of equality"
67
+ }
68
+
69
+ pub struct PatternEquality {
70
+ level : EquatablePatternLevel ,
71
+ }
41
72
42
- /// detects if pattern matches just one thing
43
- fn unary_pattern ( pat : & Pat < ' _ > ) -> bool {
44
- fn array_rec ( pats : & [ Pat < ' _ > ] ) -> bool {
45
- pats. iter ( ) . all ( unary_pattern)
73
+ impl PatternEquality {
74
+ pub fn new ( level : EquatablePatternLevel ) -> PatternEquality {
75
+ PatternEquality { level }
46
76
}
47
- match & pat. kind {
48
- PatKind :: Slice ( _, _, _) | PatKind :: Range ( _, _, _) | PatKind :: Binding ( ..) | PatKind :: Wild | PatKind :: Or ( _) => {
77
+ }
78
+
79
+ impl_lint_pass ! ( PatternEquality => [ EQUATABLE_IF_LET , EQUATABLE_MATCHES ] ) ;
80
+
81
+ fn equatable_pattern ( cx : & LateContext < ' _ > , pat : & Pat < ' _ > ) -> bool {
82
+ fn array_rec ( cx : & LateContext < ' _ > , pats : & [ Pat < ' _ > ] ) -> bool {
83
+ pats. iter ( ) . all ( |x| equatable_pattern ( cx, x) )
84
+ }
85
+ fn is_derived ( cx : & LateContext < ' _ > , pat : & Pat < ' _ > ) -> bool {
86
+ let ty = cx. typeck_results ( ) . pat_ty ( pat) ;
87
+ if let Some ( def_id) = cx. tcx . lang_items ( ) . structural_peq_trait ( ) {
88
+ implements_trait ( cx, ty, def_id, & [ ty. into ( ) ] )
89
+ } else {
49
90
false
91
+ }
92
+ }
93
+ match & pat. kind {
94
+ PatKind :: Slice ( a, None , [ ] ) => array_rec ( cx, a) ,
95
+ PatKind :: Struct ( _, a, etc) => !etc && is_derived ( cx, pat) && a. iter ( ) . all ( |x| equatable_pattern ( cx, x. pat ) ) ,
96
+ PatKind :: Tuple ( a, etc) => !etc. is_some ( ) && array_rec ( cx, a) ,
97
+ PatKind :: TupleStruct ( _, a, etc) => !etc. is_some ( ) && is_derived ( cx, pat) && array_rec ( cx, a) ,
98
+ PatKind :: Ref ( x, _) | PatKind :: Box ( x) => equatable_pattern ( cx, x) ,
99
+ PatKind :: Path ( QPath :: Resolved ( _, b) ) => match b. res {
100
+ Res :: Def ( DefKind :: Const , _) => true ,
101
+ _ => is_derived ( cx, pat) ,
50
102
} ,
51
- PatKind :: Struct ( _, a, etc) => !etc && a. iter ( ) . all ( |x| unary_pattern ( x. pat ) ) ,
52
- PatKind :: Tuple ( a, etc) | PatKind :: TupleStruct ( _, a, etc) => !etc. is_some ( ) && array_rec ( a) ,
53
- PatKind :: Ref ( x, _) | PatKind :: Box ( x) => unary_pattern ( x) ,
54
- PatKind :: Path ( _) | PatKind :: Lit ( _) => true ,
103
+ PatKind :: Path ( _) => is_derived ( cx, pat) ,
104
+ PatKind :: Lit ( _) => true ,
105
+ PatKind :: Slice ( ..) | PatKind :: Range ( ..) | PatKind :: Binding ( ..) | PatKind :: Wild | PatKind :: Or ( _) => false ,
55
106
}
56
107
}
57
108
58
- fn is_structural_partial_eq ( cx : & LateContext < ' tcx > , ty : Ty < ' tcx > , other : Ty < ' tcx > ) -> bool {
109
+ fn is_partial_eq ( cx : & LateContext < ' tcx > , t1 : Ty < ' tcx > , t2 : Ty < ' tcx > ) -> bool {
59
110
if let Some ( def_id) = cx. tcx . lang_items ( ) . eq_trait ( ) {
60
- implements_trait ( cx, ty , def_id, & [ other . into ( ) ] )
111
+ implements_trait ( cx, t1 , def_id, & [ t2 . into ( ) ] )
61
112
} else {
62
113
false
63
114
}
64
115
}
65
116
117
+ fn pat_to_string ( cx : & LateContext < ' tcx > , app : & mut Applicability , pat : & Pat < ' _ > , goal : Ty < ' _ > , ctxt : SyntaxContext ) -> Option < String > {
118
+ fn inner ( cx : & LateContext < ' tcx > , app : & mut Applicability , pat : & Pat < ' _ > , goal : Ty < ' _ > , r : & mut String , ctxt : SyntaxContext ) -> bool {
119
+ let ty = cx. typeck_results ( ) . pat_ty ( pat) ;
120
+ if ty == goal {
121
+ match & pat. kind {
122
+ PatKind :: TupleStruct ( q, ..) | PatKind :: Struct ( q, ..) => {
123
+ let ( adt_def, generic_args) = if let Adt ( x, y) = ty. kind ( ) {
124
+ ( x, y)
125
+ } else {
126
+ return false ; // shouldn't happen
127
+ } ;
128
+ let path = if let QPath :: Resolved ( .., p) = q {
129
+ p
130
+ } else {
131
+ return false ; // give up
132
+ } ;
133
+ let var = adt_def. variant_of_res ( path. res ) ;
134
+ match & pat. kind {
135
+ PatKind :: TupleStruct ( _, params, _) => {
136
+ * r += & * snippet_with_applicability ( cx, path. span , ".." , app) ;
137
+ * r += "(" ;
138
+ for ( i, ( p, f) ) in params. iter ( ) . zip ( var. fields . iter ( ) ) . enumerate ( ) {
139
+ if i != 0 {
140
+ * r += ", " ;
141
+ }
142
+ inner ( cx, app, p, f. ty ( cx. tcx , generic_args) , r, ctxt) ;
143
+ }
144
+ * r += ")" ;
145
+ } ,
146
+ PatKind :: Struct ( _, fields, _) => {
147
+ * r += & * snippet_with_applicability ( cx, path. span , ".." , app) ;
148
+ * r += " { " ;
149
+ for ( i, p) in fields. iter ( ) . enumerate ( ) {
150
+ if i != 0 {
151
+ * r += ", " ;
152
+ }
153
+ * r += & * snippet_with_applicability ( cx, p. ident . span , ".." , app) ;
154
+ * r += ": " ;
155
+ if let Some ( x) = var. fields . iter ( ) . find ( |f| f. ident == p. ident ) {
156
+ inner ( cx, app, p. pat , x. ty ( cx. tcx , generic_args) , r, ctxt) ;
157
+ } else {
158
+ return false ; // won't happen
159
+ }
160
+ }
161
+ * r += " }" ;
162
+ } ,
163
+ _ => return false , // won't happen
164
+ }
165
+ } ,
166
+ _ => {
167
+ * r += & * snippet_with_context ( cx, pat. span , ctxt, ".." , app) . 0 ;
168
+ } ,
169
+ }
170
+ return true ;
171
+ }
172
+ if goal. is_ref ( ) {
173
+ if let Some ( tam) = goal. builtin_deref ( true ) {
174
+ * r += "&" ;
175
+ return inner ( cx, app, pat, tam. ty , r, ctxt) ;
176
+ }
177
+ }
178
+ false
179
+ }
180
+ let mut r = "" . to_string ( ) ;
181
+ if let PatKind :: Struct ( ..) = pat. kind {
182
+ r += "(" ;
183
+ }
184
+ let success = inner ( cx, app, pat, goal, & mut r, ctxt) ;
185
+ if let PatKind :: Struct ( ..) = pat. kind {
186
+ r += ")" ;
187
+ }
188
+ if !success {
189
+ return None ;
190
+ }
191
+ Some ( r)
192
+ }
193
+
194
+ fn level_contains ( level : EquatablePatternLevel , pat : & Pat < ' _ > ) -> bool {
195
+ match level {
196
+ EquatablePatternLevel :: Primitive => matches ! ( pat. kind, PatKind :: Lit ( _) ) ,
197
+ EquatablePatternLevel :: Simple => matches ! ( pat. kind, PatKind :: Lit ( _) | PatKind :: Path ( _) ) ,
198
+ EquatablePatternLevel :: All => true ,
199
+ }
200
+ }
201
+
202
+ fn emit_lint (
203
+ cx : & LateContext < ' tcx > ,
204
+ pat : & Pat < ' _ > ,
205
+ exp : & Expr < ' _ > ,
206
+ ctxt : SyntaxContext ,
207
+ span : Span ,
208
+ lint : & ' static Lint ,
209
+ level : EquatablePatternLevel ,
210
+ ) {
211
+ if_chain ! {
212
+ if equatable_pattern( cx, pat) ;
213
+ if level_contains( level, pat) ;
214
+ let exp_ty = cx. typeck_results( ) . expr_ty( exp) ;
215
+ if is_partial_eq( cx, exp_ty, exp_ty) ;
216
+ let mut app = Applicability :: MachineApplicable ;
217
+ if let Some ( pat_str) = pat_to_string( cx, & mut app, pat, exp_ty, ctxt) ;
218
+ then {
219
+ let exp_str = snippet_with_context( cx, exp. span, ctxt, ".." , & mut app) . 0 ;
220
+ span_lint_and_sugg(
221
+ cx,
222
+ lint,
223
+ span,
224
+ "this pattern matching can be expressed using equality" ,
225
+ "try" ,
226
+ format!(
227
+ "{} == {}" ,
228
+ exp_str,
229
+ pat_str,
230
+ ) ,
231
+ app,
232
+ ) ;
233
+ }
234
+ }
235
+ }
236
+
66
237
impl < ' tcx > LateLintPass < ' tcx > for PatternEquality {
67
238
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
68
- if_chain ! {
69
- if let ExprKind :: Let ( pat, exp, _) = expr. kind;
70
- if unary_pattern( pat) ;
71
- let exp_ty = cx. typeck_results( ) . expr_ty( exp) ;
72
- let pat_ty = cx. typeck_results( ) . pat_ty( pat) ;
73
- if is_structural_partial_eq( cx, exp_ty, pat_ty) ;
74
- then {
75
-
76
- let mut applicability = Applicability :: MachineApplicable ;
77
- let pat_str = match pat. kind {
78
- PatKind :: Struct ( ..) => format!(
79
- "({})" ,
80
- snippet_with_context( cx, pat. span, expr. span. ctxt( ) , ".." , & mut applicability) . 0 ,
81
- ) ,
82
- _ => snippet_with_context( cx, pat. span, expr. span. ctxt( ) , ".." , & mut applicability) . 0 . to_string( ) ,
83
- } ;
84
- span_lint_and_sugg(
85
- cx,
86
- EQUATABLE_IF_LET ,
87
- expr. span,
88
- "this pattern matching can be expressed using equality" ,
89
- "try" ,
90
- format!(
91
- "{} == {}" ,
92
- snippet_with_context( cx, exp. span, expr. span. ctxt( ) , ".." , & mut applicability) . 0 ,
93
- pat_str,
94
- ) ,
95
- applicability,
96
- ) ;
97
- }
239
+ if let ExprKind :: Let ( pat, exp, _) = expr. kind {
240
+ emit_lint ( cx, pat, exp, expr. span . ctxt ( ) , expr. span , EQUATABLE_IF_LET , self . level ) ;
241
+ }
242
+ if let Some ( MatchesExpn {
243
+ call_site,
244
+ arm : Arm { pat, guard : None , .. } ,
245
+ exp,
246
+ } ) = MatchesExpn :: parse ( expr)
247
+ {
248
+ emit_lint ( cx, pat, exp, expr. span . ctxt ( ) , call_site, EQUATABLE_MATCHES , self . level ) ;
98
249
}
99
250
}
100
251
}
0 commit comments