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} ;
32use clippy_utils:: ty:: implements_trait;
3+ use clippy_utils:: { diagnostics:: span_lint_and_sugg, higher:: MatchesExpn } ;
44use if_chain:: if_chain;
55use 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 ;
1016
1117declare_clippy_lint ! {
1218 /// ### 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>`.
1421 ///
1522 /// ### Why is this bad?
1623 ///
@@ -33,68 +40,225 @@ declare_clippy_lint! {
3340 /// }
3441 /// ```
3542 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 "
3845}
3946
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+ }
4172
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 }
4676 }
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 {
4990 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) ,
50102 } ,
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 ,
55106 }
56107}
57108
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 {
59110 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 ( ) ] )
61112 } else {
62113 false
63114 }
64115}
65116
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+
66250impl < ' tcx > LateLintPass < ' tcx > for PatternEquality {
67251 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 ) ;
98262 }
99263 }
100264}
0 commit comments