wowlab_tidy/languages/rust/rules/performance/
nested_smart_pointers.rs1use ra_ap_syntax::{ast, ast::HasGenericArgs};
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7 Example {
8 label: "Arc<Box<T>> field",
9 code: "struct S { a: Arc<Box<u32>> }",
10 pass: false,
11 },
12 Example {
13 label: "Box<Arc<T>> parameter",
14 code: "fn f(x: Box<Arc<u32>>) { drop(x); }",
15 pass: false,
16 },
17 Example {
18 label: "Rc<Rc<T>> type alias",
19 code: "type T = Rc<Rc<u32>>;",
20 pass: false,
21 },
22 Example {
23 label: "Arc<Vec<T>> suggests Arc<[T]>",
24 code: "struct S { d: Arc<Vec<u8>> }",
25 pass: false,
26 },
27 Example {
28 label: "Arc<String> suggests Arc<str>",
29 code: "struct S { n: Arc<String> }",
30 pass: false,
31 },
32 Example {
33 label: "Arc<Mutex<T>> is a single heap layer",
34 code: "struct S { m: Arc<std::sync::Mutex<u32>> }",
35 pass: true,
36 },
37 Example {
38 label: "Box<dyn Trait> is fine",
39 code: "struct S { cb: Box<dyn Fn()> }",
40 pass: true,
41 },
42 Example {
43 label: "Arc<[T]> and Arc<str> are the goal state",
44 code: "fn f(b: Arc<[u8]>, s: Arc<str>) { drop(b); drop(s); }",
45 pass: true,
46 },
47 Example {
48 label: "Box<Vec<T>> is owned by rust_box_vec, not this rule",
49 code: "fn f() { let _x: Box<Vec<u32>> = Box::new(Vec::new()); }",
50 pass: true,
51 },
52 Example {
53 label: "nested pointers in test module",
54 code: "#[cfg(test)]\nmod tests {\n struct S { a: Arc<Box<u32>> }\n}",
55 pass: true,
56 },
57];
58
59crate::ast_rule!(
60 nested_smart_pointers,
61 "Flag directly nested heap pointers (`Arc<Box<T>>`, `Rc<Rc<T>>`, ...) plus `Arc<Vec<T>>`/`Arc<String>`.",
62 "Each nesting layer is another sequential DRAM lookup on access — flatten to one allocation (Arc<[T]>, Arc<str>).",
63 Medium,
64);
65
66fn check_nested_smart_pointers(ctx: &AstCtx<'_>) -> Vec<Violation> {
67 ctx.nodes::<ast::PathType>()
68 .filter(|path| !ctx.is_in_test(path))
69 .filter_map(|path_type| {
70 let path = path_type.path()?;
71
72 nested_message(&path).map(|message| ctx.violation(&path_type, message))
73 })
74 .collect()
75}
76
77const HEAP_WRAPPERS: &[&str] = &["Arc", "Box", "Rc"];
78
79fn type_arg_last_ident(segment: &ast::PathSegment) -> Option<String> {
80 segment.generic_arg_list()?.generic_args().find_map(|arg| {
81 let ast::GenericArg::TypeArg(arg) = arg else {
82 return None;
83 };
84 let ast::Type::PathType(path_type) = arg.ty()? else {
85 return None;
86 };
87
88 path_type
89 .path()?
90 .segment()?
91 .name_ref()
92 .map(|name| name.text().to_string())
93 })
94}
95
96fn nested_message(path: &ast::Path) -> Option<String> {
97 let outer = path.segment()?;
98 let outer_name = outer.name_ref()?.text().to_string();
99
100 if !HEAP_WRAPPERS.contains(&outer_name.as_str()) {
101 return None;
102 }
103
104 let inner = type_arg_last_ident(&outer)?;
105
106 match (outer_name.as_str(), inner.as_str()) {
107 ("Box", "Box" | "String" | "Vec") => None,
108 (_, "Arc" | "Box" | "Rc") => Some(format!(
109 "`{outer_name}<{inner}<..>>` stacks two heap indirections — flatten to a single allocation"
110 )),
111 ("Arc", "Vec") => Some(
112 "`Arc<Vec<T>>` — prefer `Arc<[T]>` (one indirection less, no capacity word)"
113 .to_string(),
114 ),
115 ("Arc", "String") => Some(
116 "`Arc<String>` — prefer `Arc<str>` (one indirection less, no capacity word)"
117 .to_string(),
118 ),
119 _ => None,
120 }
121}
122
123crate::tidy_ast_test!(check_nested_smart_pointers, {
124 crate::example_tests!(EXAMPLES, check_nested_smart_pointers);
125});