Skip to content

Commit 2c1f676

Browse files
nahuakangY-Nak
authored andcommitted
Add detect_same_item_push to its own module
1 parent 453e6b9 commit 2c1f676

File tree

2 files changed

+174
-165
lines changed

2 files changed

+174
-165
lines changed

clippy_lints/src/loops/mod.rs

Lines changed: 6 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,25 @@ mod for_single_element_loop;
77
mod infinite_loop;
88
mod manual_flatten;
99
mod needless_collect;
10+
mod same_item_push;
1011
mod utils;
1112

1213
use crate::utils::sugg::Sugg;
1314
use crate::utils::usage::mutated_variables;
1415
use crate::utils::{
1516
get_enclosing_block, get_parent_expr, get_trait_def_id, higher, implements_trait, is_in_panic_handler,
1617
is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, last_path_segment, match_trait_method,
17-
path_to_local, path_to_local_id, paths, snippet, snippet_with_applicability, snippet_with_macro_callsite,
18-
span_lint, span_lint_and_help, span_lint_and_sugg, sugg,
18+
path_to_local, path_to_local_id, paths, snippet, snippet_with_applicability, span_lint, span_lint_and_help,
19+
span_lint_and_sugg, sugg,
1920
};
2021
use if_chain::if_chain;
2122
use rustc_ast::ast;
2223
use rustc_data_structures::fx::FxHashMap;
2324
use rustc_errors::Applicability;
24-
use rustc_hir::def::{DefKind, Res};
2525
use rustc_hir::intravisit::{walk_block, walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor};
2626
use rustc_hir::{
27-
BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, HirId, InlineAsmOperand, LoopSource, MatchSource,
28-
Mutability, Node, Pat, PatKind, Stmt, StmtKind,
27+
BinOpKind, Block, BorrowKind, Expr, ExprKind, HirId, InlineAsmOperand, LoopSource, MatchSource, Mutability, Node,
28+
Pat, PatKind, Stmt, StmtKind,
2929
};
3030
use rustc_lint::{LateContext, LateLintPass, LintContext};
3131
use rustc_middle::hir::map::Map;
@@ -866,7 +866,7 @@ fn check_for_loop<'tcx>(
866866
for_loop_over_map_kv::check_for_loop_over_map_kv(cx, pat, arg, body, expr);
867867
for_mut_range_bound::check_for_mut_range_bound(cx, arg, body);
868868
for_single_element_loop::check_for_single_element_loop(cx, pat, arg, body, expr);
869-
detect_same_item_push(cx, pat, arg, body, expr);
869+
same_item_push::detect_same_item_push(cx, pat, arg, body, expr);
870870
manual_flatten::check_manual_flatten(cx, pat, arg, body, span);
871871
}
872872

@@ -1307,165 +1307,6 @@ fn detect_manual_memcpy<'tcx>(
13071307
false
13081308
}
13091309

