Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a lint for .clone().into_iter() #13595

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6059,6 +6059,7 @@ Released 2018-09-13
[`unnecessary_box_returns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
[`unnecessary_clippy_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_clippy_cfg
[`unnecessary_collection_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_collection_clone
[`unnecessary_fallible_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fallible_conversions
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::methods::TYPE_ID_ON_BOX_INFO,
crate::methods::UNINIT_ASSUMED_INIT_INFO,
crate::methods::UNIT_HASH_INFO,
crate::methods::UNNECESSARY_COLLECTION_CLONE_INFO,
crate::methods::UNNECESSARY_FALLIBLE_CONVERSIONS_INFO,
crate::methods::UNNECESSARY_FILTER_MAP_INFO,
crate::methods::UNNECESSARY_FIND_MAP_INFO,
Expand Down
36 changes: 36 additions & 0 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ mod suspicious_to_owned;
mod type_id_on_box;
mod uninit_assumed_init;
mod unit_hash;
mod unnecessary_collection_clone;
mod unnecessary_fallible_conversions;
mod unnecessary_filter_map;
mod unnecessary_first_then_check;
Expand Down Expand Up @@ -4253,6 +4254,36 @@ declare_clippy_lint! {
"map of a trivial closure (not dependent on parameter) over a range"
}

declare_clippy_lint! {
/// ### What it does
///
/// Detects when an entire collection is being cloned eagerly, instead of each item lazily.
///
/// ### Why is this bad?
///
/// Cloning a collection requires allocating space for all elements and cloning each element into this new space,
/// whereas using `Iterator::cloned` does not allocate any more space and only requires cloning each element as they are consumed.
Comment on lines +4264 to +4265
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this suggestion is always better. Have you benchmarked that individual clones are actually faster, or at least equivalent?

With a linked list (I know everyone's favorite structure) it might be legit better to clone it, thereby allocating all nodes close together and loading everything into cache.

This is not an argument against the lint, I still think it's a good idea, just a question about the category

Copy link
Contributor Author

@GnomedDev GnomedDev Nov 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't benchmarked this, and I may be falling into the "it's somewhat obvious" trap. It's possible we could maintain a list of collections it would be quicker to do this, or even have a #[clippy::iter_cloned_instead_of_clone)] to mark collections that would benefit from this lint. But it will be quite the low FP rate for collections that are faster to clone than iterate and clone.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this highly depends on the iterator usage. For a vector, it makes a difference of allocating a big chunk of memory once against several small allocations. For vectors it might actually be the other way around (meaning .clone().into_iter() being faster), but this is just guesstimating, since I haven't benchmarked this.

One thing that's safe to say, is that cloning each item individually will reduce the memory footprint, but only if the items are dropped and not stored somewhere afterward.

Could you try to benchmark this?

I've just checked the issue, and that one was suggestion to lint on cloned().into_iter().filter(). Linting on that should be safe again

///
/// ### Example
/// ```no_run
/// fn process_string(val: String) -> String { val }
/// fn process_strings(strings: &Vec<String>) -> Vec<String> {
/// strings.clone().into_iter().filter(|s| s.len() < 10).map(process_string).collect()
/// }
/// ```
/// Use instead:
/// ```no_run
/// fn process_string(val: String) -> String { val }
/// fn process_strings(strings: &Vec<String>) -> Vec<String> {
/// strings.iter().cloned().filter(|s| s.len() < 10).map(process_string).collect()
/// }
/// ```
#[clippy::version = "1.84.0"]
pub UNNECESSARY_COLLECTION_CLONE,
perf,
"calling `.clone().into_iter()` instead of `.iter().cloned()`"
}

pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
Expand Down Expand Up @@ -4417,6 +4448,7 @@ impl_lint_pass!(Methods => [
NEEDLESS_AS_BYTES,
MAP_ALL_ANY_IDENTITY,
MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES,
UNNECESSARY_COLLECTION_CLONE,
]);

/// Extracts a method call name, args, and `Span` of the method name.
Expand Down Expand Up @@ -4879,6 +4911,10 @@ impl Methods {
("is_none", []) => check_is_some_is_none(cx, expr, recv, call_span, false),
("is_some", []) => check_is_some_is_none(cx, expr, recv, call_span, true),
("iter" | "iter_mut" | "into_iter", []) => {
if name == "into_iter" {
unnecessary_collection_clone::check(cx, expr, recv);
}

iter_on_single_or_empty_collections::check(cx, expr, name, recv);
},
("join", [join_arg]) => {
Expand Down
59 changes: 59 additions & 0 deletions clippy_lints/src/methods/unnecessary_collection_clone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_path_mutable;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{deref_chain, get_inherent_method, implements_trait, make_normalized_projection};
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_span::sym;

use super::{UNNECESSARY_COLLECTION_CLONE, method_call};

// FIXME: This does not check if the iter method is actually compatible with the replacement, but
// you have to be actively evil to have an `IntoIterator` impl that returns one type and an `iter`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this comment xD

I wonder if there is actually a reasonable use case for this, but let's not open that can of worms ^^

// method that returns something other than references of that type.... and it is a massive
// complicated hassle to check this
fn has_iter_method<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
deref_chain(cx, ty).any(|ty| match ty.kind() {
ty::Adt(adt_def, _) => get_inherent_method(cx, adt_def.did(), sym::iter).is_some(),
ty::Slice(_) => true,
_ => false,
})
}

/// Check for `x.clone().into_iter()` to suggest `x.iter().cloned()`.
// ^^^^^^^^^ is recv
// ^^^^^^^^^^^^^^^^^^^^^ is expr
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>) {
let typeck_results = cx.typeck_results();
let diagnostic_items = cx.tcx.all_diagnostic_items(());

// If the call before `into_iter` is `.clone()`
if let Some(("clone", collection_expr, [], _, _)) = method_call(recv)
// and the binding being cloned is not mutable
&& let Some(false) = is_path_mutable(cx, collection_expr)
// and the result of `into_iter` is an Iterator
&& let Some(&iterator_def_id) = diagnostic_items.name_to_id.get(&sym::Iterator)
&& let expr_ty = typeck_results.expr_ty(expr)
&& implements_trait(cx, expr_ty, iterator_def_id, &[])
// with an item that implements clone
&& let Some(&clone_def_id) = diagnostic_items.name_to_id.get(&sym::Clone)
&& let Some(item_ty) = make_normalized_projection(cx.tcx, cx.param_env, iterator_def_id, sym::Item, [expr_ty])
&& implements_trait(cx, item_ty, clone_def_id, &[])
// and the type has an `iter` method
&& has_iter_method(cx, typeck_results.expr_ty(collection_expr))
{
let mut applicability = Applicability::MachineApplicable;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is this lint trigger from Lintcheck:

Added clippy::unnecessary_collection_clone at reqwest-0.12.5/src/dns/resolve.rs:100

warning: using clone on collection to own iterated items
   --> target/lintcheck/sources/reqwest-0.12.5/src/dns/resolve.rs:100:45
    |
100 |                 let addrs: Addrs = Box::new(dest.clone().into_iter());
    |                                             ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `dest.iter().cloned()`
    |
    = note: `--force-warn clippy::unnecessary-collection-clone` implied by `--force-warn clippy::all`

These cases might not be MachineApplicable as they change the type of the expression. To fully avoid FPs, we probably would need to track how the value is finally used, which would be hard and also prone to errors. I think this is an argument for changing the Applicability to MaybeIncorrect and having it in pedantic instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that's a shame, I hate relegating useful lints to pedantic where they won't be hit by the beginners this lint is targeting.

Would it be good if I swap the suggestion to MaybeIncorrect but keep in the perf category? Since we now have lint reasons, it would be good for a codebase to document "we don't do Iter::cloned due to XYZ" with an allow/expect.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The applicability is only given to tools like rustfix and not directly visible to the user. So changing the applicability, shouldn't have an effect on the lint category.

The category usually depends on the FP rate and the trigger rate when we introduce the lint. If a lint is reasonable, but has too many lint triggers, it will sadly still often land in pedantic. We can leave the lint group discussion open for the RFC. But the applicability should still be changed.

let collection_expr_snippet = snippet_with_applicability(cx, collection_expr.span, "...", &mut applicability);
span_lint_and_sugg(
cx,
UNNECESSARY_COLLECTION_CLONE,
expr.span,
"using clone on collection to own iterated items",
"replace with",
format!("{collection_expr_snippet}.iter().cloned()"),
applicability,
);
}
}
26 changes: 8 additions & 18 deletions clippy_lints/src/methods/unnecessary_iter_cloned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use clippy_utils::higher::ForLoop;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::{get_iterator_item_ty, implements_trait};
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{can_mut_borrow_both, fn_def_id, get_parent_expr, path_to_local};
use clippy_utils::{can_mut_borrow_both, fn_def_id, get_parent_expr, is_path_mutable};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::{BindingMode, Expr, ExprKind, Node, PatKind};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::{Symbol, sym};

Expand Down Expand Up @@ -42,22 +42,7 @@ pub fn check_for_loop_iter(
&& !clone_or_copy_needed
&& let Some(receiver_snippet) = receiver.span.get_source_text(cx)
{
// Issue 12098
// https://github.com/rust-lang/rust-clippy/issues/12098
// if the assignee have `mut borrow` conflict with the iteratee
// the lint should not execute, former didn't consider the mut case

// check whether `expr` is mutable
fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let Some(hir_id) = path_to_local(expr)
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
{
matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..))
} else {
true
}
}

fn is_caller_or_fields_change(cx: &LateContext<'_>, body: &Expr<'_>, caller: &Expr<'_>) -> bool {
let mut change = false;
if let ExprKind::Block(block, ..) = body.kind {
Expand All @@ -82,7 +67,12 @@ pub fn check_for_loop_iter(
while let ExprKind::MethodCall(_, caller, _, _) = child.kind {
child = caller;
}
if is_mutable(cx, child) && is_caller_or_fields_change(cx, body, child) {

// Issue 12098
// https://github.com/rust-lang/rust-clippy/issues/12098
// if the assignee have `mut borrow` conflict with the iteratee
// the lint should not execute, former didn't consider the mut case
if is_path_mutable(cx, child).unwrap_or(true) && is_caller_or_fields_change(cx, body, child) {
// skip lint
return true;
}
Expand Down
16 changes: 3 additions & 13 deletions clippy_lints/src/unnecessary_struct_initialization.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_copy;
use clippy_utils::{get_parent_expr, path_to_local};
use rustc_hir::{BindingMode, Expr, ExprField, ExprKind, Node, PatKind, Path, QPath, UnOp};
use clippy_utils::{get_parent_expr, is_path_mutable, path_to_local};
use rustc_hir::{Expr, ExprField, ExprKind, Path, QPath, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;

Expand Down Expand Up @@ -155,16 +155,6 @@ fn same_path_in_all_fields<'tcx>(
}
}

fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let Some(hir_id) = path_to_local(expr)
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
{
matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..))
} else {
true
}
}

fn check_references(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
if let Some(parent) = get_parent_expr(cx, expr_a)
&& let parent_ty = cx.typeck_results().expr_ty_adjusted(parent)
Expand All @@ -176,7 +166,7 @@ fn check_references(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>)
return false;
}

if parent_ty.is_mutable_ptr() && !is_mutable(cx, expr_b) {
if parent_ty.is_mutable_ptr() && !is_path_mutable(cx, expr_b).unwrap_or(true) {
// The original can be used in a mutable reference context only if it is mutable.
return false;
}
Expand Down
13 changes: 13 additions & 0 deletions clippy_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,19 @@ pub fn is_trait_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -
}
}

/// Checks if `expr` is a path to a mutable binding.
pub fn is_path_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<bool> {
if let Some(hir_id) = path_to_local(expr)
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
{
Some(matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..)))
} else if let ExprKind::Field(recv, _) = expr.kind {
is_path_mutable(cx, recv)
} else {
None
}
}

pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
match *path {
QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"),
Expand Down
25 changes: 16 additions & 9 deletions clippy_utils/src/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1323,22 +1323,29 @@ pub fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl

/// Checks if a Ty<'_> has some inherent method Symbol.
///
/// This does not look for impls in the type's `Deref::Target` type.
/// If you need this, you should wrap this call in `clippy_utils::ty::deref_chain().any(...)`.
/// This is a helper for [`get_inherent_method`].
pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<&'a AssocItem> {
if let Some(ty_did) = ty.ty_adt_def().map(AdtDef::did) {
cx.tcx.inherent_impls(ty_did).iter().find_map(|&did| {
cx.tcx
.associated_items(did)
.filter_by_name_unhygienic(method_name)
.next()
.filter(|item| item.kind == AssocKind::Fn)
})
get_inherent_method(cx, ty_did, method_name)
} else {
None
}
}

/// Checks if the [`DefId`] of a Ty has some inherent method Symbol.
///
/// This does not look for impls in the type's `Deref::Target` type.
/// If you need this, you should wrap this call in `clippy_utils::ty::deref_chain().any(...)`.
pub fn get_inherent_method<'a>(cx: &'a LateContext<'_>, ty_did: DefId, method_name: Symbol) -> Option<&'a AssocItem> {
cx.tcx.inherent_impls(ty_did).iter().find_map(|&did| {
cx.tcx
.associated_items(did)
.filter_by_name_unhygienic(method_name)
.next()
.filter(|item| item.kind == AssocKind::Fn)
})
}

