Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit b5a2192

Browse files
committed
fix: incorrect suggestions generated by manual_retain lint
1 parent 9eb2b22 commit b5a2192

File tree

4 files changed

+331
-64
lines changed

4 files changed

+331
-64
lines changed

clippy_lints/src/manual_retain.rs

Lines changed: 100 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use rustc_lint::{LateContext, LateLintPass};
1111
use rustc_semver::RustcVersion;
1212
use rustc_session::impl_lint_pass;
1313
use rustc_span::symbol::sym;
14+
use rustc_span::Span;
1415

1516
const 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

3234
declare_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

8689
fn 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

106132
fn 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

130190
fn 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+
}

tests/ui/manual_retain.fixed

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ fn main() {
1414
_msrv_153();
1515
_msrv_126();
1616
_msrv_118();
17+
18+
issue_10393();
19+
issue_12081();
1720
}
1821

1922
fn binary_heap_retain() {
@@ -23,6 +26,11 @@ fn binary_heap_retain() {
2326
binary_heap.retain(|x| x % 2 == 0);
2427
binary_heap.retain(|x| x % 2 == 0);
2528

29+
// Do lint, because we use pattern matching
30+
let mut tuples = BinaryHeap::from([(0, 1), (1, 2), (2, 3)]);
31+
tuples.retain(|(ref x, ref y)| *x == 0);
32+
tuples.retain(|(x, y)| *x == 0);
33+
2634
// Do not lint, because type conversion is performed
2735
binary_heap = binary_heap
2836
.into_iter()
@@ -55,6 +63,9 @@ fn btree_map_retain() {
5563
btree_map.retain(|_, &mut v| v % 2 == 0);
5664
btree_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0));
5765

66+
// Do not lint, because the parameters are not matched in tuple pattern
67+
btree_map = btree_map.into_iter().filter(|t| t.0 % 2 == 0).collect();
68+
5869
// Do not lint.
5970
btree_map = btree_map
6071
.into_iter()
@@ -76,6 +87,11 @@ fn btree_set_retain() {
7687
btree_set.retain(|x| x % 2 == 0);
7788
btree_set.retain(|x| x % 2 == 0);
7889

90+
// Do lint, because we use pattern matching
91+
let mut tuples = BTreeSet::from([(0, 1), (1, 2), (2, 3)]);
92+
tuples.retain(|(ref x, ref y)| *x == 0);
93+
tuples.retain(|(x, y)| *x == 0);
94+
7995
// Do not lint, because type conversion is performed
8096
btree_set = btree_set
8197
.iter()
@@ -108,6 +124,9 @@ fn hash_map_retain() {
108124
hash_map.retain(|_, &mut v| v % 2 == 0);
109125
hash_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0));
110126

127+
// Do not lint, because the parameters are not matched in tuple pattern
128+
hash_map = hash_map.into_iter().filter(|t| t.0 % 2 == 0).collect();
129+
111130
// Do not lint.
112131
hash_map = hash_map
113132
.into_iter()
@@ -128,6 +147,11 @@ fn hash_set_retain() {
128147
hash_set.retain(|x| x % 2 == 0);
129148
hash_set.retain(|x| x % 2 == 0);
130149

150+
// Do lint, because we use pattern matching
151+
let mut tuples = HashSet::from([(0, 1), (1, 2), (2, 3)]);
152+
tuples.retain(|(ref x, ref y)| *x == 0);
153+
tuples.retain(|(x, y)| *x == 0);
154+
131155
// Do not lint, because type conversion is performed
132156
hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect::<HashSet<i8>>();
133157
hash_set = hash_set
@@ -157,6 +181,9 @@ fn string_retain() {
157181
// Do lint.
158182
s.retain(|c| c != 'o');
159183

184+
// Do not lint, because we need to rewrite the lambda
185+
s = s.chars().filter(|c| *c != 'o').to_owned().collect();
186+
160187
// Do not lint, because this expression is not assign.
161188
let mut bar: String = s.chars().filter(|&c| c != 'o').to_owned().collect();
162189

@@ -171,6 +198,11 @@ fn vec_retain() {
171198
vec.retain(|x| x % 2 == 0);
172199
vec.retain(|x| x % 2 == 0);
173200

201+
// Do lint, because we use pattern matching
202+
let mut tuples = vec![(0, 1), (1, 2), (2, 3)];
203+
tuples.retain(|(ref x, ref y)| *x == 0);
204+
tuples.retain(|(x, y)| *x == 0);
205+
174206
// Do not lint, because type conversion is performed
175207
vec = vec.into_iter().filter(|x| x % 2 == 0).collect::<Vec<i8>>();
176208
vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect::<Vec<i8>>();
@@ -246,3 +278,27 @@ fn _msrv_118() {
246278
let mut hash_map: HashMap<i8, i8> = (0..8).map(|x| (x, x * 10)).collect();
247279
hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect();
248280
}
281+
282+
fn issue_10393() {
283+
// Do lint
284+
let mut vec = vec![(0, 1), (1, 2), (2, 3)];
285+
vec.retain(|(x, y)| *x == 0);
286+
287+
// Do lint
288+
let mut tuples = vec![(true, -2), (false, 3)];
289+
tuples.retain(|(_, n)| *n > 0);
290+
}
291+
292+
fn issue_12081() {
293+
let mut vec = vec![0, 1, 2];
294+
295+
// Do lint
296+
vec.retain(|&x| x == 0);
297+
vec.retain(|&x| x == 0);
298+
vec.retain(|&x| x == 0);
299+
300+
// Do lint
301+
vec.retain(|x| *x == 0);
302+
vec.retain(|x| *x == 0);
303+
vec.retain(|x| *x == 0);
304+
}

0 commit comments

Comments
 (0)