1310-
// Scans the body of the for loop and determines whether lint should be given
1311-
struct SameItemPushVisitor<'a, 'tcx> {
1312-
should_lint: bool,
1313-
// this field holds the last vec push operation visited, which should be the only push seen
1314-
vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>,
1315-
cx: &'a LateContext<'tcx>,
1316-
}
1317-
1318-
impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
1319-
type Map = Map<'tcx>;
1320-
1321-
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
1322-
match &expr.kind {
1323-
// Non-determinism may occur ... don't give a lint
1324-
ExprKind::Loop(..) | ExprKind::Match(..) => self.should_lint = false,
1325-
ExprKind::Block(block, _) => self.visit_block(block),
1326-
_ => {},
1327-
}
1328-
}
1329-
1330-
fn visit_block(&mut self, b: &'tcx Block<'_>) {
1331-
for stmt in b.stmts.iter() {
1332-
self.visit_stmt(stmt);
1333-
}
1334-
}
1335-
1336-
fn visit_stmt(&mut self, s: &'tcx Stmt<'_>) {
1337-
let vec_push_option = get_vec_push(self.cx, s);
1338-
if vec_push_option.is_none() {
1339-
// Current statement is not a push so visit inside
1340-
match &s.kind {
1341-
StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(&expr),
1342-
_ => {},
1343-
}
1344-
} else {
1345-
// Current statement is a push ...check whether another
1346-
// push had been previously done
1347-
if self.vec_push.is_none() {
1348-
self.vec_push = vec_push_option;
1349-
} else {
1350-
// There are multiple pushes ... don't lint
1351-
self.should_lint = false;
1352-
}
1353-
}
1354-
}
1355-
1356-
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
1357-
NestedVisitorMap::None
1358-
}
1359-
}
1360-
1361-
// Given some statement, determine if that statement is a push on a Vec. If it is, return
1362-
// the Vec being pushed into and the item being pushed
1363-
fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
1364-
if_chain! {
1365-
// Extract method being called
1366-
if let StmtKind::Semi(semi_stmt) = &stmt.kind;
1367-
if let ExprKind::MethodCall(path, _, args, _) = &semi_stmt.kind;
1368-
// Figure out the parameters for the method call
1369-
if let Some(self_expr) = args.get(0);
1370-
if let Some(pushed_item) = args.get(1);
1371-
// Check that the method being called is push() on a Vec
1372-
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::vec_type);
1373-
if path.ident.name.as_str() == "push";
1374-
then {
1375-
return Some((self_expr, pushed_item))
1376-
}
1377-
}
1378-
None
1379-
}
1380-
1381-
/// Detects for loop pushing the same item into a Vec
1382-
fn detect_same_item_push<'tcx>(
1383-
cx: &LateContext<'tcx>,
1384-
pat: &'tcx Pat<'_>,
1385-
_: &'tcx Expr<'_>,
1386-
body: &'tcx Expr<'_>,
1387-
_: &'tcx Expr<'_>,
1388-
) {
1389-
fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) {
1390-
let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
1391-
let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
1392-
1393-
span_lint_and_help(
1394-
cx,
1395-
SAME_ITEM_PUSH,
1396-
vec.span,
1397-
"it looks like the same item is being pushed into this Vec",
1398-
None,
1399-
&format!(
1400-
"try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})",
1401-
item_str, vec_str, item_str
1402-
),
1403-
)
1404-
}
1405-
1406-
if !matches!(pat.kind, PatKind::Wild) {
1407-
return;
1408-
}
1409-
1410-
// Determine whether it is safe to lint the body
1411-
let mut same_item_push_visitor = SameItemPushVisitor {
1412-
should_lint: true,
1413-
vec_push: None,
1414-
cx,
1415-
};
1416-
walk_expr(&mut same_item_push_visitor, body);
1417-
if same_item_push_visitor.should_lint {
1418-
if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push {
1419-
let vec_ty = cx.typeck_results().expr_ty(vec);
1420-
let ty = vec_ty.walk().nth(1).unwrap().expect_ty();
1421-
if cx
1422-
.tcx
1423-
.lang_items()
1424-
.clone_trait()
1425-
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
1426-
{
1427-
// Make sure that the push does not involve possibly mutating values
1428-
match pushed_item.kind {
1429-
ExprKind::Path(ref qpath) => {
1430-
match cx.qpath_res(qpath, pushed_item.hir_id) {
1431-
// immutable bindings that are initialized with literal or constant
1432-
Res::Local(hir_id) => {
1433-
if_chain! {
1434-
let node = cx.tcx.hir().get(hir_id);
1435-
if let Node::Binding(pat) = node;
1436-
if let PatKind::Binding(bind_ann, ..) = pat.kind;
1437-
if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable);
1438-
let parent_node = cx.tcx.hir().get_parent_node(hir_id);
1439-
if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node);
1440-
if let Some(init) = parent_let_expr.init;
1441-
then {
1442-
match init.kind {
1443-
// immutable bindings that are initialized with literal
1444-
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
1445-
// immutable bindings that are initialized with constant
1446-
ExprKind::Path(ref path) => {
1447-
if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
1448-
emit_lint(cx, vec, pushed_item);
1449-
}
1450-
}
1451-
_ => {},
1452-
}
1453-
}
1454-
}
1455-
},
1456-
// constant
1457-
Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item),
1458-
_ => {},
1459-
}
1460-
},
1461-
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
1462-
_ => {},
1463-
}
1464-
}
1465-
}
1466-
}
1467-
}
1468-
14691310
fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool {
14701311
let def_id = match path_to_local(expr) {
14711312
Some(id) => id,
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
use crate::utils::{implements_trait, is_type_diagnostic_item, snippet_with_macro_callsite, span_lint_and_help};
2+
use if_chain::if_chain;
3+
use rustc_hir::def::{DefKind, Res};
4+
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
5+
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Node, Pat, PatKind, Stmt, StmtKind};
6+
use rustc_lint::LateContext;
7+
use rustc_middle::hir::map::Map;
8+
use rustc_span::symbol::sym;
9+
use std::iter::Iterator;
10+
11+
/// Detects for loop pushing the same item into a Vec
12+
pub(super) fn detect_same_item_push<'tcx>(
13+
cx: &LateContext<'tcx>,
14+
pat: &'tcx Pat<'_>,
15+
_: &'tcx Expr<'_>,
16+
body: &'tcx Expr<'_>,
17+
_: &'tcx Expr<'_>,
18+
) {
19+
fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) {
20+
let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
21+
let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
22+
23+
span_lint_and_help(
24+
cx,
25+
super::SAME_ITEM_PUSH,
26+
vec.span,
27+
"it looks like the same item is being pushed into this Vec",
28+
None,
29+
&format!(
30+
"try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})",
31+
item_str, vec_str, item_str
32+
),
33+
)
34+
}
35+
36+
if !matches!(pat.kind, PatKind::Wild) {
37+
return;
38+
}
39+
40+
// Determine whether it is safe to lint the body
41+
let mut same_item_push_visitor = SameItemPushVisitor {
42+
should_lint: true,
43+
vec_push: None,
44+
cx,
45+
};
46+
walk_expr(&mut same_item_push_visitor, body);
47+
if same_item_push_visitor.should_lint {
48+
if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push {
49+
let vec_ty = cx.typeck_results().expr_ty(vec);
50+
let ty = vec_ty.walk().nth(1).unwrap().expect_ty();
51+
if cx
52+
.tcx
53+
.lang_items()
54+
.clone_trait()
55+
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
56+
{
57+
// Make sure that the push does not involve possibly mutating values
58+
match pushed_item.kind {
59+
ExprKind::Path(ref qpath) => {
60+
match cx.qpath_res(qpath, pushed_item.hir_id) {
61+
// immutable bindings that are initialized with literal or constant
62+
Res::Local(hir_id) => {
63+
if_chain! {
64+
let node = cx.tcx.hir().get(hir_id);
65+
if let Node::Binding(pat) = node;
66+
if let PatKind::Binding(bind_ann, ..) = pat.kind;
67+
if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable);
68+
let parent_node = cx.tcx.hir().get_parent_node(hir_id);
69+
if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node);
70+
if let Some(init) = parent_let_expr.init;
71+
then {
72+
match init.kind {
73+
// immutable bindings that are initialized with literal
74+
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
75+
// immutable bindings that are initialized with constant
76+
ExprKind::Path(ref path) => {
77+
if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
78+
emit_lint(cx, vec, pushed_item);
79+
}
80+
}
81+
_ => {},
82+
}
83+
}
84+
}
85+
},
86+
// constant
87+
Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item),
88+
_ => {},
89+
}
90+
},
91+
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
92+
_ => {},
93+
}
94+
}
95+
}
96+
}
97+
}
98+
99+
// Scans the body of the for loop and determines whether lint should be given
100+
struct SameItemPushVisitor<'a, 'tcx> {
101+
should_lint: bool,
102+
// this field holds the last vec push operation visited, which should be the only push seen
103+
vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>,
104+
cx: &'a LateContext<'tcx>,
105+
}
106+
107+
impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
108+
type Map = Map<'tcx>;
109+
110+
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
111+
match &expr.kind {
112+
// Non-determinism may occur ... don't give a lint
113+
ExprKind::Loop(..) | ExprKind::Match(..) => self.should_lint = false,
114+
ExprKind::Block(block, _) => self.visit_block(block),
115+
_ => {},
116+
}
117+
}
118+
119+
fn visit_block(&mut self, b: &'tcx Block<'_>) {
120+
for stmt in b.stmts.iter() {
121+
self.visit_stmt(stmt);
122+
}
123+
}
124+
125+
fn visit_stmt(&mut self, s: &'tcx Stmt<'_>) {
126+
let vec_push_option = get_vec_push(self.cx, s);
127+
if vec_push_option.is_none() {
128+
// Current statement is not a push so visit inside
129+
match &s.kind {
130+
StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(&expr),
131+
_ => {},
132+
}
133+
} else {
134+
// Current statement is a push ...check whether another
135+
// push had been previously done
136+
if self.vec_push.is_none() {
137+
self.vec_push = vec_push_option;
138+
} else {
139+
// There are multiple pushes ... don't lint
140+
self.should_lint = false;
141+
}
142+
}
143+
}
144+
145+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
146+
NestedVisitorMap::None
147+
}
148+
}
149+
150+
// Given some statement, determine if that statement is a push on a Vec. If it is, return
151+
// the Vec being pushed into and the item being pushed
152+
fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
153+
if_chain! {
154+
// Extract method being called
155+
if let StmtKind::Semi(semi_stmt) = &stmt.kind;
156+
if let ExprKind::MethodCall(path, _, args, _) = &semi_stmt.kind;
157+
// Figure out the parameters for the method call
158+
if let Some(self_expr) = args.get(0);
159+
if let Some(pushed_item) = args.get(1);
160+
// Check that the method being called is push() on a Vec
161+
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::vec_type);
162+
if path.ident.name.as_str() == "push";
163+
then {
164+
return Some((self_expr, pushed_item))
165+
}
166+
}
167+
None
168+
}

0 commit comments

Comments
 (0)