Skip to content

Commit 05ebde1

Browse files
committed
Lint flatten() under lines_filter_map_ok
1 parent 56c8235 commit 05ebde1

File tree

4 files changed

+67
-25
lines changed

4 files changed

+67
-25
lines changed

clippy_lints/src/lines_filter_map_ok.rs

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,35 +53,41 @@ declare_clippy_lint! {
5353
#[clippy::version = "1.70.0"]
5454
pub LINES_FILTER_MAP_OK,
5555
suspicious,
56-
"filtering `std::io::Lines` with `filter_map()` or `flat_map()` might cause an infinite loop"
56+
"filtering `std::io::Lines` with `filter_map()`, `flat_map()`, or `flatten()` might cause an infinite loop"
5757
}
5858
declare_lint_pass!(LinesFilterMapOk => [LINES_FILTER_MAP_OK]);
5959

6060
impl LateLintPass<'_> for LinesFilterMapOk {
6161
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
62-
if let ExprKind::MethodCall(fm_method, fm_receiver, [fm_arg], fm_span) = expr.kind &&
62+
if let ExprKind::MethodCall(fm_method, fm_receiver, fm_args, fm_span) = expr.kind &&
6363
is_trait_method(cx, expr, sym::Iterator) &&
64-
(fm_method.ident.as_str() == "filter_map" || fm_method.ident.as_str() == "flat_map") &&
64+
let fm_method_str = fm_method.ident.as_str() &&
65+
matches!(fm_method_str, "filter_map" | "flat_map" | "flatten") &&
6566
is_type_diagnostic_item(cx, cx.typeck_results().expr_ty_adjusted(fm_receiver), sym::IoLines)
6667
{
67-
let lint = match &fm_arg.kind {
68-
// Detect `Result::ok`
69-
ExprKind::Path(qpath) =>
70-
cx.qpath_res(qpath, fm_arg.hir_id).opt_def_id().map(|did|
71-
match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD)).unwrap_or_default(),
72-
// Detect `|x| x.ok()`
73-
ExprKind::Closure(Closure { body, .. }) =>
74-
if let Body { params: [param], value, .. } = cx.tcx.hir().body(*body) &&
75-
let ExprKind::MethodCall(method, receiver, [], _) = value.kind &&
76-
path_to_local_id(receiver, param.pat.hir_id) &&
77-
let Some(method_did) = cx.typeck_results().type_dependent_def_id(value.hir_id)
78-
{
79-
is_diag_item_method(cx, method_did, sym::Result) && method.ident.as_str() == "ok"
80-
} else {
81-
false
82-
}
83-
_ => false,
68+
let lint = if let [fm_arg] = fm_args {
69+
match &fm_arg.kind {
70+
// Detect `Result::ok`
71+
ExprKind::Path(qpath) =>
72+
cx.qpath_res(qpath, fm_arg.hir_id).opt_def_id().map(|did|
73+
match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD)).unwrap_or_default(),
74+
// Detect `|x| x.ok()`
75+
ExprKind::Closure(Closure { body, .. }) =>
76+
if let Body { params: [param], value, .. } = cx.tcx.hir().body(*body) &&
77+
let ExprKind::MethodCall(method, receiver, [], _) = value.kind &&
78+
path_to_local_id(receiver, param.pat.hir_id) &&
79+
let Some(method_did) = cx.typeck_results().type_dependent_def_id(value.hir_id)
80+
{
81+
is_diag_item_method(cx, method_did, sym::Result) && method.ident.as_str() == "ok"
82+
} else {
83+
false
84+
},
85+
_ => false,
86+
}
87+
} else {
88+
fm_method_str == "flatten"
8489
};
90+
8591
if lint {
8692
span_lint_and_then(cx,
8793
LINES_FILTER_MAP_OK,

tests/ui/lines_filter_map_ok.fixed

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ fn main() -> io::Result<()> {
1010
// Lint
1111
let f = std::fs::File::open("/")?;
1212
BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ());
13+
// Lint
14+
let f = std::fs::File::open("/")?;
15+
BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ());
16+
1317
let s = "foo\nbar\nbaz\n";
1418
// Lint
1519
io::stdin().lines().map_while(Result::ok).for_each(|_| ());
1620
// Lint
1721
io::stdin().lines().map_while(Result::ok).for_each(|_| ());
22+
// Lint
23+
io::stdin().lines().map_while(Result::ok).for_each(|_| ());
1824
// Do not lint (not a `Lines` iterator)
1925
io::stdin()
2026
.lines()

tests/ui/lines_filter_map_ok.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ fn main() -> io::Result<()> {
1010
// Lint
1111
let f = std::fs::File::open("/")?;
1212
BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ());
13+
// Lint
14+
let f = std::fs::File::open("/")?;
15+
BufReader::new(f).lines().flatten().for_each(|_| ());
16+
1317
let s = "foo\nbar\nbaz\n";
1418
// Lint
1519
io::stdin().lines().filter_map(Result::ok).for_each(|_| ());
1620
// Lint
1721
io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ());
22+
// Lint
23+
io::stdin().lines().flatten().for_each(|_| ());
1824
// Do not lint (not a `Lines` iterator)
1925
io::stdin()
2026
.lines()

