@@ -11,6 +11,7 @@ use rustc_lint::{LateContext, LateLintPass};
1111use rustc_semver:: RustcVersion ;
1212use rustc_session:: impl_lint_pass;
1313use rustc_span:: symbol:: sym;
14+ use rustc_span:: Span ;
1415
1516const ACCEPTABLE_METHODS : [ & [ & str ] ; 5 ] = [
1617 & paths:: BINARYHEAP_ITER ,
@@ -28,6 +29,7 @@ const ACCEPTABLE_TYPES: [(rustc_span::Symbol, Option<RustcVersion>); 7] = [
2829 ( sym:: Vec , None ) ,
2930 ( sym:: VecDeque , None ) ,
3031] ;
32+ const MAP_TYPES : [ rustc_span:: Symbol ; 2 ] = [ sym:: BTreeMap , sym:: HashMap ] ;
3133
3234declare_clippy_lint ! {
3335 /// ### What it does
@@ -44,6 +46,7 @@ declare_clippy_lint! {
4446 /// ```no_run
4547 /// let mut vec = vec![0, 1, 2];
4648 /// vec.retain(|x| x % 2 == 0);
49+ /// vec.retain(|x| x % 2 == 0);
4750 /// ```
4851 #[ clippy:: version = "1.64.0" ]
4952 pub MANUAL_RETAIN ,
@@ -74,9 +77,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
7477 && let Some ( collect_def_id) = cx. typeck_results ( ) . type_dependent_def_id ( collect_expr. hir_id )
7578 && cx. tcx . is_diagnostic_item ( sym:: iterator_collect_fn, collect_def_id)
7679 {
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 ) ;
8083 }
8184 }
8285
@@ -85,9 +88,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
8588
8689fn check_into_iter (
8790 cx : & LateContext < ' _ > ,
88- parent_expr : & hir:: Expr < ' _ > ,
8991 left_expr : & hir:: Expr < ' _ > ,
9092 target_expr : & hir:: Expr < ' _ > ,
93+ parent_expr_span : Span ,
9194 msrv : & Msrv ,
9295) {
9396 if let hir:: ExprKind :: MethodCall ( _, into_iter_expr, [ _] , _) = & target_expr. kind
@@ -98,16 +101,39 @@ fn check_into_iter(
98101 && Some ( into_iter_def_id) == cx. tcx . lang_items ( ) . into_iter_fn ( )
99102 && match_acceptable_type ( cx, left_expr, msrv)
100103 && 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
101108 {
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+ }
103129 }
104130}
105131
106132fn check_iter (
107133 cx : & LateContext < ' _ > ,
108- parent_expr : & hir:: Expr < ' _ > ,
109134 left_expr : & hir:: Expr < ' _ > ,
110135 target_expr : & hir:: Expr < ' _ > ,
136+ parent_expr_span : Span ,
111137 msrv : & Msrv ,
112138) {
113139 if let hir:: ExprKind :: MethodCall ( _, filter_expr, [ ] , _) = & target_expr. kind
@@ -122,16 +148,50 @@ fn check_iter(
122148 && match_acceptable_def_path ( cx, iter_expr_def_id)
123149 && match_acceptable_type ( cx, left_expr, msrv)
124150 && 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
125155 {
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+ }
127187 }
128188}
129189
130190fn check_to_owned (
131191 cx : & LateContext < ' _ > ,
132- parent_expr : & hir:: Expr < ' _ > ,
133192 left_expr : & hir:: Expr < ' _ > ,
134193 target_expr : & hir:: Expr < ' _ > ,
194+ parent_expr_span : Span ,
135195 msrv : & Msrv ,
136196) {
137197 if msrv. meets ( msrvs:: STRING_RETAIN )
@@ -147,43 +207,25 @@ fn check_to_owned(
147207 && let ty = cx. typeck_results ( ) . expr_ty ( str_expr) . peel_refs ( )
148208 && is_type_lang_item ( cx, ty, hir:: LangItem :: String )
149209 && 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 )
159213 && 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(|{}| {})" ,
170221 snippet( cx, left_expr. span, ".." ) ,
222+ snippet( cx, pat. span, ".." ) ,
171223 snippet( cx, filter_body. value. span, ".." )
172- ) ) ,
173- _ => None ,
174- } ,
175- _ => None ,
224+ ) ,
225+ ) ;
176226 }
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
187229 }
188230}
189231
@@ -229,3 +271,20 @@ fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: &Msrv
229271 && acceptable_msrv. map_or ( true , |acceptable_msrv| msrv. meets ( acceptable_msrv) )
230272 } )
231273}
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