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