@@ -5,10 +5,10 @@ use crate::utils::usage::{is_unused, mutated_variables};
55use crate :: utils:: visitors:: LocalUsedVisitor ;
66use crate :: utils:: {
77 contains_name, get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait,
8- indent_of, is_in_panic_handler, is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item ,
9- last_path_segment, match_trait_method, match_type, match_var, multispan_sugg, single_segment_path , snippet ,
10- snippet_with_applicability , snippet_with_macro_callsite , span_lint , span_lint_and_help , span_lint_and_sugg ,
11- span_lint_and_then, sugg, SpanlessEq ,
8+ indent_of, is_in_panic_handler, is_integer_const, is_no_std_crate, is_ok_ctor , is_refutable, is_some_ctor ,
9+ is_type_diagnostic_item , last_path_segment, match_trait_method, match_type, match_var, multispan_sugg,
10+ single_segment_path , snippet , snippet_with_applicability , snippet_with_macro_callsite , span_lint ,
11+ span_lint_and_help , span_lint_and_sugg , span_lint_and_then, sugg, SpanlessEq ,
1212} ;
1313use if_chain:: if_chain;
1414use rustc_ast:: ast;
@@ -495,8 +495,8 @@ declare_clippy_lint! {
495495}
496496
497497declare_clippy_lint ! {
498- /// **What it does:** Checks for iteration of `Option`s with
499- /// a single `if let Some()` expression inside .
498+ /// **What it does:** Check for unnecessary `if let` usage in a for loop
499+ /// where only the `Some` or `Ok` variant of the iterator element is used .
500500 ///
501501 /// **Why is this bad?** It is verbose and can be simplified
502502 /// by first calling the `flatten` method on the `Iterator`.
@@ -516,23 +516,23 @@ declare_clippy_lint! {
516516 /// Use instead:
517517 /// ```rust
518518 /// let x = vec![Some(1), Some(2), Some(3)];
519- /// for n in x.iter ().flatten() {
519+ /// for n in x.into_iter ().flatten() {
520520 /// println!("{}", n);
521521 /// }
522522 /// ```
523- pub FOR_LOOPS_OVER_OPTIONS_OR_RESULTS ,
523+ pub MANUAL_FLATTEN ,
524524 complexity,
525525 "for loops over `Option`s or `Result`s with a single expression can be simplified"
526526}
527527
528528declare_lint_pass ! ( Loops => [
529529 MANUAL_MEMCPY ,
530+ MANUAL_FLATTEN ,
530531 NEEDLESS_RANGE_LOOP ,
531532 EXPLICIT_ITER_LOOP ,
532533 EXPLICIT_INTO_ITER_LOOP ,
533534 ITER_NEXT_LOOP ,
534535 FOR_LOOPS_OVER_FALLIBLES ,
535- FOR_LOOPS_OVER_OPTIONS_OR_RESULTS ,
536536 WHILE_LET_LOOP ,
537537 NEEDLESS_COLLECT ,
538538 EXPLICIT_COUNTER_LOOP ,
@@ -549,14 +549,14 @@ declare_lint_pass!(Loops => [
549549impl < ' tcx > LateLintPass < ' tcx > for Loops {
550550 #[ allow( clippy:: too_many_lines) ]
551551 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
552- if let Some ( ( pat, arg, body) ) = higher:: for_loop ( expr) {
552+ if let Some ( ( pat, arg, body, span ) ) = higher:: for_loop ( expr) {
553553 // we don't want to check expanded macros
554554 // this check is not at the top of the function
555555 // since higher::for_loop expressions are marked as expansions
556556 if body. span . from_expansion ( ) {
557557 return ;
558558 }
559- check_for_loop ( cx, pat, arg, body, expr) ;
559+ check_for_loop ( cx, pat, arg, body, expr, span ) ;
560560 }
561561
562562 // we don't want to check expanded macros
@@ -851,6 +851,7 @@ fn check_for_loop<'tcx>(
851851 arg : & ' tcx Expr < ' _ > ,
852852 body : & ' tcx Expr < ' _ > ,
853853 expr : & ' tcx Expr < ' _ > ,
854+ span : Span ,
854855) {
855856 let is_manual_memcpy_triggered = detect_manual_memcpy ( cx, pat, arg, body, expr) ;
856857 if !is_manual_memcpy_triggered {
@@ -862,7 +863,7 @@ fn check_for_loop<'tcx>(
862863 check_for_mut_range_bound ( cx, arg, body) ;
863864 check_for_single_element_loop ( cx, pat, arg, body, expr) ;
864865 detect_same_item_push ( cx, pat, arg, body, expr) ;
865- check_for_loop_over_options_or_results ( cx, pat, arg, body, expr ) ;
866+ check_manual_flatten ( cx, pat, arg, body, span ) ;
866867}
867868
868869// this function assumes the given expression is a `for` loop.
@@ -1986,33 +1987,61 @@ fn check_for_single_element_loop<'tcx>(
19861987 }
19871988}
19881989
1989- /// Check if a for loop loops over `Option`s or `Result`s and contains only
1990- /// a `if let Some` or `if let Ok` expression .
1991- fn check_for_loop_over_options_or_results < ' tcx > (
1990+ /// Check for unnecessary ` if let` usage in a for loop where only the `Some` or `Ok` variant of the
1991+ /// iterator element is used .
1992+ fn check_manual_flatten < ' tcx > (
19921993 cx : & LateContext < ' tcx > ,
19931994 pat : & ' tcx Pat < ' _ > ,
19941995 arg : & ' tcx Expr < ' _ > ,
19951996 body : & ' tcx Expr < ' _ > ,
1996- expr : & ' tcx Expr < ' _ > ,
1997+ span : Span ,
19971998) {
19981999 if_chain ! {
2000+ // Ensure the `if let` statement is the only expression in the for-loop
19992001 if let ExprKind :: Block ( ref block, _) = body. kind;
20002002 if block. stmts. is_empty( ) ;
20012003 if let Some ( inner_expr) = block. expr;
2002- if let ExprKind :: Match ( ref _match_expr, ref _match_arms, MatchSource :: IfLetDesugar { contains_else_clause } ) = inner_expr. kind;
2003- if !contains_else_clause;
2004+ if let ExprKind :: Match (
2005+ ref match_expr, ref match_arms, MatchSource :: IfLetDesugar { contains_else_clause: false }
2006+ ) = inner_expr. kind;
2007+ // Ensure match_expr in `if let` statement is the same as the pat from the for-loop
2008+ if let PatKind :: Binding ( _, pat_hir_id, _, _) = pat. kind;
2009+ if let ExprKind :: Path ( QPath :: Resolved ( None , match_expr_path) ) = match_expr. kind;
2010+ if let Res :: Local ( match_expr_path_id) = match_expr_path. res;
2011+ if pat_hir_id == match_expr_path_id;
2012+ // Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result`
2013+ if let PatKind :: TupleStruct ( QPath :: Resolved ( None , path) , _, _) = match_arms[ 0 ] . pat. kind;
2014+ if is_some_ctor( cx, path. res) || is_ok_ctor( cx, path. res) ;
2015+ let if_let_type = if is_some_ctor( cx, path. res) {
2016+ "Some"
2017+ } else {
2018+ "Ok"
2019+ } ;
2020+ // Determine if `arg` is `Iterator` or implicitly calls `into_iter`
2021+ let arg_ty = cx. typeck_results( ) . expr_ty( arg) ;
2022+ if let Some ( id) = get_trait_def_id( cx, & paths:: ITERATOR ) ;
2023+ if let is_iterator = implements_trait( cx, arg_ty, id, & [ ] ) ;
2024+
20042025 then {
2005- // println!("if_let_expr:\n{:?}", snippet(cx, if_let_expr.span, ".."));
2006- // println!("pat is:\n {:?}", snippet(cx, pat.span, ".."));
2007- // println!("arg is:\n {:?}", snippet(cx, arg.span, ".."));
2008- // println!("body is:\n {:?}", snippet(cx, body.span, ".."));
2009- // println!("arg kind is: {:?}", arg.kind);
2010- // println!("expr is:\n {:?}", snippet(cx, expr.span, ".."));
2011- // todo!();
2026+ // Prepare the error message
2027+ let msg = format!( "Unnecessary `if let` since only the `{}` variant of the iterator element is used." , if_let_type) ;
2028+
2029+ // Prepare the help message
20122030 let arg_snippet = snippet( cx, arg. span, ".." ) ;
2013- let msg = "looping over `Option`s or `Result`s with an `if let` expression." ;
2014- let hint = format!( "try turn {} into an `Iterator` and use `flatten`: `{}.iter().flatten()`" , arg_snippet, arg_snippet) ;
2015- span_lint_and_help( cx, FOR_LOOPS_OVER_OPTIONS_OR_RESULTS , expr. span, msg, None , & hint) ;
2031+ let hint = if is_iterator {
2032+ format!( "try: `{}.flatten()`" , arg_snippet)
2033+ } else {
2034+ format!( "try: `{}.into_iter().flatten()`" , arg_snippet)
2035+ } ;
2036+
2037+ span_lint_and_help(
2038+ cx,
2039+ MANUAL_FLATTEN ,
2040+ span,
2041+ & msg,
2042+ Some ( arg. span) ,
2043+ & hint,
2044+ ) ;
20162045 }
20172046 }
20182047}
0 commit comments