Skip to content

Commit 0a0425e

Browse files
committed
add manual_contains lint
1 parent 8c01600 commit 0a0425e

14 files changed

+455
-25
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5742,6 +5742,7 @@ Released 2018-09-13
57425742
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
57435743
[`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals
57445744
[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
5745+
[`manual_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_contains
57455746
[`manual_div_ceil`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_div_ceil
57465747
[`manual_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter
57475748
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
416416
crate::methods::ITER_SKIP_ZERO_INFO,
417417
crate::methods::ITER_WITH_DRAIN_INFO,
418418
crate::methods::JOIN_ABSOLUTE_PATHS_INFO,
419+
crate::methods::MANUAL_CONTAINS_INFO,
419420
crate::methods::MANUAL_C_STR_LITERALS_INFO,
420421
crate::methods::MANUAL_FILTER_MAP_INFO,
421422
crate::methods::MANUAL_FIND_MAP_INFO,
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
3+
use clippy_utils::peel_hir_pat_refs;
4+
use clippy_utils::source::snippet_with_applicability;
5+
use clippy_utils::sugg::Sugg;
6+
use rustc_ast::UnOp;
7+
use rustc_errors::Applicability;
8+
use rustc_hir::def::Res;
9+
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, HirId, QPath};
10+
use rustc_lint::LateContext;
11+
use rustc_middle::ty::{self};
12+
use rustc_span::source_map::Spanned;
13+
14+
use super::MANUAL_CONTAINS;
15+
16+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) {
17+
let mut app = Applicability::MachineApplicable;
18+
19+
if !expr.span.from_expansion()
20+
// check if `iter().any()` can be replaced with `contains()`
21+
&& let ExprKind::Closure(closure) = closure_arg.kind
22+
&& let Body{params: [param],value} = cx.tcx.hir().body(closure.body)
23+
&& let ExprKind::Binary(op, lhs, rhs) = value.kind
24+
&& let (peeled_ref_pat, _)=peel_hir_pat_refs(param.pat)
25+
&& let Some((snip,snip_expr)) = can_replace_with_contains(cx, op, lhs, rhs, peeled_ref_pat.hir_id, &mut app)
26+
&& let ref_type = cx.typeck_results().expr_ty_adjusted(recv)
27+
&& let ty::Ref(_, inner_type, _) = ref_type.kind()
28+
&& let ty::Slice(slice_type) = inner_type.kind()
29+
&& *slice_type == cx.typeck_results().expr_ty(snip_expr)
30+
{
31+
span_lint_and_sugg(
32+
cx,
33+
MANUAL_CONTAINS,
34+
expr.span,
35+
"using `contains()` instead of `iter().any()` is more efficient",
36+
"try",
37+
format!(
38+
"{}.contains({})",
39+
snippet_with_applicability(cx, recv.span, "_", &mut app),
40+
snip
41+
),
42+
app,
43+
);
44+
}
45+
}
46+
47+
enum EligibleArg {
48+
IsClosureArg,
49+
ContainsArg(String),
50+
}
51+
52+
fn try_get_eligible_arg<'tcx>(
53+
cx: &LateContext<'tcx>,
54+
expr: &'tcx Expr<'tcx>,
55+
closure_arg_id: HirId,
56+
applicability: &mut Applicability,
57+
) -> Option<(EligibleArg, &'tcx Expr<'tcx>)> {
58+
let mut get_snippet = |expr: &Expr<'_>, needs_borrow: bool| {
59+
let sugg = Sugg::hir_with_applicability(cx, expr, "_", applicability);
60+
EligibleArg::ContainsArg((if needs_borrow { sugg.addr() } else { sugg }).to_string())
61+
};
62+
63+
match expr.kind {
64+
ExprKind::Path(QPath::Resolved(_, path)) => {
65+
if path.res == Res::Local(closure_arg_id) {
66+
Some((EligibleArg::IsClosureArg, expr))
67+
} else {
68+
Some((get_snippet(expr, true), expr))
69+
}
70+
},
71+
ExprKind::Unary(UnOp::Deref, inner) => {
72+
if let ExprKind::Path(QPath::Resolved(_, path)) = inner.kind {
73+
if path.res == Res::Local(closure_arg_id) {
74+
Some((EligibleArg::IsClosureArg, expr))
75+
} else {
76+
Some((get_snippet(inner, false), expr))
77+
}
78+
} else {
79+
None
80+
}
81+
},
82+
_ => {
83+
if switch_to_eager_eval(cx, expr) {
84+
Some((get_snippet(expr, true), expr))
85+
} else {
86+
None
87+
}
88+
},
89+
}
90+
}
91+
92+
fn can_replace_with_contains<'tcx>(
93+
cx: &LateContext<'tcx>,
94+
bin_op: Spanned<BinOpKind>,
95+
left_expr: &'tcx Expr<'tcx>,
96+
right_expr: &'tcx Expr<'tcx>,
97+
closure_arg_id: HirId,
98+
applicability: &mut Applicability,
99+
) -> Option<(String, &'tcx Expr<'tcx>)> {
100+
if bin_op.node != BinOpKind::Eq {
101+
return None;
102+
}
103+
104+
let left_candidate = try_get_eligible_arg(cx, left_expr, closure_arg_id, applicability)?;
105+
let right_candidate = try_get_eligible_arg(cx, right_expr, closure_arg_id, applicability)?;
106+
match (left_candidate, right_candidate) {
107+
((EligibleArg::IsClosureArg, _), (EligibleArg::ContainsArg(snip), candidate_expr))
108+
| ((EligibleArg::ContainsArg(snip), candidate_expr), (EligibleArg::IsClosureArg, _)) => {
109+
Some((snip, candidate_expr))
110+
},
111+
_ => None,
112+
}
113+
}

clippy_lints/src/methods/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ mod iter_with_drain;
5454
mod iterator_step_by_zero;
5555
mod join_absolute_paths;
5656
mod manual_c_str_literals;
57+
mod manual_contains;
5758
mod manual_inspect;
5859
mod manual_is_variant_and;
5960
mod manual_next_back;
@@ -4406,6 +4407,31 @@ declare_clippy_lint! {
44064407
"using `Option::and_then` or `Result::and_then` to chain a computation that returns an `Option` or a `Result`"
44074408
}
44084409

4410+
declare_clippy_lint! {
4411+
/// ### What it does
4412+
/// Checks for usage of `iter().any()` on slices when it can be replaced with `contains()` and suggests doing so.
4413+
///
4414+
/// ### Why is this bad?
4415+
/// `contains()` is more concise and idiomatic, sometimes more fast.
4416+
///
4417+
/// ### Example
4418+
/// ```no_run
4419+
/// fn foo(values: &[u8]) -> bool {
4420+
/// values.iter().any(|&v| v == 10)
4421+
/// }
4422+
/// ```
4423+
/// Use instead:
4424+
/// ```no_run
4425+
/// fn foo(values: &[u8]) -> bool {
4426+
/// values.contains(&10)
4427+
/// }
4428+
/// ```
4429+
#[clippy::version = "1.86.0"]
4430+
pub MANUAL_CONTAINS,
4431+
perf,
4432+
"unnecessary `iter().any()` on slices that can be replaced with `contains()`"
4433+
}
4434+
44094435
pub struct Methods {
44104436
avoid_breaking_exported_api: bool,
44114437
msrv: Msrv,
@@ -4575,6 +4601,7 @@ impl_lint_pass!(Methods => [
45754601
MANUAL_REPEAT_N,
45764602
SLICED_STRING_AS_BYTES,
45774603
RETURN_AND_THEN,
4604+
MANUAL_CONTAINS,
45784605
]);
45794606

45804607
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4832,6 +4859,9 @@ impl Methods {
48324859
Some(("map", _, [map_arg], _, map_call_span)) => {
48334860
map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "any");
48344861
},
4862+
Some(("iter", iter_recv, ..)) => {
4863+
manual_contains::check(cx, expr, iter_recv, arg);
4864+
},
48354865
_ => {},
48364866
}
48374867
},

tests/ui/manual_contains.fixed

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#![warn(clippy::manual_contains)]
2+
#![allow(clippy::eq_op, clippy::useless_vec)]
3+
4+
fn should_lint() {
5+
let vec: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
6+
let values = &vec[..];
7+
let _ = values.contains(&4);
8+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
9+
10+
let vec: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
11+
let values = &vec[..];
12+
let _ = values.contains(&4);
13+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
14+
15+
let values: [u8; 6] = [3, 14, 15, 92, 6, 5];
16+
let _ = values.contains(&10);
17+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
18+
19+
let num = 14;
20+
let values: [u8; 6] = [3, 14, 15, 92, 6, 5];
21+
let _ = values.contains(&num);
22+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
23+
24+
let num = 14;
25+
let values: [u8; 6] = [3, 14, 15, 92, 6, 5];
26+
let _ = values.contains(&num);
27+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
28+
29+
let values: [u8; 6] = [3, 14, 15, 92, 6, 5];
30+
let _ = values.contains(&4);
31+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
32+
33+
let vec: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
34+
let values = &vec[..];
35+
let _ = values.contains(&4);
36+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
37+
38+
let vec: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
39+
let values = &vec[..];
40+
let a = &4;
41+
let _ = values.contains(a);
42+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
43+
44+
let vec = vec!["1", "2", "3", "4", "5", "6"];
45+
let values = &vec[..];
46+
let _ = values.contains(&"4");
47+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
48+
49+
let vec: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
50+
let values = &vec[..];
51+
let _ = values.contains(&(4 + 1));
52+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
53+
}
54+
55+
fn should_not_lint() {
56+
let values: [u8; 6] = [3, 14, 15, 92, 6, 5];
57+
let _ = values.iter().any(|&v| v > 10);
58+
59+
let vec: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
60+
let values = &vec[..];
61+
let _ = values.iter().any(|&v| v % 2 == 0);
62+
let _ = values.iter().any(|&v| v * 2 == 6);
63+
let _ = values.iter().any(|&v| v == v);
64+
let _ = values.iter().any(|&v| 4 == 4);
65+
let _ = values.contains(&4);
66+
67+
let a = 1;
68+
let b = 2;
69+
let _ = values.iter().any(|&v| a == b);
70+
let _ = values.iter().any(|&v| a == 4);
71+
72+
let vec: Vec<String> = vec!["1", "2", "3", "4", "5", "6"]
73+
.iter()
74+
.map(|&x| x.to_string())
75+
.collect();
76+
let values = &vec[..];
77+
let _ = values.iter().any(|v| v == "4");
78+
79+
let vec: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
80+
let values = &vec[..];
81+
let mut counter = 0;
82+
let mut count = || {
83+
counter += 1;
84+
counter
85+
};
86+
let _ = values.iter().any(|&v| v == count());
87+
let _ = values.iter().any(|&v| v == v * 2);
88+
}
89+
90+
fn foo(values: &[u8]) -> bool {
91+
values.contains(&10)
92+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
93+
}
94+
95+
fn bar(values: [u8; 3]) -> bool {
96+
values.contains(&10)
97+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
98+
}

tests/ui/manual_contains.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#![warn(clippy::manual_contains)]
2+
#![allow(clippy::eq_op, clippy::useless_vec)]
3+
4+
fn should_lint() {
5+
let vec: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
6+
let values = &vec[..];
7+
let _ = values.iter().any(|&v| v == 4);
8+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
9+
10+
let vec: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
11+
let values = &vec[..];
12+
let _ = values.iter().any(|&v| v == 4);
13+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
14+
15+
let values: [u8; 6] = [3, 14, 15, 92, 6, 5];
16+
let _ = values.iter().any(|&v| v == 10);
17+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
18+
19+
let num = 14;
20+
let values: [u8; 6] = [3, 14, 15, 92, 6, 5];
21+
let _ = values.iter().any(|&v| v == num);
22+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
23+
24+
let num = 14;
25+
let values: [u8; 6] = [3, 14, 15, 92, 6, 5];
26+
let _ = values.iter().any(|&v| num == v);
27+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
28+
29+
let values: [u8; 6] = [3, 14, 15, 92, 6, 5];
30+
let _ = values.iter().any(|v| *v == 4);
31+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
32+
33+
let vec: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
34+
let values = &vec[..];
35+
let _ = values.iter().any(|&v| 4 == v);
36+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
37+
38+
let vec: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
39+
let values = &vec[..];
40+
let a = &4;
41+
let _ = values.iter().any(|v| *v == *a);
42+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
43+
44+
let vec = vec!["1", "2", "3", "4", "5", "6"];
45+
let values = &vec[..];
46+
let _ = values.iter().any(|&v| v == "4");
47+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
48+
49+
let vec: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
50+
let values = &vec[..];
51+
let _ = values.iter().any(|&v| v == 4 + 1);
52+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
53+
}
54+
55+
fn should_not_lint() {
56+
let values: [u8; 6] = [3, 14, 15, 92, 6, 5];
57+
let _ = values.iter().any(|&v| v > 10);
58+
59+
let vec: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
60+
let values = &vec[..];
61+
let _ = values.iter().any(|&v| v % 2 == 0);
62+
let _ = values.iter().any(|&v| v * 2 == 6);
63+
let _ = values.iter().any(|&v| v == v);
64+
let _ = values.iter().any(|&v| 4 == 4);
65+
let _ = values.contains(&4);
66+
67+
let a = 1;
68+
let b = 2;
69+
let _ = values.iter().any(|&v| a == b);
70+
let _ = values.iter().any(|&v| a == 4);
71+
72+
let vec: Vec<String> = vec!["1", "2", "3", "4", "5", "6"]
73+
.iter()
74+
.map(|&x| x.to_string())
75+
.collect();
76+
let values = &vec[..];
77+
let _ = values.iter().any(|v| v == "4");
78+
79+
let vec: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
80+
let values = &vec[..];
81+
let mut counter = 0;
82+
let mut count = || {
83+
counter += 1;
84+
counter
85+
};
86+
let _ = values.iter().any(|&v| v == count());
87+
let _ = values.iter().any(|&v| v == v * 2);
88+
}
89+
90+
fn foo(values: &[u8]) -> bool {
91+
values.iter().any(|&v| v == 10)
92+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
93+
}
94+
95+
fn bar(values: [u8; 3]) -> bool {
96+
values.iter().any(|&v| v == 10)
97+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
98+
}

0 commit comments

Comments
 (0)