@@ -9,7 +9,7 @@ use crate::target::{
9
9
use ast:: { Ast , SchemaAst } ;
10
10
use jtd:: Schema ;
11
11
use namespace:: Namespace ;
12
- use std:: collections:: BTreeMap ;
12
+ use std:: collections:: { BTreeMap , BTreeSet } ;
13
13
use std:: fs:: File ;
14
14
use std:: io:: Write ;
15
15
use std:: path:: Path ;
@@ -36,6 +36,7 @@ struct CodeGenerator<'a, T> {
36
36
out_dir : & ' a Path ,
37
37
strategy : Strategy ,
38
38
definition_names : BTreeMap < String , String > ,
39
+ recursive_definitions : BTreeSet < String > ,
39
40
}
40
41
41
42
struct FileData < T > {
@@ -50,6 +51,7 @@ impl<'a, T: Target> CodeGenerator<'a, T> {
50
51
out_dir,
51
52
strategy : target. strategy ( ) ,
52
53
definition_names : BTreeMap :: new ( ) ,
54
+ recursive_definitions : BTreeSet :: new ( ) ,
53
55
}
54
56
}
55
57
@@ -68,6 +70,18 @@ impl<'a, T: Target> CodeGenerator<'a, T> {
68
70
self . definition_names . insert ( name. clone ( ) , ast_name) ;
69
71
}
70
72
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
+
71
85
// If the target is using FilePerType partitioning, then this state
72
86
// won't actually be used at all. If it's using SingleFile partitioning,
73
87
// then this is the only file state that will be used.
@@ -120,8 +134,17 @@ impl<'a, T: Target> CodeGenerator<'a, T> {
120
134
// Ref nodes are a special sort of "expr-like" node, where we
121
135
// already know what the name of the expression is; it's the name of
122
136
// 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
+ }
125
148
// The remaining "expr-like" node types just build up strings and
126
149
// possibly alter the per-file state (usually in order to add
127
150
// "imports" to the file).
@@ -479,3 +502,33 @@ impl<'a, T: Target> CodeGenerator<'a, T> {
479
502
Ok ( ( ) )
480
503
}
481
504
}
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