/// Get's the type of a field by name.
pub fn get_field_by_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, name: Symbol) -> Option<Ty<'tcx>> {
match *ty.kind() {
Expand Down
1 change: 1 addition & 0 deletions tests/ui/filter_map_bool_then.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
clippy::map_identity,
clippy::unnecessary_lazy_evaluations,
clippy::unnecessary_filter_map,
clippy::unnecessary_collection_clone,
unused
)]
#![warn(clippy::filter_map_bool_then)]
Expand Down
1 change: 1 addition & 0 deletions tests/ui/filter_map_bool_then.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
clippy::map_identity,
clippy::unnecessary_lazy_evaluations,
clippy::unnecessary_filter_map,
clippy::unnecessary_collection_clone,
unused
)]
#![warn(clippy::filter_map_bool_then)]
Expand Down
20 changes: 10 additions & 10 deletions tests/ui/filter_map_bool_then.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: usage of `bool::then` in `filter_map`
--> tests/ui/filter_map_bool_then.rs:19:22
--> tests/ui/filter_map_bool_then.rs:20:22
|
LL | v.clone().iter().filter_map(|i| (i % 2 == 0).then(|| i + 1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)`
Expand All @@ -8,55 +8,55 @@ LL | v.clone().iter().filter_map(|i| (i % 2 == 0).then(|| i + 1));
= 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.rs:20:27
--> tests/ui/filter_map_bool_then.rs:21:27
|
LL | v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)`

error: usage of `bool::then` in `filter_map`
--> tests/ui/filter_map_bool_then.rs:23:10
--> tests/ui/filter_map_bool_then.rs:24:10
|
LL | .filter_map(|i| -> Option<_> { (i % 2 == 0).then(|| i + 1) });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)`

error: usage of `bool::then` in `filter_map`
--> tests/ui/filter_map_bool_then.rs:27:10
--> tests/ui/filter_map_bool_then.rs:28:10
|
LL | .filter_map(|i| (i % 2 == 0).then(|| i + 1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)`

