Skip to content

Commit 378ba2e

Browse files
authored
Rollup merge of rust-lang#5846 - dima74:map_flatten.map_to_option, r=flip1995
Handle mapping to Option in `map_flatten` lint Fixes rust-lang#4496 The existing [`map_flatten`](https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten) lint suggests changing `expr.map(...).flatten()` to `expr.flat_map(...)` when `expr` is `Iterator`. This PR changes suggestion to `filter_map` instead of `flat_map` when mapping to `Option`, because it is more natural Also here are some questions: * If expression has type which implements `Iterator` trait (`match_trait_method(cx, expr, &paths::ITERATOR) == true`), how can I get type of iterator elements? Currently I use return type of closure inside `map`, but probably it is not good way * I would like to change suggestion range to cover only `.map(...).flatten()`, that is from: ``` let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `flat_map` instead: `vec![5_i8; 6].into_iter().flat_map ``` to ``` let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); ^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `flat_map` instead: `.flat_map(|x| 0..x)` ``` Is it ok? * Is `map_flatten` lint intentionally in `pedantic` category, or could it be moved to `complexity`? changelog: Handle mapping to Option in [`map_flatten`](https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten) lint
2 parents ca2a25d + d4ba561 commit 378ba2e

File tree

4 files changed

+87
-21
lines changed

4 files changed

+87
-21
lines changed

clippy_lints/src/methods/mod.rs

+27-13
Original file line numberDiff line numberDiff line change
@@ -2569,34 +2569,48 @@ fn lint_ok_expect(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ok_args: &[hir::Ex
25692569
fn lint_map_flatten<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_args: &'tcx [hir::Expr<'_>]) {
25702570
// lint if caller of `.map().flatten()` is an Iterator
25712571
if match_trait_method(cx, expr, &paths::ITERATOR) {
2572-
let msg = "called `map(..).flatten()` on an `Iterator`. \
2573-
This is more succinctly expressed by calling `.flat_map(..)`";
2574-
let self_snippet = snippet(cx, map_args[0].span, "..");
2572+
let map_closure_ty = cx.typeck_results().expr_ty(&map_args[1]);
2573+
let is_map_to_option = match map_closure_ty.kind {
2574+
ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => {
2575+
let map_closure_sig = match map_closure_ty.kind {
2576+
ty::Closure(_, substs) => substs.as_closure().sig(),
2577+
_ => map_closure_ty.fn_sig(cx.tcx),
2578+
};
2579+
let map_closure_return_ty = cx.tcx.erase_late_bound_regions(&map_closure_sig.output());
2580+
is_type_diagnostic_item(cx, map_closure_return_ty, sym!(option_type))
2581+
},
2582+
_ => false,
2583+
};
2584+
2585+
let method_to_use = if is_map_to_option {
2586+
// `(...).map(...)` has type `impl Iterator<Item=Option<...>>
2587+
"filter_map"
2588+
} else {
2589+
// `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>>
2590+
"flat_map"
2591+
};
25752592
let func_snippet = snippet(cx, map_args[1].span, "..");
2576-
let hint = format!("{0}.flat_map({1})", self_snippet, func_snippet);
2593+
let hint = format!(".{0}({1})", method_to_use, func_snippet);
25772594
span_lint_and_sugg(
25782595
cx,
25792596
MAP_FLATTEN,
2580-
expr.span,
2581-
msg,
2582-
"try using `flat_map` instead",
2597+
expr.span.with_lo(map_args[0].span.hi()),
2598+
"called `map(..).flatten()` on an `Iterator`",
2599+
&format!("try using `{}` instead", method_to_use),
25832600
hint,
25842601
Applicability::MachineApplicable,
25852602
);
25862603
}
25872604

25882605
// lint if caller of `.map().flatten()` is an Option
25892606
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym!(option_type)) {
2590-
let msg = "called `map(..).flatten()` on an `Option`. \
2591-
This is more succinctly expressed by calling `.and_then(..)`";
2592-
let self_snippet = snippet(cx, map_args[0].span, "..");
25932607
let func_snippet = snippet(cx, map_args[1].span, "..");
2594-
let hint = format!("{0}.and_then({1})", self_snippet, func_snippet);
2608+
let hint = format!(".and_then({})", func_snippet);
25952609
span_lint_and_sugg(
25962610
cx,
25972611
MAP_FLATTEN,
2598-
expr.span,
2599-
msg,
2612+
expr.span.with_lo(map_args[0].span.hi()),
2613+
"called `map(..).flatten()` on an `Option`",
26002614
"try using `and_then` instead",
26012615
hint,
26022616
Applicability::MachineApplicable,

tests/ui/map_flatten.fixed

+14
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@
55
#![allow(clippy::map_identity)]
66

77
fn main() {
8+
// mapping to Option on Iterator
9+
fn option_id(x: i8) -> Option<i8> {
10+
Some(x)
11+
}
12+
let option_id_ref: fn(i8) -> Option<i8> = option_id;
13+
let option_id_closure = |x| Some(x);
14+
let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id).collect();
15+
let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_ref).collect();
16+
let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_closure).collect();
17+
let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(|x| x.checked_add(1)).collect();
18+
19+
// mapping to Iterator on Iterator
820
let _: Vec<_> = vec![5_i8; 6].into_iter().flat_map(|x| 0..x).collect();
21+
22+
// mapping to Option on Option
923
let _: Option<_> = (Some(Some(1))).and_then(|x| x);
1024
}

