@@ -453,6 +453,39 @@ declare_clippy_lint! {
453453 "variables used within while expression are not mutated in the body"
454454}
455455
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+
456489declare_lint_pass ! ( Loops => [
457490 MANUAL_MEMCPY ,
458491 NEEDLESS_RANGE_LOOP ,
@@ -471,6 +504,7 @@ declare_lint_pass!(Loops => [
471504 NEVER_LOOP ,
472505 MUT_RANGE_BOUND ,
473506 WHILE_IMMUTABLE_CONDITION ,
507+ SAME_ITEM_PUSH
474508] ) ;
475509
476510impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for Loops {
@@ -751,6 +785,7 @@ fn check_for_loop<'a, 'tcx>(
751785 check_for_loop_over_map_kv ( cx, pat, arg, body, expr) ;
752786 check_for_mut_range_bound ( cx, arg, body) ;
753787 detect_manual_memcpy ( cx, pat, arg, body, expr) ;
788+ detect_same_item_push ( cx, pat, arg, body, expr) ;
754789}
755790
756791fn same_var < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , expr : & Expr , var : HirId ) -> bool {
@@ -1035,6 +1070,182 @@ fn detect_manual_memcpy<'a, 'tcx>(
10351070 }
10361071}
10371072
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+
10381249/// Checks for looping over a range and then indexing a sequence with it.
10391250/// The iteratee must be a range literal.
10401251#[ allow( clippy:: too_many_lines) ]
0 commit comments