Skip to content

Commit 1e8ada3

Browse files
committed
Add lint same_item_push
1 parent 3d7e3fd commit 1e8ada3

File tree

6 files changed

+388
-0
lines changed

6 files changed

+388
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
610610
&loops::WHILE_IMMUTABLE_CONDITION,
611611
&loops::WHILE_LET_LOOP,
612612
&loops::WHILE_LET_ON_ITERATOR,
613+
&loops::SAME_ITEM_PUSH,
613614
&macro_use::MACRO_USE_IMPORTS,
614615
&main_recursion::MAIN_RECURSION,
615616
&manual_async_fn::MANUAL_ASYNC_FN,
@@ -1405,6 +1406,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14051406
LintId::of(&repeat_once::REPEAT_ONCE),
14061407
LintId::of(&returns::NEEDLESS_RETURN),
14071408
LintId::of(&returns::UNUSED_UNIT),
1409+
LintId::of(&loops::SAME_ITEM_PUSH),
14081410
LintId::of(&serde_api::SERDE_API_MISUSE),
14091411
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
14101412
LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
@@ -1543,6 +1545,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15431545
LintId::of(&regex::TRIVIAL_REGEX),
15441546
LintId::of(&returns::NEEDLESS_RETURN),
15451547
LintId::of(&returns::UNUSED_UNIT),
1548+
LintId::of(&loops::SAME_ITEM_PUSH),
15461549
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
15471550
LintId::of(&strings::STRING_LIT_AS_BYTES),
15481551
LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),

clippy_lints/src/loops.rs

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

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

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

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
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: "default lint description",
1942+
deprecation: None,
1943+
module: "same_item_push",
1944+
},
19381945
Lint {
19391946
name: "search_is_some",
19401947
group: "complexity",

0 commit comments

Comments
 (0)