Skip to main content

oapi_codegen/lower/
recurse.rs

1//! Giving a recursive type a size by boxing the field that closes the cycle.
2//!
3//! A schema may refer to itself, directly or through other schemas. Lowered
4//! as written, `Node { child: Node }` is a type that holds itself, and rustc
5//! rejects it with `E0072`. The fix rustc itself suggests is a `Box`, so this
6//! pass inserts one.
7//!
8//! # What counts as holding a type
9//!
10//! A field holds its type when the size of that type counts towards the size of
11//! the struct. `Vec<T>`, `HashMap<String, T>` and `Box<T>` keep what they hold
12//! on the heap, so they hold nothing and already break a cycle. `Option<T>`
13//! stores its `T` inline, so `Option<Node>` inside `Node` is just as infinite as
14//! `Node`. That last point is easy to get wrong: making a recursive property
15//! optional does not fix anything.
16//!
17//! # Which edge gets the box
18//!
19//! Every edge on a cycle, and not one chosen edge. Boxing a single edge is
20//! enough for rustc, but the choice would fall out of item order, so `A` and
21//! `B` that refer to each other would get one box on whichever the walk met
22//! first. Boxing both states the same fact about both types.
23//!
24//! An alias holds its target and offers nothing to box, so a cycle made only of
25//! aliases is [`Error::RecursiveAlias`] instead.
26
27use 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
39/// Box every field and variant of `module` that closes a type cycle.
40///
41/// Reject alias-only cycles and union cycles that consume no input.
42pub fn box_recursive_types(module: &mut Module) -> Result<()> {
43    let graph = Graph::of(module);
44    graph.check_alias_cycles()?;
45    graph.check_union_cycles(module)?;
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) | EnumKind::AnyOf(variants) = &mut enumeration.kind {
57                    for variant in variants {
58                        box_held(&mut variant.ty, &owner, &graph);
59                    }
60                }
61            }
62            // An alias offers nothing to box. `check_alias_cycles` has already
63            // rejected the only cycle that could reach one.
64            Item::Alias(_) => {}
65        }
66    }
67    return Ok(());
68}
69
70/// Which items hold which, plus the strongly connected components of that
71/// relation.
72///
73/// An edge `a -> b` means an item `a` holds an item `b` in a position that
74/// counts towards its size. Two items sit in one component exactly when each
75/// holds the other, so an edge needs a box exactly when its two ends share a
76/// component. Reading the components one time keeps the pass linear in the
77/// number of edges; asking "does `b` reach `a`" per edge walks the whole graph
78/// per edge instead.
79struct Graph {
80    /// Item names, in module order, indexed by node.
81    names: Vec<String>,
82    /// Node per item name.
83    nodes: BTreeMap<String, usize>,
84    /// Held items per node.
85    edges: Vec<Vec<usize>>,
86    /// Whether each node is an alias, which offers nothing to box.
87    is_alias: Vec<bool>,
88    /// Component per node.
89    component: Vec<usize>,
90}
91
92impl Graph {
93    /// Build the holding graph of `module` and its components.
94    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) | EnumKind::AnyOf(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            // A name the module does not declare cannot close a cycle inside it,
123            // so it gets no node and no edge.
124            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    /// Whether an edge between `from` and `to` closes a cycle.
143    ///
144    /// The caller only asks about a pair it holds an edge for, so one shared
145    /// component is the same statement as "each reaches the other".
146    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    /// Reject a cycle whose every member is an alias.
157    ///
158    /// Such a cycle has no field and no variant to box, and `type A = Box<B>`
159    /// with `type B = Box<A>` still expands forever.
160    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 check_union_cycles(&self, module: &Module) -> Result<()> {
180        // Object properties consume input; transparent union and alias edges do not.
181        let edges: Vec<Vec<usize>> = self
182            .edges
183            .iter()
184            .zip(&module.items)
185            .map(|(targets, item)| {
186                return if matches!(item, Item::Struct(_)) {
187                    Vec::new()
188                } else {
189                    targets.clone()
190                };
191            })
192            .collect();
193        let component = components(&edges);
194        for (node, targets) in edges.iter().enumerate() {
195            if !self.is_alias(node)
196                && targets
197                    .iter()
198                    .any(|target| return component.get(node) == component.get(*target))
199            {
200                return Err(Error::UnsupportedSchema {
201                    path: self.name_of(node),
202                    reason: "union recursion must pass through an object property or array element to consume input"
203                        .to_owned(),
204                });
205            }
206        }
207        return Ok(());
208    }
209
210    /// Whether the items of one component refer to each other in a cycle.
211    ///
212    /// Every node is its own component, so a lone node is only cyclic when it
213    /// holds itself.
214    fn is_cyclic(&self, group: &[usize]) -> bool {
215        if group.len() > 1 {
216            return true;
217        }
218        return group
219            .first()
220            .is_some_and(|node| return self.edges_of(*node).contains(node));
221    }
222
223    /// The items `node` holds. An unknown node holds nothing.
224    fn edges_of(&self, node: usize) -> &[usize] {
225        return self.edges.get(node).map_or(&[], Vec::as_slice);
226    }
227
228    /// Whether `node` is an alias. An unknown node is not.
229    fn is_alias(&self, node: usize) -> bool {
230        return self.is_alias.get(node).copied().unwrap_or(false);
231    }
232
233    /// The name of `node`, empty for an unknown one.
234    fn name_of(&self, node: usize) -> String {
235        return self.names.get(node).cloned().unwrap_or_default();
236    }
237
238    /// Name the members of `group` in the order they refer to each other,
239    /// closing on the name the walk started from.
240    ///
241    /// An alias holds at most one item, so the walk never branches.
242    fn cycle_through(&self, group: &[usize]) -> Vec<String> {
243        let Some(start) = group.first().copied() else {
244            return Vec::new();
245        };
246        let mut path = vec![self.name_of(start)];
247        let mut current = start;
248        for _ in 0..group.len() {
249            let Some(next) = self.edges_of(current).first().copied() else {
250                break;
251            };
252            path.push(self.name_of(next));
253            if next == start {
254                break;
255            }
256            current = next;
257        }
258        return path;
259    }
260}
261
262/// The strongly connected component of each node, by Tarjan's algorithm.
263///
264/// The walk carries its own stack, so a long chain of items cannot overflow the
265/// real one. It reads each node and each edge one time.
266fn components(edges: &[Vec<usize>]) -> Vec<usize> {
267    let mut walk = Walk::over(edges.len());
268    // Each entry is a node and how many of its edges the walk has taken.
269    let mut work: Vec<(usize, usize)> = Vec::new();
270
271    for root in 0..edges.len() {
272        if walk.is_open_or_done(root) {
273            continue;
274        }
275        walk.open(root);
276        work.push((root, 0));
277
278        while let Some(&(node, taken)) = work.last() {
279            let next = edges.get(node).and_then(|held| return held.get(taken)).copied();
280            if let Some(next) = next {
281                if let Some(entry) = work.last_mut() {
282                    entry.1 += 1;
283                }
284                walk.step(node, next, &mut work);
285                continue;
286            }
287
288            work.pop();
289            if let Some(&(parent, _)) = work.last() {
290                walk.carry_up(parent, node);
291            }
292            walk.close(node);
293        }
294    }
295    return walk.component;
296}
297
298/// The per-node bookkeeping of [`components`].
299///
300/// Every method reads a node through `get`, because the workspace denies
301/// `indexing_slicing`. A node outside the graph cannot arise: each one comes
302/// from `0..edges.len()` or from an edge, and an edge is built from a node that
303/// the module declares.
304struct Walk {
305    /// The order the walk reached each node in, or [`Walk::UNREACHED`].
306    order: Vec<usize>,
307    /// The oldest order each node can reach while the walk holds it open.
308    lowest: Vec<usize>,
309    /// Whether each node is on [`Walk::path`].
310    open: Vec<bool>,
311    /// The component of each node, filled in as each component closes.
312    component: Vec<usize>,
313    /// The nodes the walk holds open, oldest first.
314    path: Vec<usize>,
315    /// The order to give the next node the walk reaches.
316    next_order: usize,
317    /// The number to give the next component that closes.
318    next_component: usize,
319}
320
321impl Walk {
322    /// The order of a node the walk has not reached.
323    const UNREACHED: usize = usize::MAX;
324
325    /// Bookkeeping for a graph of `count` nodes.
326    fn over(count: usize) -> Self {
327        return Self {
328            order: vec![Self::UNREACHED; count],
329            lowest: vec![0; count],
330            open: vec![false; count],
331            component: vec![0; count],
332            path: Vec::new(),
333            next_order: 0,
334            next_component: 0,
335        };
336    }
337
338    /// Whether the walk has already reached `node`.
339    fn is_open_or_done(&self, node: usize) -> bool {
340        return self.order_of(node) != Self::UNREACHED;
341    }
342
343    /// The order the walk reached `node` in.
344    fn order_of(&self, node: usize) -> usize {
345        return self.order.get(node).copied().unwrap_or(Self::UNREACHED);
346    }
347
348    /// The oldest order `node` can reach.
349    fn lowest_of(&self, node: usize) -> usize {
350        return self.lowest.get(node).copied().unwrap_or(Self::UNREACHED);
351    }
352
353    /// Give `node` its order and hold it open.
354    fn open(&mut self, node: usize) {
355        if let Some(order) = self.order.get_mut(node) {
356            *order = self.next_order;
357        }
358        if let Some(lowest) = self.lowest.get_mut(node) {
359            *lowest = self.next_order;
360        }
361        if let Some(open) = self.open.get_mut(node) {
362            *open = true;
363        }
364        self.next_order += 1;
365        self.path.push(node);
366    }
367
368    /// Take the edge from `node` to `next`.
369    ///
370    /// An unreached `next` is opened and queued on `work`. A `next` the walk
371    /// still holds open closes a loop, so `node` inherits its order.
372    fn step(&mut self, node: usize, next: usize, work: &mut Vec<(usize, usize)>) {
373        if !self.is_open_or_done(next) {
374            self.open(next);
375            work.push((next, 0));
376            return;
377        }
378        if self.open.get(next).copied().unwrap_or(false) {
379            self.lower(node, self.order_of(next));
380        }
381    }
382
383    /// Carry what `node` reached up to the `parent` the walk came from.
384    fn carry_up(&mut self, parent: usize, node: usize) {
385        self.lower(parent, self.lowest_of(node));
386    }
387
388    /// Lower the oldest order `node` can reach to `order`, when that is older.
389    fn lower(&mut self, node: usize, order: usize) {
390        if let Some(lowest) = self.lowest.get_mut(node) {
391            *lowest = (*lowest).min(order);
392        }
393    }
394
395    /// Close the component `node` roots, when it roots one.
396    ///
397    /// A node that reaches nothing older than itself roots a component holding
398    /// every node opened since.
399    fn close(&mut self, node: usize) {
400        if self.lowest_of(node) != self.order_of(node) {
401            return;
402        }
403        while let Some(member) = self.path.pop() {
404            if let Some(open) = self.open.get_mut(member) {
405                *open = false;
406            }
407            if let Some(component) = self.component.get_mut(member) {
408                *component = self.next_component;
409            }
410            if member == node {
411                break;
412            }
413        }
414        self.next_component += 1;
415    }
416}
417
418/// Add every named type that `ty` holds to `out`.
419///
420/// `Vec`, `Map` and `Box` put what they hold on the heap, so the walk stops at
421/// each of them. `Box` counts here as well as the other two, so a run over a
422/// module this pass has already boxed sees the cycle as broken and changes
423/// nothing.
424fn collect_held(ty: &RustType, out: &mut BTreeSet<String>) {
425    match ty {
426        RustType::Named(name) => {
427            out.insert(canonical(name));
428        }
429        RustType::Option(inner) | RustType::Nullable(inner) => collect_held(inner, out),
430        _ => {}
431    }
432}
433
434/// Box the named types inside `ty` that hold `owner` back.
435///
436/// A type that holds itself is one node and one component, so it needs no case
437/// of its own.
438fn box_held(ty: &mut RustType, owner: &str, graph: &Graph) {
439    match ty {
440        RustType::Named(name) => {
441            let target = canonical(name);
442            if graph.on_a_cycle(&target, owner) {
443                let inner = std::mem::replace(ty, RustType::Bool);
444                *ty = RustType::Boxed(Box::new(inner));
445            }
446        }
447        RustType::Option(inner) | RustType::Nullable(inner) => box_held(inner, owner, graph),
448        _ => {}
449    }
450}
451
452/// The name an item is known by in the graph, matching how the emitter names it.
453fn canonical(name: &str) -> String {
454    return to_ident(name, Case::Pascal).logical().to_owned();
455}
456
457#[cfg(test)]
458mod tests {
459    use super::*;
460    use crate::ir::Access;
461    use crate::ir::Alias;
462    use crate::ir::Enum;
463    use crate::ir::Field;
464    use crate::ir::Struct;
465    use crate::ir::UnionVariant;
466
467    /// A struct of one field, which is the shape every case below needs.
468    fn one_field(name: &str, field: &str, ty: RustType) -> Item {
469        return Item::Struct(Struct {
470            name: to_ident(name, Case::Pascal),
471            doc: None,
472            deprecated: None,
473            fields: vec![Field {
474                name: to_ident(field, Case::Snake),
475                rename: None,
476                doc: None,
477                deprecated: None,
478                ty,
479                required: true,
480                omit_empty: None,
481                serde_skip: false,
482                default: None,
483                constraints: None,
484                access: Access::ReadWrite,
485            }],
486            additional_properties: None,
487            deny_unknown_fields: false,
488        });
489    }
490
491    /// The type of the first field of the first item named `name`.
492    fn field_type(module: &Module, name: &str) -> RustType {
493        for item in &module.items {
494            if let Item::Struct(strukt) = item
495                && strukt.name.logical() == name
496            {
497                return strukt.fields[0].ty.clone();
498            }
499        }
500        panic!("no struct named `{name}`");
501    }
502
503    fn named(name: &str) -> RustType {
504        return RustType::Named(name.to_owned());
505    }
506
507    fn boxed(inner: RustType) -> RustType {
508        return RustType::Boxed(Box::new(inner));
509    }
510
511    /// A field is boxed when, and only when, its type holds the owner back
512    /// without going through the heap.
513    #[test]
514    fn only_a_field_that_holds_its_owner_is_boxed() {
515        let cases: &[(&str, RustType, RustType)] = &[
516            ("direct", named("Node"), boxed(named("Node"))),
517            (
518                "through an option",
519                RustType::Option(Box::new(named("Node"))),
520                RustType::Option(Box::new(boxed(named("Node")))),
521            ),
522            (
523                "through a vec",
524                RustType::Vec(Box::new(named("Node"))),
525                RustType::Vec(Box::new(named("Node"))),
526            ),
527            (
528                "through a map",
529                RustType::Map(Box::new(named("Node"))),
530                RustType::Map(Box::new(named("Node"))),
531            ),
532            ("a scalar", RustType::String, RustType::String),
533        ];
534
535        for (label, input, want) in cases {
536            let mut module = Module {
537                items: vec![one_field("Node", "child", input.clone())],
538            };
539            box_recursive_types(&mut module).expect("no alias cycle in this module");
540            assert_eq!(field_type(&module, "Node"), *want, "self-reference {label}");
541        }
542    }
543
544    /// Both sides of a two-type cycle are boxed, so neither depends on the order
545    /// the items happen to sit in.
546    #[test]
547    fn both_sides_of_a_mutual_cycle_are_boxed() {
548        let mut module = Module {
549            items: vec![
550                one_field("Parent", "child", named("Kid")),
551                one_field("Kid", "parent", named("Parent")),
552            ],
553        };
554        box_recursive_types(&mut module).expect("no alias cycle in this module");
555        assert_eq!(field_type(&module, "Parent"), boxed(named("Kid")));
556        assert_eq!(field_type(&module, "Kid"), boxed(named("Parent")));
557    }
558
559    /// A type that a cycle merely points at is not part of the cycle, so it
560    /// keeps its plain field.
561    #[test]
562    fn a_type_the_cycle_only_points_at_is_left_alone() {
563        let mut module = Module {
564            items: vec![
565                one_field("Node", "child", named("Node")),
566                one_field("Holder", "node", named("Node")),
567            ],
568        };
569        box_recursive_types(&mut module).expect("no alias cycle in this module");
570        assert_eq!(field_type(&module, "Holder"), named("Node"));
571    }
572
573    #[test]
574    fn a_union_cycle_through_an_object_is_boxed() {
575        let mut module = Module {
576            items: vec![
577                Item::Enum(Enum {
578                    name: to_ident("Expression", Case::Pascal),
579                    doc: None,
580                    deprecated: None,
581                    kind: EnumKind::Union(vec![
582                        UnionVariant {
583                            name: to_ident("Text", Case::Pascal),
584                            ty: RustType::String,
585                        },
586                        UnionVariant {
587                            name: to_ident("Nested", Case::Pascal),
588                            ty: named("Nested"),
589                        },
590                    ]),
591                }),
592                one_field("Nested", "expression", named("Expression")),
593            ],
594        };
595        box_recursive_types(&mut module).expect("no alias cycle in this module");
596        let Item::Enum(enumeration) = &module.items[0] else {
597            panic!("the item is an enum");
598        };
599        let EnumKind::Union(variants) = &enumeration.kind else {
600            panic!("the enum is a union");
601        };
602        assert_eq!(variants[0].ty, RustType::String);
603        assert_eq!(variants[1].ty, boxed(named("Nested")));
604        assert_eq!(field_type(&module, "Nested"), boxed(named("Expression")));
605    }
606
607    #[test]
608    fn non_consuming_union_cycles_are_rejected() {
609        for inclusive in [false, true] {
610            for through_alias in [false, true] {
611                let variants = vec![
612                    UnionVariant {
613                        name: to_ident("Text", Case::Pascal),
614                        ty: RustType::String,
615                    },
616                    UnionVariant {
617                        name: to_ident("Loop", Case::Pascal),
618                        ty: named(if through_alias { "Alias" } else { "Loop" }),
619                    },
620                ];
621                let mut module = Module {
622                    items: vec![Item::Enum(Enum {
623                        name: to_ident("Loop", Case::Pascal),
624                        doc: None,
625                        deprecated: None,
626                        kind: if inclusive {
627                            EnumKind::AnyOf(variants)
628                        } else {
629                            EnumKind::Union(variants)
630                        },
631                    })],
632                };
633                if through_alias {
634                    module.items.push(Item::Alias(Alias {
635                        name: to_ident("Alias", Case::Pascal),
636                        doc: None,
637                        deprecated: None,
638                        ty: named("Loop"),
639                    }));
640                }
641                assert!(matches!(
642                    box_recursive_types(&mut module),
643                    Err(Error::UnsupportedSchema { .. })
644                ));
645            }
646        }
647    }
648
649    /// An alias offers nothing to box, so a cycle running through one is broken
650    /// at the struct field instead.
651    #[test]
652    fn a_cycle_through_an_alias_is_boxed_at_the_struct() {
653        let mut module = Module {
654            items: vec![
655                Item::Alias(Alias {
656                    name: to_ident("Wrapper", Case::Pascal),
657                    doc: None,
658                    deprecated: None,
659                    ty: named("Holder"),
660                }),
661                one_field("Holder", "wrapped", named("Wrapper")),
662            ],
663        };
664        box_recursive_types(&mut module).expect("this cycle holds a struct, so it is not alias-only");
665        assert_eq!(field_type(&module, "Holder"), boxed(named("Wrapper")));
666    }
667
668    /// A cycle made only of aliases has nowhere to put a box, so it is an error.
669    #[test]
670    fn an_alias_only_cycle_is_rejected() {
671        let alias = |name: &str, target: &str| {
672            return Item::Alias(Alias {
673                name: to_ident(name, Case::Pascal),
674                doc: None,
675                deprecated: None,
676                ty: named(target),
677            });
678        };
679        let mut module = Module {
680            items: vec![alias("Loop", "Ring"), alias("Ring", "Loop")],
681        };
682        let outcome = box_recursive_types(&mut module);
683        assert!(
684            matches!(outcome, Err(Error::RecursiveAlias { .. })),
685            "a cycle of aliases must be rejected, and gave: {outcome:?}",
686        );
687    }
688
689    /// An alias that refers to itself is the same problem with one member.
690    #[test]
691    fn a_self_referencing_alias_is_rejected() {
692        let mut module = Module {
693            items: vec![Item::Alias(Alias {
694                name: to_ident("Loop", Case::Pascal),
695                doc: None,
696                deprecated: None,
697                ty: named("Loop"),
698            })],
699        };
700        assert!(matches!(
701            box_recursive_types(&mut module),
702            Err(Error::RecursiveAlias { .. })
703        ));
704    }
705
706    /// A module with no cycle keeps every type exactly as it was.
707    #[test]
708    fn a_module_without_a_cycle_is_unchanged() {
709        let mut module = Module {
710            items: vec![
711                one_field("Holder", "node", named("Node")),
712                one_field("Node", "id", RustType::String),
713            ],
714        };
715        let before = module.clone();
716        box_recursive_types(&mut module).expect("no alias cycle in this module");
717        assert_eq!(module, before);
718    }
719
720    /// An edge that is already boxed breaks the cycle, so nothing else on it is
721    /// boxed.
722    ///
723    /// A `Box` is heap indirection, so a boxed field holds nothing. A walk that
724    /// stepped through a `Box` would still see the old cycle and would box a
725    /// second edge that needs no box.
726    #[test]
727    fn an_existing_box_breaks_the_cycle_for_every_other_edge() {
728        let mut module = Module {
729            items: vec![
730                one_field("Parent", "child", boxed(named("Kid"))),
731                one_field("Kid", "parent", named("Parent")),
732            ],
733        };
734        box_recursive_types(&mut module).expect("no alias cycle in this module");
735        assert_eq!(field_type(&module, "Parent"), boxed(named("Kid")));
736        assert_eq!(field_type(&module, "Kid"), named("Parent"));
737    }
738}