@@ -56,6 +56,7 @@ mod iter_with_drain;
5656mod iterator_step_by_zero;
5757mod join_absolute_paths;
5858mod lib;
59+ mod lines_filter_map_ok;
5960mod manual_c_str_literals;
6061mod manual_contains;
6162mod manual_inspect;
@@ -4665,6 +4666,55 @@ declare_clippy_lint! {
46654666 "making no use of the \" map closure\" when calling `.map_or_else(|| 2 * k, |n| n)`"
46664667}
46674668
4669+ declare_clippy_lint ! {
4670+ /// ### What it does
4671+ /// Checks for usage of `lines.filter_map(Result::ok)` or `lines.flat_map(Result::ok)`
4672+ /// when `lines` has type `std::io::Lines`.
4673+ ///
4674+ /// ### Why is this bad?
4675+ /// `Lines` instances might produce a never-ending stream of `Err`, in which case
4676+ /// `filter_map(Result::ok)` will enter an infinite loop while waiting for an
4677+ /// `Ok` variant. Calling `next()` once is sufficient to enter the infinite loop,
4678+ /// even in the absence of explicit loops in the user code.
4679+ ///
4680+ /// This situation can arise when working with user-provided paths. On some platforms,
4681+ /// `std::fs::File::open(path)` might return `Ok(fs)` even when `path` is a directory,
4682+ /// but any later attempt to read from `fs` will return an error.
4683+ ///
4684+ /// ### Known problems
4685+ /// This lint suggests replacing `filter_map()` or `flat_map()` applied to a `Lines`
4686+ /// instance in all cases. There are two cases where the suggestion might not be
4687+ /// appropriate or necessary:
4688+ ///
4689+ /// - If the `Lines` instance can never produce any error, or if an error is produced
4690+ /// only once just before terminating the iterator, using `map_while()` is not
4691+ /// necessary but will not do any harm.
4692+ /// - If the `Lines` instance can produce intermittent errors then recover and produce
4693+ /// successful results, using `map_while()` would stop at the first error.
4694+ ///
4695+ /// ### Example
4696+ /// ```no_run
4697+ /// # use std::{fs::File, io::{self, BufRead, BufReader}};
4698+ /// # let _ = || -> io::Result<()> {
4699+ /// let mut lines = BufReader::new(File::open("some-path")?).lines().filter_map(Result::ok);
4700+ /// // If "some-path" points to a directory, the next statement never terminates:
4701+ /// let first_line: Option<String> = lines.next();
4702+ /// # Ok(()) };
4703+ /// ```
4704+ /// Use instead:
4705+ /// ```no_run
4706+ /// # use std::{fs::File, io::{self, BufRead, BufReader}};
4707+ /// # let _ = || -> io::Result<()> {
4708+ /// let mut lines = BufReader::new(File::open("some-path")?).lines().map_while(Result::ok);
4709+ /// let first_line: Option<String> = lines.next();
4710+ /// # Ok(()) };
4711+ /// ```
4712+ #[ clippy:: version = "1.70.0" ]
4713+ pub LINES_FILTER_MAP_OK ,
4714+ suspicious,
4715+ "filtering `std::io::Lines` with `filter_map()`, `flat_map()`, or `flatten()` might cause an infinite loop"
4716+ }
4717+
46684718#[ expect( clippy:: struct_excessive_bools) ]
46694719pub struct Methods {
46704720 avoid_breaking_exported_api : bool ,
@@ -4847,6 +4897,7 @@ impl_lint_pass!(Methods => [
48474897 IP_CONSTANT ,
48484898 REDUNDANT_ITER_CLONED ,
48494899 UNNECESSARY_OPTION_MAP_OR_ELSE ,
4900+ LINES_FILTER_MAP_OK ,
48504901] ) ;
48514902
48524903/// Extracts a method call name, args, and `Span` of the method name.
@@ -5165,6 +5216,15 @@ impl Methods {
51655216 unnecessary_filter_map:: check ( cx, expr, arg, name) ;
51665217 filter_map_bool_then:: check ( cx, expr, arg, call_span) ;
51675218 filter_map_identity:: check ( cx, expr, arg, span) ;
5219+ lines_filter_map_ok:: check_filter_or_flat_map (
5220+ cx,
5221+ expr,
5222+ recv,
5223+ "filter_map" ,
5224+ arg,
5225+ call_span,
5226+ self . msrv ,
5227+ ) ;
51685228 } ,
51695229 ( sym:: find_map, [ arg] ) => {
51705230 unused_enumerate_index:: check ( cx, expr, recv, arg) ;
@@ -5174,20 +5234,26 @@ impl Methods {
51745234 unused_enumerate_index:: check ( cx, expr, recv, arg) ;
51755235 flat_map_identity:: check ( cx, expr, arg, span) ;
51765236 flat_map_option:: check ( cx, expr, arg, span) ;
5237+ lines_filter_map_ok:: check_filter_or_flat_map (
5238+ cx, expr, recv, "flat_map" , arg, call_span, self . msrv ,
5239+ ) ;
51775240 } ,
5178- ( sym:: flatten, [ ] ) => match method_call ( recv) {
5179- Some ( ( sym:: map, recv, [ map_arg] , map_span, _) ) => {
5180- map_flatten:: check ( cx, expr, recv, map_arg, map_span) ;
5181- } ,
5182- Some ( ( sym:: cloned, recv2, [ ] , _, _) ) => iter_overeager_cloned:: check (
5183- cx,
5184- expr,
5185- recv,
5186- recv2,
5187- iter_overeager_cloned:: Op :: LaterCloned ,
5188- true ,
5189- ) ,
5190- _ => { } ,
5241+ ( sym:: flatten, [ ] ) => {
5242+ match method_call ( recv) {
5243+ Some ( ( sym:: map, recv, [ map_arg] , map_span, _) ) => {
5244+ map_flatten:: check ( cx, expr, recv, map_arg, map_span) ;
5245+ } ,
5246+ Some ( ( sym:: cloned, recv2, [ ] , _, _) ) => iter_overeager_cloned:: check (
5247+ cx,
5248+ expr,
5249+ recv,
5250+ recv2,
5251+ iter_overeager_cloned:: Op :: LaterCloned ,
5252+ true ,
5253+ ) ,
5254+ _ => { } ,
5255+ }
5256+ lines_filter_map_ok:: check_flatten ( cx, expr, recv, call_span, self . msrv ) ;
51915257 } ,
51925258 ( sym:: fold, [ init, acc] ) => {
51935259 manual_try_fold:: check ( cx, expr, init, acc, call_span, self . msrv ) ;
0 commit comments