]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/vec_init_then_push.rs
New upstream version 1.75.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / vec_init_then_push.rs
CommitLineData
cdc7bbd5 1use clippy_utils::diagnostics::span_lint_and_sugg;
3c0e092e 2use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
cdc7bbd5 3use clippy_utils::source::snippet;
923072b8
FG
4use clippy_utils::visitors::for_each_local_use_after_expr;
5use clippy_utils::{get_parent_expr, path_to_local_id};
6use core::ops::ControlFlow;
f20569fa 7use rustc_errors::Applicability;
923072b8
FG
8use rustc_hir::def::Res;
9use rustc_hir::{
10 BindingAnnotation, Block, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, Stmt, StmtKind, UnOp,
11};
f20569fa
XL
12use rustc_lint::{LateContext, LateLintPass, LintContext};
13use rustc_middle::lint::in_external_macro;
14use rustc_session::{declare_tool_lint, impl_lint_pass};
923072b8 15use rustc_span::{Span, Symbol};
f20569fa
XL
16
17declare_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
49impl_lint_pass!(VecInitThenPush => [VEC_INIT_THEN_PUSH]);
50
51#[derive(Default)]
52pub struct VecInitThenPush {
53 searcher: Option<VecPushSearcher>,
54}
55
f20569fa
XL
56struct 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}
66impl 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 155impl<'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}