@@ -9,10 +9,10 @@ use std::borrow::Cow;
9
9
use std:: fmt;
10
10
use std:: iter;
11
11
use 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,
14
14
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,
16
16
span_lint_and_sugg, span_lint_and_then, span_note_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth} ;
17
17
use utils:: paths;
18
18
use utils:: sugg;
@@ -622,6 +622,29 @@ declare_lint! {
622
622
"using `as_ref` where the types before and after the call are the same"
623
623
}
624
624
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
+
625
648
impl LintPass for Pass {
626
649
fn get_lints ( & self ) -> LintArray {
627
650
lint_array ! (
@@ -652,7 +675,8 @@ impl LintPass for Pass {
652
675
GET_UNWRAP ,
653
676
STRING_EXTEND_CHARS ,
654
677
ITER_CLONED_COLLECT ,
655
- USELESS_ASREF
678
+ USELESS_ASREF ,
679
+ UNNECESSARY_FOLD
656
680
)
657
681
}
658
682
}
@@ -716,6 +740,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
716
740
lint_asref ( cx, expr, "as_ref" , arglists[ 0 ] ) ;
717
741
} else if let Some ( arglists) = method_chain_args ( expr, & [ "as_mut" ] ) {
718
742
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 ] ) ;
719
745
}
720
746
721
747
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
1106
1132
}
1107
1133
}
1108
1134
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
+
1109
1222
fn lint_iter_nth ( cx : & LateContext , expr : & hir:: Expr , iter_args : & [ hir:: Expr ] , is_mut : bool ) {
1110
1223
let mut_str = if is_mut { "_mut" } else { "" } ;
1111
1224
let caller_type = if derefs_to_slice ( cx, & iter_args[ 0 ] , cx. tables . expr_ty ( & iter_args[ 0 ] ) ) . is_some ( ) {
0 commit comments