Skip to content

Commit 9f77080

Browse files
committed
Add lint same_item_push
1 parent 61e3d8a commit 9f77080

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
@@ -1686,6 +1686,7 @@ Released 2018-09-13
16861686
[`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn
16871687
[`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges
16881688
[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition
1689+
[`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push
16891690
[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
16901691
[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
16911692
[`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
@@ -613,6 +613,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
613613
&loops::WHILE_IMMUTABLE_CONDITION,
614614
&loops::WHILE_LET_LOOP,
615615
&loops::WHILE_LET_ON_ITERATOR,
616+
&loops::SAME_ITEM_PUSH,
616617
&macro_use::MACRO_USE_IMPORTS,
617618
&main_recursion::MAIN_RECURSION,
618619
&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
@@ -420,6 +420,39 @@ declare_clippy_lint! {
420420
"variables used within while expression are not mutated in the body"
421421
}
422422

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+
423456
declare_lint_pass!(Loops => [
424457
MANUAL_MEMCPY,
425458
NEEDLESS_RANGE_LOOP,
@@ -436,6 +469,7 @@ declare_lint_pass!(Loops => [
436469
NEVER_LOOP,
437470
MUT_RANGE_BOUND,
438471
WHILE_IMMUTABLE_CONDITION,
472+
SAME_ITEM_PUSH,
439473
]);
440474

441475
impl<'tcx> LateLintPass<'tcx> for Loops {
@@ -741,6 +775,7 @@ fn check_for_loop<'tcx>(
741775
check_for_loop_over_map_kv(cx, pat, arg, body, expr);
742776
check_for_mut_range_bound(cx, arg, body);
743777
detect_manual_memcpy(cx, pat, arg, body, expr);
778+
detect_same_item_push(cx, pat, arg, body, expr);
744779
}
745780

746781
fn same_var<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, var: HirId) -> bool {
@@ -1017,6 +1052,236 @@ fn detect_manual_memcpy<'tcx>(
10171052
}
10181053
}
10191054

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

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1928,6 +1928,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
19281928
deprecation: None,
19291929
module: "copies",
19301930
},
1931+
Lint {
1932+
name: "same_item_push",
1933+
group: "style",
1934+
desc: "default lint description",
1935+
deprecation: None,
1936+
module: "same_item_push",
1937+
},
19311938
Lint {
19321939
name: "search_is_some",
19331940
group: "complexity",

0 commit comments

Comments
 (0)