Skip to main content

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

1use crate::{Example, FileCtx, Fix, Violation, infra::parse, violation};
2
3#[rustfmt::skip]
4const EXAMPLES: &[Example] = &[
5    Example {
6        label: "Box<Vec<T>>",
7        code: "let x: Box<Vec<i32>> = Box::new(vec![]);",
8        pass: false,
9    },
10    Example {
11        label: "Box<String>",
12        code: "let x: Box<String> = Box::new(String::new());",
13        pass: false,
14    },
15    Example {
16        label: "Box<Box<T>>",
17        code: "let x: Box<Box<i32>> = Box::new(Box::new(0));",
18        pass: false,
19    },
20    Example {
21        label: "Box<dyn Trait>",
22        code: "let x: Box<dyn Trait> = Box::new(foo);",
23        pass: true,
24    },
25    Example {
26        label: "comment with pattern",
27        code: "// Box<Vec<i32>> is bad",
28        pass: true,
29    },
30];
31
32crate::line_rule!(
33    box_vec,
34    "Ban `Box<Vec<T>>`, `Box<String>`, `Box<Box<T>>` (unnecessary double indirection).",
35    "Box<Vec<T>> adds a pointless heap indirection since Vec already heap-allocates. Use Vec<T> or Box<[T]>.",
36    Medium,
37    fix_box_vec,
38);
39
40const BANNED_PATTERNS: &[(&str, &str)] = &[
41    (
42        "Box<Vec<",
43        "Box<Vec<T>> is double indirection (use Vec<T> directly)",
44    ),
45    (
46        "Box<String>",
47        "Box<String> is double indirection (use String directly)",
48    ),
49    (
50        "Box<Box<",
51        "Box<Box<T>> is double indirection (use Box<T> directly)",
52    ),
53];
54
55fn check_box_vec(ctx: &FileCtx<'_>) -> Vec<Violation> {
56    let mut out = Vec::new();
57
58    for (i, line) in ctx.lines.iter().enumerate() {
59        let lineno = i + 1;
60        let trimmed = line.trim();
61
62        if parse::is_comment(trimmed) {
63            continue;
64        }
65
66        for (pattern, message) in BANNED_PATTERNS {
67            if line.contains(pattern) {
68                out.push(violation(ctx.rel, lineno, *message));
69            }
70        }
71    }
72
73    out
74}
75
76fn replace_box_wrapper(line: &str, prefix: &str) -> Option<String> {
77    let start = line.find(prefix)?;
78    let after_box = start + "Box<".len();
79    let rest = line.get(after_box..)?;
80
81    let mut depth: u32 = 1;
82    let closing = rest.char_indices().find_map(|(index, character)| {
83        match character {
84            '<' => depth += 1,
85            '>' => {
86                depth -= 1;
87
88                if depth == 0 {
89                    return Some(index);
90                }
91            }
92            _ => {}
93        }
94
95        None
96    })?;
97    let inner = rest.get(..closing)?;
98    let after = rest.get(closing + 1..)?;
99
100    Some(format!("{}{inner}{after}", line.get(..start)?))
101}
102
103fn fix_box_vec(ctx: &FileCtx<'_>, v: &Violation) -> Option<Fix> {
104    let line = ctx.line(v.line)?;
105
106    for (pattern, _) in BANNED_PATTERNS {
107        if line.contains(pattern) {
108            let fixed = replace_box_wrapper(line, pattern)?;
109
110            return Some(Fix::replace_line(v.line, fixed));
111        }
112    }
113
114    None
115}
116
117crate::tidy_test!(check_box_vec, {
118    crate::example_tests!(EXAMPLES, check_box_vec);
119    crate::fix_tests!(line, check_box_vec, fix_box_vec);
120});