@@ -9,7 +9,7 @@ use crate::target::{
99use ast:: { Ast , SchemaAst } ;
1010use jtd:: Schema ;
1111use namespace:: Namespace ;
12- use std:: collections:: BTreeMap ;
12+ use std:: collections:: { BTreeMap , BTreeSet } ;
1313use std:: fs:: File ;
1414use std:: io:: Write ;
1515use std:: path:: Path ;
@@ -36,6 +36,7 @@ struct CodeGenerator<'a, T> {
3636 out_dir : & ' a Path ,
3737 strategy : Strategy ,
3838 definition_names : BTreeMap < String , String > ,
39+ recursive_definitions : BTreeSet < String > ,
3940}
4041
4142struct FileData < T > {
@@ -50,6 +51,7 @@ impl<'a, T: Target> CodeGenerator<'a, T> {
5051 out_dir,
5152 strategy : target. strategy ( ) ,
5253 definition_names : BTreeMap :: new ( ) ,
54+ recursive_definitions : BTreeSet :: new ( ) ,
5355 }
5456 }
5557
@@ -68,6 +70,18 @@ impl<'a, T: Target> CodeGenerator<'a, T> {
6870 self . definition_names . insert ( name. clone ( ) , ast_name) ;
6971 }
7072
73+ // Detect recursive definitions, as some target language need to handle
74+ // them specifically (e.g. Rust).
75+ // Note that a type is *not* considered to be recursive it it contains
76+ // instances of itself only through "elements" or "values"
77+ // (the underlying container is considered to break the recursion).
78+ for ( name, ast) in & schema_ast. definitions {
79+ let mut visited = vec ! [ ] ;
80+ if find_recursion ( name, ast, & schema_ast. definitions , & mut visited) {
81+ self . recursive_definitions . insert ( name. clone ( ) ) ;
82+ }
83+ }
84+
7185 // If the target is using FilePerType partitioning, then this state
7286 // won't actually be used at all. If it's using SingleFile partitioning,
7387 // then this is the only file state that will be used.
@@ -120,8 +134,17 @@ impl<'a, T: Target> CodeGenerator<'a, T> {
120134 // Ref nodes are a special sort of "expr-like" node, where we
121135 // already know what the name of the expression is; it's the name of
122136 // the definition.
123- Ast :: Ref { definition, .. } => self . definition_names [ & definition] . clone ( ) ,
124-
137+ // Note however that recursive definition may need some special
138+ // treatment by the target.
139+ Ast :: Ref { metadata, definition } => {
140+ let sub_expr = self . definition_names [ & definition] . clone ( ) ;
141+ if self . recursive_definitions . iter ( ) . any ( |i| i == & definition) {
142+ self . target
143+ . expr ( & mut file_data. state , metadata, Expr :: RecursiveRef ( sub_expr) )
144+ } else {
145+ sub_expr
146+ }
147+ }
125148 // The remaining "expr-like" node types just build up strings and
126149 // possibly alter the per-file state (usually in order to add
127150 // "imports" to the file).
@@ -479,3 +502,33 @@ impl<'a, T: Target> CodeGenerator<'a, T> {
479502 Ok ( ( ) )
480503 }
481504}
505+
506+ fn find_recursion ( name : & str , ast : & Ast , definitions : & BTreeMap < String , Ast > , visited : & mut Vec < String > ) -> bool {
507+ match ast {
508+ Ast :: Ref { definition, .. } => {
509+ if definition == name {
510+ true
511+ } else if visited. iter ( ) . any ( |i| i == & name) {
512+ false
513+ } else if let Some ( ast2) = definitions. get ( definition) {
514+ visited. push ( definition. clone ( ) ) ;
515+ find_recursion ( name, & ast2, definitions, visited)
516+ } else {
517+ false
518+ }
519+ }
520+ Ast :: NullableOf { type_, .. } => {
521+ find_recursion ( name, type_, definitions, visited)
522+ }
523+ Ast :: Struct { fields, .. } => {
524+ fields. iter ( )
525+ . any ( |f| find_recursion ( name, & f. type_ , definitions, visited) )
526+ }
527+ Ast :: Discriminator { variants, .. } => {
528+ variants. iter ( )
529+ . flat_map ( |v| v. fields . iter ( ) )
530+ . any ( |f| find_recursion ( name, & f. type_ , definitions, visited) )
531+ }
532+ _ => false ,
533+ }
534+ }
0 commit comments