@@ -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.
@@ -5169,6 +5220,15 @@ impl Methods {
51695220 unnecessary_filter_map:: check ( cx, expr, arg, name) ;
51705221 filter_map_bool_then:: check ( cx, expr, arg, call_span) ;
51715222 filter_map_identity:: check ( cx, expr, arg, span) ;
5223+ lines_filter_map_ok:: check_filter_or_flat_map (
5224+ cx,
5225+ expr,
5226+ recv,
5227+ "filter_map" ,
5228+ arg,
5229+ call_span,
5230+ self . msrv ,
5231+ ) ;
51725232 } ,
51735233 ( sym:: find_map, [ arg] ) => {
51745234 unused_enumerate_index:: check ( cx, expr, recv, arg) ;
@@ -5178,20 +5238,26 @@ impl Methods {
51785238 unused_enumerate_index:: check ( cx, expr, recv, arg) ;
51795239 flat_map_identity:: check ( cx, expr, arg, span) ;
51805240 flat_map_option:: check ( cx, expr, arg, span) ;
5241+ lines_filter_map_ok:: check_filter_or_flat_map (
5242+ cx, expr, recv, "flat_map" , arg, call_span, self . msrv ,
5243+ ) ;
51815244 } ,
5182- ( sym:: flatten, [ ] ) => match method_call ( recv) {
5183- Some ( ( sym:: map, recv, [ map_arg] , map_span, _) ) => {
5184- map_flatten:: check ( cx, expr, recv, map_arg, map_span) ;
5185- } ,
5186- Some ( ( sym:: cloned, recv2, [ ] , _, _) ) => iter_overeager_cloned:: check (
5187- cx,
5188- expr,
5189- recv,
5190- recv2,
5191- iter_overeager_cloned:: Op :: LaterCloned ,
5192- true ,
5193- ) ,
5194- _ => { } ,
5245+ ( sym:: flatten, [ ] ) => {
5246+ match method_call ( recv) {
5247+ Some ( ( sym:: map, recv, [ map_arg] , map_span, _) ) => {
5248+ map_flatten:: check ( cx, expr, recv, map_arg, map_span) ;
5249+ } ,
5250+ Some ( ( sym:: cloned, recv2, [ ] , _, _) ) => iter_overeager_cloned:: check (
5251+ cx,
5252+ expr,
5253+ recv,
5254+ recv2,
5255+ iter_overeager_cloned:: Op :: LaterCloned ,
5256+ true ,
5257+ ) ,
5258+ _ => { } ,
5259+ }
5260+ lines_filter_map_ok:: check_flatten ( cx, expr, recv, call_span, self . msrv ) ;
51955261 } ,
51965262 ( sym:: fold, [ init, acc] ) => {
51975263 manual_try_fold:: check ( cx, expr, init, acc, call_span, self . msrv ) ;
0 commit comments