about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_lints/src/methods/mod.rs40
-rw-r--r--tests/ui/map_flatten.fixed14
-rw-r--r--tests/ui/map_flatten.rs14
-rw-r--r--tests/ui/map_flatten.stderr40
4 files changed, 87 insertions, 21 deletions
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 9edcdd979ff..9217324b18c 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -2569,17 +2569,34 @@ fn lint_ok_expect(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ok_args: &[hir::Ex
 fn lint_map_flatten<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_args: &'tcx [hir::Expr<'_>]) {
     // lint if caller of `.map().flatten()` is an Iterator
     if match_trait_method(cx, expr, &paths::ITERATOR) {
-        let msg = "called `map(..).flatten()` on an `Iterator`. \
-                    This is more succinctly expressed by calling `.flat_map(..)`";
-        let self_snippet = snippet(cx, map_args[0].span, "..");
+        let map_closure_ty = cx.typeck_results().expr_ty(&map_args[1]);
+        let is_map_to_option = match map_closure_ty.kind {
+            ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => {
+                let map_closure_sig = match map_closure_ty.kind {
+                    ty::Closure(_, substs) => substs.as_closure().sig(),
+                    _ => map_closure_ty.fn_sig(cx.tcx),
+                };
+                let map_closure_return_ty = cx.tcx.erase_late_bound_regions(&map_closure_sig.output());
+                is_type_diagnostic_item(cx, map_closure_return_ty, sym!(option_type))
+            },
+            _ => false,
+        };
+
+        let method_to_use = if is_map_to_option {
+            // `(...).map(...)` has type `impl Iterator<Item=Option<...>>
+            "filter_map"
+        } else {
+            // `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>>
+            "flat_map"
+        };
         let func_snippet = snippet(cx, map_args[1].span, "..");
-        let hint = format!("{0}.flat_map({1})", self_snippet, func_snippet);
+        let hint = format!(".{0}({1})", method_to_use, func_snippet);
         span_lint_and_sugg(
             cx,
             MAP_FLATTEN,
-            expr.span,
-            msg,
-            "try using `flat_map` instead",
+            expr.span.with_lo(map_args[0].span.hi()),
+            "called `map(..).flatten()` on an `Iterator`",
+            &format!("try using `{}` instead", method_to_use),
             hint,
             Applicability::MachineApplicable,
         );
@@ -2587,16 +2604,13 @@ fn lint_map_flatten<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map
 
     // lint if caller of `.map().flatten()` is an Option
     if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym!(option_type)) {
-        let msg = "called `map(..).flatten()` on an `Option`. \
-                    This is more succinctly expressed by calling `.and_then(..)`";
-        let self_snippet = snippet(cx, map_args[0].span, "..");
         let func_snippet = snippet(cx, map_args[1].span, "..");
-        let hint = format!("{0}.and_then({1})", self_snippet, func_snippet);
+        let hint = format!(".and_then({})", func_snippet);
         span_lint_and_sugg(
             cx,
             MAP_FLATTEN,
-            expr.span,
-            msg,
+            expr.span.with_lo(map_args[0].span.hi()),
+            "called `map(..).flatten()` on an `Option`",
             "try using `and_then` instead",
             hint,
             Applicability::MachineApplicable,
diff --git a/tests/ui/map_flatten.fixed b/tests/ui/map_flatten.fixed
index 4171d80f48a..a5fdf7df613 100644
--- a/tests/ui/map_flatten.fixed
+++ b/tests/ui/map_flatten.fixed
@@ -5,6 +5,20 @@
 #![allow(clippy::map_identity)]
 
 fn main() {
+    // mapping to Option on Iterator
+    fn option_id(x: i8) -> Option<i8> {
+        Some(x)
+    }
+    let option_id_ref: fn(i8) -> Option<i8> = option_id;
+    let option_id_closure = |x| Some(x);
+    let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id).collect();
+    let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_ref).collect();
+    let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_closure).collect();
+    let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(|x| x.checked_add(1)).collect();
+
+    // mapping to Iterator on Iterator
     let _: Vec<_> = vec![5_i8; 6].into_iter().flat_map(|x| 0..x).collect();
+
+    // mapping to Option on Option
     let _: Option<_> = (Some(Some(1))).and_then(|x| x);
 }
diff --git a/tests/ui/map_flatten.rs b/tests/ui/map_flatten.rs
index 16a0fd090ad..abbc4e16e56 100644
--- a/tests/ui/map_flatten.rs
+++ b/tests/ui/map_flatten.rs
@@ -5,6 +5,20 @@
 #![allow(clippy::map_identity)]
 
 fn main() {
+    // mapping to Option on Iterator
+    fn option_id(x: i8) -> Option<i8> {
+        Some(x)
+    }
+    let option_id_ref: fn(i8) -> Option<i8> = option_id;
+    let option_id_closure = |x| Some(x);
+    let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect();
+    let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect();
+    let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect();
+    let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect();
+
+    // mapping to Iterator on Iterator
     let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
+
+    // mapping to Option on Option
     let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
 }
diff --git a/tests/ui/map_flatten.stderr b/tests/ui/map_flatten.stderr
index 00bc41c15e9..b6479cd69ea 100644
--- a/tests/ui/map_flatten.stderr
+++ b/tests/ui/map_flatten.stderr
@@ -1,16 +1,40 @@
-error: called `map(..).flatten()` on an `Iterator`. This is more succinctly expressed by calling `.flat_map(..)`
-  --> $DIR/map_flatten.rs:8:21
+error: called `map(..).flatten()` on an `Iterator`
+  --> $DIR/map_flatten.rs:14:46
    |
-LL |     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(|x| 0..x)`
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect();
+   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id)`
    |
    = note: `-D clippy::map-flatten` implied by `-D warnings`
 
-error: called `map(..).flatten()` on an `Option`. This is more succinctly expressed by calling `.and_then(..)`
-  --> $DIR/map_flatten.rs:9:24
+error: called `map(..).flatten()` on an `Iterator`
+  --> $DIR/map_flatten.rs:15:46
+   |
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect();
+   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_ref)`
+
+error: called `map(..).flatten()` on an `Iterator`
+  --> $DIR/map_flatten.rs:16:46
+   |
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect();
+   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_closure)`
+
+error: called `map(..).flatten()` on an `Iterator`
+  --> $DIR/map_flatten.rs:17:46
+   |
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect();
+   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(|x| x.checked_add(1))`
+
+error: called `map(..).flatten()` on an `Iterator`
+  --> $DIR/map_flatten.rs:20:46
+   |
+LL |     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)`
+
+error: called `map(..).flatten()` on an `Option`
+  --> $DIR/map_flatten.rs:23:39
    |
 LL |     let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
-   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `(Some(Some(1))).and_then(|x| x)`
+   |                                       ^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `.and_then(|x| x)`
 
-error: aborting due to 2 previous errors
+error: aborting due to 6 previous errors