Skip to content

Commit baab560

Browse files
committed
Auto merge of #5825 - giraffate:same_item_push, r=Manishearth
Add the new lint `same_item_push` changelog: Add the new lint `same_item_push` Fixed #4078. As I said in #4078 (comment), I referrerd to #4647.
2 parents 2d4c337 + 610d4e3 commit baab560

File tree

6 files changed

+284
-2
lines changed

6 files changed

+284
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1687,6 +1687,7 @@ Released 2018-09-13
16871687
[`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn
16881688
[`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges
16891689
[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition
1690+
[`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push
16901691
[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
16911692
[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
16921693
[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse

clippy_lints/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
608608
&loops::NEEDLESS_COLLECT,
609609
&loops::NEEDLESS_RANGE_LOOP,
610610
&loops::NEVER_LOOP,
611+
&loops::SAME_ITEM_PUSH,
611612
&loops::WHILE_IMMUTABLE_CONDITION,
612613
&loops::WHILE_LET_LOOP,
613614
&loops::WHILE_LET_ON_ITERATOR,
@@ -1295,6 +1296,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12951296
LintId::of(&loops::NEEDLESS_COLLECT),
12961297
LintId::of(&loops::NEEDLESS_RANGE_LOOP),
12971298
LintId::of(&loops::NEVER_LOOP),
1299+
LintId::of(&loops::SAME_ITEM_PUSH),
12981300
LintId::of(&loops::WHILE_IMMUTABLE_CONDITION),
12991301
LintId::of(&loops::WHILE_LET_LOOP),
13001302
LintId::of(&loops::WHILE_LET_ON_ITERATOR),
@@ -1497,6 +1499,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14971499
LintId::of(&loops::EMPTY_LOOP),
14981500
LintId::of(&loops::FOR_KV_MAP),
14991501
LintId::of(&loops::NEEDLESS_RANGE_LOOP),
1502+
LintId::of(&loops::SAME_ITEM_PUSH),
15001503
LintId::of(&loops::WHILE_LET_ON_ITERATOR),
15011504
LintId::of(&main_recursion::MAIN_RECURSION),
15021505
LintId::of(&manual_async_fn::MANUAL_ASYNC_FN),

clippy_lints/src/loops.rs

+149-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use crate::utils::usage::{is_unused, mutated_variables};
55
use crate::utils::{
66
get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait,
77
is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, last_path_segment, match_trait_method,
8-
match_type, match_var, multispan_sugg, qpath_res, snippet, snippet_opt, snippet_with_applicability, span_lint,
9-
span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, SpanlessEq,
8+
match_type, match_var, multispan_sugg, qpath_res, snippet, snippet_opt, snippet_with_applicability,
9+
snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg,
10+
SpanlessEq,
1011
};
1112
use if_chain::if_chain;
1213
use rustc_ast::ast;
@@ -419,6 +420,39 @@ declare_clippy_lint! {
419420
"variables used within while expression are not mutated in the body"
420421
}
421422

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+
422456
declare_lint_pass!(Loops => [
423457
MANUAL_MEMCPY,
424458
NEEDLESS_RANGE_LOOP,
@@ -435,6 +469,7 @@ declare_lint_pass!(Loops => [
435469
NEVER_LOOP,
436470
MUT_RANGE_BOUND,
437471
WHILE_IMMUTABLE_CONDITION,
472+
SAME_ITEM_PUSH,
438473
]);
439474

440475
impl<'tcx> LateLintPass<'tcx> for Loops {
@@ -740,6 +775,7 @@ fn check_for_loop<'tcx>(
740775
check_for_loop_over_map_kv(cx, pat, arg, body, expr);
741776
check_for_mut_range_bound(cx, arg, body);
742777
detect_manual_memcpy(cx, pat, arg, body, expr);
778+
detect_same_item_push(cx, pat, arg, body, expr);
743779
}
744780

745781
fn same_var<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, var: HirId) -> bool {
@@ -1016,6 +1052,117 @@ fn detect_manual_memcpy<'tcx>(
10161052
}
10171053
}
10181054

1055+
// Scans the body of the for loop and determines whether lint should be given
1056+
struct SameItemPushVisitor<'a, 'tcx> {
1057+
should_lint: bool,
1058+
// this field holds the last vec push operation visited, which should be the only push seen
1059+
vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>,
1060+
cx: &'a LateContext<'tcx>,
1061+
}
1062+
1063+
impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
1064+
type Map = Map<'tcx>;
1065+
1066+
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
1067+
match &expr.kind {
1068+
// Non-determinism may occur ... don't give a lint
1069+
ExprKind::Loop(_, _, _) | ExprKind::Match(_, _, _) => self.should_lint = false,
1070+
ExprKind::Block(block, _) => self.visit_block(block),
1071+
_ => {},
1072+
}
1073+
}
1074+
1075+
fn visit_block(&mut self, b: &'tcx Block<'_>) {
1076+
for stmt in b.stmts.iter() {
1077+
self.visit_stmt(stmt);
1078+
}
1079+
}
1080+
1081+
fn visit_stmt(&mut self, s: &'tcx Stmt<'_>) {
1082+
let vec_push_option = get_vec_push(self.cx, s);
1083+
if vec_push_option.is_none() {
1084+
// Current statement is not a push so visit inside
1085+
match &s.kind {
1086+
StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(&expr),
1087+
_ => {},
1088+
}
1089+
} else {
1090+
// Current statement is a push ...check whether another
1091+
// push had been previously done
1092+
if self.vec_push.is_none() {
1093+
self.vec_push = vec_push_option;
1094+
} else {
1095+
// There are multiple pushes ... don't lint
1096+
self.should_lint = false;
1097+
}
1098+
}
1099+
}
1100+
1101+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
1102+
NestedVisitorMap::None
1103+
}
1104+
}
1105+
1106+
// Given some statement, determine if that statement is a push on a Vec. If it is, return
1107+
// the Vec being pushed into and the item being pushed
1108+
fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
1109+
if_chain! {
1110+
// Extract method being called
1111+
if let StmtKind::Semi(semi_stmt) = &stmt.kind;
1112+
if let ExprKind::MethodCall(path, _, args, _) = &semi_stmt.kind;
1113+
// Figure out the parameters for the method call
1114+
if let Some(self_expr) = args.get(0);
1115+
if let Some(pushed_item) = args.get(1);
1116+
// Check that the method being called is push() on a Vec
1117+
if match_type(cx, cx.typeck_results().expr_ty(self_expr), &paths::VEC);
1118+
if path.ident.name.as_str() == "push";
1119+
then {
1120+
return Some((self_expr, pushed_item))
1121+
}
1122+
}
1123+
None
1124+
}
1125+
1126+
/// Detects for loop pushing the same item into a Vec
1127+
fn detect_same_item_push<'tcx>(
1128+
cx: &LateContext<'tcx>,
1129+
pat: &'tcx Pat<'_>,
1130+
_: &'tcx Expr<'_>,
1131+
body: &'tcx Expr<'_>,
1132+
_: &'tcx Expr<'_>,
1133+
) {
1134+
// Determine whether it is safe to lint the body
1135+
let mut same_item_push_visitor = SameItemPushVisitor {
1136+
should_lint: true,
1137+
vec_push: None,
1138+
cx,
1139+
};
1140+
walk_expr(&mut same_item_push_visitor, body);
1141+
if same_item_push_visitor.should_lint {
1142+
if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push {
1143+
// Make sure that the push does not involve possibly mutating values
1144+
if mutated_variables(pushed_item, cx).map_or(false, |mutvars| mutvars.is_empty()) {
1145+
if let PatKind::Wild = pat.kind {
1146+
let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
1147+
let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
1148+
1149+
span_lint_and_help(
1150+
cx,
1151+
SAME_ITEM_PUSH,
1152+
vec.span,
1153+
"it looks like the same item is being pushed into this Vec",
1154+
None,
1155+
&format!(
1156+
"try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})",
1157+
item_str, vec_str, item_str
1158+
),
1159+
)
1160+
}
1161+
}
1162+
}
1163+
}
1164+
}
1165+
10191166
/// Checks for looping over a range and then indexing a sequence with it.
10201167
/// The iteratee must be a range literal.
10211168
#[allow(clippy::too_many_lines)]

src/lintlist/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1935,6 +1935,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
19351935
deprecation: None,
19361936
module: "copies",
19371937
},
1938+
Lint {
1939+
name: "same_item_push",
1940+
group: "style",
1941+
desc: "the same item is pushed inside of a for loop",
1942+
deprecation: None,
1943+
module: "loops",
1944+
},
19381945
Lint {
19391946
name: "search_is_some",
19401947
group: "complexity",

tests/ui/same_item_push.rs

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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<usize> = Vec::new();
51+
for (a, b) in [0, 1, 4, 9, 16].iter().enumerate() {
52+
vec7.push(a);
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+
78+
struct A {
79+
kind: u32,
80+
}
81+
let mut vec_a: Vec<A> = Vec::new();
82+
for i in 0..30 {
83+
vec_a.push(A { kind: i });
84+
}
85+
let mut vec12: Vec<u8> = Vec::new();
86+
for a in vec_a {
87+
vec12.push(2u8.pow(a.kind));
88+
}
89+
}

tests/ui/same_item_push.stderr

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
error: it looks like the same item is being pushed into this Vec
2+
--> $DIR/same_item_push.rs:16:9
3+
|
4+
LL | spaces.push(vec![b' ']);
5+
| ^^^^^^
6+
|
7+
= note: `-D clippy::same-item-push` implied by `-D warnings`
8+
= help: try using vec![vec![b' '];SIZE] or spaces.resize(NEW_SIZE, vec![b' '])
9+
10+
error: it looks like the same item is being pushed into this Vec
11+
--> $DIR/same_item_push.rs:22:9
12+
|
13+
LL | vec2.push(item);
14+
| ^^^^
15+
|
16+
= help: try using vec![item;SIZE] or vec2.resize(NEW_SIZE, item)
17+
18+
error: it looks like the same item is being pushed into this Vec
19+
--> $DIR/same_item_push.rs:28:9
20+
|
21+
LL | vec3.push(item);
22+
| ^^^^
23+
|
24+
= help: try using vec![item;SIZE] or vec3.resize(NEW_SIZE, item)
25+
26+
error: it looks like the same item is being pushed into this Vec
27+
--> $DIR/same_item_push.rs:33:9
28+
|
29+
LL | vec4.push(13);
30+
| ^^^^
31+
|
32+
= help: try using vec![13;SIZE] or vec4.resize(NEW_SIZE, 13)
33+
34+
error: aborting due to 4 previous errors
35+

0 commit comments

Comments
 (0)