]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
3c0e092e | 2 | use clippy_utils::higher::{get_vec_init_kind, VecInitKind}; |
cdc7bbd5 | 3 | use clippy_utils::source::snippet; |
923072b8 FG |
4 | use clippy_utils::visitors::for_each_local_use_after_expr; |
5 | use clippy_utils::{get_parent_expr, path_to_local_id}; | |
6 | use core::ops::ControlFlow; | |
f20569fa | 7 | use rustc_errors::Applicability; |
923072b8 FG |
8 | use rustc_hir::def::Res; |
9 | use rustc_hir::{ | |
10 | BindingAnnotation, Block, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, Stmt, StmtKind, UnOp, | |
11 | }; | |
f20569fa XL |
12 | use rustc_lint::{LateContext, LateLintPass, LintContext}; |
13 | use rustc_middle::lint::in_external_macro; | |
14 | use rustc_session::{declare_tool_lint, impl_lint_pass}; | |
923072b8 | 15 | use rustc_span::{Span, Symbol}; |
f20569fa XL |
16 | |
17 | declare_clippy_lint! { | |
94222f64 XL |
18 | /// ### What it does |
19 | /// Checks for calls to `push` immediately after creating a new `Vec`. | |
f20569fa | 20 | /// |
923072b8 FG |
21 | /// If the `Vec` is created using `with_capacity` this will only lint if the capacity is a |
22 | /// constant and the number of pushes is greater than or equal to the initial capacity. | |
23 | /// | |
24 | /// If the `Vec` is extended after the initial sequence of pushes and it was default initialized | |
25 | /// then this will only lint after there were at least four pushes. This number may change in | |
26 | /// the future. | |
27 | /// | |
94222f64 XL |
28 | /// ### Why is this bad? |
29 | /// The `vec![]` macro is both more performant and easier to read than | |
f20569fa XL |
30 | /// multiple `push` calls. |
31 | /// | |
94222f64 | 32 | /// ### Example |
ed00b5ec | 33 | /// ```no_run |
f20569fa XL |
34 | /// let mut v = Vec::new(); |
35 | /// v.push(0); | |
ed00b5ec FG |
36 | /// v.push(1); |
37 | /// v.push(2); | |
f20569fa XL |
38 | /// ``` |
39 | /// Use instead: | |
ed00b5ec FG |
40 | /// ```no_run |
41 | /// let v = vec![0, 1, 2]; | |
f20569fa | 42 | /// ``` |
a2a8927a | 43 | #[clippy::version = "1.51.0"] |
f20569fa XL |
44 | pub VEC_INIT_THEN_PUSH, |
45 | perf, | |
46 | "`push` immediately after `Vec` creation" | |
47 | } | |
48 | ||
49 | impl_lint_pass!(VecInitThenPush => [VEC_INIT_THEN_PUSH]); | |
50 | ||
51 | #[derive(Default)] | |
52 | pub struct VecInitThenPush { | |
53 | searcher: Option<VecPushSearcher>, | |
54 | } | |
55 | ||
f20569fa XL |
56 | struct VecPushSearcher { |
57 | local_id: HirId, | |
58 | init: VecInitKind, | |
923072b8 FG |
59 | lhs_is_let: bool, |
60 | let_ty_span: Option<Span>, | |
61 | name: Symbol, | |
f20569fa | 62 | err_span: Span, |
923072b8 FG |
63 | found: u128, |
64 | last_push_expr: HirId, | |
f20569fa XL |
65 | } |
66 | impl VecPushSearcher { | |
67 | fn display_err(&self, cx: &LateContext<'_>) { | |
923072b8 | 68 | let required_pushes_before_extension = match self.init { |
f20569fa | 69 | _ if self.found == 0 => return, |
923072b8 FG |
70 | VecInitKind::WithConstCapacity(x) if x > self.found => return, |
71 | VecInitKind::WithConstCapacity(x) => x, | |
3c0e092e | 72 | VecInitKind::WithExprCapacity(_) => return, |
923072b8 | 73 | _ => 3, |
f20569fa XL |
74 | }; |
75 | ||
923072b8 FG |
76 | let mut needs_mut = false; |
77 | let res = for_each_local_use_after_expr(cx, self.local_id, self.last_push_expr, |e| { | |
78 | let Some(parent) = get_parent_expr(cx, e) else { | |
add651ee | 79 | return ControlFlow::Continue(()); |
923072b8 FG |
80 | }; |
81 | let adjusted_ty = cx.typeck_results().expr_ty_adjusted(e); | |
82 | let adjusted_mut = adjusted_ty.ref_mutability().unwrap_or(Mutability::Not); | |
83 | needs_mut |= adjusted_mut == Mutability::Mut; | |
84 | match parent.kind { | |
85 | ExprKind::AddrOf(_, Mutability::Mut, _) => { | |
86 | needs_mut = true; | |
87 | return ControlFlow::Break(true); | |
88 | }, | |
89 | ExprKind::Unary(UnOp::Deref, _) | ExprKind::Index(..) if !needs_mut => { | |
90 | let mut last_place = parent; | |
064997fb | 91 | while let Some(parent) = get_parent_expr(cx, last_place) { |
923072b8 | 92 | if matches!(parent.kind, ExprKind::Unary(UnOp::Deref, _) | ExprKind::Field(..)) |
add651ee | 93 | || matches!(parent.kind, ExprKind::Index(e, _, _) if e.hir_id == last_place.hir_id) |
923072b8 FG |
94 | { |
95 | last_place = parent; | |
96 | } else { | |
97 | break; | |
98 | } | |
99 | } | |
100 | needs_mut |= cx.typeck_results().expr_ty_adjusted(last_place).ref_mutability() | |
101 | == Some(Mutability::Mut) | |
102 | || get_parent_expr(cx, last_place) | |
103 | .map_or(false, |e| matches!(e.kind, ExprKind::AddrOf(_, Mutability::Mut, _))); | |
104 | }, | |
f2b60f7d | 105 | ExprKind::MethodCall(_, recv, ..) |
923072b8 FG |
106 | if recv.hir_id == e.hir_id |
107 | && adjusted_mut == Mutability::Mut | |
108 | && !adjusted_ty.peel_refs().is_slice() => | |
109 | { | |
110 | // No need to set `needs_mut` to true. The receiver will be either explicitly borrowed, or it will | |
111 | // be implicitly borrowed via an adjustment. Both of these cases are already handled by this point. | |
112 | return ControlFlow::Break(true); | |
113 | }, | |
114 | ExprKind::Assign(lhs, ..) if e.hir_id == lhs.hir_id => { | |
115 | needs_mut = true; | |
116 | return ControlFlow::Break(false); | |
117 | }, | |
118 | _ => (), | |
119 | } | |
120 | ControlFlow::Continue(()) | |
121 | }); | |
122 | ||
123 | // Avoid allocating small `Vec`s when they'll be extended right after. | |
124 | if res == ControlFlow::Break(true) && self.found <= required_pushes_before_extension { | |
125 | return; | |
126 | } | |
127 | ||
128 | let mut s = if self.lhs_is_let { | |
f20569fa XL |
129 | String::from("let ") |
130 | } else { | |
131 | String::new() | |
132 | }; | |
923072b8 FG |
133 | if needs_mut { |
134 | s.push_str("mut "); | |
135 | } | |
136 | s.push_str(self.name.as_str()); | |
137 | if let Some(span) = self.let_ty_span { | |
138 | s.push_str(": "); | |
139 | s.push_str(&snippet(cx, span, "_")); | |
140 | } | |
f20569fa XL |
141 | s.push_str(" = vec![..];"); |
142 | ||
143 | span_lint_and_sugg( | |
144 | cx, | |
145 | VEC_INIT_THEN_PUSH, | |
146 | self.err_span, | |
147 | "calls to `push` immediately after creation", | |
148 | "consider using the `vec![]` macro", | |
149 | s, | |
150 | Applicability::HasPlaceholders, | |
151 | ); | |
152 | } | |
153 | } | |
154 | ||
5099ac24 | 155 | impl<'tcx> LateLintPass<'tcx> for VecInitThenPush { |
f20569fa XL |
156 | fn check_block(&mut self, _: &LateContext<'tcx>, _: &'tcx Block<'tcx>) { |
157 | self.searcher = None; | |
158 | } | |
159 | ||
160 | fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { | |
923072b8 | 161 | if let Some(init_expr) = local.init |
f2b60f7d | 162 | && let PatKind::Binding(BindingAnnotation::MUT, id, name, None) = local.pat.kind |
923072b8 FG |
163 | && !in_external_macro(cx.sess(), local.span) |
164 | && let Some(init) = get_vec_init_kind(cx, init_expr) | |
165 | && !matches!(init, VecInitKind::WithExprCapacity(_)) | |
166 | { | |
167 | self.searcher = Some(VecPushSearcher { | |
168 | local_id: id, | |
169 | init, | |
170 | lhs_is_let: true, | |
171 | name: name.name, | |
172 | let_ty_span: local.ty.map(|ty| ty.span), | |
173 | err_span: local.span, | |
174 | found: 0, | |
175 | last_push_expr: init_expr.hir_id, | |
176 | }); | |
f20569fa XL |
177 | } |
178 | } | |
179 | ||
180 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
923072b8 FG |
181 | if self.searcher.is_none() |
182 | && let ExprKind::Assign(left, right, _) = expr.kind | |
183 | && let ExprKind::Path(QPath::Resolved(None, path)) = left.kind | |
184 | && let [name] = &path.segments | |
185 | && let Res::Local(id) = path.res | |
186 | && !in_external_macro(cx.sess(), expr.span) | |
187 | && let Some(init) = get_vec_init_kind(cx, right) | |
188 | && !matches!(init, VecInitKind::WithExprCapacity(_)) | |
189 | { | |
190 | self.searcher = Some(VecPushSearcher { | |
191 | local_id: id, | |
192 | init, | |
193 | lhs_is_let: false, | |
194 | let_ty_span: None, | |
195 | name: name.ident.name, | |
196 | err_span: expr.span, | |
197 | found: 0, | |
198 | last_push_expr: expr.hir_id, | |
199 | }); | |
f20569fa XL |
200 | } |
201 | } | |
202 | ||
203 | fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { | |
204 | if let Some(searcher) = self.searcher.take() { | |
923072b8 | 205 | if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind |
f2b60f7d | 206 | && let ExprKind::MethodCall(name, self_arg, [_], _) = expr.kind |
923072b8 FG |
207 | && path_to_local_id(self_arg, searcher.local_id) |
208 | && name.ident.as_str() == "push" | |
209 | { | |
210 | self.searcher = Some(VecPushSearcher { | |
211 | found: searcher.found + 1, | |
212 | err_span: searcher.err_span.to(stmt.span), | |
213 | last_push_expr: expr.hir_id, | |
ed00b5ec | 214 | ..searcher |
923072b8 FG |
215 | }); |
216 | } else { | |
217 | searcher.display_err(cx); | |
f20569fa XL |
218 | } |
219 | } | |
220 | } | |
221 | ||
222 | fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx Block<'tcx>) { | |
223 | if let Some(searcher) = self.searcher.take() { | |
224 | searcher.display_err(cx); | |
225 | } | |
226 | } | |
227 | } |