1 use super::SAME_ITEM_PUSH;
2 use clippy_utils::diagnostics::span_lint_and_help;
3 use clippy_utils::path_to_local;
4 use clippy_utils::source::snippet_with_macro_callsite;
5 use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
6 use if_chain::if_chain;
7 use rustc_data_structures::fx::FxHashSet;
8 use rustc_hir::def::{DefKind, Res};
9 use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
10 use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Node, Pat, PatKind, Stmt, StmtKind};
11 use rustc_lint::LateContext;
12 use rustc_middle::hir::map::Map;
13 use rustc_span::symbol::sym;
14 use std::iter::Iterator;
16 /// Detects for loop pushing the same item into a Vec
17 pub(super) fn check<'tcx>(
18 cx: &LateContext<'tcx>,
24 fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) {
25 let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
26 let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
32 "it looks like the same item is being pushed into this Vec",
35 "try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})",
36 item_str, vec_str, item_str
41 if !matches!(pat.kind, PatKind::Wild) {
45 // Determine whether it is safe to lint the body
46 let mut same_item_push_visitor = SameItemPushVisitor::new(cx);
47 walk_expr(&mut same_item_push_visitor, body);
49 if same_item_push_visitor.should_lint();
50 if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push;
51 let vec_ty = cx.typeck_results().expr_ty(vec);
52 let ty = vec_ty.walk().nth(1).unwrap().expect_ty();
57 .map_or(false, |id| implements_trait(cx, ty, id, &[]));
59 // Make sure that the push does not involve possibly mutating values
60 match pushed_item.kind {
61 ExprKind::Path(ref qpath) => {
62 match cx.qpath_res(qpath, pushed_item.hir_id) {
63 // immutable bindings that are initialized with literal or constant
64 Res::Local(hir_id) => {
65 let node = cx.tcx.hir().get(hir_id);
67 if let Node::Binding(pat) = node;
68 if let PatKind::Binding(bind_ann, ..) = pat.kind;
69 if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable);
70 let parent_node = cx.tcx.hir().get_parent_node(hir_id);
71 if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node);
72 if let Some(init) = parent_let_expr.init;
75 // immutable bindings that are initialized with literal
76 ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
77 // immutable bindings that are initialized with constant
78 ExprKind::Path(ref path) => {
79 if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
80 emit_lint(cx, vec, pushed_item);
89 Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item),
93 ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
100 // Scans the body of the for loop and determines whether lint should be given
101 struct SameItemPushVisitor<'a, 'tcx> {
102 non_deterministic_expr: bool,
103 multiple_pushes: bool,
104 // this field holds the last vec push operation visited, which should be the only push seen
105 vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>,
106 cx: &'a LateContext<'tcx>,
107 used_locals: FxHashSet<HirId>,
110 impl<'a, 'tcx> SameItemPushVisitor<'a, 'tcx> {
111 fn new(cx: &'a LateContext<'tcx>) -> Self {
113 non_deterministic_expr: false,
114 multiple_pushes: false,
117 used_locals: FxHashSet::default(),
121 fn should_lint(&self) -> bool {
123 if !self.non_deterministic_expr;
124 if !self.multiple_pushes;
125 if let Some((vec, _)) = self.vec_push;
126 if let Some(hir_id) = path_to_local(vec);
128 !self.used_locals.contains(&hir_id)
136 impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
137 type Map = Map<'tcx>;
139 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
141 // Non-determinism may occur ... don't give a lint
142 ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::If(..) => self.non_deterministic_expr = true,
143 ExprKind::Block(block, _) => self.visit_block(block),
145 if let Some(hir_id) = path_to_local(expr) {
146 self.used_locals.insert(hir_id);
148 walk_expr(self, expr);
153 fn visit_block(&mut self, b: &'tcx Block<'_>) {
154 for stmt in b.stmts.iter() {
155 self.visit_stmt(stmt);
159 fn visit_stmt(&mut self, s: &'tcx Stmt<'_>) {
160 let vec_push_option = get_vec_push(self.cx, s);
161 if vec_push_option.is_none() {
162 // Current statement is not a push so visit inside
164 StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(expr),
168 // Current statement is a push ...check whether another
169 // push had been previously done
170 if self.vec_push.is_none() {
171 self.vec_push = vec_push_option;
173 // There are multiple pushes ... don't lint
174 self.multiple_pushes = true;
179 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
180 NestedVisitorMap::None
184 // Given some statement, determine if that statement is a push on a Vec. If it is, return
185 // the Vec being pushed into and the item being pushed
186 fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
188 // Extract method being called
189 if let StmtKind::Semi(semi_stmt) = &stmt.kind;
190 if let ExprKind::MethodCall(path, _, args, _) = &semi_stmt.kind;
191 // Figure out the parameters for the method call
192 if let Some(self_expr) = args.get(0);
193 if let Some(pushed_item) = args.get(1);
194 // Check that the method being called is push() on a Vec
195 if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::vec_type);
196 if path.ident.name.as_str() == "push";
198 return Some((self_expr, pushed_item))