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