Skip to content

Commit 4f8bdf3

Browse files
committed
Auto merge of #4231 - jeremystucki:flat-map, r=flip1995
Implement flat_map lint Fixes #4224 changelog: New Lint `flat_map_identity` to detect unnecessary calls to `flat_map`
2 parents 36f7fae + 2bfcf89 commit 4f8bdf3

File tree

9 files changed

+131
-2
lines changed

9 files changed

+131
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,7 @@ Released 2018-09-13
947947
[`filter_map_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next
948948
[`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next
949949
[`find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#find_map
950+
[`flat_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_identity
950951
[`float_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_arithmetic
951952
[`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
952953
[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
88

9-
[There are 309 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 310 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1010

1111
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1212

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
777777
methods::CLONE_ON_COPY,
778778
methods::EXPECT_FUN_CALL,
779779
methods::FILTER_NEXT,
780+
methods::FLAT_MAP_IDENTITY,
780781
methods::INTO_ITER_ON_ARRAY,
781782
methods::INTO_ITER_ON_REF,
782783
methods::ITER_CLONED_COLLECT,
@@ -1021,6 +1022,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
10211022
methods::CHARS_NEXT_CMP,
10221023
methods::CLONE_ON_COPY,
10231024
methods::FILTER_NEXT,
1025+
methods::FLAT_MAP_IDENTITY,
10241026
methods::SEARCH_IS_SOME,
10251027
methods::UNNECESSARY_FILTER_MAP,
10261028
methods::USELESS_ASREF,

clippy_lints/src/methods/mod.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,28 @@ declare_clippy_lint! {
342342
"using combination of `filter_map` and `next` which can usually be written as a single method call"
343343
}
344344

345+
declare_clippy_lint! {
346+
/// **What it does:** Checks for usage of `flat_map(|x| x)`.
347+
///
348+
/// **Why is this bad?** Readability, this can be written more concisely by using `flatten`.
349+
///
350+
/// **Known problems:** None
351+
///
352+
/// **Example:**
353+
/// ```rust
354+
/// # let iter = vec![vec![0]].into_iter();
355+
/// iter.flat_map(|x| x);
356+
/// ```
357+
/// Can be written as
358+
/// ```rust
359+
/// # let iter = vec![vec![0]].into_iter();
360+
/// iter.flatten();
361+
/// ```
362+
pub FLAT_MAP_IDENTITY,
363+
complexity,
364+
"call to `flat_map` where `flatten` is sufficient"
365+
}
366+
345367
declare_clippy_lint! {
346368
/// **What it does:** Checks for usage of `_.find(_).map(_)`.
347369
///
@@ -892,6 +914,7 @@ declare_lint_pass!(Methods => [
892914
FILTER_NEXT,
893915
FILTER_MAP,
894916
FILTER_MAP_NEXT,
917+
FLAT_MAP_IDENTITY,
895918
FIND_MAP,
896919
MAP_FLATTEN,
897920
ITER_NTH,
@@ -932,6 +955,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
932955
["map", "find"] => lint_find_map(cx, expr, arg_lists[1], arg_lists[0]),
933956
["flat_map", "filter"] => lint_filter_flat_map(cx, expr, arg_lists[1], arg_lists[0]),
934957
["flat_map", "filter_map"] => lint_filter_map_flat_map(cx, expr, arg_lists[1], arg_lists[0]),
958+
["flat_map", ..] => lint_flat_map_identity(cx, expr, arg_lists[0]),
935959
["flatten", "map"] => lint_map_flatten(cx, expr, arg_lists[1]),
936960
["is_some", "find"] => lint_search_is_some(cx, expr, "find", arg_lists[1], arg_lists[0]),
937961
["is_some", "position"] => lint_search_is_some(cx, expr, "position", arg_lists[1], arg_lists[0]),
@@ -2143,6 +2167,56 @@ fn lint_filter_map_flat_map<'a, 'tcx>(
21432167
}
21442168
}
21452169

2170+
/// lint use of `flat_map` for `Iterators` where `flatten` would be sufficient
2171+
fn lint_flat_map_identity<'a, 'tcx>(
2172+
cx: &LateContext<'a, 'tcx>,
2173+
expr: &'tcx hir::Expr,
2174+
flat_map_args: &'tcx [hir::Expr],
2175+
) {
2176+
if match_trait_method(cx, expr, &paths::ITERATOR) {
2177+
let arg_node = &flat_map_args[1].node;
2178+
2179+
let apply_lint = |message: &str| {
2180+
if let hir::ExprKind::MethodCall(_, span, _) = &expr.node {
2181+
span_lint_and_sugg(
2182+
cx,
2183+
FLAT_MAP_IDENTITY,
2184+
span.with_hi(expr.span.hi()),
2185+
message,
2186+
"try",
2187+
"flatten()".to_string(),
2188+
Applicability::MachineApplicable,
2189+
);
2190+
}
2191+
};
2192+
2193+
if_chain! {
2194+
if let hir::ExprKind::Closure(_, _, body_id, _, _) = arg_node;
2195+
let body = cx.tcx.hir().body(*body_id);
2196+
2197+
if let hir::PatKind::Binding(_, _, binding_ident, _) = body.arguments[0].pat.node;
2198+
if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = body.value.node;
2199+
2200+
if path.segments.len() == 1;
2201+
if path.segments[0].ident.as_str() == binding_ident.as_str();
2202+
2203+
then {
2204+
apply_lint("called `flat_map(|x| x)` on an `Iterator`");
2205+
}
2206+
}
2207+
2208+
if_chain! {
2209+
if let hir::ExprKind::Path(ref qpath) = arg_node;
2210+
2211+
if match_qpath(qpath, &paths::STD_CONVERT_IDENTITY);
2212+
2213+
then {
2214+
apply_lint("called `flat_map(std::convert::identity)` on an `Iterator`");
2215+
}
2216+
}
2217+
}
2218+
}
2219+
21462220
/// lint searching an Iterator followed by `is_some()`
21472221
fn lint_search_is_some<'a, 'tcx>(
21482222
cx: &LateContext<'a, 'tcx>,

clippy_lints/src/utils/paths.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec
9595
pub const SLICE_ITER: [&str; 3] = ["core", "slice", "Iter"];
9696
pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
9797
pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
98+
pub const STD_CONVERT_IDENTITY: [&str; 3] = ["std", "convert", "identity"];
9899
pub const STD_MEM_TRANSMUTE: [&str; 3] = ["std", "mem", "transmute"];
99100
pub const STD_PTR_NULL: [&str; 3] = ["std", "ptr", "null"];
100101
pub const STRING: [&str; 3] = ["alloc", "string", "String"];

src/lintlist/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 309] = [
9+
pub const ALL_LINTS: [Lint; 310] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -553,6 +553,13 @@ pub const ALL_LINTS: [Lint; 309] = [
553553
deprecation: None,
554554
module: "methods",
555555
},
556+
Lint {
557+
name: "flat_map_identity",
558+
group: "complexity",
559+
desc: "call to `flat_map` where `flatten` is sufficient",
560+
deprecation: None,
561+
module: "methods",
562+
},
556563
Lint {
557564
name: "float_arithmetic",
558565
group: "restriction",

tests/ui/unnecessary_flat_map.fixed

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

tests/ui/unnecessary_flat_map.rs

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

tests/ui/unnecessary_flat_map.stderr

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

0 commit comments

Comments
 (0)