tests/ui/map_flatten.rs

+14
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@
55
#![allow(clippy::map_identity)]
66

77
fn main() {
8+
// mapping to Option on Iterator
9+
fn option_id(x: i8) -> Option<i8> {
10+
Some(x)
11+
}
12+
let option_id_ref: fn(i8) -> Option<i8> = option_id;
13+
let option_id_closure = |x| Some(x);
14+
let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect();
15+
let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect();
16+
let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect();
17+
let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect();
18+
19+
// mapping to Iterator on Iterator
820
let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
21+
22+
// mapping to Option on Option
923
let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
1024
}

tests/ui/map_flatten.stderr

+32-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,40 @@
1-
error: called `map(..).flatten()` on an `Iterator`. This is more succinctly expressed by calling `.flat_map(..)`
2-
--> $DIR/map_flatten.rs:8:21
1+
error: called `map(..).flatten()` on an `Iterator`
2+
--> $DIR/map_flatten.rs:14:46
33
|
4-
LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `flat_map` instead: `vec![5_i8; 6].into_iter().flat_map(|x| 0..x)`
4+
LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect();
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id)`
66
|
77
= note: `-D clippy::map-flatten` implied by `-D warnings`
88

9-
error: called `map(..).flatten()` on an `Option`. This is more succinctly expressed by calling `.and_then(..)`
10-
--> $DIR/map_flatten.rs:9:24
9+
error: called `map(..).flatten()` on an `Iterator`
10+
--> $DIR/map_flatten.rs:15:46
11+
|
12+
LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect();
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_ref)`
14+
15+
error: called `map(..).flatten()` on an `Iterator`
16+
--> $DIR/map_flatten.rs:16:46
17+
|
18+
LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect();
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_closure)`
20+
21+
error: called `map(..).flatten()` on an `Iterator`
22+
--> $DIR/map_flatten.rs:17:46
23+
|
24+
LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect();
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(|x| x.checked_add(1))`
26+
27+
error: called `map(..).flatten()` on an `Iterator`
28+
--> $DIR/map_flatten.rs:20:46
29+
|
30+
LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `flat_map` instead: `.flat_map(|x| 0..x)`
32+
33+
error: called `map(..).flatten()` on an `Option`
34+
--> $DIR/map_flatten.rs:23:39
1135
|
1236
LL | let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
13-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `(Some(Some(1))).and_then(|x| x)`
37+
| ^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `.and_then(|x| x)`
1438

15-
error: aborting due to 2 previous errors
39+
error: aborting due to 6 previous errors
1640

0 commit comments

Comments
 (0)