]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/vec_init_then_push.rs
Auto merge of #81993 - flip1995:clippyup, r=Manishearth
[rust.git] / clippy_lints / src / vec_init_then_push.rs
1 use crate::utils::{is_type_diagnostic_item, match_def_path, paths, snippet, span_lint_and_sugg};
2 use if_chain::if_chain;
3 use rustc_ast::ast::LitKind;
4 use rustc_errors::Applicability;
5 use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Local, PatKind, QPath, Stmt, StmtKind};
6 use rustc_lint::{LateContext, LateLintPass, LintContext};
7 use rustc_middle::lint::in_external_macro;
8 use rustc_session::{declare_tool_lint, impl_lint_pass};
9 use rustc_span::{symbol::sym, Span, Symbol};
10 use std::convert::TryInto;
11
12 declare_clippy_lint! {
13     /// **What it does:** Checks for calls to `push` immediately after creating a new `Vec`.
14     ///
15     /// **Why is this bad?** The `vec![]` macro is both more performant and easier to read than
16     /// multiple `push` calls.
17     ///
18     /// **Known problems:** None.
19     ///
20     /// **Example:**
21     ///
22     /// ```rust
23     /// let mut v = Vec::new();
24     /// v.push(0);
25     /// ```
26     /// Use instead:
27     /// ```rust
28     /// let v = vec![0];
29     /// ```
30     pub VEC_INIT_THEN_PUSH,
31     perf,
32     "`push` immediately after `Vec` creation"
33 }
34
35 impl_lint_pass!(VecInitThenPush => [VEC_INIT_THEN_PUSH]);
36
37 #[derive(Default)]
38 pub struct VecInitThenPush {
39     searcher: Option<VecPushSearcher>,
40 }
41
42 #[derive(Clone, Copy)]
43 enum VecInitKind {
44     New,
45     WithCapacity(u64),
46 }
47 struct VecPushSearcher {
48     init: VecInitKind,
49     name: Symbol,
50     lhs_is_local: bool,
51     lhs_span: Span,
52     err_span: Span,
53     found: u64,
54 }
55 impl VecPushSearcher {
56     fn display_err(&self, cx: &LateContext<'_>) {
57         match self.init {
58             _ if self.found == 0 => return,
59             VecInitKind::WithCapacity(x) if x > self.found => return,
60             _ => (),
61         };
62
63         let mut s = if self.lhs_is_local {
64             String::from("let ")
65         } else {
66             String::new()
67         };
68         s.push_str(&snippet(cx, self.lhs_span, ".."));
69         s.push_str(" = vec![..];");
70
71         span_lint_and_sugg(
72             cx,
73             VEC_INIT_THEN_PUSH,
74             self.err_span,
75             "calls to `push` immediately after creation",
76             "consider using the `vec![]` macro",
77             s,
78             Applicability::HasPlaceholders,
79         );
80     }
81 }
82
83 impl LateLintPass<'_> for VecInitThenPush {
84     fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
85         self.searcher = None;
86         if_chain! {
87             if !in_external_macro(cx.sess(), local.span);
88             if let Some(init) = local.init;
89             if let PatKind::Binding(BindingAnnotation::Mutable, _, ident, None) = local.pat.kind;
90             if let Some(init_kind) = get_vec_init_kind(cx, init);
91             then {
92                 self.searcher = Some(VecPushSearcher {
93                         init: init_kind,
94                         name: ident.name,
95                         lhs_is_local: true,
96                         lhs_span: local.ty.map_or(local.pat.span, |t| local.pat.span.to(t.span)),
97                         err_span: local.span,
98                         found: 0,
99                     });
100             }
101         }
102     }
103
104     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
105         if self.searcher.is_none() {
106             if_chain! {
107                 if !in_external_macro(cx.sess(), expr.span);
108                 if let ExprKind::Assign(left, right, _) = expr.kind;
109                 if let ExprKind::Path(QPath::Resolved(_, path)) = left.kind;
110                 if let Some(name) = path.segments.get(0);
111                 if let Some(init_kind) = get_vec_init_kind(cx, right);
112                 then {
113                     self.searcher = Some(VecPushSearcher {
114                         init: init_kind,
115                         name: name.ident.name,
116                         lhs_is_local: false,
117                         lhs_span: left.span,
118                         err_span: expr.span,
119                         found: 0,
120                     });
121                 }
122             }
123         }
124     }
125
126     fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
127         if let Some(searcher) = self.searcher.take() {
128             if_chain! {
129                 if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind;
130                 if let ExprKind::MethodCall(path, _, [self_arg, _], _) = expr.kind;
131                 if path.ident.name.as_str() == "push";
132                 if let ExprKind::Path(QPath::Resolved(_, self_path)) = self_arg.kind;
133                 if let [self_name] = self_path.segments;
134                 if self_name.ident.name == searcher.name;
135                 then {
136                     self.searcher = Some(VecPushSearcher {
137                         found: searcher.found + 1,
138                         err_span: searcher.err_span.to(stmt.span),
139                         .. searcher
140                     });
141                 } else {
142                     searcher.display_err(cx);
143                 }
144             }
145         }
146     }
147
148     fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx Block<'tcx>) {
149         if let Some(searcher) = self.searcher.take() {
150             searcher.display_err(cx);
151         }
152     }
153 }
154
155 fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
156     if let ExprKind::Call(func, args) = expr.kind {
157         match func.kind {
158             ExprKind::Path(QPath::TypeRelative(ty, name))
159                 if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::vec_type) =>
160             {
161                 if name.ident.name == sym::new {
162                     return Some(VecInitKind::New);
163                 } else if name.ident.name.as_str() == "with_capacity" {
164                     return args.get(0).and_then(|arg| {
165                         if_chain! {
166                             if let ExprKind::Lit(lit) = &arg.kind;
167                             if let LitKind::Int(num, _) = lit.node;
168                             then {
169                                 Some(VecInitKind::WithCapacity(num.try_into().ok()?))
170                             } else {
171                                 None
172                             }
173                         }
174                     });
175                 }
176             }
177             ExprKind::Path(QPath::Resolved(_, path))
178                 if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD)
179                     && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::vec_type) =>
180             {
181                 return Some(VecInitKind::New);
182             }
183             _ => (),
184         }
185     }
186     None
187 }