From 39eded7b0547df68c171195e0437bac57d05c3df Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Fri, 20 Oct 2023 10:19:46 -0700 Subject: [PATCH 1/5] Lint `flatten()` under `lines_filter_map_ok` --- clippy_lints/src/lines_filter_map_ok.rs | 55 +++++++++++++------------ tests/ui/lines_filter_map_ok.fixed | 6 +++ tests/ui/lines_filter_map_ok.rs | 6 +++ tests/ui/lines_filter_map_ok.stderr | 38 +++++++++++++---- 4 files changed, 71 insertions(+), 34 deletions(-) diff --git a/clippy_lints/src/lines_filter_map_ok.rs b/clippy_lints/src/lines_filter_map_ok.rs index 0a5f5a80cb72..1449357e25c9 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 74ef6f72957f..621115cc1325 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 345f4dc5f304..a86efbd66862 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 fa2ba0a9a462..9833ab16473c 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: `filter_map()` will run forever if the iterator repeatedly produces an `Err` - --> $DIR/lines_filter_map_ok.rs:15:25 +error: `flatten()` will run forever if the iterator repeatedly produces an `Err` + --> $DIR/lines_filter_map_ok.rs:15:31 | -LL | io::stdin().lines().filter_map(Result::ok).for_each(|_| ()); - | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` +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: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: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 From 103200967e690d25eb8c042fd63f6abe848b0fdd Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Mon, 30 Oct 2023 18:39:39 +0000 Subject: [PATCH 2/5] Use `match` on method args instead of `if let` --- clippy_lints/src/lines_filter_map_ok.rs | 42 +++++++++++++------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/clippy_lints/src/lines_filter_map_ok.rs b/clippy_lints/src/lines_filter_map_ok.rs index 1449357e25c9..f12d042ec66a 100644 --- a/clippy_lints/src/lines_filter_map_ok.rs +++ b/clippy_lints/src/lines_filter_map_ok.rs @@ -65,27 +65,29 @@ impl LateLintPass<'_> for LinesFilterMapOk { 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 = 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, + let lint = match fm_args { + [] => fm_method_str == "flatten", + [fm_arg] => { + 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" + _ => false, }; if lint { From 10f3977ebaf384ffa1e99914fa8f7663e9399025 Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Mon, 30 Oct 2023 19:12:03 +0000 Subject: [PATCH 3/5] Split arg/method checking into its own function --- clippy_lints/src/lines_filter_map_ok.rs | 110 +++++++++++++----------- 1 file changed, 58 insertions(+), 52 deletions(-) diff --git a/clippy_lints/src/lines_filter_map_ok.rs b/clippy_lints/src/lines_filter_map_ok.rs index f12d042ec66a..39e5e271d73a 100644 --- a/clippy_lints/src/lines_filter_map_ok.rs +++ b/clippy_lints/src/lines_filter_map_ok.rs @@ -59,59 +59,65 @@ 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_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) + 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) + && should_lint(cx, fm_args, fm_method_str) { - let lint = match fm_args { - [] => fm_method_str == "flatten", - [fm_arg] => { - 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, - } - } - _ => false, - }; - - if lint { - span_lint_and_then( - cx, - LINES_FILTER_MAP_OK, - fm_span, - &format!( - "`{}()` will run forever if the iterator repeatedly produces an `Err`", - fm_method.ident - ), - |diag| { - diag.span_note( - fm_receiver.span, - "this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error"); - diag.span_suggestion( - fm_span, - "replace with", - "map_while(Result::ok)", - Applicability::MaybeIncorrect, - ); - }, - ); - } + span_lint_and_then( + cx, + LINES_FILTER_MAP_OK, + fm_span, + &format!( + "`{}()` will run forever if the iterator repeatedly produces an `Err`", + fm_method.ident + ), + |diag| { + diag.span_note( + fm_receiver.span, + "this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error"); + diag.span_suggestion( + fm_span, + "replace with", + "map_while(Result::ok)", + Applicability::MaybeIncorrect, + ); + }, + ); } } } + +fn should_lint(cx: &LateContext<'_>, args: &[Expr<'_>], method_str: &str) -> bool { + match args { + [] => method_str == "flatten", + [fm_arg] => { + 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, + } + }, + _ => false, + } +} From c50af350a1bf2b120844fd1dc8dcca362ce437d5 Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Fri, 3 Nov 2023 10:32:19 -0700 Subject: [PATCH 4/5] Use `fm_method_str` in lint formatting Co-authored-by: Catherine Flores --- clippy_lints/src/lines_filter_map_ok.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clippy_lints/src/lines_filter_map_ok.rs b/clippy_lints/src/lines_filter_map_ok.rs index 39e5e271d73a..eec65d6a1fd8 100644 --- a/clippy_lints/src/lines_filter_map_ok.rs +++ b/clippy_lints/src/lines_filter_map_ok.rs @@ -71,8 +71,7 @@ impl LateLintPass<'_> for LinesFilterMapOk { LINES_FILTER_MAP_OK, fm_span, &format!( - "`{}()` will run forever if the iterator repeatedly produces an `Err`", - fm_method.ident + "`{fm_method_str}()` will run forever if the iterator repeatedly produces an `Err`", ), |diag| { diag.span_note( From 4388158d248a7a601de2b53a206a4281cc5d6d57 Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Fri, 3 Nov 2023 13:48:04 -0700 Subject: [PATCH 5/5] Fix formatting --- clippy_lints/src/lines_filter_map_ok.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/clippy_lints/src/lines_filter_map_ok.rs b/clippy_lints/src/lines_filter_map_ok.rs index eec65d6a1fd8..00ca553445fc 100644 --- a/clippy_lints/src/lines_filter_map_ok.rs +++ b/clippy_lints/src/lines_filter_map_ok.rs @@ -70,9 +70,7 @@ impl LateLintPass<'_> for LinesFilterMapOk { cx, LINES_FILTER_MAP_OK, fm_span, - &format!( - "`{fm_method_str}()` will run forever if the iterator repeatedly produces an `Err`", - ), + &format!("`{fm_method_str}()` will run forever if the iterator repeatedly produces an `Err`",), |diag| { diag.span_note( fm_receiver.span,