Skip to content

Commit 46c1b24

Browse files
committed
add manual_contains lint
1 parent 8c01600 commit 46c1b24

14 files changed

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

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: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#![warn(clippy::manual_contains)]
2+
#![allow(clippy::eq_op, clippy::useless_vec)]
3+
4+
fn should_lint_numeric() {
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+
50+
fn should_not_lint() {
51+
let values: [u8; 6] = [3, 14, 15, 92, 6, 5];
52+
let _ = values.iter().any(|&v| v > 10);
53+
54+
let vec: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
55+
let values = &vec[..];
56+
let _ = values.iter().any(|&v| v % 2 == 0);
57+
let _ = values.iter().any(|&v| v * 2 == 6);
58+
let _ = values.iter().any(|&v| v == v);
59+
let _ = values.iter().any(|&v| 4 == 4);
60+
let _ = values.contains(&4);
61+
62+
let a = 1;
63+
let b = 2;
64+
let _ = values.iter().any(|&v| a == b);
65+
let _ = values.iter().any(|&v| a == 4);
66+
67+
let vec: Vec<String> = vec!["1", "2", "3", "4", "5", "6"]
68+
.iter()
69+
.map(|&x| x.to_string())
70+
.collect();
71+
let values = &vec[..];
72+
let _ = values.iter().any(|v| v == "4");
73+
}
74+
75+
fn foo(values: &[u8]) -> bool {
76+
values.contains(&10)
77+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
78+
}
79+
80+
fn bar(values: [u8; 3]) -> bool {
81+
values.contains(&10)
82+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
83+
}
84+
85+
#[clippy::msrv = "1.83"]
86+
fn u8_slice_and_msrv_is_1_83_0() {
87+
let vec: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
88+
let values = &vec[..];
89+
let _ = values.contains(&4);
90+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
91+
}
92+
93+
#[clippy::msrv = "1.83"]
94+
fn u32_slice_and_msrv_is_1_83_0() {
95+
let vec: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
96+
let values = &vec[..];
97+
let _ = values.contains(&4);
98+
// Shouldn't trigger the performance lint because the MSRV is 1.83.0 and this is a `u32` slice
99+
}
100+
101+
#[clippy::msrv = "1.84"]
102+
fn u8_slice_and_msrv_is_1_84_0() {
103+
let vec: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
104+
let values = &vec[..];
105+
let _ = values.contains(&4);
106+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
107+
}
108+
109+
#[clippy::msrv = "1.84"]
110+
fn u32_slice_and_msrv_is_1_84_0() {
111+
let vec: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
112+
let values = &vec[..];
113+
let _ = values.contains(&4);
114+
//~^ ERROR: using `contains()` instead of `iter().any()` is more efficient
115+
}

0 commit comments

Comments
 (0)