Auto merge of #11691 - sjwang05:lines-filter-map-ok-fix, r=Centri3

Lint `flatten()` under `lines_filter_map_ok`

Fixes #11686

changelog: [`lines_filter_map_ok`]: Also lint calls to `flatten()`
This commit is contained in:
bors 2023-11-19 01:50:24 +00:00
commit dbd19f9b48
4 changed files with 76 additions and 34 deletions

View File

@ -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)
@ -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,
}
}

View File

@ -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()

View File

@ -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()

View File

@ -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