]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/loops/same_item_push.rs
Rollup merge of #105128 - Sp00ph:vec_vec_deque_conversion, r=dtolnay
[rust.git] / src / tools / clippy / clippy_lints / src / loops / same_item_push.rs
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, Visitor};
10 use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Mutability, Node, Pat, PatKind, Stmt, StmtKind};
11 use rustc_lint::LateContext;
12 use rustc_span::symbol::sym;
13 use std::iter::Iterator;
14
15 /// Detects for loop pushing the same item into a Vec
16 pub(super) fn check<'tcx>(
17     cx: &LateContext<'tcx>,
18     pat: &'tcx Pat<'_>,
19     _: &'tcx Expr<'_>,
20     body: &'tcx Expr<'_>,
21     _: &'tcx Expr<'_>,
22 ) {
23     fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) {
24         let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
25         let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
26
27         span_lint_and_help(
28             cx,
29             SAME_ITEM_PUSH,
30             vec.span,
31             "it looks like the same item is being pushed into this Vec",
32             None,
33             &format!("try using vec![{item_str};SIZE] or {vec_str}.resize(NEW_SIZE, {item_str})"),
34         );
35     }
36
37     if !matches!(pat.kind, PatKind::Wild) {
38         return;
39     }
40
41     // Determine whether it is safe to lint the body
42     let mut same_item_push_visitor = SameItemPushVisitor::new(cx);
43     walk_expr(&mut same_item_push_visitor, body);
44     if_chain! {
45         if same_item_push_visitor.should_lint();
46         if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push;
47         let vec_ty = cx.typeck_results().expr_ty(vec);
48         let ty = vec_ty.walk().nth(1).unwrap().expect_ty();
49         if cx
50             .tcx
51             .lang_items()
52             .clone_trait()
53             .map_or(false, |id| implements_trait(cx, ty, id, &[]));
54         then {
55             // Make sure that the push does not involve possibly mutating values
56             match pushed_item.kind {
57                 ExprKind::Path(ref qpath) => {
58                     match cx.qpath_res(qpath, pushed_item.hir_id) {
59                         // immutable bindings that are initialized with literal or constant
60                         Res::Local(hir_id) => {
61                             let node = cx.tcx.hir().get(hir_id);
62                             if_chain! {
63                                 if let Node::Pat(pat) = node;
64                                 if let PatKind::Binding(bind_ann, ..) = pat.kind;
65                                 if !matches!(bind_ann, BindingAnnotation(_, Mutability::Mut));
66                                 let parent_node = cx.tcx.hir().parent_id(hir_id);
67                                 if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node);
68                                 if let Some(init) = parent_let_expr.init;
69                                 then {
70                                     match init.kind {
71                                         // immutable bindings that are initialized with literal
72                                         ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
73                                         // immutable bindings that are initialized with constant
74                                         ExprKind::Path(ref path) => {
75                                             if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
76                                                 emit_lint(cx, vec, pushed_item);
77                                             }
78                                         }
79                                         _ => {},
80                                     }
81                                 }
82                             }
83                         },
84                         // constant
85                         Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item),
86                         _ => {},
87                     }
88                 },
89                 ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
90                 _ => {},
91             }
92         }
93     }
94 }
95
96 // Scans the body of the for loop and determines whether lint should be given
97 struct SameItemPushVisitor<'a, 'tcx> {
98     non_deterministic_expr: bool,
99     multiple_pushes: bool,
100     // this field holds the last vec push operation visited, which should be the only push seen
101     vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>,
102     cx: &'a LateContext<'tcx>,
103     used_locals: FxHashSet<HirId>,
104 }
105
106 impl<'a, 'tcx> SameItemPushVisitor<'a, 'tcx> {
107     fn new(cx: &'a LateContext<'tcx>) -> Self {
108         Self {
109             non_deterministic_expr: false,
110             multiple_pushes: false,
111             vec_push: None,
112             cx,
113             used_locals: FxHashSet::default(),
114         }
115     }
116
117     fn should_lint(&self) -> bool {
118         if_chain! {
119             if !self.non_deterministic_expr;
120             if !self.multiple_pushes;
121             if let Some((vec, _)) = self.vec_push;
122             if let Some(hir_id) = path_to_local(vec);
123             then {
124                 !self.used_locals.contains(&hir_id)
125             } else {
126                 false
127             }
128         }
129     }
130 }
131
132 impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
133     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
134         match &expr.kind {
135             // Non-determinism may occur ... don't give a lint
136             ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::If(..) => self.non_deterministic_expr = true,
137             ExprKind::Block(block, _) => self.visit_block(block),
138             _ => {
139                 if let Some(hir_id) = path_to_local(expr) {
140                     self.used_locals.insert(hir_id);
141                 }
142                 walk_expr(self, expr);
143             },
144         }
145     }
146
147     fn visit_block(&mut self, b: &'tcx Block<'_>) {
148         for stmt in b.stmts.iter() {
149             self.visit_stmt(stmt);
150         }
151     }
152
153     fn visit_stmt(&mut self, s: &'tcx Stmt<'_>) {
154         let vec_push_option = get_vec_push(self.cx, s);
155         if vec_push_option.is_none() {
156             // Current statement is not a push so visit inside
157             match &s.kind {
158                 StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(expr),
159                 _ => {},
160             }
161         } else {
162             // Current statement is a push ...check whether another
163             // push had been previously done
164             if self.vec_push.is_none() {
165                 self.vec_push = vec_push_option;
166             } else {
167                 // There are multiple pushes ... don't lint
168                 self.multiple_pushes = true;
169             }
170         }
171     }
172 }
173
174 // Given some statement, determine if that statement is a push on a Vec. If it is, return
175 // the Vec being pushed into and the item being pushed
176 fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
177     if_chain! {
178             // Extract method being called
179             if let StmtKind::Semi(semi_stmt) = &stmt.kind;
180             if let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind;
181             // Figure out the parameters for the method call
182             if let Some(pushed_item) = args.get(0);
183             // Check that the method being called is push() on a Vec
184             if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec);
185             if path.ident.name.as_str() == "push";
186             then {
187                 return Some((self_expr, pushed_item))
188             }
189     }
190     None
191 }