Skip to content

Lint flatten() under lines_filter_map_ok #11691

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 33 additions & 27 deletions clippy_lints/src/lines_filter_map_ok.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,45 @@ 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
if let ExprKind::MethodCall(fm_method, fm_receiver, fm_args, 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")
&& 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_arg.kind {
span_lint_and_then(
cx,
LINES_FILTER_MAP_OK,
fm_span,
&format!("`{fm_method_str}()` will run forever if the iterator repeatedly produces an `Err`",),
|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)
Expand All @@ -86,29 +113,8 @@ impl LateLintPass<'_> for LinesFilterMapOk {
}
},
_ => 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,
);
},
);
}
}
},
_ => false,
}
}
6 changes: 6 additions & 0 deletions tests/ui/lines_filter_map_ok.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 6 additions & 0 deletions tests/ui/lines_filter_map_ok.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
34 changes: 29 additions & 5 deletions tests/ui/lines_filter_map_ok.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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