1use std::collections::BTreeMap;
28use std::collections::BTreeSet;
29
30use crate::error::Error;
31use crate::error::Result;
32use crate::ir::EnumKind;
33use crate::ir::Item;
34use crate::ir::Module;
35use crate::ir::RustType;
36use crate::naming::Case;
37use crate::naming::to_ident;
38
39pub fn box_recursive_types(module: &mut Module) -> Result<()> {
44 let graph = Graph::of(module);
45 graph.check_alias_cycles()?;
46
47 for item in &mut module.items {
48 let owner = canonical(item.name());
49 match item {
50 Item::Struct(strukt) => {
51 for field in &mut strukt.fields {
52 box_held(&mut field.ty, &owner, &graph);
53 }
54 }
55 Item::Enum(enumeration) => {
56 if let EnumKind::Union(variants) = &mut enumeration.kind {
57 for variant in variants {
58 box_held(&mut variant.ty, &owner, &graph);
59 }
60 }
61 }
62 Item::Alias(_) => {}
65 }
66 }
67 return Ok(());
68}
69
70struct Graph {
80 names: Vec<String>,
82 nodes: BTreeMap<String, usize>,
84 edges: Vec<Vec<usize>>,
86 is_alias: Vec<bool>,
88 component: Vec<usize>,
90}
91
92impl Graph {
93 fn of(module: &Module) -> Self {
95 let names: Vec<String> = module.items.iter().map(|item| return canonical(item.name())).collect();
96 let nodes: BTreeMap<String, usize> = names
97 .iter()
98 .enumerate()
99 .map(|(node, name)| return (name.clone(), node))
100 .collect();
101
102 let mut edges = Vec::with_capacity(module.items.len());
103 let mut is_alias = Vec::with_capacity(module.items.len());
104 for item in &module.items {
105 let mut targets = BTreeSet::new();
106 match item {
107 Item::Struct(strukt) => {
108 for field in &strukt.fields {
109 collect_held(&field.ty, &mut targets);
110 }
111 }
112 Item::Enum(enumeration) => {
113 if let EnumKind::Union(variants) = &enumeration.kind {
114 for variant in variants {
115 collect_held(&variant.ty, &mut targets);
116 }
117 }
118 }
119 Item::Alias(alias) => collect_held(&alias.ty, &mut targets),
120 }
121 is_alias.push(matches!(item, Item::Alias(_)));
122 edges.push(
125 targets
126 .iter()
127 .filter_map(|target| return nodes.get(target).copied())
128 .collect(),
129 );
130 }
131
132 let component = components(&edges);
133 return Self {
134 names,
135 nodes,
136 edges,
137 is_alias,
138 component,
139 };
140 }
141
142 fn on_a_cycle(&self, from: &str, to: &str) -> bool {
147 let (Some(from), Some(to)) = (self.nodes.get(from), self.nodes.get(to)) else {
148 return false;
149 };
150 let (Some(from), Some(to)) = (self.component.get(*from), self.component.get(*to)) else {
151 return false;
152 };
153 return from == to;
154 }
155
156 fn check_alias_cycles(&self) -> Result<()> {
161 let mut members: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
162 for (node, component) in self.component.iter().enumerate() {
163 members.entry(*component).or_default().push(node);
164 }
165 for group in members.values() {
166 if !self.is_cyclic(group) || !group.iter().all(|node| return self.is_alias(*node)) {
167 continue;
168 }
169 return Err(Error::RecursiveAlias {
170 cycle: self.cycle_through(group),
171 hint: "Give one of these schemas `type: object` with properties, so the generator emits a struct it \
172 can box, or break the chain of `$ref`s."
173 .to_owned(),
174 });
175 }
176 return Ok(());
177 }
178
179 fn is_cyclic(&self, group: &[usize]) -> bool {
184 if group.len() > 1 {
185 return true;
186 }
187 return group
188 .first()
189 .is_some_and(|node| return self.edges_of(*node).contains(node));
190 }
191
192 fn edges_of(&self, node: usize) -> &[usize] {
194 return self.edges.get(node).map_or(&[], Vec::as_slice);
195 }
196
197 fn is_alias(&self, node: usize) -> bool {
199 return self.is_alias.get(node).copied().unwrap_or(false);
200 }
201
202 fn name_of(&self, node: usize) -> String {
204 return self.names.get(node).cloned().unwrap_or_default();
205 }
206
207 fn cycle_through(&self, group: &[usize]) -> Vec<String> {
212 let Some(start) = group.first().copied() else {
213 return Vec::new();
214 };
215 let mut path = vec![self.name_of(start)];
216 let mut current = start;
217 for _ in 0..group.len() {
218 let Some(next) = self.edges_of(current).first().copied() else {
219 break;
220 };
221 path.push(self.name_of(next));
222 if next == start {
223 break;
224 }
225 current = next;
226 }
227 return path;
228 }
229}
230
231fn components(edges: &[Vec<usize>]) -> Vec<usize> {
236 let mut walk = Walk::over(edges.len());
237 let mut work: Vec<(usize, usize)> = Vec::new();
239
240 for root in 0..edges.len() {
241 if walk.is_open_or_done(root) {
242 continue;
243 }
244 walk.open(root);
245 work.push((root, 0));
246
247 while let Some(&(node, taken)) = work.last() {
248 let next = edges.get(node).and_then(|held| return held.get(taken)).copied();
249 if let Some(next) = next {
250 if let Some(entry) = work.last_mut() {
251 entry.1 += 1;
252 }
253 walk.step(node, next, &mut work);
254 continue;
255 }
256
257 work.pop();
258 if let Some(&(parent, _)) = work.last() {
259 walk.carry_up(parent, node);
260 }
261 walk.close(node);
262 }
263 }
264 return walk.component;
265}
266
267struct Walk {
274 order: Vec<usize>,
276 lowest: Vec<usize>,
278 open: Vec<bool>,
280 component: Vec<usize>,
282 path: Vec<usize>,
284 next_order: usize,
286 next_component: usize,
288}
289
290impl Walk {
291 const UNREACHED: usize = usize::MAX;
293
294 fn over(count: usize) -> Self {
296 return Self {
297 order: vec![Self::UNREACHED; count],
298 lowest: vec![0; count],
299 open: vec![false; count],
300 component: vec![0; count],
301 path: Vec::new(),
302 next_order: 0,
303 next_component: 0,
304 };
305 }
306
307 fn is_open_or_done(&self, node: usize) -> bool {
309 return self.order_of(node) != Self::UNREACHED;
310 }
311
312 fn order_of(&self, node: usize) -> usize {
314 return self.order.get(node).copied().unwrap_or(Self::UNREACHED);
315 }
316
317 fn lowest_of(&self, node: usize) -> usize {
319 return self.lowest.get(node).copied().unwrap_or(Self::UNREACHED);
320 }
321
322 fn open(&mut self, node: usize) {
324 if let Some(order) = self.order.get_mut(node) {
325 *order = self.next_order;
326 }
327 if let Some(lowest) = self.lowest.get_mut(node) {
328 *lowest = self.next_order;
329 }
330 if let Some(open) = self.open.get_mut(node) {
331 *open = true;
332 }
333 self.next_order += 1;
334 self.path.push(node);
335 }
336
337 fn step(&mut self, node: usize, next: usize, work: &mut Vec<(usize, usize)>) {
342 if !self.is_open_or_done(next) {
343 self.open(next);
344 work.push((next, 0));
345 return;
346 }
347 if self.open.get(next).copied().unwrap_or(false) {
348 self.lower(node, self.order_of(next));
349 }
350 }
351
352 fn carry_up(&mut self, parent: usize, node: usize) {
354 self.lower(parent, self.lowest_of(node));
355 }
356
357 fn lower(&mut self, node: usize, order: usize) {
359 if let Some(lowest) = self.lowest.get_mut(node) {
360 *lowest = (*lowest).min(order);
361 }
362 }
363
364 fn close(&mut self, node: usize) {
369 if self.lowest_of(node) != self.order_of(node) {
370 return;
371 }
372 while let Some(member) = self.path.pop() {
373 if let Some(open) = self.open.get_mut(member) {
374 *open = false;
375 }
376 if let Some(component) = self.component.get_mut(member) {
377 *component = self.next_component;
378 }
379 if member == node {
380 break;
381 }
382 }
383 self.next_component += 1;
384 }
385}
386
387fn collect_held(ty: &RustType, out: &mut BTreeSet<String>) {
394 match ty {
395 RustType::Named(name) => {
396 out.insert(canonical(name));
397 }
398 RustType::Option(inner) => collect_held(inner, out),
399 _ => {}
400 }
401}
402
403fn box_held(ty: &mut RustType, owner: &str, graph: &Graph) {
408 match ty {
409 RustType::Named(name) => {
410 let target = canonical(name);
411 if graph.on_a_cycle(&target, owner) {
412 let inner = std::mem::replace(ty, RustType::Bool);
413 *ty = RustType::Boxed(Box::new(inner));
414 }
415 }
416 RustType::Option(inner) => box_held(inner, owner, graph),
417 _ => {}
418 }
419}
420
421fn canonical(name: &str) -> String {
423 return to_ident(name, Case::Pascal).logical().to_owned();
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429 use crate::ir::Access;
430 use crate::ir::Alias;
431 use crate::ir::Enum;
432 use crate::ir::Field;
433 use crate::ir::Struct;
434 use crate::ir::UnionVariant;
435
436 fn one_field(name: &str, field: &str, ty: RustType) -> Item {
438 return Item::Struct(Struct {
439 name: to_ident(name, Case::Pascal),
440 doc: None,
441 deprecated: None,
442 fields: vec![Field {
443 name: to_ident(field, Case::Snake),
444 rename: None,
445 doc: None,
446 deprecated: None,
447 ty,
448 required: true,
449 omit_empty: None,
450 serde_skip: false,
451 default: None,
452 constraints: None,
453 access: Access::ReadWrite,
454 }],
455 additional_properties: None,
456 deny_unknown_fields: false,
457 });
458 }
459
460 fn field_type(module: &Module, name: &str) -> RustType {
462 for item in &module.items {
463 if let Item::Struct(strukt) = item
464 && strukt.name.logical() == name
465 {
466 return strukt.fields[0].ty.clone();
467 }
468 }
469 panic!("no struct named `{name}`");
470 }
471
472 fn named(name: &str) -> RustType {
473 return RustType::Named(name.to_owned());
474 }
475
476 fn boxed(inner: RustType) -> RustType {
477 return RustType::Boxed(Box::new(inner));
478 }
479
480 #[test]
483 fn only_a_field_that_holds_its_owner_is_boxed() {
484 let cases: &[(&str, RustType, RustType)] = &[
485 ("direct", named("Node"), boxed(named("Node"))),
486 (
487 "through an option",
488 RustType::Option(Box::new(named("Node"))),
489 RustType::Option(Box::new(boxed(named("Node")))),
490 ),
491 (
492 "through a vec",
493 RustType::Vec(Box::new(named("Node"))),
494 RustType::Vec(Box::new(named("Node"))),
495 ),
496 (
497 "through a map",
498 RustType::Map(Box::new(named("Node"))),
499 RustType::Map(Box::new(named("Node"))),
500 ),
501 ("a scalar", RustType::String, RustType::String),
502 ];
503
504 for (label, input, want) in cases {
505 let mut module = Module {
506 items: vec![one_field("Node", "child", input.clone())],
507 };
508 box_recursive_types(&mut module).expect("no alias cycle in this module");
509 assert_eq!(field_type(&module, "Node"), *want, "self-reference {label}");
510 }
511 }
512
513 #[test]
516 fn both_sides_of_a_mutual_cycle_are_boxed() {
517 let mut module = Module {
518 items: vec![
519 one_field("Parent", "child", named("Kid")),
520 one_field("Kid", "parent", named("Parent")),
521 ],
522 };
523 box_recursive_types(&mut module).expect("no alias cycle in this module");
524 assert_eq!(field_type(&module, "Parent"), boxed(named("Kid")));
525 assert_eq!(field_type(&module, "Kid"), boxed(named("Parent")));
526 }
527
528 #[test]
531 fn a_type_the_cycle_only_points_at_is_left_alone() {
532 let mut module = Module {
533 items: vec![
534 one_field("Node", "child", named("Node")),
535 one_field("Holder", "node", named("Node")),
536 ],
537 };
538 box_recursive_types(&mut module).expect("no alias cycle in this module");
539 assert_eq!(field_type(&module, "Holder"), named("Node"));
540 }
541
542 #[test]
544 fn a_union_variant_that_holds_its_own_enum_is_boxed() {
545 let mut module = Module {
546 items: vec![Item::Enum(Enum {
547 name: to_ident("Expression", Case::Pascal),
548 doc: None,
549 deprecated: None,
550 kind: EnumKind::Union(vec![
551 UnionVariant {
552 name: to_ident("Text", Case::Pascal),
553 ty: RustType::String,
554 },
555 UnionVariant {
556 name: to_ident("Nested", Case::Pascal),
557 ty: named("Expression"),
558 },
559 ]),
560 })],
561 };
562 box_recursive_types(&mut module).expect("no alias cycle in this module");
563 let Item::Enum(enumeration) = &module.items[0] else {
564 panic!("the item is an enum");
565 };
566 let EnumKind::Union(variants) = &enumeration.kind else {
567 panic!("the enum is a union");
568 };
569 assert_eq!(variants[0].ty, RustType::String);
570 assert_eq!(variants[1].ty, boxed(named("Expression")));
571 }
572
573 #[test]
576 fn a_cycle_through_an_alias_is_boxed_at_the_struct() {
577 let mut module = Module {
578 items: vec![
579 Item::Alias(Alias {
580 name: to_ident("Wrapper", Case::Pascal),
581 doc: None,
582 deprecated: None,
583 ty: named("Holder"),
584 }),
585 one_field("Holder", "wrapped", named("Wrapper")),
586 ],
587 };
588 box_recursive_types(&mut module).expect("this cycle holds a struct, so it is not alias-only");
589 assert_eq!(field_type(&module, "Holder"), boxed(named("Wrapper")));
590 }
591
592 #[test]
594 fn an_alias_only_cycle_is_rejected() {
595 let alias = |name: &str, target: &str| {
596 return Item::Alias(Alias {
597 name: to_ident(name, Case::Pascal),
598 doc: None,
599 deprecated: None,
600 ty: named(target),
601 });
602 };
603 let mut module = Module {
604 items: vec![alias("Loop", "Ring"), alias("Ring", "Loop")],
605 };
606 let outcome = box_recursive_types(&mut module);
607 assert!(
608 matches!(outcome, Err(Error::RecursiveAlias { .. })),
609 "a cycle of aliases must be rejected, and gave: {outcome:?}",
610 );
611 }
612
613 #[test]
615 fn a_self_referencing_alias_is_rejected() {
616 let mut module = Module {
617 items: vec![Item::Alias(Alias {
618 name: to_ident("Loop", Case::Pascal),
619 doc: None,
620 deprecated: None,
621 ty: named("Loop"),
622 })],
623 };
624 assert!(matches!(
625 box_recursive_types(&mut module),
626 Err(Error::RecursiveAlias { .. })
627 ));
628 }
629
630 #[test]
632 fn a_module_without_a_cycle_is_unchanged() {
633 let mut module = Module {
634 items: vec![
635 one_field("Holder", "node", named("Node")),
636 one_field("Node", "id", RustType::String),
637 ],
638 };
639 let before = module.clone();
640 box_recursive_types(&mut module).expect("no alias cycle in this module");
641 assert_eq!(module, before);
642 }
643
644 #[test]
651 fn an_existing_box_breaks_the_cycle_for_every_other_edge() {
652 let mut module = Module {
653 items: vec![
654 one_field("Parent", "child", boxed(named("Kid"))),
655 one_field("Kid", "parent", named("Parent")),
656 ],
657 };
658 box_recursive_types(&mut module).expect("no alias cycle in this module");
659 assert_eq!(field_type(&module, "Parent"), boxed(named("Kid")));
660 assert_eq!(field_type(&module, "Kid"), named("Parent"));
661 }
662}