@@ -453,6 +453,39 @@ declare_clippy_lint! {
453
453
"variables used within while expression are not mutated in the body"
454
454
}
455
455
456
+ declare_clippy_lint ! {
457
+ /// **What it does:** Checks whether a for loop is being used to push a constant
458
+ /// value into a Vec.
459
+ ///
460
+ /// **Why is this bad?** This kind of operation can be expressed more succinctly with
461
+ /// `vec![item;SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also
462
+ /// have better performance.
463
+ /// **Known problems:** None
464
+ ///
465
+ /// **Example:**
466
+ /// ```rust
467
+ /// let item1 = 2;
468
+ /// let item2 = 3;
469
+ /// let mut vec: Vec<u8> = Vec::new();
470
+ /// for _ in 0..20 {
471
+ /// vec.push(item1);
472
+ /// }
473
+ /// for _ in 0..30 {
474
+ /// vec.push(item2);
475
+ /// }
476
+ /// ```
477
+ /// could be written as
478
+ /// ```rust
479
+ /// let item1 = 2;
480
+ /// let item2 = 3;
481
+ /// let mut vec: Vec<u8> = vec![item1; 20];
482
+ /// vec.resize(20 + 30, item2);
483
+ /// ```
484
+ pub SAME_ITEM_PUSH ,
485
+ style,
486
+ "the same item is pushed inside of a for loop"
487
+ }
488
+
456
489
declare_lint_pass ! ( Loops => [
457
490
MANUAL_MEMCPY ,
458
491
NEEDLESS_RANGE_LOOP ,
@@ -471,6 +504,7 @@ declare_lint_pass!(Loops => [
471
504
NEVER_LOOP ,
472
505
MUT_RANGE_BOUND ,
473
506
WHILE_IMMUTABLE_CONDITION ,
507
+ SAME_ITEM_PUSH
474
508
] ) ;
475
509
476
510
impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for Loops {
@@ -751,6 +785,7 @@ fn check_for_loop<'a, 'tcx>(
751
785
check_for_loop_over_map_kv ( cx, pat, arg, body, expr) ;
752
786
check_for_mut_range_bound ( cx, arg, body) ;
753
787
detect_manual_memcpy ( cx, pat, arg, body, expr) ;
788
+ detect_same_item_push ( cx, pat, arg, body, expr) ;
754
789
}
755
790
756
791
fn same_var < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , expr : & Expr , var : HirId ) -> bool {
@@ -1035,6 +1070,182 @@ fn detect_manual_memcpy<'a, 'tcx>(
1035
1070
}
1036
1071
}
1037
1072
1073
+ // Delegate that traverses expression and detects mutable variables being used
1074
+ struct UsesMutableDelegate {
1075
+ found_mutable : bool ,
1076
+ }
1077
+
1078
+ impl < ' a , ' tcx > Delegate < ' tcx > for UsesMutableDelegate {
1079
+ fn consume ( & mut self , _: & cmt_ < ' tcx > , _: ConsumeMode ) { }
1080
+
1081
+ fn borrow ( & mut self , _: & cmt_ < ' tcx > , bk : rustc:: ty:: BorrowKind ) {
1082
+ // Mutable variable is found
1083
+ if let rustc:: ty:: BorrowKind :: MutBorrow = bk {
1084
+ self . found_mutable = true ;
1085
+ }
1086
+ }
1087
+
1088
+ fn mutate ( & mut self , _: & cmt_ < ' tcx > ) { }
1089
+ }
1090
+
1091
+ // Uses UsesMutableDelegate to find mutable variables in an expression expr
1092
+ fn has_mutable_variables < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , expr : & ' tcx Expr ) -> bool {
1093
+ let mut delegate = UsesMutableDelegate { found_mutable : false } ;
1094
+ let def_id = def_id:: DefId :: local ( expr. hir_id . owner ) ;
1095
+ let region_scope_tree = & cx. tcx . region_scope_tree ( def_id) ;
1096
+ ExprUseVisitor :: new (
1097
+ & mut delegate,
1098
+ cx. tcx ,
1099
+ def_id,
1100
+ cx. param_env ,
1101
+ region_scope_tree,
1102
+ cx. tables ,
1103
+ )
1104
+ . walk_expr ( expr) ;
1105
+
1106
+ delegate. found_mutable
1107
+ }
1108
+
1109
+ // Scans for the usage of the for loop pattern
1110
+ struct ForPatternVisitor < ' a , ' tcx > {
1111
+ found_pattern : bool ,
1112
+ // Pattern that we are searching for
1113
+ for_pattern_hir_id : HirId ,
1114
+ cx : & ' a LateContext < ' a , ' tcx > ,
1115
+ }
1116
+
1117
+ impl < ' a , ' tcx > Visitor < ' tcx > for ForPatternVisitor < ' a , ' tcx > {
1118
+ fn visit_expr ( & mut self , expr : & ' tcx Expr ) {
1119
+ // Recursively explore an expression until a ExprKind::Path is found
1120
+ match & expr. kind {
1121
+ ExprKind :: Box ( expr) => self . visit_expr ( expr) ,
1122
+ ExprKind :: Array ( expr_list) => {
1123
+ for expr in expr_list {
1124
+ self . visit_expr ( expr)
1125
+ }
1126
+ } ,
1127
+ ExprKind :: MethodCall ( _, _, expr_list) => {
1128
+ for expr in expr_list {
1129
+ self . visit_expr ( expr)
1130
+ }
1131
+ } ,
1132
+ ExprKind :: Tup ( expr_list) => {
1133
+ for expr in expr_list {
1134
+ self . visit_expr ( expr)
1135
+ }
1136
+ } ,
1137
+ ExprKind :: Binary ( _, lhs_expr, rhs_expr) => {
1138
+ self . visit_expr ( lhs_expr) ;
1139
+ self . visit_expr ( rhs_expr) ;
1140
+ } ,
1141
+ ExprKind :: Unary ( _, expr) => self . visit_expr ( expr) ,
1142
+ ExprKind :: Cast ( expr, _) => self . visit_expr ( expr) ,
1143
+ ExprKind :: Type ( expr, _) => self . visit_expr ( expr) ,
1144
+ ExprKind :: AddrOf ( _, expr) => self . visit_expr ( expr) ,
1145
+ ExprKind :: Struct ( _, _, Some ( expr) ) => self . visit_expr ( expr) ,
1146
+ _ => {
1147
+ // Exploration cannot continue ... calculate the hir_id of the current
1148
+ // expr assuming it is a Path
1149
+ if let Some ( hir_id) = var_def_id ( self . cx , & expr) {
1150
+ // Pattern is found
1151
+ if hir_id == self . for_pattern_hir_id {
1152
+ self . found_pattern = true ;
1153
+ }
1154
+ }
1155
+ } ,
1156
+ }
1157
+ }
1158
+
1159
+ // This is triggered by walk_expr() for the case of vec.push(pat)
1160
+ fn visit_qpath ( & mut self , qpath : & ' tcx QPath , _: HirId , _: Span ) {
1161
+ if_chain ! {
1162
+ if let rustc:: hir:: QPath :: Resolved ( _, path) = qpath;
1163
+ if let rustc:: hir:: def:: Res :: Local ( hir_id) = & path. res;
1164
+ if * hir_id == self . for_pattern_hir_id;
1165
+ then {
1166
+ self . found_pattern = true ;
1167
+ }
1168
+ }
1169
+ }
1170
+
1171
+ fn nested_visit_map < ' this > ( & ' this mut self ) -> NestedVisitorMap < ' this , ' tcx > {
1172
+ NestedVisitorMap :: None
1173
+ }
1174
+ }
1175
+
1176
+ // Given some statement, determine if that statement is a push on a Vec. If it is, return
1177
+ // the Vec being pushed into and the item being pushed
1178
+ fn get_vec_push < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , stmt : & ' tcx Stmt ) -> Option < ( & ' tcx Expr , & ' tcx Expr ) > {
1179
+ if_chain ! {
1180
+ // Extract method being called
1181
+ if let rustc:: hir:: StmtKind :: Semi ( semi_stmt) = & stmt. kind;
1182
+ if let rustc:: hir:: ExprKind :: MethodCall ( path, _, args) = & semi_stmt. kind;
1183
+ // Figure out the parameters for the method call
1184
+ if let Some ( self_expr) = args. get( 0 ) ;
1185
+ if let Some ( pushed_item) = args. get( 1 ) ;
1186
+ // Check that the method being called is push() on a Vec
1187
+ if match_type( cx, cx. tables. expr_ty( self_expr) , & paths:: VEC ) ;
1188
+ if path. ident. name. as_str( ) == "push" ;
1189
+ then {
1190
+ return Some ( ( self_expr, pushed_item) )
1191
+ }
1192
+ }
1193
+ None
1194
+ }
1195
+
1196
+ /// Detects for loop pushing the same item into a Vec
1197
+ fn detect_same_item_push < ' a , ' tcx > (
1198
+ cx : & LateContext < ' a , ' tcx > ,
1199
+ pat : & ' tcx Pat ,
1200
+ _: & ' tcx Expr ,
1201
+ body : & ' tcx Expr ,
1202
+ _: & ' tcx Expr ,
1203
+ ) {
1204
+ // Extract for loop body
1205
+ if let rustc:: hir:: ExprKind :: Block ( block, _) = & body. kind {
1206
+ // Analyze only for loops with 1 push statement
1207
+ let pushes: Vec < ( & Expr , & Expr ) > = block
1208
+ . stmts
1209
+ . iter ( )
1210
+ // Map each statement to a Vec push (if possible)
1211
+ . map ( |stmt| get_vec_push ( cx, stmt) )
1212
+ // Filter out statements that are not pushes
1213
+ . filter ( |stmt_option| stmt_option. is_some ( ) )
1214
+ // Unwrap
1215
+ . map ( |stmt_option| stmt_option. unwrap ( ) )
1216
+ . collect ( ) ;
1217
+ if pushes. len ( ) == 1 {
1218
+ // Make sure that the push does not involve possibly mutating values
1219
+ if !has_mutable_variables ( cx, pushes[ 0 ] . 1 ) {
1220
+ // Walk through the expression being pushed and make sure that it
1221
+ // does not contain the for loop pattern
1222
+ let mut for_pat_visitor = ForPatternVisitor {
1223
+ found_pattern : false ,
1224
+ for_pattern_hir_id : pat. hir_id ,
1225
+ cx,
1226
+ } ;
1227
+ intravisit:: walk_expr ( & mut for_pat_visitor, pushes[ 0 ] . 1 ) ;
1228
+
1229
+ if !for_pat_visitor. found_pattern {
1230
+ let vec_str = snippet ( cx, pushes[ 0 ] . 0 . span , "" ) ;
1231
+ let item_str = snippet ( cx, pushes[ 0 ] . 1 . span , "" ) ;
1232
+
1233
+ span_help_and_lint (
1234
+ cx,
1235
+ SAME_ITEM_PUSH ,
1236
+ pushes[ 0 ] . 0 . span ,
1237
+ "it looks like the same item is being pushed into this Vec" ,
1238
+ & format ! (
1239
+ "try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})" ,
1240
+ item_str, vec_str, item_str
1241
+ ) ,
1242
+ )
1243
+ }
1244
+ }
1245
+ }
1246
+ }
1247
+ }
1248
+
1038
1249
/// Checks for looping over a range and then indexing a sequence with it.
1039
1250
/// The iteratee must be a range literal.
1040
1251
#[ allow( clippy:: too_many_lines) ]
0 commit comments