about summary refs log tree commit diff
diff options
context:
space:
mode:
authorsjwang05 <63834813+sjwang05@users.noreply.github.com>2023-10-20 10:19:46 -0700
committersjwang05 <63834813+sjwang05@users.noreply.github.com>2023-11-02 18:24:15 -0700
commit39eded7b0547df68c171195e0437bac57d05c3df (patch)
treef89f6f9f3f51a532f46eb195037237480789107d
parent902c79c654b45ab9b8ae425bd2c1fa0c50a3edf7 (diff)
downloadrust-39eded7b0547df68c171195e0437bac57d05c3df.tar.gz
rust-39eded7b0547df68c171195e0437bac57d05c3df.zip
Lint `flatten()` under `lines_filter_map_ok`
-rw-r--r--clippy_lints/src/lines_filter_map_ok.rs55
-rw-r--r--tests/ui/lines_filter_map_ok.fixed6
-rw-r--r--tests/ui/lines_filter_map_ok.rs6
-rw-r--r--tests/ui/lines_filter_map_ok.stderr34
4 files changed, 69 insertions, 32 deletions
diff --git a/clippy_lints/src/lines_filter_map_ok.rs b/clippy_lints/src/lines_filter_map_ok.rs
index 0a5f5a80cb7..1449357e25c 100644
--- a/clippy_lints/src/lines_filter_map_ok.rs
+++ b/clippy_lints/src/lines_filter_map_ok.rs
@@ -53,40 +53,41 @@ declare_clippy_lint! {
     #[clippy::version = "1.70.0"]
     pub LINES_FILTER_MAP_OK,
     suspicious,
-    "filtering `std::io::Lines` with `filter_map()` or `flat_map()` might cause an infinite loop"
+    "filtering `std::io::Lines` with `filter_map()`, `flat_map()`, or `flatten()` might cause an infinite loop"
 }
 declare_lint_pass!(LinesFilterMapOk => [LINES_FILTER_MAP_OK]);
 
 impl LateLintPass<'_> for LinesFilterMapOk {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
-        if let ExprKind::MethodCall(fm_method, fm_receiver, [fm_arg], fm_span) = expr.kind
-            && is_trait_method(cx, expr, sym::Iterator)
-            && (fm_method.ident.as_str() == "filter_map" || fm_method.ident.as_str() == "flat_map")
-            && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty_adjusted(fm_receiver), sym::IoLines)
+        if let ExprKind::MethodCall(fm_method, fm_receiver, fm_args, fm_span) = expr.kind &&
+            is_trait_method(cx, expr, sym::Iterator) &&
+            let fm_method_str = fm_method.ident.as_str() &&
+            matches!(fm_method_str, "filter_map" | "flat_map" | "flatten") &&
+            is_type_diagnostic_item(cx, cx.typeck_results().expr_ty_adjusted(fm_receiver), sym::IoLines)
         {
-            let lint = match &fm_arg.kind {
-                // Detect `Result::ok`
-                ExprKind::Path(qpath) => cx
-                    .qpath_res(qpath, fm_arg.hir_id)
-                    .opt_def_id()
-                    .map(|did| match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD))
-                    .unwrap_or_default(),
-                // Detect `|x| x.ok()`
-                ExprKind::Closure(Closure { body, .. }) => {
-                    if let Body {
-                        params: [param], value, ..
-                    } = cx.tcx.hir().body(*body)
-                        && let ExprKind::MethodCall(method, receiver, [], _) = value.kind
-                        && path_to_local_id(receiver, param.pat.hir_id)
-                        && let Some(method_did) = cx.typeck_results().type_dependent_def_id(value.hir_id)
-                    {
-                        is_diag_item_method(cx, method_did, sym::Result) && method.ident.as_str() == "ok"
-                    } else {
-                        false
-                    }
-                },
-                _ => false,
+            let lint = if let [fm_arg] = fm_args {
+                match &fm_arg.kind {
+                    // Detect `Result::ok`
+                    ExprKind::Path(qpath) =>
+                        cx.qpath_res(qpath, fm_arg.hir_id).opt_def_id().map(|did|
+                            match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD)).unwrap_or_default(),
+                    // Detect `|x| x.ok()`
+                    ExprKind::Closure(Closure { body, .. }) =>
+                        if let Body { params: [param], value, .. } = cx.tcx.hir().body(*body) &&
+                            let ExprKind::MethodCall(method, receiver, [], _) = value.kind &&
+                            path_to_local_id(receiver, param.pat.hir_id) &&
+                            let Some(method_did) = cx.typeck_results().type_dependent_def_id(value.hir_id)
+                        {
+                            is_diag_item_method(cx, method_did, sym::Result) && method.ident.as_str() == "ok"
+                        } else {
+                            false
+                        },
+                    _ => false,
+                }
+            } else {
+                fm_method_str == "flatten"
             };
