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