From 860e800fa08734ff9d15a67115f8220e5fa2202e Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Sat, 16 Sep 2023 14:14:51 +0200 Subject: [PATCH 1/2] [`filter_map_bool_then`]: peel as many refs as needed --- clippy_lints/src/methods/filter_map_bool_then.rs | 15 +++++++++++---- tests/ui/filter_map_bool_then.fixed | 8 ++++++++ tests/ui/filter_map_bool_then.rs | 8 ++++++++ tests/ui/filter_map_bool_then.stderr | 14 +++++++++++++- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/methods/filter_map_bool_then.rs b/clippy_lints/src/methods/filter_map_bool_then.rs index ac5bff6fee0..dd4aa276663 100644 --- a/clippy_lints/src/methods/filter_map_bool_then.rs +++ b/clippy_lints/src/methods/filter_map_bool_then.rs @@ -2,13 +2,13 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::paths::BOOL_THEN; use clippy_utils::source::snippet_opt; -use clippy_utils::ty::is_copy; +use clippy_utils::ty::{is_copy, peel_mid_ty_refs_is_mutable}; use clippy_utils::{is_from_proc_macro, is_trait_method, match_def_path, peel_blocks}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, Binder}; +use rustc_middle::ty::Binder; use rustc_span::{sym, Span}; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &Expr<'_>, call_span: Span) { @@ -36,7 +36,11 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: & && let Some(def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id) && match_def_path(cx, def_id, &BOOL_THEN) && !is_from_proc_macro(cx, expr) - && let ref_bool = matches!(cx.typeck_results().expr_ty(recv).kind(), ty::Ref(..)) + // Peel all refs (e.g. `&&&&mut &&&bool` -> `bool`) and get its count so we can suggest the exact + // amount of derefs to get to the bool in the filter. + // `peel_mid_ty_refs` alone doesn't handle mutable reference, so we use `_is_mutable` + // instead which counts them too and just ignore the resulting mutability + && let (_, needed_derefs, _) = peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(recv)) && let Some(param_snippet) = snippet_opt(cx, param.span) && let Some(filter) = snippet_opt(cx, recv.span) && let Some(map) = snippet_opt(cx, then_body.span) @@ -47,7 +51,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: & call_span, "usage of `bool::then` in `filter_map`", "use `filter` then `map` instead", - format!("filter(|&{param_snippet}| {}{filter}).map(|{param_snippet}| {map})", if ref_bool { "*" } else { "" }), + 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.fixed b/tests/ui/filter_map_bool_then.fixed index 67b984ce025..d1c946585f5 100644 --- a/tests/ui/filter_map_bool_then.fixed +++ b/tests/ui/filter_map_bool_then.fixed @@ -59,4 +59,12 @@ fn issue11309<'a>(iter: impl Iterator) -> Vec<&'a str fn issue11503() { let bools: &[bool] = &[true, false, false, true]; let _: Vec = bools.iter().enumerate().filter(|&(i, b)| *b).map(|(i, b)| i).collect(); + + // Need to insert multiple derefs if there is more than one layer of references + let bools: &[&&bool] = &[&&true, &&false, &&false, &&true]; + let _: Vec = bools.iter().enumerate().filter(|&(i, b)| ***b).map(|(i, b)| i).collect(); + + // Should also suggest derefs when going through a mutable reference + let bools: &[&mut bool] = &[&mut true]; + let _: Vec = bools.iter().enumerate().filter(|&(i, b)| **b).map(|(i, b)| i).collect(); } diff --git a/tests/ui/filter_map_bool_then.rs b/tests/ui/filter_map_bool_then.rs index b716fd70fa0..2d0ed282013 100644 --- a/tests/ui/filter_map_bool_then.rs +++ b/tests/ui/filter_map_bool_then.rs @@ -59,4 +59,12 @@ fn issue11309<'a>(iter: impl Iterator) -> Vec<&'a str fn issue11503() { let bools: &[bool] = &[true, false, false, true]; let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); + + // Need to insert multiple derefs if there is more than one layer of references + let bools: &[&&bool] = &[&&true, &&false, &&false, &&true]; + let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); + + // Should also suggest derefs when going through a mutable reference + let bools: &[&mut bool] = &[&mut true]; + let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); } diff --git a/tests/ui/filter_map_bool_then.stderr b/tests/ui/filter_map_bool_then.stderr index 1fee93afb36..174786ff32e 100644 --- a/tests/ui/filter_map_bool_then.stderr +++ b/tests/ui/filter_map_bool_then.stderr @@ -43,5 +43,17 @@ error: usage of `bool::then` in `filter_map` LL | let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&(i, b)| *b).map(|(i, b)| i)` -error: aborting due to 7 previous errors +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:65:50 + | +LL | let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&(i, b)| ***b).map(|(i, b)| i)` + +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:69:50 + | +LL | let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&(i, b)| **b).map(|(i, b)| i)` + +error: aborting due to 9 previous errors From 2ec6f3b1ed22c3fa0b20eda5cf335c54f81b4462 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Sat, 16 Sep 2023 15:13:44 +0200 Subject: [PATCH 2/2] also count derefs through custom `Deref` impls --- clippy_lints/src/methods/filter_map_bool_then.rs | 13 +++++++------ tests/ui/filter_map_bool_then.fixed | 11 +++++++++++ tests/ui/filter_map_bool_then.rs | 11 +++++++++++ tests/ui/filter_map_bool_then.stderr | 8 +++++++- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/methods/filter_map_bool_then.rs b/clippy_lints/src/methods/filter_map_bool_then.rs index dd4aa276663..33657254965 100644 --- a/clippy_lints/src/methods/filter_map_bool_then.rs +++ b/clippy_lints/src/methods/filter_map_bool_then.rs @@ -2,12 +2,13 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::paths::BOOL_THEN; use clippy_utils::source::snippet_opt; -use clippy_utils::ty::{is_copy, peel_mid_ty_refs_is_mutable}; +use clippy_utils::ty::is_copy; use clippy_utils::{is_from_proc_macro, is_trait_method, match_def_path, peel_blocks}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::adjustment::Adjust; use rustc_middle::ty::Binder; use rustc_span::{sym, Span}; @@ -36,11 +37,11 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: & && let Some(def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id) && match_def_path(cx, def_id, &BOOL_THEN) && !is_from_proc_macro(cx, expr) - // Peel all refs (e.g. `&&&&mut &&&bool` -> `bool`) and get its count so we can suggest the exact - // amount of derefs to get to the bool in the filter. - // `peel_mid_ty_refs` alone doesn't handle mutable reference, so we use `_is_mutable` - // instead which counts them too and just ignore the resulting mutability - && let (_, needed_derefs, _) = peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(recv)) + // Count the number of derefs needed to get to the bool because we need those in the suggestion + && let needed_derefs = cx.typeck_results().expr_adjustments(recv) + .iter() + .filter(|adj| matches!(adj.kind, Adjust::Deref(_))) + .count() && let Some(param_snippet) = snippet_opt(cx, param.span) && let Some(filter) = snippet_opt(cx, recv.span) && let Some(map) = snippet_opt(cx, then_body.span) diff --git a/tests/ui/filter_map_bool_then.fixed b/tests/ui/filter_map_bool_then.fixed index d1c946585f5..6a1b81fdbcb 100644 --- a/tests/ui/filter_map_bool_then.fixed +++ b/tests/ui/filter_map_bool_then.fixed @@ -67,4 +67,15 @@ fn issue11503() { // Should also suggest derefs when going through a mutable reference let bools: &[&mut bool] = &[&mut true]; let _: Vec = bools.iter().enumerate().filter(|&(i, b)| **b).map(|(i, b)| i).collect(); + + // Should also suggest derefs when going through a custom deref + struct DerefToBool; + impl std::ops::Deref for DerefToBool { + type Target = bool; + fn deref(&self) -> &Self::Target { + &true + } + } + let bools: &[&&DerefToBool] = &[&&DerefToBool]; + let _: Vec = bools.iter().enumerate().filter(|&(i, b)| ****b).map(|(i, b)| i).collect(); } diff --git a/tests/ui/filter_map_bool_then.rs b/tests/ui/filter_map_bool_then.rs index 2d0ed282013..a41e88f8805 100644 --- a/tests/ui/filter_map_bool_then.rs +++ b/tests/ui/filter_map_bool_then.rs @@ -67,4 +67,15 @@ fn issue11503() { // Should also suggest derefs when going through a mutable reference let bools: &[&mut bool] = &[&mut true]; let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); + + // Should also suggest derefs when going through a custom deref + struct DerefToBool; + impl std::ops::Deref for DerefToBool { + type Target = bool; + fn deref(&self) -> &Self::Target { + &true + } + } + let bools: &[&&DerefToBool] = &[&&DerefToBool]; + let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); } diff --git a/tests/ui/filter_map_bool_then.stderr b/tests/ui/filter_map_bool_then.stderr index 174786ff32e..fab6987913a 100644 --- a/tests/ui/filter_map_bool_then.stderr +++ b/tests/ui/filter_map_bool_then.stderr @@ -55,5 +55,11 @@ error: usage of `bool::then` in `filter_map` LL | let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&(i, b)| **b).map(|(i, b)| i)` -error: aborting due to 9 previous errors +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:80:50 + | +LL | let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&(i, b)| ****b).map(|(i, b)| i)` + +error: aborting due to 10 previous errors