tests/ui/lines_filter_map_ok.stderr

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,53 @@ note: this expression returning a `std::io::Lines` may produce an infinite numbe
2424
LL | BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ());
2525
| ^^^^^^^^^^^^^^^^^^^^^^^^^
2626

27+
error: `flatten()` will run forever if the iterator repeatedly produces an `Err`
28+
--> $DIR/lines_filter_map_ok.rs:15:31
29+
|
30+
LL | BufReader::new(f).lines().flatten().for_each(|_| ());
31+
| ^^^^^^^^^ help: replace with: `map_while(Result::ok)`
32+
|
33+
note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error
34+
--> $DIR/lines_filter_map_ok.rs:15:5
35+
|
36+
LL | BufReader::new(f).lines().flatten().for_each(|_| ());
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
38+
2739
error: `filter_map()` will run forever if the iterator repeatedly produces an `Err`
28-
--> $DIR/lines_filter_map_ok.rs:15:25
40+
--> $DIR/lines_filter_map_ok.rs:19:25
2941
|
3042
LL | io::stdin().lines().filter_map(Result::ok).for_each(|_| ());
3143
| ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)`
3244
|
3345
note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error
34-
--> $DIR/lines_filter_map_ok.rs:15:5
46+
--> $DIR/lines_filter_map_ok.rs:19:5
3547
|
3648
LL | io::stdin().lines().filter_map(Result::ok).for_each(|_| ());
3749
| ^^^^^^^^^^^^^^^^^^^
3850

3951
error: `filter_map()` will run forever if the iterator repeatedly produces an `Err`
40-
--> $DIR/lines_filter_map_ok.rs:17:25
52+
--> $DIR/lines_filter_map_ok.rs:21:25
4153
|
4254
LL | io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ());
4355
| ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)`
4456
|
4557
note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error
46-
--> $DIR/lines_filter_map_ok.rs:17:5
58+
--> $DIR/lines_filter_map_ok.rs:21:5
4759
|
4860
LL | io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ());
4961
| ^^^^^^^^^^^^^^^^^^^
5062

51-
error: aborting due to 4 previous errors
63+
error: `flatten()` will run forever if the iterator repeatedly produces an `Err`
64+
--> $DIR/lines_filter_map_ok.rs:23:25
65+
|
66+
LL | io::stdin().lines().flatten().for_each(|_| ());
67+
| ^^^^^^^^^ help: replace with: `map_while(Result::ok)`
68+
|
69+
note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error
70+
--> $DIR/lines_filter_map_ok.rs:23:5
71+
|
72+
LL | io::stdin().lines().flatten().for_each(|_| ());
73+
| ^^^^^^^^^^^^^^^^^^^
74+
75+
error: aborting due to 6 previous errors
5276

0 commit comments

Comments
 (0)