Skip to content

Commit a60c143

Browse files
committed
Add new lint filter_map_identity
This commit adds a new lint named `filter_map_identity`. This lint is the same as `flat_map_identity` except that it checks for `filter_map`. Closes #6643
1 parent a507c27 commit a60c143

File tree

7 files changed

+143
-1
lines changed

7 files changed

+143
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1955,6 +1955,7 @@ Released 2018-09-13
19551955
[`field_reassign_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_reassign_with_default
19561956
[`filetype_is_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#filetype_is_file
19571957
[`filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map
1958+
[`filter_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_identity
19581959
[`filter_map_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next
19591960
[`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next
19601961
[`find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#find_map

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
742742
&methods::EXPECT_USED,
743743
&methods::FILETYPE_IS_FILE,
744744
&methods::FILTER_MAP,
745+
&methods::FILTER_MAP_IDENTITY,
745746
&methods::FILTER_MAP_NEXT,
746747
&methods::FILTER_NEXT,
747748
&methods::FLAT_MAP_IDENTITY,
@@ -1531,6 +1532,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15311532
LintId::of(&methods::CLONE_DOUBLE_REF),
15321533
LintId::of(&methods::CLONE_ON_COPY),
15331534
LintId::of(&methods::EXPECT_FUN_CALL),
1535+
LintId::of(&methods::FILTER_MAP_IDENTITY),
15341536
LintId::of(&methods::FILTER_NEXT),
15351537
LintId::of(&methods::FLAT_MAP_IDENTITY),
15361538
LintId::of(&methods::FROM_ITER_INSTEAD_OF_COLLECT),
@@ -1838,6 +1840,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
18381840
LintId::of(&matches::WILDCARD_IN_OR_PATTERNS),
18391841
LintId::of(&methods::BIND_INSTEAD_OF_MAP),
18401842
LintId::of(&methods::CLONE_ON_COPY),
1843+
LintId::of(&methods::FILTER_MAP_IDENTITY),
18411844
LintId::of(&methods::FILTER_NEXT),
18421845
LintId::of(&methods::FLAT_MAP_IDENTITY),
18431846
LintId::of(&methods::INSPECT_FOR_EACH),
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use crate::utils::{match_qpath, match_trait_method, paths, span_lint_and_sugg};
2+
use if_chain::if_chain;
3+
use rustc_errors::Applicability;
4+
use rustc_hir as hir;
5+
use rustc_lint::LateContext;
6+
use rustc_span::source_map::Span;
7+
8+
use super::FILTER_MAP_IDENTITY;
9+
10+
pub(super) fn check(
11+
cx: &LateContext<'_>,
12+
expr: &hir::Expr<'_>,
13+
filter_map_args: &[hir::Expr<'_>],
14+
filter_map_span: Span,
15+
) {
16+
if match_trait_method(cx, expr, &paths::ITERATOR) {
17+
let arg_node = &filter_map_args[1].kind;
18+
19+
let apply_lint = |message: &str| {
20+
span_lint_and_sugg(
21+
cx,
22+
FILTER_MAP_IDENTITY,
23+
filter_map_span.with_hi(expr.span.hi()),
24+
message,
25+
"try",
26+
"flatten()".to_string(),
27+
Applicability::MachineApplicable,
28+
);
29+
};
30+
31+
if_chain! {
32+
if let hir::ExprKind::Closure(_, _, body_id, _, _) = arg_node;
33+
let body = cx.tcx.hir().body(*body_id);
34+
35+
if let hir::PatKind::Binding(_, _, binding_ident, _) = body.params[0].pat.kind;
36+
if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = body.value.kind;
37+
38+
if path.segments.len() == 1;
39+
if path.segments[0].ident.name == binding_ident.name;
40+
41+
then {
42+
apply_lint("called `filter_map(|x| x)` on an `Iterator`");
43+
}
44+
}
45+
46+
if_chain! {
47+
if let hir::ExprKind::Path(ref qpath) = arg_node;
48+
49+
if match_qpath(qpath, &paths::STD_CONVERT_IDENTITY);
50+
51+
then {
52+
apply_lint("called `filter_map(std::convert::identity)` on an `Iterator`");
53+
}
54+
}
55+
}
56+
}

clippy_lints/src/methods/mod.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod bind_instead_of_map;
2+
mod filter_map_identity;
23
mod inefficient_to_string;
34
mod inspect_for_each;
45
mod manual_saturating_arithmetic;
@@ -1467,6 +1468,29 @@ declare_clippy_lint! {
14671468
"using `.inspect().for_each()`, which can be replaced with `.for_each()`"
14681469
}
14691470

1471+
declare_clippy_lint! {
1472+
/// **What it does:** Checks for usage of `filter_map(|x| x)`.
1473+
///
1474+
/// **Why is this bad?** Readability, this can be written more concisely by using `flatten`.
1475+
///
1476+
/// **Known problems:** None.
1477+
///
1478+
/// **Example:**
1479+
///
1480+
/// ```rust
1481+
/// # let iter = vec![Some(1)].into_iter();
1482+
/// iter.filter_map(|x| x);
1483+
/// ```
1484+
/// Use instead:
1485+
/// ```rust
1486+
/// # let iter = vec![Some(1)].into_iter();
1487+
/// iter.flatten();
1488+
/// ```
1489+
pub FILTER_MAP_IDENTITY,
1490+
complexity,
1491+
"call to `filter_map` where `flatten` is sufficient"
1492+
}
1493+
14701494
pub struct Methods {
14711495
msrv: Option<RustcVersion>,
14721496
}
@@ -1504,6 +1528,7 @@ impl_lint_pass!(Methods => [
15041528
FILTER_NEXT,
15051529
SKIP_WHILE_NEXT,
15061530
FILTER_MAP,
1531+
FILTER_MAP_IDENTITY,
15071532
MANUAL_FILTER_MAP,
15081533
MANUAL_FIND_MAP,
15091534
FILTER_MAP_NEXT,
@@ -1597,7 +1622,10 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
15971622
["as_ref"] => lint_asref(cx, expr, "as_ref", arg_lists[0]),
15981623
["as_mut"] => lint_asref(cx, expr, "as_mut", arg_lists[0]),
15991624
["fold", ..] => lint_unnecessary_fold(cx, expr, arg_lists[0], method_spans[0]),
1600-
["filter_map", ..] => unnecessary_filter_map::lint(cx, expr, arg_lists[0]),
1625+
["filter_map", ..] => {
1626+
unnecessary_filter_map::lint(cx, expr, arg_lists[0]);
1627+
filter_map_identity::check(cx, expr, arg_lists[0], method_spans[0]);
1628+
},
16011629
["count", "map"] => lint_suspicious_map(cx, expr),
16021630
["assume_init"] => lint_maybe_uninit(cx, &arg_lists[0][0], expr),
16031631
["unwrap_or", arith @ ("checked_add" | "checked_sub" | "checked_mul")] => {

tests/ui/filter_map_identity.fixed

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// run-rustfix
2+
3+
#![allow(unused_imports)]
4+
#![warn(clippy::filter_map_identity)]
5+
6+
fn main() {
7+
let iterator = vec![Some(1), None, Some(2)].into_iter();
8+
let _ = iterator.flatten();
9+
10+
let iterator = vec![Some(1), None, Some(2)].into_iter();
11+
let _ = iterator.flatten();
12+
13+
use std::convert::identity;
14+
let iterator = vec![Some(1), None, Some(2)].into_iter();
15+
let _ = iterator.flatten();
16+
}

tests/ui/filter_map_identity.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// run-rustfix
2+
3+
#![allow(unused_imports)]
4+
#![warn(clippy::filter_map_identity)]
5+
6+
fn main() {
7+
let iterator = vec![Some(1), None, Some(2)].into_iter();
8+
let _ = iterator.filter_map(|x| x);
9+
10+
let iterator = vec![Some(1), None, Some(2)].into_iter();
11+
let _ = iterator.filter_map(std::convert::identity);
12+
13+
use std::convert::identity;
14+
let iterator = vec![Some(1), None, Some(2)].into_iter();
15+
let _ = iterator.filter_map(identity);
16+
}

tests/ui/filter_map_identity.stderr

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: called `filter_map(|x| x)` on an `Iterator`
2+
--> $DIR/filter_map_identity.rs:8:22
3+
|
4+
LL | let _ = iterator.filter_map(|x| x);
5+
| ^^^^^^^^^^^^^^^^^ help: try: `flatten()`
6+
|
7+
= note: `-D clippy::filter-map-identity` implied by `-D warnings`
8+
9+
error: called `filter_map(std::convert::identity)` on an `Iterator`
10+
--> $DIR/filter_map_identity.rs:11:22
11+
|
12+
LL | let _ = iterator.filter_map(std::convert::identity);
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()`
14+
15+
error: called `filter_map(std::convert::identity)` on an `Iterator`
16+
--> $DIR/filter_map_identity.rs:15:22
17+
|
18+
LL | let _ = iterator.filter_map(identity);
19+
| ^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()`
20+
21+
error: aborting due to 3 previous errors
22+

0 commit comments

Comments
 (0)