1
1
use clippy_utils:: diagnostics:: span_lint_and_then;
2
2
use clippy_utils:: is_from_proc_macro;
3
3
use clippy_utils:: source:: snippet_with_applicability;
4
+ use clippy_utils:: visitors:: for_each_expr;
4
5
use rustc_errors:: Applicability ;
5
- use rustc_hir:: def:: Res ;
6
+ use rustc_hir:: def:: { DefKind , Res } ;
6
7
use rustc_hir:: { Arm , BinOpKind , Expr , ExprKind , Guard , HirId , MatchSource , Pat , PatKind } ;
7
8
use rustc_lint:: LintContext ;
8
9
use rustc_lint:: { LateContext , LateLintPass } ;
9
10
use rustc_middle:: lint:: in_external_macro;
10
11
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
11
12
use rustc_span:: Span ;
12
13
use std:: borrow:: Cow ;
14
+ use std:: ops:: ControlFlow ;
13
15
14
16
declare_clippy_lint ! {
15
17
/// ### What it does
@@ -75,10 +77,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantGuard {
75
77
] ,
76
78
MatchSource :: Normal ,
77
79
) = if_expr. kind
78
- && let ExprKind :: Path ( qpath) = scrutinee. kind
79
- && let res = cx. qpath_res ( & qpath, scrutinee. hir_id )
80
- && local_came_from ( cx, & res, outer_arm. pat . hir_id )
81
- && let Some ( pat_binding) = cx. tcx . hir ( ) . res_span ( res)
80
+ && let Some ( pat_binding) = get_pat_binding ( cx, scrutinee, outer_arm)
82
81
{
83
82
emit_redundant_guard (
84
83
cx,
@@ -97,10 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantGuard {
97
96
// `Some(x) if let Some(2) = x`
98
97
if let Guard :: IfLet ( let_expr) = guard
99
98
&& let pat = let_expr. pat
100
- && let ExprKind :: Path ( qpath) = let_expr. init . kind
101
- && let res = cx. qpath_res ( & qpath, let_expr. init . hir_id )
102
- && local_came_from ( cx, & res, outer_arm. pat . hir_id )
103
- && let Some ( pat_binding) = cx. tcx . hir ( ) . res_span ( res)
99
+ && let Some ( pat_binding) = get_pat_binding ( cx, let_expr. init , outer_arm)
104
100
{
105
101
emit_redundant_guard (
106
102
cx,
@@ -116,21 +112,14 @@ impl<'tcx> LateLintPass<'tcx> for RedundantGuard {
116
112
continue ;
117
113
}
118
114
119
- // TODO: I'm not sure if this is always right. Can we always inline rhs? My intuition
120
- // says no, but I can't find a case where it can't be...
121
-
122
- // ^ My intuition was correct, This will trigger on `input.span` and any function calls. It should
123
- // not do that
124
-
125
115
// `Some(x) if x == Some(2)`
126
116
if let Guard :: If ( if_expr) = guard
127
117
&& let ExprKind :: Binary ( bin_op, local, pat) = if_expr. kind
128
118
&& matches ! ( bin_op. node, BinOpKind :: Eq )
129
- && let ExprKind :: Path ( qpath) = local. kind
130
- && let res = cx. qpath_res ( & qpath, local. hir_id )
131
- && local_came_from ( cx, & res, outer_arm. pat . hir_id )
132
- && let Some ( pat_binding) = cx. tcx . hir ( ) . res_span ( res)
119
+ && let Some ( pat_binding) = get_pat_binding ( cx, local, outer_arm)
120
+ && expr_can_be_pat ( cx, pat)
133
121
{
122
+
134
123
emit_redundant_guard (
135
124
cx,
136
125
expr,
@@ -159,6 +148,17 @@ fn remove_binding(
159
148
( pat_start, pat_end)
160
149
}
161
150
151
+ fn get_pat_binding ( cx : & LateContext < ' _ > , guard_expr : & Expr < ' _ > , outer_arm : & Arm < ' _ > ) -> Option < Span > {
152
+ if let ExprKind :: Path ( qpath) = guard_expr. kind
153
+ && let res = cx. qpath_res ( & qpath, guard_expr. hir_id )
154
+ && local_came_from ( cx, & res, outer_arm. pat . hir_id )
155
+ {
156
+ return cx. tcx . hir ( ) . res_span ( res) ;
157
+ }
158
+
159
+ None
160
+ }
161
+
162
162
fn get_guard ( cx : & LateContext < ' _ > , guard : Option < Guard < ' _ > > , app : & mut Applicability ) -> Cow < ' static , str > {
163
163
guard. map_or_else (
164
164
|| Cow :: Borrowed ( "" ) ,
@@ -220,3 +220,58 @@ fn emit_redundant_guard<'tcx>(
220
220
} ,
221
221
) ;
222
222
}
223
+
224
+ /// Checks if the given `Expr` can also be represented as a `Pat`.
225
+ fn expr_can_be_pat ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
226
+ fn helper ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
227
+ for_each_expr ( expr, |expr| {
228
+ let expr = expr. peel_blocks ( ) ;
229
+ if match expr. kind {
230
+ ExprKind :: ConstBlock ( ..) => cx. tcx . features ( ) . inline_const_pat ,
231
+ ExprKind :: Call ( c, ..) if let ExprKind :: Path ( qpath) = c. kind => {
232
+ // Allow ctors
233
+ matches ! ( cx. qpath_res( & qpath, c. hir_id) , Res :: Def ( DefKind :: Ctor ( ..) , ..) )
234
+ } ,
235
+ // TODO: This might be incorrect, specifically `AddrOf`
236
+ ExprKind :: Repeat ( expr, ..) | ExprKind :: AddrOf ( .., expr) => helper ( cx, expr) ,
237
+ ExprKind :: Array ( exprs) | ExprKind :: Tup ( exprs) => {
238
+ for expr in exprs {
239
+ if !helper ( cx, expr) {
240
+ return ControlFlow :: Break ( ( ) ) ;
241
+ }
242
+ }
243
+
244
+ true
245
+ } ,
246
+ ExprKind :: Struct ( _, fields, rest) => {
247
+ for field in fields {
248
+ if !helper ( cx, field. expr ) {
249
+ return ControlFlow :: Break ( ( ) ) ;
250
+ }
251
+ }
252
+
253
+ if let Some ( rest) = rest {
254
+ if !helper ( cx, rest) {
255
+ return ControlFlow :: Break ( ( ) ) ;
256
+ }
257
+ }
258
+
259
+ true
260
+ } ,
261
+ ExprKind :: Path ( qpath) => {
262
+ // Can't compare a local with another local in a pat
263
+ !matches ! ( cx. qpath_res( & qpath, expr. hir_id) , Res :: Local ( ..) )
264
+ } ,
265
+ ExprKind :: Lit ( ..) => true ,
266
+ _ => false ,
267
+ } {
268
+ return ControlFlow :: Continue ( ( ) ) ;
269
+ }
270
+
271
+ ControlFlow :: Break ( ( ) )
272
+ } )
273
+ . is_none ( )
274
+ }
275
+
276
+ helper ( cx, expr)
277
+ }
0 commit comments