Skip to content

Commit bd82efd

Browse files
Addresses Issue #4078
1 parent 5cb9833 commit bd82efd

File tree

7 files changed

+335
-2
lines changed

7 files changed

+335
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,7 @@ Released 2018-09-13
11661166
[`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else
11671167
[`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used
11681168
[`reverse_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#reverse_range_loop
1169+
[`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push
11691170
[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
11701171
[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
11711172
[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
88

9-
[There are 319 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 320 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1010

1111
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1212

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
774774
loops::NEEDLESS_RANGE_LOOP,
775775
loops::NEVER_LOOP,
776776
loops::REVERSE_RANGE_LOOP,
777+
loops::SAME_ITEM_PUSH,
777778
loops::WHILE_IMMUTABLE_CONDITION,
778779
loops::WHILE_LET_LOOP,
779780
loops::WHILE_LET_ON_ITERATOR,
@@ -960,6 +961,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
960961
loops::EMPTY_LOOP,
961962
loops::FOR_KV_MAP,
962963
loops::NEEDLESS_RANGE_LOOP,
964+
loops::SAME_ITEM_PUSH,
963965
loops::WHILE_LET_ON_ITERATOR,
964966
main_recursion::MAIN_RECURSION,
965967
map_clone::MAP_CLONE,

clippy_lints/src/loops.rs

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
456489
declare_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

476510
impl<'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

756791
fn 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)]

src/lintlist/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 319] = [
9+
pub const ALL_LINTS: [Lint; 320] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -1645,6 +1645,13 @@ pub const ALL_LINTS: [Lint; 319] = [
16451645
deprecation: None,
16461646
module: "loops",
16471647
},
1648+
Lint {
1649+
name: "same_item_push",
1650+
group: "style",
1651+
desc: "same item is being pushed inside of a for loop",
1652+
deprecation: None,
1653+
module: "loops",
1654+
},
16481655
Lint {
16491656
name: "search_is_some",
16501657
group: "complexity",

tests/ui/same_item_push.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#![warn(clippy::same_item_push)]
2+
3+
fn mutate_increment(x: &mut u8) -> u8 {
4+
*x += 1;
5+
*x
6+
}
7+
8+
fn increment(x: u8) -> u8 {
9+
x + 1
10+
}
11+
12+
fn main() {
13+
// Test for basic case
14+
let mut spaces = Vec::with_capacity(10);
15+
for _ in 0..10 {
16+
spaces.push(vec![b' ']);
17+
}
18+
19+
let mut vec2: Vec<u8> = Vec::new();
20+
let item = 2;
21+
for _ in 5..=20 {
22+
vec2.push(item);
23+
}
24+
25+
let mut vec3: Vec<u8> = Vec::new();
26+
for _ in 0..15 {
27+
let item = 2;
28+
vec3.push(item);
29+
}
30+
31+
let mut vec4: Vec<u8> = Vec::new();
32+
for _ in 0..15 {
33+
vec4.push(13);
34+
}
35+
36+
// Suggestion should not be given as pushed variable can mutate
37+
let mut vec5: Vec<u8> = Vec::new();
38+
let mut item: u8 = 2;
39+
for _ in 0..30 {
40+
vec5.push(mutate_increment(&mut item));
41+
}
42+
43+
let mut vec6: Vec<u8> = Vec::new();
44+
let mut item: u8 = 2;
45+
let mut item2 = &mut mutate_increment(&mut item);
46+
for _ in 0..30 {
47+
vec6.push(mutate_increment(item2));
48+
}
49+
50+
let mut vec7: Vec<u8> = Vec::new();
51+
for i in 0..30 {
52+
vec7.push(i);
53+
}
54+
55+
let mut vec8: Vec<u8> = Vec::new();
56+
for i in 0..30 {
57+
vec8.push(increment(i));
58+
}
59+
60+
let mut vec9: Vec<u8> = Vec::new();
61+
for i in 0..30 {
62+
vec9.push(i + i * i);
63+
}
64+
65+
// Suggestion should not be given as there are multiple pushes that are not the same
66+
let mut vec10: Vec<u8> = Vec::new();
67+
let item: u8 = 2;
68+
for _ in 0..30 {
69+
vec10.push(item);
70+
vec10.push(item * 2);
71+
}
72+
73+
// Suggestion should not be given as Vec is not involved
74+
for _ in 0..5 {
75+
println!("Same Item Push");
76+
}
77+
}

0 commit comments

Comments
 (0)