@@ -420,6 +420,39 @@ declare_clippy_lint! {
420
420
"variables used within while expression are not mutated in the body"
421
421
}
422
422
423
+ declare_clippy_lint ! {
424
+ /// **What it does:** Checks whether a for loop is being used to push a constant
425
+ /// value into a Vec.
426
+ ///
427
+ /// **Why is this bad?** This kind of operation can be expressed more succinctly with
428
+ /// `vec![item;SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also
429
+ /// have better performance.
430
+ /// **Known problems:** None
431
+ ///
432
+ /// **Example:**
433
+ /// ```rust
434
+ /// let item1 = 2;
435
+ /// let item2 = 3;
436
+ /// let mut vec: Vec<u8> = Vec::new();
437
+ /// for _ in 0..20 {
438
+ /// vec.push(item1);
439
+ /// }
440
+ /// for _ in 0..30 {
441
+ /// vec.push(item2);
442
+ /// }
443
+ /// ```
444
+ /// could be written as
445
+ /// ```rust
446
+ /// let item1 = 2;
447
+ /// let item2 = 3;
448
+ /// let mut vec: Vec<u8> = vec![item1; 20];
449
+ /// vec.resize(20 + 30, item2);
450
+ /// ```
451
+ pub SAME_ITEM_PUSH ,
452
+ style,
453
+ "the same item is pushed inside of a for loop"
454
+ }
455
+
423
456
declare_lint_pass ! ( Loops => [
424
457
MANUAL_MEMCPY ,
425
458
NEEDLESS_RANGE_LOOP ,
@@ -436,6 +469,7 @@ declare_lint_pass!(Loops => [
436
469
NEVER_LOOP ,
437
470
MUT_RANGE_BOUND ,
438
471
WHILE_IMMUTABLE_CONDITION ,
472
+ SAME_ITEM_PUSH ,
439
473
] ) ;
440
474
441
475
impl < ' tcx > LateLintPass < ' tcx > for Loops {
@@ -741,6 +775,7 @@ fn check_for_loop<'tcx>(
741
775
check_for_loop_over_map_kv ( cx, pat, arg, body, expr) ;
742
776
check_for_mut_range_bound ( cx, arg, body) ;
743
777
detect_manual_memcpy ( cx, pat, arg, body, expr) ;
778
+ detect_same_item_push ( cx, pat, arg, body, expr) ;
744
779
}
745
780
746
781
fn same_var < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' _ > , var : HirId ) -> bool {
@@ -1017,6 +1052,236 @@ fn detect_manual_memcpy<'tcx>(
1017
1052
}
1018
1053
}
1019
1054
1055
+ // Delegate that traverses expression and detects mutable variables being used
1056
+ struct UsesMutableDelegate {
1057
+ found_mutable : bool ,
1058
+ }
1059
+
1060
+ impl < ' tcx > Delegate < ' tcx > for UsesMutableDelegate {
1061
+ fn consume ( & mut self , _: & PlaceWithHirId < ' tcx > , _: ConsumeMode ) { }
1062
+
1063
+ fn borrow ( & mut self , _: & PlaceWithHirId < ' tcx > , bk : ty:: BorrowKind ) {
1064
+ // Mutable variable is found
1065
+ if let ty:: BorrowKind :: MutBorrow = bk {
1066
+ self . found_mutable = true ;
1067
+ }
1068
+ }
1069
+
1070
+ fn mutate ( & mut self , _: & PlaceWithHirId < ' tcx > ) { }
1071
+ }
1072
+
1073
+ // Uses UsesMutableDelegate to find mutable variables in an expression expr
1074
+ fn has_mutable_variables < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> bool {
1075
+ let mut delegate = UsesMutableDelegate { found_mutable : false } ;
1076
+ let def_id = expr. hir_id . owner . to_def_id ( ) ;
1077
+ cx. tcx . infer_ctxt ( ) . enter ( |infcx| {
1078
+ ExprUseVisitor :: new (
1079
+ & mut delegate,
1080
+ & infcx,
1081
+ def_id. expect_local ( ) ,
1082
+ cx. param_env ,
1083
+ cx. tables ( ) ,
1084
+ ) . walk_expr ( expr) ;
1085
+ } ) ;
1086
+
1087
+ delegate. found_mutable
1088
+ }
1089
+
1090
+ // Scans for the usage of the for loop pattern
1091
+ struct ForPatternVisitor < ' a , ' tcx > {
1092
+ found_pattern : bool ,
1093
+ // Pattern that we are searching for
1094
+ for_pattern : & ' a Pat < ' tcx > ,
1095
+ cx : & ' a LateContext < ' tcx > ,
1096
+ }
1097
+
1098
+ impl < ' a , ' tcx > Visitor < ' tcx > for ForPatternVisitor < ' a , ' tcx > {
1099
+ type Map = Map < ' tcx > ;
1100
+
1101
+ fn visit_expr ( & mut self , expr : & ' tcx Expr < ' _ > ) {
1102
+ // Recursively explore an expression until a ExprKind::Path is found
1103
+ match & expr. kind {
1104
+ ExprKind :: Array ( expr_list) | ExprKind :: MethodCall ( _, _, expr_list, _) | ExprKind :: Tup ( expr_list) => {
1105
+ for expr in * expr_list {
1106
+ self . visit_expr ( expr)
1107
+ }
1108
+ } ,
1109
+ ExprKind :: Binary ( _, lhs_expr, rhs_expr) => {
1110
+ self . visit_expr ( lhs_expr) ;
1111
+ self . visit_expr ( rhs_expr) ;
1112
+ } ,
1113
+ ExprKind :: Box ( expr)
1114
+ | ExprKind :: Unary ( _, expr)
1115
+ | ExprKind :: Cast ( expr, _)
1116
+ | ExprKind :: Type ( expr, _)
1117
+ | ExprKind :: AddrOf ( _, _, expr)
1118
+ | ExprKind :: Struct ( _, _, Some ( expr) ) => self . visit_expr ( expr) ,
1119
+ _ => {
1120
+ // Exploration cannot continue ... calculate the hir_id of the current
1121
+ // expr assuming it is a Path
1122
+ if let Some ( hir_id) = var_def_id ( self . cx , & expr) {
1123
+ // Pattern is found
1124
+ if hir_id == self . for_pattern . hir_id {
1125
+ self . found_pattern = true ;
1126
+ }
1127
+ // If the for loop pattern is a tuple, determine whether the current
1128
+ // expr is inside that tuple pattern
1129
+ if let PatKind :: Tuple ( pat_list, _) = & self . for_pattern . kind {
1130
+ let hir_id_list: Vec < HirId > = pat_list. iter ( ) . map ( |p| p. hir_id ) . collect ( ) ;
1131
+ if hir_id_list. contains ( & hir_id) {
1132
+ self . found_pattern = true ;
1133
+ }
1134
+ }
1135
+ }
1136
+ } ,
1137
+ }
1138
+ }
1139
+
1140
+ // This is triggered by walk_expr() for the case of vec.push(pat)
1141
+ fn visit_qpath ( & mut self , qpath : & ' tcx QPath < ' _ > , _: HirId , _: Span ) {
1142
+ if_chain ! {
1143
+ if let QPath :: Resolved ( _, path) = qpath;
1144
+ if let Res :: Local ( hir_id) = & path. res;
1145
+ then {
1146
+ if * hir_id == self . for_pattern. hir_id{
1147
+ self . found_pattern = true ;
1148
+ }
1149
+
1150
+ if let PatKind :: Tuple ( pat_list, _) = & self . for_pattern. kind {
1151
+ let hir_id_list: Vec <HirId > = pat_list. iter( ) . map( |p| p. hir_id) . collect( ) ;
1152
+ if hir_id_list. contains( & hir_id) {
1153
+ self . found_pattern = true ;
1154
+ }
1155
+ }
1156
+ }
1157
+ }
1158
+ }
1159
+
1160
+ fn nested_visit_map ( & mut self ) -> NestedVisitorMap < Self :: Map > {
1161
+ NestedVisitorMap :: None
1162
+ }
1163
+ }
1164
+
1165
+ // Scans the body of the for loop and determines whether lint should be given
1166
+ struct SameItemPushVisitor < ' a , ' tcx > {
1167
+ should_lint : bool ,
1168
+ // this field holds the last vec push operation visited, which should be the only push seen
1169
+ vec_push : Option < ( & ' tcx Expr < ' tcx > , & ' tcx Expr < ' tcx > ) > ,
1170
+ cx : & ' a LateContext < ' tcx > ,
1171
+ }
1172
+
1173
+ impl < ' a , ' tcx > Visitor < ' tcx > for SameItemPushVisitor < ' a , ' tcx > {
1174
+ type Map = Map < ' tcx > ;
1175
+
1176
+ fn visit_expr ( & mut self , expr : & ' tcx Expr < ' _ > ) {
1177
+ match & expr. kind {
1178
+ // Non-determinism may occur ... don't give a lint
1179
+ ExprKind :: Loop ( _, _, _) | ExprKind :: Match ( _, _, _) => self . should_lint = false ,
1180
+ ExprKind :: Block ( block, _) => self . visit_block ( block) ,
1181
+ _ => { } ,
1182
+ }
1183
+ }
1184
+
1185
+ fn visit_block ( & mut self , b : & ' tcx Block < ' _ > ) {
1186
+ for stmt in b. stmts . iter ( ) {
1187
+ self . visit_stmt ( stmt) ;
1188
+ }
1189
+ }
1190
+
1191
+ fn visit_stmt ( & mut self , s : & ' tcx Stmt < ' _ > ) {
1192
+ let vec_push_option = get_vec_push ( self . cx , s) ;
1193
+ if vec_push_option. is_none ( ) {
1194
+ // Current statement is not a push so visit inside
1195
+ match & s. kind {
1196
+ StmtKind :: Expr ( expr) | StmtKind :: Semi ( expr) => self . visit_expr ( & expr) ,
1197
+ _ => { } ,
1198
+ }
1199
+ } else {
1200
+ // Current statement is a push ...check whether another
1201
+ // push had been previously done
1202
+ if self . vec_push . is_none ( ) {
1203
+ self . vec_push = vec_push_option;
1204
+ } else {
1205
+ // There are multiple pushes ... don't lint
1206
+ self . should_lint = false ;
1207
+ }
1208
+ }
1209
+ }
1210
+
1211
+ fn nested_visit_map ( & mut self ) -> NestedVisitorMap < Self :: Map > {
1212
+ NestedVisitorMap :: None
1213
+ }
1214
+ }
1215
+
1216
+ // Given some statement, determine if that statement is a push on a Vec. If it is, return
1217
+ // the Vec being pushed into and the item being pushed
1218
+ fn get_vec_push < ' tcx > ( cx : & LateContext < ' tcx > , stmt : & ' tcx Stmt < ' _ > ) -> Option < ( & ' tcx Expr < ' tcx > , & ' tcx Expr < ' tcx > ) > {
1219
+ if_chain ! {
1220
+ // Extract method being called
1221
+ if let StmtKind :: Semi ( semi_stmt) = & stmt. kind;
1222
+ if let ExprKind :: MethodCall ( path, _, args, _) = & semi_stmt. kind;
1223
+ // Figure out the parameters for the method call
1224
+ if let Some ( self_expr) = args. get( 0 ) ;
1225
+ if let Some ( pushed_item) = args. get( 1 ) ;
1226
+ // Check that the method being called is push() on a Vec
1227
+ if match_type( cx, cx. tables( ) . expr_ty( self_expr) , & paths:: VEC ) ;
1228
+ if path. ident. name. as_str( ) == "push" ;
1229
+ then {
1230
+ return Some ( ( self_expr, pushed_item) )
1231
+ }
1232
+ }
1233
+ None
1234
+ }
1235
+
1236
+ /// Detects for loop pushing the same item into a Vec
1237
+ fn detect_same_item_push < ' tcx > (
1238
+ cx : & LateContext < ' tcx > ,
1239
+ pat : & ' tcx Pat < ' _ > ,
1240
+ _: & ' tcx Expr < ' _ > ,
1241
+ body : & ' tcx Expr < ' _ > ,
1242
+ _: & ' tcx Expr < ' _ > ,
1243
+ ) {
1244
+ // Determine whether it is safe to lint the body
1245
+ let mut same_item_push_visitor = SameItemPushVisitor {
1246
+ should_lint : true ,
1247
+ vec_push : None ,
1248
+ cx,
1249
+ } ;
1250
+ walk_expr ( & mut same_item_push_visitor, body) ;
1251
+ if same_item_push_visitor. should_lint {
1252
+ if let Some ( ( vec, pushed_item) ) = same_item_push_visitor. vec_push {
1253
+ // Make sure that the push does not involve possibly mutating values
1254
+ if !has_mutable_variables ( cx, pushed_item) {
1255
+ // Walk through the expression being pushed and make sure that it
1256
+ // does not contain the for loop pattern
1257
+ let mut for_pat_visitor = ForPatternVisitor {
1258
+ found_pattern : false ,
1259
+ for_pattern : pat,
1260
+ cx,
1261
+ } ;
1262
+ walk_expr ( & mut for_pat_visitor, pushed_item) ;
1263
+
1264
+ if !for_pat_visitor. found_pattern {
1265
+ let vec_str = snippet ( cx, vec. span , "" ) ;
1266
+ let item_str = snippet ( cx, pushed_item. span , "" ) ;
1267
+
1268
+ span_lint_and_help (
1269
+ cx,
1270
+ SAME_ITEM_PUSH ,
1271
+ vec. span ,
1272
+ "it looks like the same item is being pushed into this Vec" ,
1273
+ None ,
1274
+ & format ! (
1275
+ "try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})" ,
1276
+ item_str, vec_str, item_str
1277
+ ) ,
1278
+ )
1279
+ }
1280
+ }
1281
+ }
1282
+ }
1283
+ }
1284
+
1020
1285
/// Checks for looping over a range and then indexing a sequence with it.
1021
1286
/// The iteratee must be a range literal.
1022
1287
#[ allow( clippy:: too_many_lines) ]
0 commit comments