Auto merge of #11289 - lengyijun:filter_find, r=blyxyas
[iter_overeager_cloned]: detect `.cloned().filter()` and `.cloned().find()` changelog: [`iter_overeager_cloned`]: detect `.cloned().filter()` and `.cloned().find()` Key idea: ``` // before iter.cloned().filter(|x| unimplemented!() ) // after iter.filter(|&x| unimplemented!() ).cloned() // before iter.cloned().filter( foo ) // after // notice `iter` must be `Iterator<Item= &T>` (callee of `cloned()`) // so the parameter in the closure of `filter` must be `&&T` // so the deref is safe iter.filter(|&x| foo(x) ).cloned() ```
This commit is contained in:
commit
b5bfd1176b
@ -2,7 +2,7 @@
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::{implements_trait, is_copy};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::sym;
|
||||
@ -10,12 +10,28 @@
|
||||
use super::ITER_OVEREAGER_CLONED;
|
||||
use crate::redundant_clone::REDUNDANT_CLONE;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) enum Op<'a> {
|
||||
// rm `.cloned()`
|
||||
// e.g. `count`
|
||||
RmCloned,
|
||||
|
||||
// later `.cloned()`
|
||||
// and add `&` to the parameter of closure parameter
|
||||
// e.g. `find` `filter`
|
||||
FixClosure(&'a str, &'a Expr<'a>),
|
||||
|
||||
// later `.cloned()`
|
||||
// e.g. `skip` `take`
|
||||
LaterCloned,
|
||||
}
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
cloned_call: &'tcx Expr<'_>,
|
||||
cloned_recv: &'tcx Expr<'_>,
|
||||
is_count: bool,
|
||||
op: Op<'tcx>,
|
||||
needs_into_iter: bool,
|
||||
) {
|
||||
let typeck = cx.typeck_results();
|
||||
@ -35,10 +51,9 @@ pub(super) fn check<'tcx>(
|
||||
return;
|
||||
}
|
||||
|
||||
let (lint, msg, trailing_clone) = if is_count {
|
||||
(REDUNDANT_CLONE, "unneeded cloning of iterator items", "")
|
||||
} else {
|
||||
(ITER_OVEREAGER_CLONED, "unnecessarily eager cloning of iterator items", ".cloned()")
|
||||
let (lint, msg, trailing_clone) = match op {
|
||||
Op::RmCloned => (REDUNDANT_CLONE, "unneeded cloning of iterator items", ""),
|
||||
Op::LaterCloned | Op::FixClosure(_, _) => (ITER_OVEREAGER_CLONED, "unnecessarily eager cloning of iterator items", ".cloned()"),
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
@ -47,6 +62,8 @@ pub(super) fn check<'tcx>(
|
||||
expr.span,
|
||||
msg,
|
||||
|diag| {
|
||||
match op {
|
||||
Op::RmCloned | Op::LaterCloned => {
|
||||
let method_span = expr.span.with_lo(cloned_call.span.hi());
|
||||
if let Some(mut snip) = snippet_opt(cx, method_span) {
|
||||
snip.push_str(trailing_clone);
|
||||
@ -54,6 +71,20 @@ pub(super) fn check<'tcx>(
|
||||
diag.span_suggestion(replace_span, "try", snip, Applicability::MachineApplicable);
|
||||
}
|
||||
}
|
||||
Op::FixClosure(name, predicate_expr) => {
|
||||
if let Some(predicate) = snippet_opt(cx, predicate_expr.span) {
|
||||
let new_closure = if let ExprKind::Closure(_) = predicate_expr.kind {
|
||||
predicate.replacen('|', "|&", 1)
|
||||
} else {
|
||||
format!("|&x| {predicate}(x)")
|
||||
};
|
||||
let snip = format!(".{name}({new_closure}).cloned()" );
|
||||
let replace_span = expr.span.with_lo(cloned_recv.span.hi());
|
||||
diag.span_suggestion(replace_span, "try", snip, Applicability::MachineApplicable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3919,7 +3919,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
}
|
||||
},
|
||||
("count", []) if is_trait_method(cx, expr, sym::Iterator) => match method_call(recv) {
|
||||
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, true, false),
|
||||
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::RmCloned , false),
|
||||
Some((name2 @ ("into_iter" | "iter" | "iter_mut"), recv2, [], _, _)) => {
|
||||
iter_count::check(cx, expr, recv2, name2);
|
||||
},
|
||||
@ -3973,6 +3973,13 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
string_extend_chars::check(cx, expr, recv, arg);
|
||||
extend_with_drain::check(cx, expr, recv, arg);
|
||||
},
|
||||
(name @ ( "filter" | "find" ) , [arg]) => {
|
||||
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
|
||||
// if `arg` has side-effect, the semantic will change
|
||||
iter_overeager_cloned::check(cx, expr, recv, recv2,
|
||||
iter_overeager_cloned::Op::FixClosure(name, arg), false);
|
||||
}
|
||||
}
|
||||
("filter_map", [arg]) => {
|
||||
unnecessary_filter_map::check(cx, expr, arg, name);
|
||||
filter_map_bool_then::check(cx, expr, arg, call_span);
|
||||
@ -3987,7 +3994,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
},
|
||||
("flatten", []) => match method_call(recv) {
|
||||
Some(("map", recv, [map_arg], map_span, _)) => map_flatten::check(cx, expr, recv, map_arg, map_span),
|
||||
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, true),
|
||||
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::LaterCloned , true),
|
||||
_ => {},
|
||||
},
|
||||
("fold", [init, acc]) => {
|
||||
@ -4021,7 +4028,8 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
},
|
||||
("last", []) => {
|
||||
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
|
||||
iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
|
||||
iter_overeager_cloned::check(cx, expr, recv, recv2,
|
||||
iter_overeager_cloned::Op::LaterCloned , false);
|
||||
}
|
||||
},
|
||||
("lock", []) => {
|
||||
@ -4058,7 +4066,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
("next", []) => {
|
||||
if let Some((name2, recv2, args2, _, _)) = method_call(recv) {
|
||||
match (name2, args2) {
|
||||
("cloned", []) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false),
|
||||
("cloned", []) => iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::LaterCloned, false),
|
||||
("filter", [arg]) => filter_next::check(cx, expr, recv2, arg),
|
||||
("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, &self.msrv),
|
||||
("iter", []) => iter_next_slice::check(cx, expr, recv2),
|
||||
@ -4071,7 +4079,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
},
|
||||
("nth", [n_arg]) => match method_call(recv) {
|
||||
Some(("bytes", recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg),
|
||||
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false),
|
||||
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::LaterCloned , false),
|
||||
Some(("iter", recv2, [], _, _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false),
|
||||
Some(("iter_mut", recv2, [], _, _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true),
|
||||
_ => iter_nth_zero::check(cx, expr, recv, n_arg),
|
||||
@ -4126,7 +4134,8 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
iter_skip_zero::check(cx, expr, arg);
|
||||
|
||||
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
|
||||
iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
|
||||
iter_overeager_cloned::check(cx, expr, recv, recv2,
|
||||
iter_overeager_cloned::Op::LaterCloned , false);
|
||||
}
|
||||
}
|
||||
("sort", []) => {
|
||||
@ -4152,7 +4161,8 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
|
||||
("take", [_arg]) => {
|
||||
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
|
||||
iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
|
||||
iter_overeager_cloned::check(cx, expr, recv, recv2,
|
||||
iter_overeager_cloned::Op::LaterCloned, false);
|
||||
}
|
||||
},
|
||||
("take", []) => needless_option_take::check(cx, expr, recv),
|
||||
|
@ -20,8 +20,41 @@ fn main() {
|
||||
.iter()
|
||||
.flatten().cloned();
|
||||
|
||||
// Not implemented yet
|
||||
let _ = vec.iter().cloned().filter(|x| x.starts_with('2'));
|
||||
let _ = vec.iter().filter(|&x| x.starts_with('2')).cloned();
|
||||
|
||||
let _ = vec.iter().find(|&x| x == "2").cloned();
|
||||
|
||||
{
|
||||
let f = |x: &String| x.starts_with('2');
|
||||
let _ = vec.iter().filter(|&x| f(x)).cloned();
|
||||
let _ = vec.iter().find(|&x| f(x)).cloned();
|
||||
}
|
||||
|
||||
{
|
||||
let vec: Vec<(String, String)> = vec![];
|
||||
let f = move |x: &(String, String)| x.0.starts_with('2');
|
||||
let _ = vec.iter().filter(|&x| f(x)).cloned();
|
||||
let _ = vec.iter().find(|&x| f(x)).cloned();
|
||||
}
|
||||
|
||||
fn test_move<'a>(
|
||||
iter: impl Iterator<Item = &'a (&'a u32, String)> + 'a,
|
||||
target: String,
|
||||
) -> impl Iterator<Item = (&'a u32, String)> + 'a {
|
||||
iter.filter(move |&(&a, b)| a == 1 && b == &target).cloned()
|
||||
}
|
||||
|
||||
{
|
||||
#[derive(Clone)]
|
||||
struct S<'a> {
|
||||
a: &'a u32,
|
||||
b: String,
|
||||
}
|
||||
|
||||
fn bar<'a>(iter: impl Iterator<Item = &'a S<'a>> + 'a, target: String) -> impl Iterator<Item = S<'a>> + 'a {
|
||||
iter.filter(move |&S { a, b }| **a == 1 && b == &target).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
// Not implemented yet
|
||||
let _ = vec.iter().cloned().map(|x| x.len());
|
||||
@ -29,9 +62,6 @@ fn main() {
|
||||
// This would fail if changed.
|
||||
let _ = vec.iter().cloned().map(|x| x + "2");
|
||||
|
||||
// Not implemented yet
|
||||
let _ = vec.iter().cloned().find(|x| x == "2");
|
||||
|
||||
// Not implemented yet
|
||||
let _ = vec.iter().cloned().for_each(|x| assert!(!x.is_empty()));
|
||||
|
||||
|
@ -21,18 +21,48 @@ fn main() {
|
||||
.cloned()
|
||||
.flatten();
|
||||
|
||||
// Not implemented yet
|
||||
let _ = vec.iter().cloned().filter(|x| x.starts_with('2'));
|
||||
|
||||
let _ = vec.iter().cloned().find(|x| x == "2");
|
||||
|
||||
{
|
||||
let f = |x: &String| x.starts_with('2');
|
||||
let _ = vec.iter().cloned().filter(f);
|
||||
let _ = vec.iter().cloned().find(f);
|
||||
}
|
||||
|
||||
{
|
||||
let vec: Vec<(String, String)> = vec![];
|
||||
let f = move |x: &(String, String)| x.0.starts_with('2');
|
||||
let _ = vec.iter().cloned().filter(f);
|
||||
let _ = vec.iter().cloned().find(f);
|
||||
}
|
||||
|
||||
fn test_move<'a>(
|
||||
iter: impl Iterator<Item = &'a (&'a u32, String)> + 'a,
|
||||
target: String,
|
||||
) -> impl Iterator<Item = (&'a u32, String)> + 'a {
|
||||
iter.cloned().filter(move |(&a, b)| a == 1 && b == &target)
|
||||
}
|
||||
|
||||
{
|
||||
#[derive(Clone)]
|
||||
struct S<'a> {
|
||||
a: &'a u32,
|
||||
b: String,
|
||||
}
|
||||
|
||||
fn bar<'a>(iter: impl Iterator<Item = &'a S<'a>> + 'a, target: String) -> impl Iterator<Item = S<'a>> + 'a {
|
||||
iter.cloned().filter(move |S { a, b }| **a == 1 && b == &target)
|
||||
}
|
||||
}
|
||||
|
||||
// Not implemented yet
|
||||
let _ = vec.iter().cloned().map(|x| x.len());
|
||||
|
||||
// This would fail if changed.
|
||||
let _ = vec.iter().cloned().map(|x| x + "2");
|
||||
|
||||
// Not implemented yet
|
||||
let _ = vec.iter().cloned().find(|x| x == "2");
|
||||
|
||||
// Not implemented yet
|
||||
let _ = vec.iter().cloned().for_each(|x| assert!(!x.is_empty()));
|
||||
|
||||
|
@ -66,5 +66,69 @@ LL ~ .iter()
|
||||
LL ~ .flatten().cloned();
|
||||
|
|
||||
|
||||
error: aborting due to 7 previous errors
|
||||
error: unnecessarily eager cloning of iterator items
|
||||
--> $DIR/iter_overeager_cloned.rs:24:13
|
||||
|
|
||||
LL | let _ = vec.iter().cloned().filter(|x| x.starts_with('2'));
|
||||
| ^^^^^^^^^^----------------------------------------
|
||||
| |
|
||||
| help: try: `.filter(|&x| x.starts_with('2')).cloned()`
|
||||
|
||||
error: unnecessarily eager cloning of iterator items
|
||||
--> $DIR/iter_overeager_cloned.rs:26:13
|
||||
|
|
||||
LL | let _ = vec.iter().cloned().find(|x| x == "2");
|
||||
| ^^^^^^^^^^----------------------------
|
||||
| |
|
||||
| help: try: `.find(|&x| x == "2").cloned()`
|
||||
|
||||
error: unnecessarily eager cloning of iterator items
|
||||
--> $DIR/iter_overeager_cloned.rs:30:17
|
||||
|
|
||||
LL | let _ = vec.iter().cloned().filter(f);
|
||||
| ^^^^^^^^^^-------------------
|
||||
| |
|
||||
| help: try: `.filter(|&x| f(x)).cloned()`
|
||||
|
||||
error: unnecessarily eager cloning of iterator items
|
||||
--> $DIR/iter_overeager_cloned.rs:31:17
|
||||
|
|
||||
LL | let _ = vec.iter().cloned().find(f);
|
||||
| ^^^^^^^^^^-----------------
|
||||
| |
|
||||
| help: try: `.find(|&x| f(x)).cloned()`
|
||||
|
||||
error: unnecessarily eager cloning of iterator items
|
||||
--> $DIR/iter_overeager_cloned.rs:37:17
|
||||
|
|
||||
LL | let _ = vec.iter().cloned().filter(f);
|
||||
| ^^^^^^^^^^-------------------
|
||||
| |
|
||||
| help: try: `.filter(|&x| f(x)).cloned()`
|
||||
|
||||
error: unnecessarily eager cloning of iterator items
|
||||
--> $DIR/iter_overeager_cloned.rs:38:17
|
||||
|
|
||||
LL | let _ = vec.iter().cloned().find(f);
|
||||
| ^^^^^^^^^^-----------------
|
||||
| |
|
||||
| help: try: `.find(|&x| f(x)).cloned()`
|
||||
|
||||
error: unnecessarily eager cloning of iterator items
|
||||
--> $DIR/iter_overeager_cloned.rs:45:9
|
||||
|
|
||||
LL | iter.cloned().filter(move |(&a, b)| a == 1 && b == &target)
|
||||
| ^^^^-------------------------------------------------------
|
||||
| |
|
||||
| help: try: `.filter(move |&(&a, b)| a == 1 && b == &target).cloned()`
|
||||
|
||||
error: unnecessarily eager cloning of iterator items
|
||||
--> $DIR/iter_overeager_cloned.rs:56:13
|
||||
|
|
||||
LL | iter.cloned().filter(move |S { a, b }| **a == 1 && b == &target)
|
||||
| ^^^^------------------------------------------------------------
|
||||
| |
|
||||
| help: try: `.filter(move |&S { a, b }| **a == 1 && b == &target).cloned()`
|
||||
|
||||
error: aborting due to 15 previous errors
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user