1
- use clippy_utils:: diagnostics:: span_lint_and_sugg;
2
- use clippy_utils:: source:: snippet_with_context;
1
+ use clippy_utils:: source:: { snippet_with_applicability, snippet_with_context} ;
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,225 @@ 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 (
118
+ cx : & LateContext < ' tcx > ,
119
+ app : & mut Applicability ,
120
+ pat : & Pat < ' _ > ,
121
+ goal : Ty < ' _ > ,
122
+ ctxt : SyntaxContext ,
123
+ ) -> Option < String > {
124
+ fn inner (
125
+ cx : & LateContext < ' tcx > ,
126
+ app : & mut Applicability ,
127
+ pat : & Pat < ' _ > ,
128
+ goal : Ty < ' _ > ,
129
+ r : & mut String ,
130
+ ctxt : SyntaxContext ,
131
+ ) -> bool {
132
+ let ty = cx. typeck_results ( ) . pat_ty ( pat) ;
133
+ if ty == goal {
134
+ match & pat. kind {
135
+ PatKind :: TupleStruct ( q, ..) | PatKind :: Struct ( q, ..) => {
136
+ let ( adt_def, generic_args) = if let Adt ( x, y) = ty. kind ( ) {
137
+ ( x, y)
138
+ } else {
139
+ return false ; // shouldn't happen
140
+ } ;
141
+ let path = if let QPath :: Resolved ( .., p) = q {
142
+ p
143
+ } else {
144
+ return false ; // give up
145
+ } ;
146
+ let var = adt_def. variant_of_res ( path. res ) ;
147
+ match & pat. kind {
148
+ PatKind :: TupleStruct ( _, params, _) => {
149
+ * r += & * snippet_with_applicability ( cx, path. span , ".." , app) ;
150
+ * r += "(" ;
151
+ for ( i, ( p, f) ) in params. iter ( ) . zip ( var. fields . iter ( ) ) . enumerate ( ) {
152
+ if i != 0 {
153
+ * r += ", " ;
154
+ }
155
+ inner ( cx, app, p, f. ty ( cx. tcx , generic_args) , r, ctxt) ;
156
+ }
157
+ * r += ")" ;
158
+ } ,
159
+ PatKind :: Struct ( _, fields, _) => {
160
+ * r += & * snippet_with_applicability ( cx, path. span , ".." , app) ;
161
+ * r += " { " ;
162
+ for ( i, p) in fields. iter ( ) . enumerate ( ) {
163
+ if i != 0 {
164
+ * r += ", " ;
165
+ }
166
+ * r += & * snippet_with_applicability ( cx, p. ident . span , ".." , app) ;
167
+ * r += ": " ;
168
+ if let Some ( x) = var. fields . iter ( ) . find ( |f| f. ident == p. ident ) {
169
+ inner ( cx, app, p. pat , x. ty ( cx. tcx , generic_args) , r, ctxt) ;
170
+ } else {
171
+ return false ; // won't happen
172
+ }
173
+ }
174
+ * r += " }" ;
175
+ } ,
176
+ _ => return false , // won't happen
177
+ }
178
+ } ,
179
+ _ => {
180
+ * r += & * snippet_with_context ( cx, pat. span , ctxt, ".." , app) . 0 ;
181
+ } ,
182
+ }
183
+ return true ;
184
+ }
185
+ if goal. is_ref ( ) {
186
+ if let Some ( tam) = goal. builtin_deref ( true ) {
187
+ * r += "&" ;
188
+ return inner ( cx, app, pat, tam. ty , r, ctxt) ;
189
+ }
190
+ }
191
+ false
192
+ }
193
+ let mut r = "" . to_string ( ) ;
194
+ if let PatKind :: Struct ( ..) = pat. kind {
195
+ r += "(" ;
196
+ }
197
+ let success = inner ( cx, app, pat, goal, & mut r, ctxt) ;
198
+ if let PatKind :: Struct ( ..) = pat. kind {
199
+ r += ")" ;
200
+ }
201
+ if !success {
202
+ return None ;
203
+ }
204
+ Some ( r)
205
+ }
206
+
207
+ fn level_contains ( level : EquatablePatternLevel , pat : & Pat < ' _ > ) -> bool {
208
+ match level {
209
+ EquatablePatternLevel :: Primitive => matches ! ( pat. kind, PatKind :: Lit ( _) ) ,
210
+ EquatablePatternLevel :: Simple => matches ! ( pat. kind, PatKind :: Lit ( _) | PatKind :: Path ( _) ) ,
211
+ EquatablePatternLevel :: All => true ,
212
+ }
213
+ }
214
+
215
+ fn emit_lint (
216
+ cx : & LateContext < ' tcx > ,
217
+ pat : & Pat < ' _ > ,
218
+ exp : & Expr < ' _ > ,
219
+ ctxt : SyntaxContext ,
220
+ span : Span ,
221
+ lint : & ' static Lint ,
222
+ level : EquatablePatternLevel ,
223
+ ) {
224
+ if_chain ! {
225
+ if equatable_pattern( cx, pat) ;
226
+ if level_contains( level, pat) ;
227
+ let exp_ty = cx. typeck_results( ) . expr_ty( exp) ;
228
+ if is_partial_eq( cx, exp_ty, exp_ty) ;
229
+ let mut app = Applicability :: MachineApplicable ;
230
+ if let Some ( pat_str) = pat_to_string( cx, & mut app, pat, exp_ty, ctxt) ;
231
+ then {
232
+ let exp_str = snippet_with_context( cx, exp. span, ctxt, ".." , & mut app) . 0 ;
233
+ span_lint_and_sugg(
234
+ cx,
235
+ lint,
236
+ span,
237
+ "this pattern matching can be expressed using equality" ,
238
+ "try" ,
239
+ format!(
240
+ "{} == {}" ,
241
+ exp_str,
242
+ pat_str,
243
+ ) ,
244
+ app,
245
+ ) ;
246
+ }
247
+ }
248
+ }
249
+
66
250
impl < ' tcx > LateLintPass < ' tcx > for PatternEquality {
67
251
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
- }
252
+ if let ExprKind :: Let ( pat, exp, _) = expr. kind {
253
+ emit_lint ( cx, pat, exp, expr. span . ctxt ( ) , expr. span , EQUATABLE_IF_LET , self . level ) ;
254
+ }
255
+ if let Some ( MatchesExpn {
256
+ call_site,
257
+ arm : Arm { pat, guard : None , .. } ,
258
+ exp,
259
+ } ) = MatchesExpn :: parse ( expr)
260
+ {
261
+ emit_lint ( cx, pat, exp, expr. span . ctxt ( ) , call_site, EQUATABLE_MATCHES , self . level ) ;
98
262
}
99
263
}
100
264
}
0 commit comments