@@ -9,10 +9,10 @@ use std::borrow::Cow;
99use std:: fmt;
1010use std:: iter;
1111use syntax:: ast;
12- use syntax:: codemap:: Span ;
13- use utils:: { get_trait_def_id, implements_trait, in_external_macro, in_macro, is_copy, is_self, is_self_ty,
12+ use syntax:: codemap:: { Span , BytePos } ;
13+ use utils:: { get_arg_name , get_trait_def_id, implements_trait, in_external_macro, in_macro, is_copy, is_self, is_self_ty,
1414 iter_input_pats, last_path_segment, match_def_path, match_path, match_qpath, match_trait_method,
15- match_type, method_chain_args, return_ty, same_tys, single_segment_path, snippet, span_lint,
15+ match_type, method_chain_args, return_ty, remove_blocks , same_tys, single_segment_path, snippet, span_lint,
1616 span_lint_and_sugg, span_lint_and_then, span_note_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth} ;
1717use utils:: paths;
1818use utils:: sugg;
@@ -622,6 +622,29 @@ declare_lint! {
622622 "using `as_ref` where the types before and after the call are the same"
623623}
624624
625+
626+ /// **What it does:** Checks for using `fold` when a more succinct alternative exists.
627+ /// Specifically, this checks for `fold`s which could be replaced by `any`, `all`,
628+ /// `sum` or `product`.
629+ ///
630+ /// **Why is this bad?** Readability.
631+ ///
632+ /// **Known problems:** None.
633+ ///
634+ /// **Example:**
635+ /// ```rust
636+ /// let _ = (0..3).fold(false, |acc, x| acc || x > 2);
637+ /// ```
638+ /// This could be written as:
639+ /// ```rust
640+ /// let _ = (0..3).any(|x| x > 2);
641+ /// ```
642+ declare_lint ! {
643+ pub UNNECESSARY_FOLD ,
644+ Warn ,
645+ "using `fold` when a more succinct alternative exists"
646+ }
647+
625648impl LintPass for Pass {
626649 fn get_lints ( & self ) -> LintArray {
627650 lint_array ! (
@@ -652,7 +675,8 @@ impl LintPass for Pass {
652675 GET_UNWRAP ,
653676 STRING_EXTEND_CHARS ,
654677 ITER_CLONED_COLLECT ,
655- USELESS_ASREF
678+ USELESS_ASREF ,
679+ UNNECESSARY_FOLD
656680 )
657681 }
658682}
@@ -716,6 +740,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
716740 lint_asref ( cx, expr, "as_ref" , arglists[ 0 ] ) ;
717741 } else if let Some ( arglists) = method_chain_args ( expr, & [ "as_mut" ] ) {
718742 lint_asref ( cx, expr, "as_mut" , arglists[ 0 ] ) ;
743+ } else if let Some ( arglists) = method_chain_args ( expr, & [ "fold" ] ) {
744+ lint_unnecessary_fold ( cx, expr, arglists[ 0 ] ) ;
719745 }
720746
721747 lint_or_fun_call ( cx, expr, & method_call. name . as_str ( ) , args) ;
@@ -1106,6 +1132,93 @@ fn lint_iter_cloned_collect(cx: &LateContext, expr: &hir::Expr, iter_args: &[hir
11061132 }
11071133}
11081134
1135+ fn lint_unnecessary_fold ( cx : & LateContext , expr : & hir:: Expr , fold_args : & [ hir:: Expr ] ) {
1136+ // Check that this is a call to Iterator::fold rather than just some function called fold
1137+ if !match_trait_method ( cx, expr, & paths:: ITERATOR ) {
1138+ return ;
1139+ }
1140+
1141+ assert ! ( fold_args. len( ) == 3 ,
1142+ "Expected fold_args to have three entries - the receiver, the initial value and the closure" ) ;
1143+
1144+ fn check_fold_with_op (
1145+ cx : & LateContext ,
1146+ fold_args : & [ hir:: Expr ] ,
1147+ op : hir:: BinOp_ ,
1148+ replacement_method_name : & str ,
1149+ replacement_has_args : bool ) {
1150+
1151+ if_chain ! {
1152+ // Extract the body of the closure passed to fold
1153+ if let hir:: ExprClosure ( _, _, body_id, _, _) = fold_args[ 2 ] . node;
1154+ let closure_body = cx. tcx. hir. body( body_id) ;
1155+ let closure_expr = remove_blocks( & closure_body. value) ;
1156+
1157+ // Check if the closure body is of the form `acc <op> some_expr(x)`
1158+ if let hir:: ExprBinary ( ref bin_op, ref left_expr, ref right_expr) = closure_expr. node;
1159+ if bin_op. node == op;
1160+
1161+ // Extract the names of the two arguments to the closure
1162+ if let Some ( first_arg_ident) = get_arg_name( & closure_body. arguments[ 0 ] . pat) ;
1163+ if let Some ( second_arg_ident) = get_arg_name( & closure_body. arguments[ 1 ] . pat) ;
1164+
1165+ if let hir:: ExprPath ( hir:: QPath :: Resolved ( None , ref path) ) = left_expr. node;
1166+ if path. segments. len( ) == 1 && & path. segments[ 0 ] . name == & first_arg_ident;
1167+
1168+ then {
1169+ // Span containing `.fold(...)`
1170+ let fold_span = fold_args[ 0 ] . span. next_point( ) . with_hi( fold_args[ 2 ] . span. hi( ) + BytePos ( 1 ) ) ;
1171+
1172+ let sugg = if replacement_has_args {
1173+ format!(
1174+ ".{replacement}(|{s}| {r})" ,
1175+ replacement = replacement_method_name,
1176+ s = second_arg_ident,
1177+ r = snippet( cx, right_expr. span, "EXPR" ) ,
1178+ )
1179+ } else {
1180+ format!(
1181+ ".{replacement}()" ,
1182+ replacement = replacement_method_name,
1183+ )
1184+ } ;
1185+
1186+ span_lint_and_sugg(
1187+ cx,
1188+ UNNECESSARY_FOLD ,
1189+ fold_span,
1190+ // TODO #2371 don't suggest e.g. .any(|x| f(x)) if we can suggest .any(f)
1191+ "this `.fold` can be written more succinctly using another method" ,
1192+ "try" ,
1193+ sugg,
1194+ ) ;
1195+ }
1196+ }
1197+ }
1198+
1199+ // Check if the first argument to .fold is a suitable literal
1200+ match fold_args[ 1 ] . node {
1201+ hir:: ExprLit ( ref lit) => {
1202+ match lit. node {
1203+ ast:: LitKind :: Bool ( false ) => check_fold_with_op (
1204+ cx, fold_args, hir:: BinOp_ :: BiOr , "any" , true
1205+ ) ,
1206+ ast:: LitKind :: Bool ( true ) => check_fold_with_op (
1207+ cx, fold_args, hir:: BinOp_ :: BiAnd , "all" , true
1208+ ) ,
1209+ ast:: LitKind :: Int ( 0 , _) => check_fold_with_op (
1210+ cx, fold_args, hir:: BinOp_ :: BiAdd , "sum" , false
1211+ ) ,
1212+ ast:: LitKind :: Int ( 1 , _) => check_fold_with_op (
1213+ cx, fold_args, hir:: BinOp_ :: BiMul , "product" , false
1214+ ) ,
1215+ _ => return
1216+ }
1217+ }
1218+ _ => return
1219+ } ;
1220+ }
1221+
11091222fn lint_iter_nth ( cx : & LateContext , expr : & hir:: Expr , iter_args : & [ hir:: Expr ] , is_mut : bool ) {
11101223 let mut_str = if is_mut { "_mut" } else { "" } ;
11111224 let caller_type = if derefs_to_slice ( cx, & iter_args[ 0 ] , cx. tables . expr_ty ( & iter_args[ 0 ] ) ) . is_some ( ) {
0 commit comments