vexil_codegen_rust/
boxing.rs1use std::collections::HashSet;
2use vexil_lang::ir::{CompiledSchema, ResolvedType, TypeDef, TypeId};
3
4pub fn detect_boxing(compiled: &CompiledSchema) -> HashSet<(TypeId, usize)> {
6 let mut needs_box = HashSet::new();
7 for &id in &compiled.declarations {
8 let mut path = Vec::new();
9 path.push(id);
10 match compiled.registry.get(id) {
11 Some(TypeDef::Message(msg)) => {
12 for (fi, field) in msg.fields.iter().enumerate() {
13 walk_for_boxing(
14 &field.resolved_type,
15 id,
16 fi,
17 &path,
18 compiled,
19 &mut needs_box,
20 );
21 }
22 }
23 Some(TypeDef::Union(un)) => {
24 for variant in &un.variants {
25 for (fi, field) in variant.fields.iter().enumerate() {
26 walk_for_boxing(
27 &field.resolved_type,
28 id,
29 fi,
30 &path,
31 compiled,
32 &mut needs_box,
33 );
34 }
35 }
36 }
37 _ => {}
38 }
39 }
40 needs_box
41}
42
43fn walk_for_boxing(
44 ty: &ResolvedType,
45 parent_id: TypeId,
46 field_index: usize,
47 path: &[TypeId],
48 compiled: &CompiledSchema,
49 needs_box: &mut HashSet<(TypeId, usize)>,
50) {
51 match ty {
52 ResolvedType::Optional(inner) => {
53 check_inner_for_cycle(inner, parent_id, field_index, path, compiled, needs_box);
54 }
55 ResolvedType::Result(ok, err) => {
56 check_inner_for_cycle(ok, parent_id, field_index, path, compiled, needs_box);
57 check_inner_for_cycle(err, parent_id, field_index, path, compiled, needs_box);
58 }
59 ResolvedType::Named(id) => {
60 if path.contains(id) {
61 needs_box.insert((parent_id, field_index));
64 return;
65 }
66 let mut new_path = path.to_vec();
67 new_path.push(*id);
68 match compiled.registry.get(*id) {
69 Some(TypeDef::Message(msg)) => {
70 for (fi, field) in msg.fields.iter().enumerate() {
71 walk_for_boxing(
72 &field.resolved_type,
73 *id,
74 fi,
75 &new_path,
76 compiled,
77 needs_box,
78 );
79 }
80 }
81 Some(TypeDef::Union(un)) => {
82 for variant in &un.variants {
83 for (fi, field) in variant.fields.iter().enumerate() {
84 walk_for_boxing(
85 &field.resolved_type,
86 *id,
87 fi,
88 &new_path,
89 compiled,
90 needs_box,
91 );
92 }
93 }
94 }
95 _ => {}
96 }
97 }
98 ResolvedType::Array(_) | ResolvedType::Map(_, _) => {
99 }
101 _ => {} }
103}
104
105fn check_inner_for_cycle(
106 ty: &ResolvedType,
107 parent_id: TypeId,
108 field_index: usize,
109 path: &[TypeId],
110 compiled: &CompiledSchema,
111 needs_box: &mut HashSet<(TypeId, usize)>,
112) {
113 if let ResolvedType::Named(id) = ty {
114 if path.contains(id) {
115 needs_box.insert((parent_id, field_index));
116 return;
117 }
118 }
119 walk_for_boxing(ty, parent_id, field_index, path, compiled, needs_box);
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 fn analyze(src: &str) -> HashSet<(TypeId, usize)> {
127 let result = vexil_lang::compile(src);
128 let compiled = result.compiled.unwrap();
129 detect_boxing(&compiled)
130 }
131
132 #[test]
133 fn no_recursion_no_boxing() {
134 let needs = analyze(
135 r#"
136 namespace test.box
137 message Simple { name @0 : string }
138 "#,
139 );
140 assert!(needs.is_empty());
141 }
142
143 #[test]
144 fn optional_self_reference_needs_box() {
145 let needs = analyze(
146 r#"
147 namespace test.box
148 message Node {
149 value @0 : i32
150 next @1 : optional<Node>
151 }
152 "#,
153 );
154 assert!(!needs.is_empty());
155 }
156
157 #[test]
158 fn mutual_recursion_needs_box() {
159 let needs = analyze(
160 r#"
161 namespace test.box
162 message Expr {
163 kind @0 : ExprKind
164 }
165 union ExprKind {
166 Literal @0 { value @0 : i64 }
167 Binary @1 { left @0 : Expr op @1 : u8 right @2 : Expr }
168 }
169 "#,
170 );
171 assert!(!needs.is_empty());
172 }
173
174 #[test]
175 fn array_self_reference_no_box() {
176 let needs = analyze(
177 r#"
178 namespace test.box
179 message Tree {
180 value @0 : i32
181 children @1 : array<Tree>
182 }
183 "#,
184 );
185 assert!(needs.is_empty());
186 }
187}