From 721ac284de4b31ba2e4dd05e6afae08596bbc541 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Fri, 7 Mar 2025 15:05:11 +0800 Subject: [PATCH 1/2] fix: `filter_map_bool_then` suggest wrongly when contain return --- .../src/methods/filter_map_bool_then.rs | 27 ++++++---- tests/ui/filter_map_bool_then_unfixable.rs | 52 +++++++++++++++++++ .../ui/filter_map_bool_then_unfixable.stderr | 50 ++++++++++++++++++ 3 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 tests/ui/filter_map_bool_then_unfixable.rs create mode 100644 tests/ui/filter_map_bool_then_unfixable.stderr diff --git a/clippy_lints/src/methods/filter_map_bool_then.rs b/clippy_lints/src/methods/filter_map_bool_then.rs index f7e116c5310e..3461f539b098 100644 --- a/clippy_lints/src/methods/filter_map_bool_then.rs +++ b/clippy_lints/src/methods/filter_map_bool_then.rs @@ -1,8 +1,8 @@ use super::FILTER_MAP_BOOL_THEN; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::is_copy; -use clippy_utils::{is_from_proc_macro, is_trait_method, peel_blocks}; +use clippy_utils::{contains_return, is_from_proc_macro, is_trait_method, peel_blocks}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LintContext}; @@ -44,17 +44,26 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: & && let Some(filter) = recv.span.get_source_text(cx) && let Some(map) = then_body.span.get_source_text(cx) { - span_lint_and_sugg( + span_lint_and_then( cx, FILTER_MAP_BOOL_THEN, call_span, "usage of `bool::then` in `filter_map`", - "use `filter` then `map` instead", - format!( - "filter(|&{param_snippet}| {derefs}{filter}).map(|{param_snippet}| {map})", - derefs = "*".repeat(needed_derefs) - ), - Applicability::MachineApplicable, + |diag| { + if contains_return(recv) { + diag.help("consider using `filter` then `map` instead"); + } else { + diag.span_suggestion( + call_span, + "use `filter` then `map` instead", + format!( + "filter(|&{param_snippet}| {derefs}{filter}).map(|{param_snippet}| {map})", + derefs = "*".repeat(needed_derefs) + ), + Applicability::MachineApplicable, + ); + } + }, ); } } diff --git a/tests/ui/filter_map_bool_then_unfixable.rs b/tests/ui/filter_map_bool_then_unfixable.rs new file mode 100644 index 000000000000..f65b0b66f836 --- /dev/null +++ b/tests/ui/filter_map_bool_then_unfixable.rs @@ -0,0 +1,52 @@ +#![allow(clippy::question_mark, unused)] +#![warn(clippy::filter_map_bool_then)] +//@no-rustfix + +mod issue14368 { + + fn do_something(_: ()) -> bool { + true + } + + fn option_with_early_return(x: &[Option]) { + let _ = x.iter().filter_map(|&x| x?.then(|| do_something(()))); + //~^ filter_map_bool_then + let _ = x + .iter() + .filter_map(|&x| if let Some(x) = x { x } else { return None }.then(|| do_something(()))); + //~^ filter_map_bool_then + let _ = x.iter().filter_map(|&x| { + //~^ filter_map_bool_then + match x { + Some(x) => x, + None => return None, + } + .then(|| do_something(())) + }); + } + + #[derive(Copy, Clone)] + enum Foo { + One(bool), + Two, + Three(Option), + } + + fn nested_type_with_early_return(x: &[Foo]) { + let _ = x.iter().filter_map(|&x| { + //~^ filter_map_bool_then + match x { + Foo::One(x) => x, + Foo::Two => return None, + Foo::Three(inner) => { + if inner? == 0 { + return Some(false); + } else { + true + } + }, + } + .then(|| do_something(())) + }); + } +} diff --git a/tests/ui/filter_map_bool_then_unfixable.stderr b/tests/ui/filter_map_bool_then_unfixable.stderr new file mode 100644 index 000000000000..f72bcac8a750 --- /dev/null +++ b/tests/ui/filter_map_bool_then_unfixable.stderr @@ -0,0 +1,50 @@ +error: usage of `bool::then` in `filter_map` + --> tests/ui/filter_map_bool_then_unfixable.rs:12:26 + | +LL | let _ = x.iter().filter_map(|&x| x?.then(|| do_something(()))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using `filter` then `map` instead + = note: `-D clippy::filter-map-bool-then` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::filter_map_bool_then)]` + +error: usage of `bool::then` in `filter_map` + --> tests/ui/filter_map_bool_then_unfixable.rs:16:14 + | +LL | .filter_map(|&x| if let Some(x) = x { x } else { return None }.then(|| do_something(()))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using `filter` then `map` instead + +error: usage of `bool::then` in `filter_map` + --> tests/ui/filter_map_bool_then_unfixable.rs:18:26 + | +LL | let _ = x.iter().filter_map(|&x| { + | __________________________^ +LL | | +LL | | match x { +LL | | Some(x) => x, +... | +LL | | .then(|| do_something(())) +LL | | }); + | |__________^ + | + = help: consider using `filter` then `map` instead + +error: usage of `bool::then` in `filter_map` + --> tests/ui/filter_map_bool_then_unfixable.rs:36:26 + | +LL | let _ = x.iter().filter_map(|&x| { + | __________________________^ +LL | | +LL | | match x { +LL | | Foo::One(x) => x, +... | +LL | | .then(|| do_something(())) +LL | | }); + | |__________^ + | + = help: consider using `filter` then `map` instead + +error: aborting due to 4 previous errors + From 3f9ecbe341a7d1975d688946e58929402399189c Mon Sep 17 00:00:00 2001 From: yanglsh Date: Tue, 11 Mar 2025 19:44:30 +0800 Subject: [PATCH 2/2] fix: `filter_map_bool_then`: suggests wrongly when mut capture in then --- .../src/methods/filter_map_bool_then.rs | 57 +++++++++++++++++-- tests/ui/filter_map_bool_then_unfixable.rs | 11 ++++ .../ui/filter_map_bool_then_unfixable.stderr | 29 +++++++--- 3 files changed, 85 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/methods/filter_map_bool_then.rs b/clippy_lints/src/methods/filter_map_bool_then.rs index 3461f539b098..965993808f6b 100644 --- a/clippy_lints/src/methods/filter_map_bool_then.rs +++ b/clippy_lints/src/methods/filter_map_bool_then.rs @@ -2,9 +2,13 @@ use super::FILTER_MAP_BOOL_THEN; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::is_copy; -use clippy_utils::{contains_return, is_from_proc_macro, is_trait_method, peel_blocks}; +use clippy_utils::{ + CaptureKind, can_move_expr_to_closure, contains_return, is_from_proc_macro, is_trait_method, peel_blocks, +}; +use rustc_ast::Mutability; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, HirId, Param, Pat}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty::Binder; use rustc_middle::ty::adjustment::Adjust; @@ -50,9 +54,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: & call_span, "usage of `bool::then` in `filter_map`", |diag| { - if contains_return(recv) { - diag.help("consider using `filter` then `map` instead"); - } else { + if can_filter_and_then_move_to_closure(cx, ¶m, recv, then_body) { diag.span_suggestion( call_span, "use `filter` then `map` instead", @@ -62,8 +64,53 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: & ), Applicability::MachineApplicable, ); + } else { + diag.help("consider using `filter` then `map` instead"); } }, ); } } + +/// Returns a set of all bindings found in the given pattern. +fn find_bindings_from_pat(pat: &Pat<'_>) -> FxHashSet { + let mut bindings = FxHashSet::default(); + pat.walk(|p| { + if let rustc_hir::PatKind::Binding(_, hir_id, _, _) = p.kind { + bindings.insert(hir_id); + } + true + }); + bindings +} + +/// Returns true if we can take a closure parameter and have it in both the `filter` function and +/// the`map` function. This is not the case if: +/// +/// - The `filter` would contain an early return, +/// - `filter` and `then` contain captures, and any of those are &mut +fn can_filter_and_then_move_to_closure<'tcx>( + cx: &LateContext<'tcx>, + param: &Param<'tcx>, + filter: &'tcx Expr<'tcx>, + then: &'tcx Expr<'tcx>, +) -> bool { + if contains_return(filter) { + return false; + } + + let Some(filter_captures) = can_move_expr_to_closure(cx, filter) else { + return true; + }; + let Some(then_captures) = can_move_expr_to_closure(cx, then) else { + return true; + }; + + let param_bindings = find_bindings_from_pat(param.pat); + filter_captures.iter().all(|(hir_id, filter_cap)| { + param_bindings.contains(hir_id) + || !then_captures + .get(hir_id) + .is_some_and(|then_cap| matches!(*filter_cap | *then_cap, CaptureKind::Ref(Mutability::Mut))) + }) +} diff --git a/tests/ui/filter_map_bool_then_unfixable.rs b/tests/ui/filter_map_bool_then_unfixable.rs index f65b0b66f836..68294292502a 100644 --- a/tests/ui/filter_map_bool_then_unfixable.rs +++ b/tests/ui/filter_map_bool_then_unfixable.rs @@ -2,6 +2,17 @@ #![warn(clippy::filter_map_bool_then)] //@no-rustfix +fn issue11617() { + let mut x: Vec = vec![0; 10]; + let _ = (0..x.len()).zip(x.clone().iter()).filter_map(|(i, v)| { + //~^ filter_map_bool_then + (x[i] != *v).then(|| { + x[i] = i; + i + }) + }); +} + mod issue14368 { fn do_something(_: ()) -> bool { diff --git a/tests/ui/filter_map_bool_then_unfixable.stderr b/tests/ui/filter_map_bool_then_unfixable.stderr index f72bcac8a750..2025958136ba 100644 --- a/tests/ui/filter_map_bool_then_unfixable.stderr +++ b/tests/ui/filter_map_bool_then_unfixable.stderr @@ -1,15 +1,30 @@ error: usage of `bool::then` in `filter_map` - --> tests/ui/filter_map_bool_then_unfixable.rs:12:26 + --> tests/ui/filter_map_bool_then_unfixable.rs:7:48 | -LL | let _ = x.iter().filter_map(|&x| x?.then(|| do_something(()))); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _ = (0..x.len()).zip(x.clone().iter()).filter_map(|(i, v)| { + | ________________________________________________^ +LL | | +LL | | (x[i] != *v).then(|| { +LL | | x[i] = i; +LL | | i +LL | | }) +LL | | }); + | |______^ | = help: consider using `filter` then `map` instead = note: `-D clippy::filter-map-bool-then` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::filter_map_bool_then)]` error: usage of `bool::then` in `filter_map` - --> tests/ui/filter_map_bool_then_unfixable.rs:16:14 + --> tests/ui/filter_map_bool_then_unfixable.rs:23:26 + | +LL | let _ = x.iter().filter_map(|&x| x?.then(|| do_something(()))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using `filter` then `map` instead + +error: usage of `bool::then` in `filter_map` + --> tests/ui/filter_map_bool_then_unfixable.rs:27:14 | LL | .filter_map(|&x| if let Some(x) = x { x } else { return None }.then(|| do_something(()))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -17,7 +32,7 @@ LL | .filter_map(|&x| if let Some(x) = x { x } else { return None }. = help: consider using `filter` then `map` instead error: usage of `bool::then` in `filter_map` - --> tests/ui/filter_map_bool_then_unfixable.rs:18:26 + --> tests/ui/filter_map_bool_then_unfixable.rs:29:26 | LL | let _ = x.iter().filter_map(|&x| { | __________________________^ @@ -32,7 +47,7 @@ LL | | }); = help: consider using `filter` then `map` instead error: usage of `bool::then` in `filter_map` - --> tests/ui/filter_map_bool_then_unfixable.rs:36:26 + --> tests/ui/filter_map_bool_then_unfixable.rs:47:26 | LL | let _ = x.iter().filter_map(|&x| { | __________________________^ @@ -46,5 +61,5 @@ LL | | }); | = help: consider using `filter` then `map` instead -error: aborting due to 4 previous errors +error: aborting due to 5 previous errors