@@ -11,6 +11,7 @@ use rustc_lint::{LateContext, LateLintPass};
11
11
use rustc_semver:: RustcVersion ;
12
12
use rustc_session:: impl_lint_pass;
13
13
use rustc_span:: symbol:: sym;
14
+ use rustc_span:: Span ;
14
15
15
16
const ACCEPTABLE_METHODS : [ & [ & str ] ; 5 ] = [
16
17
& paths:: BINARYHEAP_ITER ,
@@ -28,6 +29,7 @@ const ACCEPTABLE_TYPES: [(rustc_span::Symbol, Option<RustcVersion>); 7] = [
28
29
( sym:: Vec , None ) ,
29
30
( sym:: VecDeque , None ) ,
30
31
] ;
32
+ const MAP_TYPES : [ rustc_span:: Symbol ; 2 ] = [ sym:: BTreeMap , sym:: HashMap ] ;
31
33
32
34
declare_clippy_lint ! {
33
35
/// ### What it does
@@ -44,6 +46,7 @@ declare_clippy_lint! {
44
46
/// ```no_run
45
47
/// let mut vec = vec![0, 1, 2];
46
48
/// vec.retain(|x| x % 2 == 0);
49
+ /// vec.retain(|x| x % 2 == 0);
47
50
/// ```
48
51
#[ clippy:: version = "1.64.0" ]
49
52
pub MANUAL_RETAIN ,
@@ -74,9 +77,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
74
77
&& let Some ( collect_def_id) = cx. typeck_results ( ) . type_dependent_def_id ( collect_expr. hir_id )
75
78
&& cx. tcx . is_diagnostic_item ( sym:: iterator_collect_fn, collect_def_id)
76
79
{
77
- check_into_iter ( cx, parent_expr , left_expr, target_expr, & self . msrv ) ;
78
- check_iter ( cx, parent_expr , left_expr, target_expr, & self . msrv ) ;
79
- check_to_owned ( cx, parent_expr , left_expr, target_expr, & self . msrv ) ;
80
+ check_into_iter ( cx, left_expr, target_expr, parent_expr . span , & self . msrv ) ;
81
+ check_iter ( cx, left_expr, target_expr, parent_expr . span , & self . msrv ) ;
82
+ check_to_owned ( cx, left_expr, target_expr, parent_expr . span , & self . msrv ) ;
80
83
}
81
84
}
82
85
@@ -85,9 +88,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
85
88
86
89
fn check_into_iter (
87
90
cx : & LateContext < ' _ > ,
88
- parent_expr : & hir:: Expr < ' _ > ,
89
91
left_expr : & hir:: Expr < ' _ > ,
90
92
target_expr : & hir:: Expr < ' _ > ,
93
+ parent_expr_span : Span ,
91
94
msrv : & Msrv ,
92
95
) {
93
96
if let hir:: ExprKind :: MethodCall ( _, into_iter_expr, [ _] , _) = & target_expr. kind
@@ -98,16 +101,39 @@ fn check_into_iter(
98
101
&& Some ( into_iter_def_id) == cx. tcx . lang_items ( ) . into_iter_fn ( )
99
102
&& match_acceptable_type ( cx, left_expr, msrv)
100
103
&& SpanlessEq :: new ( cx) . eq_expr ( left_expr, struct_expr)
104
+ && let hir:: ExprKind :: MethodCall ( _, _, [ closure_expr] , _) = target_expr. kind
105
+ && let hir:: ExprKind :: Closure ( closure) = closure_expr. kind
106
+ && let filter_body = cx. tcx . hir ( ) . body ( closure. body )
107
+ && let [ filter_params] = filter_body. params
101
108
{
102
- suggest ( cx, parent_expr, left_expr, target_expr) ;
109
+ if match_map_type ( cx, left_expr) {
110
+ if let hir:: PatKind :: Tuple ( [ key_pat, value_pat] , _) = filter_params. pat . kind {
111
+ if let Some ( sugg) = make_sugg ( cx, key_pat, value_pat, left_expr, filter_body) {
112
+ make_span_lint_and_sugg ( cx, parent_expr_span, sugg) ;
113
+ }
114
+ }
115
+ // Cannot lint other cases because `retain` requires two parameters
116
+ } else {
117
+ // Can always move because `retain` and `filter` have the same bound on the predicate
118
+ // for other types
119
+ make_span_lint_and_sugg (
120
+ cx,
121
+ parent_expr_span,
122
+ format ! (
123
+ "{}.retain({})" ,
124
+ snippet( cx, left_expr. span, ".." ) ,
125
+ snippet( cx, closure_expr. span, ".." )
126
+ ) ,
127
+ ) ;
128
+ }
103
129
}
104
130
}
105
131
106
132
fn check_iter (
107
133
cx : & LateContext < ' _ > ,
108
- parent_expr : & hir:: Expr < ' _ > ,
109
134
left_expr : & hir:: Expr < ' _ > ,
110
135
target_expr : & hir:: Expr < ' _ > ,
136
+ parent_expr_span : Span ,
111
137
msrv : & Msrv ,
112
138
) {
113
139
if let hir:: ExprKind :: MethodCall ( _, filter_expr, [ ] , _) = & target_expr. kind
@@ -122,16 +148,50 @@ fn check_iter(
122
148
&& match_acceptable_def_path ( cx, iter_expr_def_id)
123
149
&& match_acceptable_type ( cx, left_expr, msrv)
124
150
&& SpanlessEq :: new ( cx) . eq_expr ( left_expr, struct_expr)
151
+ && let hir:: ExprKind :: MethodCall ( _, _, [ closure_expr] , _) = filter_expr. kind
152
+ && let hir:: ExprKind :: Closure ( closure) = closure_expr. kind
153
+ && let filter_body = cx. tcx . hir ( ) . body ( closure. body )
154
+ && let [ filter_params] = filter_body. params
125
155
{
126
- suggest ( cx, parent_expr, left_expr, filter_expr) ;
156
+ match filter_params. pat . kind {
157
+ // hir::PatKind::Binding(_, _, _, None) => {
158
+ // // Be conservative now. Do nothing here.
159
+ // // TODO: Ideally, we can rewrite the lambda by stripping one level of reference
160
+ // },
161
+ hir:: PatKind :: Tuple ( [ _, _] , _) => {
162
+ // the `&&` reference for the `filter` method will be auto derefed to `ref`
163
+ // so, we can directly use the lambda
164
+ // https://doc.rust-lang.org/reference/patterns.html#binding-modes
165
+ make_span_lint_and_sugg (
166
+ cx,
167
+ parent_expr_span,
168
+ format ! (
169
+ "{}.retain({})" ,
170
+ snippet( cx, left_expr. span, ".." ) ,
171
+ snippet( cx, closure_expr. span, ".." )
172
+ ) ,
173
+ ) ;
174
+ } ,
175
+ hir:: PatKind :: Ref ( pat, _) => make_span_lint_and_sugg (
176
+ cx,
177
+ parent_expr_span,
178
+ format ! (
179
+ "{}.retain(|{}| {})" ,
180
+ snippet( cx, left_expr. span, ".." ) ,
181
+ snippet( cx, pat. span, ".." ) ,
182
+ snippet( cx, filter_body. value. span, ".." )
183
+ ) ,
184
+ ) ,
185
+ _ => { } ,
186
+ }
127
187
}
128
188
}
129
189
130
190
fn check_to_owned (
131
191
cx : & LateContext < ' _ > ,
132
- parent_expr : & hir:: Expr < ' _ > ,
133
192
left_expr : & hir:: Expr < ' _ > ,
134
193
target_expr : & hir:: Expr < ' _ > ,
194
+ parent_expr_span : Span ,
135
195
msrv : & Msrv ,
136
196
) {
137
197
if msrv. meets ( msrvs:: STRING_RETAIN )
@@ -147,43 +207,25 @@ fn check_to_owned(
147
207
&& let ty = cx. typeck_results ( ) . expr_ty ( str_expr) . peel_refs ( )
148
208
&& is_type_lang_item ( cx, ty, hir:: LangItem :: String )
149
209
&& SpanlessEq :: new ( cx) . eq_expr ( left_expr, str_expr)
150
- {
151
- suggest ( cx, parent_expr, left_expr, filter_expr) ;
152
- }
153
- }
154
-
155
- fn suggest ( cx : & LateContext < ' _ > , parent_expr : & hir:: Expr < ' _ > , left_expr : & hir:: Expr < ' _ > , filter_expr : & hir:: Expr < ' _ > ) {
156
- if let hir:: ExprKind :: MethodCall ( _, _, [ closure] , _) = filter_expr. kind
157
- && let hir:: ExprKind :: Closure ( & hir:: Closure { body, .. } ) = closure. kind
158
- && let filter_body = cx. tcx . hir ( ) . body ( body)
210
+ && let hir:: ExprKind :: MethodCall ( _, _, [ closure_expr] , _) = filter_expr. kind
211
+ && let hir:: ExprKind :: Closure ( closure) = closure_expr. kind
212
+ && let filter_body = cx. tcx . hir ( ) . body ( closure. body )
159
213
&& let [ filter_params] = filter_body. params
160
- && let Some ( sugg) = match filter_params. pat . kind {
161
- hir:: PatKind :: Binding ( _, _, filter_param_ident, None ) => Some ( format ! (
162
- "{}.retain(|{filter_param_ident}| {})" ,
163
- snippet( cx, left_expr. span, ".." ) ,
164
- snippet( cx, filter_body. value. span, ".." )
165
- ) ) ,
166
- hir:: PatKind :: Tuple ( [ key_pat, value_pat] , _) => make_sugg ( cx, key_pat, value_pat, left_expr, filter_body) ,
167
- hir:: PatKind :: Ref ( pat, _) => match pat. kind {
168
- hir:: PatKind :: Binding ( _, _, filter_param_ident, None ) => Some ( format ! (
169
- "{}.retain(|{filter_param_ident}| {})" ,
214
+ {
215
+ if let hir:: PatKind :: Ref ( pat, _) = filter_params. pat . kind {
216
+ make_span_lint_and_sugg (
217
+ cx,
218
+ parent_expr_span,
219
+ format ! (
220
+ "{}.retain(|{}| {})" ,
170
221
snippet( cx, left_expr. span, ".." ) ,
222
+ snippet( cx, pat. span, ".." ) ,
171
223
snippet( cx, filter_body. value. span, ".." )
172
- ) ) ,
173
- _ => None ,
174
- } ,
175
- _ => None ,
224
+ ) ,
225
+ ) ;
176
226
}
177
- {
178
- span_lint_and_sugg (
179
- cx,
180
- MANUAL_RETAIN ,
181
- parent_expr. span ,
182
- "this expression can be written more simply using `.retain()`" ,
183
- "consider calling `.retain()` instead" ,
184
- sugg,
185
- Applicability :: MachineApplicable ,
186
- ) ;
227
+ // Be conservative now. Do nothing for the `Binding` case.
228
+ // TODO: Ideally, we can rewrite the lambda by stripping one level of reference
187
229
}
188
230
}
189
231
@@ -229,3 +271,20 @@ fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: &Msrv
229
271
&& acceptable_msrv. map_or ( true , |acceptable_msrv| msrv. meets ( acceptable_msrv) )
230
272
} )
231
273
}
274
+
275
+ fn match_map_type ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > ) -> bool {
276
+ let expr_ty = cx. typeck_results ( ) . expr_ty ( expr) . peel_refs ( ) ;
277
+ MAP_TYPES . iter ( ) . any ( |ty| is_type_diagnostic_item ( cx, expr_ty, * ty) )
278
+ }
279
+
280
+ fn make_span_lint_and_sugg ( cx : & LateContext < ' _ > , span : Span , sugg : String ) {
281
+ span_lint_and_sugg (
282
+ cx,
283
+ MANUAL_RETAIN ,
284
+ span,
285
+ "this expression can be written more simply using `.retain()`" ,
286
+ "consider calling `.retain()` instead" ,
287
+ sugg,
288
+ Applicability :: MachineApplicable ,
289
+ ) ;
290
+ }
0 commit comments