error: usage of `bool::then` in `filter_map`
--> tests/ui/filter_map_bool_then.rs:31:10
--> tests/ui/filter_map_bool_then.rs:32:10
|
LL | .filter_map(|i| (i.clone() % 2 == 0).then(|| i + 1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i.clone() % 2 == 0)).map(|i| i + 1)`

error: usage of `bool::then` in `filter_map`
--> tests/ui/filter_map_bool_then.rs:37:22
--> tests/ui/filter_map_bool_then.rs:38:22
|
LL | v.clone().iter().filter_map(|i| (i == &NonCopy).then(|| i));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i == &NonCopy)).map(|i| i)`

error: usage of `bool::then` in `filter_map`
--> tests/ui/filter_map_bool_then.rs:61:50
--> tests/ui/filter_map_bool_then.rs:62:50
|
LL | let _: Vec<usize> = 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`
--> tests/ui/filter_map_bool_then.rs:65:50
--> tests/ui/filter_map_bool_then.rs:66:50
|
LL | let _: Vec<usize> = 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`
--> tests/ui/filter_map_bool_then.rs:69:50
--> tests/ui/filter_map_bool_then.rs:70:50
|
LL | let _: Vec<usize> = 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`
--> tests/ui/filter_map_bool_then.rs:80:50
--> tests/ui/filter_map_bool_then.rs:81:50
|
LL | let _: Vec<usize> = 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)`
Expand Down
3 changes: 2 additions & 1 deletion tests/ui/iter_filter_is_ok.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
clippy::map_identity,
clippy::result_filter_map,
clippy::needless_borrow,
clippy::redundant_closure
clippy::redundant_closure,
clippy::unnecessary_collection_clone
)]

fn main() {
Expand Down
3 changes: 2 additions & 1 deletion tests/ui/iter_filter_is_ok.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
clippy::map_identity,
clippy::result_filter_map,
clippy::needless_borrow,
clippy::redundant_closure
clippy::redundant_closure,
clippy::unnecessary_collection_clone
)]

fn main() {
Expand Down
Loading