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 8eb9d35
Show file tree
Hide file tree
Showing 6 changed files with 118 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
65 changes: 65 additions & 0 deletions clippy_lints/src/contains_for_slice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::methods::method_call;
use clippy_utils::diagnostics::span_lint;
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self};
use rustc_session::declare_lint_pass;

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"
}

declare_lint_pass!(ContainsForSlice => [CONTAINS_FOR_SLICE]);

impl LateLintPass<'_> for ContainsForSlice {
fn check_expr(&mut self, 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",
);
},
_ => {},
}
}
}
}
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::collapsible_if::COLLAPSIBLE_IF_INFO,
crate::collection_is_never_read::COLLECTION_IS_NEVER_READ_INFO,
crate::comparison_chain::COMPARISON_CHAIN_INFO,
crate::contains_for_slice::CONTAINS_FOR_SLICE_INFO,
crate::copies::BRANCHES_SHARING_CODE_INFO,
crate::copies::IFS_SAME_COND_INFO,
crate::copies::IF_SAME_THEN_ELSE_INFO,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ mod cognitive_complexity;
mod collapsible_if;
mod collection_is_never_read;
mod comparison_chain;
mod contains_for_slice;
mod copies;
mod copy_iterator;
mod crate_in_macro_def;
Expand Down Expand Up @@ -967,5 +968,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf)));
store.register_late_pass(|_| Box::new(contains_for_slice::ContainsForSlice));
// add lints here, do not remove this comment, it's used in `new_lint`
}
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 8eb9d35

Please sign in to comment.