Skip to content

Commit 3f9ecbe

Browse files
committed
fix: filter_map_bool_then: suggests wrongly when mut capture in then
1 parent 721ac28 commit 3f9ecbe

File tree

3 files changed

+85
-12
lines changed

3 files changed

+85
-12
lines changed

clippy_lints/src/methods/filter_map_bool_then.rs

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ use super::FILTER_MAP_BOOL_THEN;
22
use clippy_utils::diagnostics::span_lint_and_then;
33
use clippy_utils::source::SpanRangeExt;
44
use clippy_utils::ty::is_copy;
5-
use clippy_utils::{contains_return, is_from_proc_macro, is_trait_method, peel_blocks};
5+
use clippy_utils::{
6+
CaptureKind, can_move_expr_to_closure, contains_return, is_from_proc_macro, is_trait_method, peel_blocks,
7+
};
8+
use rustc_ast::Mutability;
9+
use rustc_data_structures::fx::FxHashSet;
610
use rustc_errors::Applicability;
7-
use rustc_hir::{Expr, ExprKind};
11+
use rustc_hir::{Expr, ExprKind, HirId, Param, Pat};
812
use rustc_lint::{LateContext, LintContext};
913
use rustc_middle::ty::Binder;
1014
use rustc_middle::ty::adjustment::Adjust;
@@ -50,9 +54,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &
5054
call_span,
5155
"usage of `bool::then` in `filter_map`",
5256
|diag| {
53-
if contains_return(recv) {
54-
diag.help("consider using `filter` then `map` instead");
55-
} else {
57+
if can_filter_and_then_move_to_closure(cx, &param, recv, then_body) {
5658
diag.span_suggestion(
5759
call_span,
5860
"use `filter` then `map` instead",
@@ -62,8 +64,53 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &
6264
),
6365
Applicability::MachineApplicable,
6466
);
67+
} else {
68+
diag.help("consider using `filter` then `map` instead");
6569
}
6670
},
6771
);
6872
}
6973
}
74+
75+
/// Returns a set of all bindings found in the given pattern.
76+
fn find_bindings_from_pat(pat: &Pat<'_>) -> FxHashSet<HirId> {
77+
let mut bindings = FxHashSet::default();
78+
pat.walk(|p| {
79+
if let rustc_hir::PatKind::Binding(_, hir_id, _, _) = p.kind {
80+
bindings.insert(hir_id);
81+
}
82+
true
83+
});
84+
bindings
85+
}
86+
87+
/// Returns true if we can take a closure parameter and have it in both the `filter` function and
88+
/// the`map` function. This is not the case if:
89+
///
90+
/// - The `filter` would contain an early return,
91+
/// - `filter` and `then` contain captures, and any of those are &mut
92+
fn can_filter_and_then_move_to_closure<'tcx>(
93+
cx: &LateContext<'tcx>,
94+
param: &Param<'tcx>,
95+
filter: &'tcx Expr<'tcx>,
96+
then: &'tcx Expr<'tcx>,
97+
) -> bool {
98+
if contains_return(filter) {
99+
return false;
100+
}
101+
102+
let Some(filter_captures) = can_move_expr_to_closure(cx, filter) else {
103+
return true;
104+
};
105+
let Some(then_captures) = can_move_expr_to_closure(cx, then) else {
106+
return true;
107+
};
108+
109+
let param_bindings = find_bindings_from_pat(param.pat);
110+
filter_captures.iter().all(|(hir_id, filter_cap)| {
111+
param_bindings.contains(hir_id)
112+
|| !then_captures
113+
.get(hir_id)
114+
.is_some_and(|then_cap| matches!(*filter_cap | *then_cap, CaptureKind::Ref(Mutability::Mut)))
115+
})
116+
}

tests/ui/filter_map_bool_then_unfixable.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
#![warn(clippy::filter_map_bool_then)]
33
//@no-rustfix
44

5+
fn issue11617() {
6+
let mut x: Vec<usize> = vec![0; 10];
7+
let _ = (0..x.len()).zip(x.clone().iter()).filter_map(|(i, v)| {
8+
//~^ filter_map_bool_then
9+
(x[i] != *v).then(|| {
10+
x[i] = i;
11+
i
12+
})
13+
});
14+
}
15+
516
mod issue14368 {
617

718
fn do_something(_: ()) -> bool {

tests/ui/filter_map_bool_then_unfixable.stderr

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,38 @@
11
error: usage of `bool::then` in `filter_map`
2-
--> tests/ui/filter_map_bool_then_unfixable.rs:12:26
2+
--> tests/ui/filter_map_bool_then_unfixable.rs:7:48
33
|
4-
LL | let _ = x.iter().filter_map(|&x| x?.then(|| do_something(())));
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
LL | let _ = (0..x.len()).zip(x.clone().iter()).filter_map(|(i, v)| {
5+
| ________________________________________________^
6+
LL | |
7+
LL | | (x[i] != *v).then(|| {
8+
LL | | x[i] = i;
9+
LL | | i
10+
LL | | })
11+
LL | | });
12+
| |______^
613
|
714
= help: consider using `filter` then `map` instead
815
= note: `-D clippy::filter-map-bool-then` implied by `-D warnings`
916
= help: to override `-D warnings` add `#[allow(clippy::filter_map_bool_then)]`
1017

1118
error: usage of `bool::then` in `filter_map`
12-
--> tests/ui/filter_map_bool_then_unfixable.rs:16:14
19+
--> tests/ui/filter_map_bool_then_unfixable.rs:23:26
20+
|
21+
LL | let _ = x.iter().filter_map(|&x| x?.then(|| do_something(())));
22+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
23+
|
24+
= help: consider using `filter` then `map` instead
25+
26+
error: usage of `bool::then` in `filter_map`
27+
--> tests/ui/filter_map_bool_then_unfixable.rs:27:14
1328
|
1429
LL | .filter_map(|&x| if let Some(x) = x { x } else { return None }.then(|| do_something(())));
1530
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1631
|
1732
= help: consider using `filter` then `map` instead
1833

1934
error: usage of `bool::then` in `filter_map`
20-
--> tests/ui/filter_map_bool_then_unfixable.rs:18:26
35+
--> tests/ui/filter_map_bool_then_unfixable.rs:29:26
2136
|
2237
LL | let _ = x.iter().filter_map(|&x| {
2338
| __________________________^
@@ -32,7 +47,7 @@ LL | | });
3247
= help: consider using `filter` then `map` instead
3348

3449
error: usage of `bool::then` in `filter_map`
35-
--> tests/ui/filter_map_bool_then_unfixable.rs:36:26
50+
--> tests/ui/filter_map_bool_then_unfixable.rs:47:26
3651
|
3752
LL | let _ = x.iter().filter_map(|&x| {
3853
| __________________________^
@@ -46,5 +61,5 @@ LL | | });
4661
|
4762
= help: consider using `filter` then `map` instead
4863

49-
error: aborting due to 4 previous errors
64+
error: aborting due to 5 previous errors
5065

0 commit comments

Comments
 (0)