Skip to content

Commit 17be043

Browse files
Lint is not suggested for for loops with possible non-determinant behavior
1 parent bd82efd commit 17be043

File tree

1 file changed

+70
-36
lines changed

1 file changed

+70
-36
lines changed

clippy_lints/src/loops.rs

+70-36
Original file line numberDiff line numberDiff line change
@@ -1118,18 +1118,7 @@ impl<'a, 'tcx> Visitor<'tcx> for ForPatternVisitor<'a, 'tcx> {
11181118
fn visit_expr(&mut self, expr: &'tcx Expr) {
11191119
// Recursively explore an expression until a ExprKind::Path is found
11201120
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) => {
1121+
ExprKind::Array(expr_list) | ExprKind::MethodCall(_, _, expr_list) | ExprKind::Tup(expr_list) => {
11331122
for expr in expr_list {
11341123
self.visit_expr(expr)
11351124
}
@@ -1138,11 +1127,12 @@ impl<'a, 'tcx> Visitor<'tcx> for ForPatternVisitor<'a, 'tcx> {
11381127
self.visit_expr(lhs_expr);
11391128
self.visit_expr(rhs_expr);
11401129
},
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),
1130+
ExprKind::Box(expr)
1131+
| ExprKind::Unary(_, expr)
1132+
| ExprKind::Cast(expr, _)
1133+
| ExprKind::Type(expr, _)
1134+
| ExprKind::AddrOf(_, expr)
1135+
| ExprKind::Struct(_, _, Some(expr)) => self.visit_expr(expr),
11461136
_ => {
11471137
// Exploration cannot continue ... calculate the hir_id of the current
11481138
// expr assuming it is a Path
@@ -1173,6 +1163,55 @@ impl<'a, 'tcx> Visitor<'tcx> for ForPatternVisitor<'a, 'tcx> {
11731163
}
11741164
}
11751165

1166+
// Scans the body of the for loop and determines whether lint should be given
1167+
struct SameItemPushVisitor<'a, 'tcx> {
1168+
should_lint: bool,
1169+
// this field holds the last vec push operation visited, which should be the only push seen
1170+
vec_push: Option<(&'tcx Expr, &'tcx Expr)>,
1171+
cx: &'a LateContext<'a, 'tcx>,
1172+
}
1173+
1174+
impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
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+
rustc::hir::StmtKind::Expr(expr) | rustc::hir::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<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
1211+
NestedVisitorMap::None
1212+
}
1213+
}
1214+
11761215
// Given some statement, determine if that statement is a push on a Vec. If it is, return
11771216
// the Vec being pushed into and the item being pushed
11781217
fn get_vec_push<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, stmt: &'tcx Stmt) -> Option<(&'tcx Expr, &'tcx Expr)> {
@@ -1201,39 +1240,34 @@ fn detect_same_item_push<'a, 'tcx>(
12011240
body: &'tcx Expr,
12021241
_: &'tcx Expr,
12031242
) {
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 {
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+
intravisit::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 {
12181252
// Make sure that the push does not involve possibly mutating values
1219-
if !has_mutable_variables(cx, pushes[0].1) {
1253+
if !has_mutable_variables(cx, pushed_item) {
12201254
// Walk through the expression being pushed and make sure that it
12211255
// does not contain the for loop pattern
12221256
let mut for_pat_visitor = ForPatternVisitor {
12231257
found_pattern: false,
12241258
for_pattern_hir_id: pat.hir_id,
12251259
cx,
12261260
};
1227-
intravisit::walk_expr(&mut for_pat_visitor, pushes[0].1);
1261+
intravisit::walk_expr(&mut for_pat_visitor, pushed_item);
12281262

12291263
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, "");
1264+
let vec_str = snippet(cx, vec.span, "");
1265+
let item_str = snippet(cx, pushed_item.span, "");
12321266

12331267
span_help_and_lint(
12341268
cx,
12351269
SAME_ITEM_PUSH,
1236-
pushes[0].0.span,
1270+
vec.span,
12371271
"it looks like the same item is being pushed into this Vec",
12381272
&format!(
12391273
"try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})",

0 commit comments

Comments
 (0)