wowlab_tidy/languages/rust/rules/performance/
vec_init_then_push.rs1use ra_ap_syntax::{
2 ast,
3 ast::{HasArgList, HasName},
4};
5
6use crate::{AstCtx, Example, Violation};
7
8const ADJACENT_STATEMENTS: usize = 2;
9
10#[rustfmt::skip]
11const EXAMPLES: &[Example] = &[
12 Example {
13 label: "Vec::new then push",
14 code: "fn f() { let mut v = Vec::new(); v.push(1); v.push(2); }",
15 pass: false,
16 },
17 Example {
18 label: "vec! macro is fine",
19 code: "fn f() { let v = vec![1, 2]; }",
20 pass: true,
21 },
22 Example {
23 label: "Vec::new with_capacity is fine",
24 code: "fn f() { let mut v = Vec::with_capacity(10); v.push(1); }",
25 pass: true,
26 },
27 Example {
28 label: "Vec::new then push in test",
29 code: "#[cfg(test)]\nmod tests {\n fn t() { let mut v = Vec::new(); v.push(1); }\n}",
30 pass: true,
31 },
32 Example {
33 label: "Vec::new with logic between",
34 code: "fn f() { let mut v = Vec::new(); let x = 1; if x > 0 { v.push(x); } }",
35 pass: true,
36 },
37 Example {
38 label: "annotated Vec binding is outside the simple-pattern rule",
39 code: "fn f() { let mut v: Vec<u32> = Vec::new(); v.push(1); }",
40 pass: true,
41 },
42];
43
44crate::ast_rule!(
45 vec_init_then_push,
46 "Flag `Vec::new()` immediately followed by `.push()` calls (use `vec![]` or `with_capacity`).",
47 "Vec::new() followed by push() calls can be replaced with vec![...] or Vec::with_capacity for clarity and performance.",
48);
49
50fn check_vec_init_then_push(ctx: &AstCtx<'_>) -> Vec<Violation> {
51 let mut violations = Vec::new();
52
53 for statements in ctx.nodes::<ast::StmtList>() {
54 let entries: Vec<ast::Stmt> = statements.statements().collect();
55
56 for pair in entries.windows(ADJACENT_STATEMENTS) {
57 let ast::Stmt::LetStmt(local) = &pair[0] else {
58 continue;
59 };
60
61 if ctx.is_in_test(local) || !local.initializer().is_some_and(|expr| is_vec_new(&expr)) {
62 continue;
63 }
64
65 let Some(name) = let_mut_name(local) else {
66 continue;
67 };
68
69 if is_push_on(&pair[1], &name) {
70 violations.push(ctx.violation(
71 local,
72 "Vec::new() immediately followed by .push() — use vec![...] or Vec::with_capacity()",
73 ));
74 }
75 }
76 }
77
78 violations
79}
80
81fn is_vec_new(expr: &ast::Expr) -> bool {
82 let ast::Expr::CallExpr(call) = expr else {
83 return false;
84 };
85 let Some(ast::Expr::PathExpr(function)) = call.expr() else {
86 return false;
87 };
88 let Some(path) = function.path() else {
89 return false;
90 };
91 let last = path
92 .segment()
93 .and_then(|segment| segment.name_ref())
94 .map(|name| name.text().to_string());
95 let qualifier = path.qualifier().and_then(|qualifier| qualifier.segment());
96 let owner = qualifier
97 .and_then(|segment| segment.name_ref())
98 .map(|name| name.text().to_string());
99
100 owner.as_deref() == Some("Vec")
101 && last.as_deref() == Some("new")
102 && call
103 .arg_list()
104 .is_none_or(|arguments| arguments.args().next().is_none())
105}
106
107fn let_mut_name(local: &ast::LetStmt) -> Option<String> {
108 if local.ty().is_some() {
109 return None;
110 }
111
112 let ast::Pat::IdentPat(pattern) = local.pat()? else {
113 return None;
114 };
115
116 pattern.mut_token()?;
117
118 pattern.name().map(|name| name.text().to_string())
119}
120
121fn is_push_on(statement: &ast::Stmt, name: &str) -> bool {
122 let ast::Stmt::ExprStmt(statement) = statement else {
123 return false;
124 };
125 let Some(ast::Expr::MethodCallExpr(call)) = statement.expr() else {
126 return false;
127 };
128
129 if call.name_ref().is_none_or(|method| method.text() != "push") {
130 return false;
131 }
132
133 let Some(ast::Expr::PathExpr(receiver)) = call.receiver() else {
134 return false;
135 };
136
137 let name_ref = receiver
138 .path()
139 .and_then(|path| path.segment())
140 .and_then(|segment| segment.name_ref());
141
142 name_ref.is_some_and(|receiver| receiver.text() == name)
143}
144
145crate::tidy_ast_test!(check_vec_init_then_push, {
146 crate::example_tests!(EXAMPLES, check_vec_init_then_push);
147});