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::Alias;
430 use crate::ir::Enum;
431 use crate::ir::Field;
432 use crate::ir::Struct;
433 use crate::ir::UnionVariant;
434
435 fn one_field(name: &str, field: &str, ty: RustType) -> Item {
437 return Item::Struct(Struct {
438 name: to_ident(name, Case::Pascal),
439 doc: None,
440 deprecated: None,
441 fields: vec![Field {
442 name: to_ident(field, Case::Snake),
443 rename: None,
444 doc: None,
445 deprecated: None,
446 ty,
447 required: true,
448 omit_empty: None,
449 serde_skip: false,
450 default: None,
451 constraints: None,
452 }],
453 additional_properties: None,
454 deny_unknown_fields: false,
455 });
456 }
457
458 fn field_type(module: &Module, name: &str) -> RustType {
460 for item in &module.items {
461 if let Item::Struct(strukt) = item
462 && strukt.name.logical() == name
463 {
464 return strukt.fields[0].ty.clone();
465 }
466 }
467 panic!("no struct named `{name}`");
468 }
469
470 fn named(name: &str) -> RustType {
471 return RustType::Named(name.to_owned());
472 }
473
474 fn boxed(inner: RustType) -> RustType {
475 return RustType::Boxed(Box::new(inner));
476 }
477
478 #[test]
481 fn only_a_field_that_holds_its_owner_is_boxed() {
482 let cases: &[(&str, RustType, RustType)] = &[
483 ("direct", named("Node"), boxed(named("Node"))),
484 (
485 "through an option",
486 RustType::Option(Box::new(named("Node"))),
487 RustType::Option(Box::new(boxed(named("Node")))),
488 ),
489 (
490 "through a vec",
491 RustType::Vec(Box::new(named("Node"))),
492 RustType::Vec(Box::new(named("Node"))),
493 ),
494 (
495 "through a map",
496 RustType::Map(Box::new(named("Node"))),
497 RustType::Map(Box::new(named("Node"))),
498 ),
499 ("a scalar", RustType::String, RustType::String),
500 ];
501
502 for (label, input, want) in cases {
503 let mut module = Module {
504 items: vec![one_field("Node", "child", input.clone())],
505 };
506 box_recursive_types(&mut module).expect("no alias cycle in this module");
507 assert_eq!(field_type(&module, "Node"), *want, "self-reference {label}");
508 }
509 }
510
511 #[test]
514 fn both_sides_of_a_mutual_cycle_are_boxed() {
515 let mut module = Module {
516 items: vec![
517 one_field("Parent", "child", named("Kid")),
518 one_field("Kid", "parent", named("Parent")),
519 ],
520 };
521 box_recursive_types(&mut module).expect("no alias cycle in this module");
522 assert_eq!(field_type(&module, "Parent"), boxed(named("Kid")));
523 assert_eq!(field_type(&module, "Kid"), boxed(named("Parent")));
524 }
525
526 #[test]
529 fn a_type_the_cycle_only_points_at_is_left_alone() {
530 let mut module = Module {
531 items: vec![
532 one_field("Node", "child", named("Node")),
533 one_field("Holder", "node", named("Node")),
534 ],
535 };
536 box_recursive_types(&mut module).expect("no alias cycle in this module");
537 assert_eq!(field_type(&module, "Holder"), named("Node"));
538 }
539
540 #[test]
542 fn a_union_variant_that_holds_its_own_enum_is_boxed() {
543 let mut module = Module {
544 items: vec![Item::Enum(Enum {
545 name: to_ident("Expression", Case::Pascal),
546 doc: None,
547 deprecated: None,
548 kind: EnumKind::Union(vec![
549 UnionVariant {
550 name: to_ident("Text", Case::Pascal),
551 ty: RustType::String,
552 },
553 UnionVariant {
554 name: to_ident("Nested", Case::Pascal),
555 ty: named("Expression"),
556 },
557 ]),
558 })],
559 };
560 box_recursive_types(&mut module).expect("no alias cycle in this module");
561 let Item::Enum(enumeration) = &module.items[0] else {
562 panic!("the item is an enum");
563 };
564 let EnumKind::Union(variants) = &enumeration.kind else {
565 panic!("the enum is a union");
566 };
567 assert_eq!(variants[0].ty, RustType::String);
568 assert_eq!(variants[1].ty, boxed(named("Expression")));
569 }
570
571 #[test]
574 fn a_cycle_through_an_alias_is_boxed_at_the_struct() {
575 let mut module = Module {
576 items: vec![
577 Item::Alias(Alias {
578 name: to_ident("Wrapper", Case::Pascal),
579 doc: None,
580 deprecated: None,
581 ty: named("Holder"),
582 }),
583 one_field("Holder", "wrapped", named("Wrapper")),
584 ],
585 };
586 box_recursive_types(&mut module).expect("this cycle holds a struct, so it is not alias-only");
587 assert_eq!(field_type(&module, "Holder"), boxed(named("Wrapper")));
588 }
589
590 #[test]
592 fn an_alias_only_cycle_is_rejected() {
593 let alias = |name: &str, target: &str| {
594 return Item::Alias(Alias {
595 name: to_ident(name, Case::Pascal),
596 doc: None,
597 deprecated: None,
598 ty: named(target),
599 });
600 };
601 let mut module = Module {
602 items: vec![alias("Loop", "Ring"), alias("Ring", "Loop")],
603 };
604 let outcome = box_recursive_types(&mut module);
605 assert!(
606 matches!(outcome, Err(Error::RecursiveAlias { .. })),
607 "a cycle of aliases must be rejected, and gave: {outcome:?}",
608 );
609 }
610
611 #[test]
613 fn a_self_referencing_alias_is_rejected() {
614 let mut module = Module {
615 items: vec![Item::Alias(Alias {
616 name: to_ident("Loop", Case::Pascal),
617 doc: None,
618 deprecated: None,
619 ty: named("Loop"),
620 })],
621 };
622 assert!(matches!(
623 box_recursive_types(&mut module),
624 Err(Error::RecursiveAlias { .. })
625 ));
626 }
627
628 #[test]
630 fn a_module_without_a_cycle_is_unchanged() {
631 let mut module = Module {
632 items: vec![
633 one_field("Holder", "node", named("Node")),
634 one_field("Node", "id", RustType::String),
635 ],
636 };
637 let before = module.clone();
638 box_recursive_types(&mut module).expect("no alias cycle in this module");
639 assert_eq!(module, before);
640 }
641
642 #[test]
649 fn an_existing_box_breaks_the_cycle_for_every_other_edge() {
650 let mut module = Module {
651 items: vec![
652 one_field("Parent", "child", boxed(named("Kid"))),
653 one_field("Kid", "parent", named("Parent")),
654 ],
655 };
656 box_recursive_types(&mut module).expect("no alias cycle in this module");
657 assert_eq!(field_type(&module, "Parent"), boxed(named("Kid")));
658 assert_eq!(field_type(&module, "Kid"), named("Parent"));
659 }
660}