Skip to content

Commit

Permalink
new lint to use contains() instead of iter().any() for u8 and i8 slices
Browse files Browse the repository at this point in the history
  • Loading branch information
lapla-cogito committed Dec 12, 2024
1 parent c2d23ad commit 14694b1
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5455,6 +5455,7 @@ Released 2018-09-13
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
[`const_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_is_empty
[`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime
[`contains_for_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#contains_for_slice
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
[`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def
[`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir
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 @@ -371,6 +371,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::methods::CLONE_ON_REF_PTR_INFO,
crate::methods::COLLAPSIBLE_STR_REPLACE_INFO,
crate::methods::CONST_IS_EMPTY_INFO,
crate::methods::CONTAINS_FOR_SLICE_INFO,
crate::methods::DRAIN_COLLECT_INFO,
crate::methods::ERR_EXPECT_INFO,
crate::methods::EXPECT_FUN_CALL_INFO,
Expand Down
36 changes: 36 additions & 0 deletions clippy_lints/src/methods/contains_for_slice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use clippy_utils::diagnostics::span_lint;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self};

use super::{CONTAINS_FOR_SLICE, method_call};

pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if !expr.span.from_expansion()
// any()
&& let Some((name, recv, _, _, _)) = method_call(expr)
&& name == "any"
// iter()
&& let Some((name, recv, _, _, _)) = method_call(recv)
&& name == "iter"
{
// check if the receiver is a u8/i8 slice
let ref_type = cx.typeck_results().expr_ty(recv);

match ref_type.kind() {
ty::Ref(_, inner_type, _)
if inner_type.is_slice()
&& let ty::Slice(slice_type) = inner_type.kind()
&& (slice_type.to_string() == "u8" || slice_type.to_string() == "i8") =>
{
span_lint(
cx,
CONTAINS_FOR_SLICE,
expr.span,
"using `contains()` instead of `iter().any()` on u8/i8 slices is more efficient",
);
},
_ => {},
}
}
}
28 changes: 28 additions & 0 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod clone_on_copy;
mod clone_on_ref_ptr;
mod cloned_instead_of_copied;
mod collapsible_str_replace;
mod contains_for_slice;
mod drain_collect;
mod err_expect;
mod expect_fun_call;
Expand Down Expand Up @@ -4284,6 +4285,31 @@ declare_clippy_lint! {
"map of a trivial closure (not dependent on parameter) over a range"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `iter().any()` on slices of `u8` or `i8` and suggests using `contains()` instead.
///
/// ### Why is this bad?
/// `iter().any()` on slices of `u8` or `i8` is optimized to use `memchr`.
///
/// ### Example
/// ```no_run
/// fn foo(values: &[u8]) -> bool {
/// values.iter().any(|&v| v == 10)
/// }
/// ```
/// Use instead:
/// ```no_run
/// fn foo(values: &[u8]) -> bool {
/// values.contains(&10)
/// }
/// ```
#[clippy::version = "1.85.0"]
pub CONTAINS_FOR_SLICE,
perf,
"using `contains()` instead of `iter().any()` on u8/i8 slices is more efficient"
}

pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
Expand Down Expand Up @@ -4449,6 +4475,7 @@ impl_lint_pass!(Methods => [
MAP_ALL_ANY_IDENTITY,
MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES,
UNNECESSARY_MAP_OR,
CONTAINS_FOR_SLICE,
]);

/// Extracts a method call name, args, and `Span` of the method name.
Expand Down Expand Up @@ -4683,6 +4710,7 @@ impl Methods {
("any", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
needless_character_iteration::check(cx, expr, recv, arg, false);
contains_for_slice::check(cx, expr);
match method_call(recv) {
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
Expand Down
32 changes: 32 additions & 0 deletions tests/ui/contains_for_slice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#![warn(clippy::contains_for_slice)]

fn main() {
let vec: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
let values = &vec[..];
let _ = values.iter().any(|&v| v == 4);
//~^ ERROR: using `contains()` instead of `iter().any()` on u8/i8 slices is more efficient

let vec: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
let values = &vec[..];
let _ = values.contains(&4);
// no error, because it uses `contains()`

let vec: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
let values = &vec[..];
let _ = values.iter().any(|&v| v == 4);
// no error, because it's not a slice of u8/i8

let values: [u8; 6] = [3, 14, 15, 92, 6, 5];
let _ = values.iter().any(|&v| v == 10);
// no error, because it's an array
}

fn foo(values: &[u8]) -> bool {
values.iter().any(|&v| v == 10)
//~^ ERROR: using `contains()` instead of `iter().any()` on u8/i8 slices is more efficient
}

fn bar(values: [u8; 3]) -> bool {
values.iter().any(|&v| v == 10)
// no error, because it's an array
}
17 changes: 17 additions & 0 deletions tests/ui/contains_for_slice.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
error: using `contains()` instead of `iter().any()` on u8/i8 slices is more efficient
--> tests/ui/contains_for_slice.rs:6:13
|
LL | let _ = values.iter().any(|&v| v == 4);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::contains-for-slice` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::contains_for_slice)]`

error: using `contains()` instead of `iter().any()` on u8/i8 slices is more efficient
--> tests/ui/contains_for_slice.rs:25:5
|
LL | values.iter().any(|&v| v == 10)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

0 comments on commit 14694b1

Please sign in to comment.