Skip to content

Commit bd308dd

Browse files
committed
Add new lint: map_all_any_identity
1 parent cc51437 commit bd308dd

7 files changed

+143
-9
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5653,6 +5653,7 @@ Released 2018-09-13
56535653
[`manual_unwrap_or_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or_default
56545654
[`manual_while_let_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_while_let_some
56555655
[`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names
5656+
[`map_all_any_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_all_any_identity
56565657
[`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone
56575658
[`map_collect_result_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_collect_result_unit
56585659
[`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
413413
crate::methods::MANUAL_SPLIT_ONCE_INFO,
414414
crate::methods::MANUAL_STR_REPEAT_INFO,
415415
crate::methods::MANUAL_TRY_FOLD_INFO,
416+
crate::methods::MAP_ALL_ANY_IDENTITY_INFO,
416417
crate::methods::MAP_CLONE_INFO,
417418
crate::methods::MAP_COLLECT_RESULT_UNIT_INFO,
418419
crate::methods::MAP_ERR_IGNORE_INFO,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::is_expr_identity_function;
3+
use clippy_utils::source::SpanRangeExt;
4+
use rustc_errors::Applicability;
5+
use rustc_hir::Expr;
6+
use rustc_lint::LateContext;
7+
use rustc_span::Span;
8+
9+
use super::MAP_ALL_ANY_IDENTITY;
10+
11+
pub(super) fn check(
12+
cx: &LateContext<'_>,
13+
map_call_span: Span,
14+
map_arg: &Expr<'_>,
15+
any_call_span: Span,
16+
any_arg: &Expr<'_>,
17+
method: &str,
18+
) {
19+
if is_expr_identity_function(cx, any_arg)
20+
&& let map_any_call_span = map_call_span.with_hi(any_call_span.hi())
21+
&& let Some(map_arg) = map_arg.span.get_source_text(cx)
22+
{
23+
span_lint_and_sugg(
24+
cx,
25+
MAP_ALL_ANY_IDENTITY,
26+
map_any_call_span,
27+
format!("usage of `.map(…).{method}(identity)`"),
28+
format!("use `.{method}(…)` directly"),
29+
format!("{method}({map_arg})"),
30+
Applicability::MachineApplicable,
31+
);
32+
}
33+
}

clippy_lints/src/methods/mod.rs

+49-9
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ mod manual_ok_or;
6060
mod manual_saturating_arithmetic;
6161
mod manual_str_repeat;
6262
mod manual_try_fold;
63+
mod map_all_any_identity;
6364
mod map_clone;
6465
mod map_collect_result_unit;
6566
mod map_err_ignore;
@@ -4166,6 +4167,31 @@ declare_clippy_lint! {
41664167
"calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`"
41674168
}
41684169

4170+
declare_clippy_lint! {
4171+
/// ### What it does
4172+
/// Checks for usage of `.map(…)`, followed by `.all(identity)` or `.any(identity)`.
4173+
///
4174+
/// ### Why is this bad?
4175+
/// The `.all(…)` or `.any(…)` methods can be called directly in place of `.map(…)`.
4176+
///
4177+
/// ### Example
4178+
/// ```
4179+
/// # let mut v = [""].into_iter();
4180+
/// let e1 = v.iter().map(|s| s.is_empty()).all(|a| a);
4181+
/// let e2 = v.iter().map(|s| s.is_empty()).any(std::convert::identity);
4182+
/// ```
4183+
/// Use instead:
4184+
/// ```
4185+
/// # let mut v = [""].into_iter();
4186+
/// let e1 = v.iter().all(|s| s.is_empty());
4187+
/// let e2 = v.iter().any(|s| s.is_empty());
4188+
/// ```
4189+
#[clippy::version = "1.83.0"]
4190+
pub MAP_ALL_ANY_IDENTITY,
4191+
complexity,
4192+
"combine `.map(_)` followed by `.all(identity)`/`.any(identity)` into a single call"
4193+
}
4194+
41694195
pub struct Methods {
41704196
avoid_breaking_exported_api: bool,
41714197
msrv: Msrv,
@@ -4327,6 +4353,7 @@ impl_lint_pass!(Methods => [
43274353
NEEDLESS_CHARACTER_ITERATION,
43284354
MANUAL_INSPECT,
43294355
UNNECESSARY_MIN_OR_MAX,
4356+
MAP_ALL_ANY_IDENTITY,
43304357
]);
43314358

43324359
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4534,15 +4561,23 @@ impl Methods {
45344561
("all", [arg]) => {
45354562
unused_enumerate_index::check(cx, expr, recv, arg);
45364563
needless_character_iteration::check(cx, expr, recv, arg, true);
4537-
if let Some(("cloned", recv2, [], _, _)) = method_call(recv) {
4538-
iter_overeager_cloned::check(
4539-
cx,
4540-
expr,
4541-
recv,
4542-
recv2,
4543-
iter_overeager_cloned::Op::NeedlessMove(arg),
4544-
false,
4545-
);
4564+
match method_call(recv) {
4565+
Some(("cloned", recv2, [], _, _)) => {
4566+
iter_overeager_cloned::check(
4567+
cx,
4568+
expr,
4569+
recv,
4570+
recv2,
4571+
iter_overeager_cloned::Op::NeedlessMove(arg),
4572+
false,
4573+
);
4574+
},
4575+
Some(("map", _, [map_arg], _, map_call_span))
4576+
if is_trait_method(cx, expr, sym::Iterator) && is_trait_method(cx, recv, sym::Iterator) =>
4577+
{
4578+
map_all_any_identity::check(cx, map_call_span, map_arg, call_span, arg, "all");
4579+
},
4580+
_ => {},
45464581
}
45474582
},
45484583
("and_then", [arg]) => {
@@ -4571,6 +4606,11 @@ impl Methods {
45714606
{
45724607
string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
45734608
},
4609+
Some(("map", _, [map_arg], _, map_call_span))
4610+
if is_trait_method(cx, expr, sym::Iterator) && is_trait_method(cx, recv, sym::Iterator) =>
4611+
{
4612+
map_all_any_identity::check(cx, map_call_span, map_arg, call_span, arg, "any");
4613+
},
45744614
_ => {},
45754615
}
45764616
},

tests/ui/map_all_any_identity.fixed

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![warn(clippy::map_all_any_identity)]
2+
3+
fn main() {
4+
let _ = ["foo"].into_iter().any(|s| s == "foo");
5+
//~^ map_all_any_identity
6+
let _ = ["foo"].into_iter().all(|s| s == "foo");
7+
//~^ map_all_any_identity
8+
9+
//
10+
// Do not lint
11+
//
12+
// Not identity
13+
let _ = ["foo"].into_iter().map(|s| s.len()).any(|n| n > 0);
14+
// Macro
15+
macro_rules! map {
16+
($x:expr) => {
17+
$x.into_iter().map(|s| s == "foo")
18+
};
19+
}
20+
map!(["foo"]).any(|a| a);
21+
}

tests/ui/map_all_any_identity.rs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![warn(clippy::map_all_any_identity)]
2+
3+
fn main() {
4+
let _ = ["foo"].into_iter().map(|s| s == "foo").any(|a| a);
5+
//~^ map_all_any_identity
6+
let _ = ["foo"].into_iter().map(|s| s == "foo").all(std::convert::identity);
7+
//~^ map_all_any_identity
8+
9+
//
10+
// Do not lint
11+
//
12+
// Not identity
13+
let _ = ["foo"].into_iter().map(|s| s.len()).any(|n| n > 0);
14+
// Macro
15+
macro_rules! map {
16+
($x:expr) => {
17+
$x.into_iter().map(|s| s == "foo")
18+
};
19+
}
20+
map!(["foo"]).any(|a| a);
21+
}

tests/ui/map_all_any_identity.stderr

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: usage of `.map(…).any(identity)`
2+
--> tests/ui/map_all_any_identity.rs:4:33
3+
|
4+
LL | let _ = ["foo"].into_iter().map(|s| s == "foo").any(|a| a);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.any(…)` directly: `any(|s| s == "foo")`
6+
|
7+
= note: `-D clippy::map-all-any-identity` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::map_all_any_identity)]`
9+
10+
error: usage of `.map(…).all(identity)`
11+
--> tests/ui/map_all_any_identity.rs:6:33
12+
|
13+
LL | let _ = ["foo"].into_iter().map(|s| s == "foo").all(std::convert::identity);
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.all(…)` directly: `all(|s| s == "foo")`
15+
16+
error: aborting due to 2 previous errors
17+

0 commit comments

Comments
 (0)