Skip to main content

wowlab_tidy/languages/rust/rules/performance/
vec_init_then_push.rs

1use 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});