+
             if lint {
                 span_lint_and_then(
                     cx,
diff --git a/tests/ui/lines_filter_map_ok.fixed b/tests/ui/lines_filter_map_ok.fixed
index 74ef6f72957..621115cc132 100644
--- a/tests/ui/lines_filter_map_ok.fixed
+++ b/tests/ui/lines_filter_map_ok.fixed
@@ -10,11 +10,17 @@ fn main() -> io::Result<()> {
     // Lint
     let f = std::fs::File::open("/")?;
     BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ());
+    // Lint
+    let f = std::fs::File::open("/")?;
+    BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ());
+
     let s = "foo\nbar\nbaz\n";
     // Lint
     io::stdin().lines().map_while(Result::ok).for_each(|_| ());
     // Lint
     io::stdin().lines().map_while(Result::ok).for_each(|_| ());
+    // Lint
+    io::stdin().lines().map_while(Result::ok).for_each(|_| ());
     // Do not lint (not a `Lines` iterator)
     io::stdin()
         .lines()
diff --git a/tests/ui/lines_filter_map_ok.rs b/tests/ui/lines_filter_map_ok.rs
index 345f4dc5f30..a86efbd6686 100644
--- a/tests/ui/lines_filter_map_ok.rs
+++ b/tests/ui/lines_filter_map_ok.rs
@@ -10,11 +10,17 @@ fn main() -> io::Result<()> {
     // Lint
     let f = std::fs::File::open("/")?;
     BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ());
+    // Lint
+    let f = std::fs::File::open("/")?;
+    BufReader::new(f).lines().flatten().for_each(|_| ());
+
     let s = "foo\nbar\nbaz\n";
     // Lint
     io::stdin().lines().filter_map(Result::ok).for_each(|_| ());
     // Lint
     io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ());
+    // Lint
+    io::stdin().lines().flatten().for_each(|_| ());
     // Do not lint (not a `Lines` iterator)
     io::stdin()
         .lines()
diff --git a/tests/ui/lines_filter_map_ok.stderr b/tests/ui/lines_filter_map_ok.stderr
index fa2ba0a9a46..9833ab16473 100644
--- a/tests/ui/lines_filter_map_ok.stderr
+++ b/tests/ui/lines_filter_map_ok.stderr
@@ -24,29 +24,53 @@ note: this expression returning a `std::io::Lines` may produce an infinite numbe
 LL |     BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ());
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^
 
+error: `flatten()` will run forever if the iterator repeatedly produces an `Err`
+  --> $DIR/lines_filter_map_ok.rs:15:31
+   |
+LL |     BufReader::new(f).lines().flatten().for_each(|_| ());
+   |                               ^^^^^^^^^ help: replace with: `map_while(Result::ok)`
+   |
+note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error
+  --> $DIR/lines_filter_map_ok.rs:15:5
+   |
+LL |     BufReader::new(f).lines().flatten().for_each(|_| ());
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^
+
 error: `filter_map()` will run forever if the iterator repeatedly produces an `Err`
-  --> $DIR/lines_filter_map_ok.rs:15:25
+  --> $DIR/lines_filter_map_ok.rs:19:25
    |
 LL |     io::stdin().lines().filter_map(Result::ok).for_each(|_| ());
    |                         ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)`
    |
 note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error
-  --> $DIR/lines_filter_map_ok.rs:15:5
+  --> $DIR/lines_filter_map_ok.rs:19:5
    |
 LL |     io::stdin().lines().filter_map(Result::ok).for_each(|_| ());
    |     ^^^^^^^^^^^^^^^^^^^
 
 error: `filter_map()` will run forever if the iterator repeatedly produces an `Err`
-  --> $DIR/lines_filter_map_ok.rs:17:25
+  --> $DIR/lines_filter_map_ok.rs:21:25
    |
 LL |     io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ());
    |                         ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)`
    |
 note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error
-  --> $DIR/lines_filter_map_ok.rs:17:5
+  --> $DIR/lines_filter_map_ok.rs:21:5
    |
 LL |     io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ());
    |     ^^^^^^^^^^^^^^^^^^^
 
-error: aborting due to 4 previous errors
+error: `flatten()` will run forever if the iterator repeatedly produces an `Err`
+  --> $DIR/lines_filter_map_ok.rs:23:25
+   |
+LL |     io::stdin().lines().flatten().for_each(|_| ());
+   |                         ^^^^^^^^^ help: replace with: `map_while(Result::ok)`
+   |
+note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error
+  --> $DIR/lines_filter_map_ok.rs:23:5
+   |
+LL |     io::stdin().lines().flatten().for_each(|_| ());
+   |     ^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 6 previous errors