@@ -255,9 +255,58 @@ export class CPlusPlusRenderer extends ConvenienceRenderer {
255255 return ! this . isCycleBreakerType ( t ) ;
256256 }
257257
258+ private typeContainsUnion ( t : Type , visited = new Set < Type > ( ) ) : boolean {
259+ if ( t instanceof UnionType ) return true ;
260+ if ( visited . has ( t ) ) return false ;
261+
262+ visited . add ( t ) ;
263+ return iterableSome ( t . getChildren ( ) , ( child ) =>
264+ this . typeContainsUnion ( child , visited ) ,
265+ ) ;
266+ }
267+
268+ private typeReaches (
269+ t : Type ,
270+ target : Type ,
271+ visited = new Set < Type > ( ) ,
272+ ) : boolean {
273+ if ( t === target ) return true ;
274+ if ( visited . has ( t ) ) return false ;
275+
276+ visited . add ( t ) ;
277+ return iterableSome ( t . getChildren ( ) , ( child ) =>
278+ this . typeReaches ( child , target , visited ) ,
279+ ) ;
280+ }
281+
282+ private isRecursiveUnion ( u : UnionType ) : boolean {
283+ return iterableSome ( u . getChildren ( ) , ( child ) =>
284+ this . typeReaches ( child , u ) ,
285+ ) ;
286+ }
287+
288+ private recursiveUnionUsesStackOptional ( u : UnionType ) : boolean {
289+ const [ maybeNull ] = removeNullFromUnion ( u , true ) ;
290+ return (
291+ this . isRecursiveUnion ( u ) &&
292+ maybeNull !== null &&
293+ this . isOptionalAsValuePossible ( u )
294+ ) ;
295+ }
296+
258297 public isImplicitCycleBreaker ( t : Type ) : boolean {
298+ // Containers break completeness cycles for classes, but not for type
299+ // aliases: an alias name is not in scope on its own right-hand side.
259300 const kind = t . kind ;
260- return kind === "array" || kind === "map" ;
301+ if ( kind !== "array" && kind !== "map" ) return false ;
302+
303+ return ! iterableSome ( t . getChildren ( ) , ( child ) =>
304+ this . typeContainsUnion ( child ) ,
305+ ) ;
306+ }
307+
308+ protected canBreakCycles ( t : Type ) : boolean {
309+ return t instanceof ClassType || t instanceof UnionType ;
261310 }
262311
263312 // Is likely to return std::optional or boost::optional
@@ -534,7 +583,13 @@ export class CPlusPlusRenderer extends ConvenienceRenderer {
534583 this . emitLine ( "#pragma once" ) ;
535584 this . ensureBlankLine ( ) ;
536585
537- if ( this . haveOptionalProperties ) {
586+ if (
587+ this . haveOptionalProperties ||
588+ ( ! this . _options . codeFormat &&
589+ iterableSome ( this . namedUnions , ( u ) =>
590+ this . recursiveUnionUsesStackOptional ( u ) ,
591+ ) )
592+ ) {
538593 if ( this . _options . boost ) {
539594 this . emitInclude ( true , "boost/optional.hpp" ) ;
540595 } else {
@@ -1465,9 +1520,61 @@ export class CPlusPlusRenderer extends ConvenienceRenderer {
14651520 set . add ( propType ) ;
14661521 return [ true , set ] ;
14671522 } ) ( ) ;
1523+ const isRecursiveNamedUnion =
1524+ propType instanceof UnionType &&
1525+ this . namedUnions . has ( propType ) &&
1526+ this . isRecursiveUnion ( propType ) ;
1527+ if (
1528+ isRecursiveNamedUnion &&
1529+ p . isOptional &&
1530+ removeNullFromUnion ( propType , true ) [ 0 ] !== null
1531+ ) {
1532+ cppType = this . cppType (
1533+ propType ,
1534+ {
1535+ needsForwardIndirection : true ,
1536+ needsOptionalIndirection : true ,
1537+ inJsonNamespace : false ,
1538+ } ,
1539+ false ,
1540+ true ,
1541+ true ,
1542+ ) ;
1543+ const propertyName =
1544+ this . _stringType . wrapEncodingChange (
1545+ [ ourQualifier ] ,
1546+ this . _stringType . getType ( ) ,
1547+ this . NarrowString . getType ( ) ,
1548+ this . _stringType . createStringLiteral ( [
1549+ stringEscape ( json ) ,
1550+ ] ) ,
1551+ ) ;
1552+ this . emitLine (
1553+ assignment . wrap (
1554+ [ ] ,
1555+ [
1556+ "j.find(" ,
1557+ propertyName ,
1558+ ") != j.end() ? j.at(" ,
1559+ propertyName ,
1560+ ").get<" ,
1561+ cppType ,
1562+ ">() : " ,
1563+ cppType ,
1564+ "()" ,
1565+ ] ,
1566+ ) ,
1567+ ";" ,
1568+ ) ;
1569+ return ;
1570+ }
1571+
14681572 if ( nullOrOptional ) {
1573+ const optionalTypes = isRecursiveNamedUnion
1574+ ? new Set < Type > ( [ propType ] )
1575+ : typeSet ;
14691576 cppType = this . cppTypeInOptional (
1470- typeSet ,
1577+ optionalTypes ,
14711578 {
14721579 needsForwardIndirection : false ,
14731580 needsOptionalIndirection : false ,
@@ -1477,7 +1584,7 @@ export class CPlusPlusRenderer extends ConvenienceRenderer {
14771584 true ,
14781585 ) ;
14791586 toType = this . cppTypeInOptional (
1480- typeSet ,
1587+ optionalTypes ,
14811588 {
14821589 needsForwardIndirection : false ,
14831590 needsOptionalIndirection : false ,
@@ -1697,12 +1804,66 @@ export class CPlusPlusRenderer extends ConvenienceRenderer {
16971804 }
16981805
16991806 protected emitUnionTypedefs ( u : UnionType , unionName : Name ) : void {
1807+ const variantType = this . variantType ( u , false ) ;
1808+ if ( ! this . isRecursiveUnion ( u ) ) {
1809+ this . emitLine ( "using " , unionName , " = " , variantType , ";" ) ;
1810+ return ;
1811+ }
1812+
1813+ // A struct introduces its name before the base type is parsed, allowing
1814+ // recursion through containers such as std::vector.
1815+ this . emitBlock ( [ "struct " , unionName , " : " , variantType ] , true , ( ) => {
1816+ this . emitLine ( "using base = " , variantType , ";" ) ;
1817+ this . emitLine ( "using base::base;" ) ;
1818+ this . emitLine ( "using base::operator=;" ) ;
1819+ } ) ;
1820+ }
1821+
1822+ protected emitUnionWrapperHeaders ( u : UnionType , unionName : Name ) : void {
1823+ if ( ! this . isRecursiveUnion ( u ) ) return ;
1824+
17001825 this . emitLine (
1701- "using " ,
1826+ "void from_json(" ,
1827+ this . withConst ( "json" ) ,
1828+ " & j, " ,
17021829 unionName ,
1703- " = " ,
1704- this . variantType ( u , false ) ,
1705- ";" ,
1830+ " & x);" ,
1831+ ) ;
1832+ this . emitLine (
1833+ "void to_json(json & j, " ,
1834+ this . withConst ( unionName ) ,
1835+ " & x);" ,
1836+ ) ;
1837+ }
1838+
1839+ protected emitUnionWrapperFunctions ( u : UnionType , unionName : Name ) : void {
1840+ if ( ! this . isRecursiveUnion ( u ) ) return ;
1841+
1842+ this . emitBlock (
1843+ [
1844+ "inline void from_json(" ,
1845+ this . withConst ( "json" ) ,
1846+ " & j, " ,
1847+ unionName ,
1848+ " & x)" ,
1849+ ] ,
1850+ false ,
1851+ ( ) => this . emitLine ( "x = j.get<" , unionName , "::base>();" ) ,
1852+ ) ;
1853+ this . ensureBlankLine ( ) ;
1854+ this . emitBlock (
1855+ [
1856+ "inline void to_json(json & j, " ,
1857+ this . withConst ( unionName ) ,
1858+ " & x)" ,
1859+ ] ,
1860+ false ,
1861+ ( ) =>
1862+ this . emitLine (
1863+ "j = static_cast<" ,
1864+ this . withConst ( [ unionName , "::base" ] ) ,
1865+ " &>(x);" ,
1866+ ) ,
17061867 ) ;
17071868 }
17081869
@@ -2881,6 +3042,15 @@ export class CPlusPlusRenderer extends ConvenienceRenderer {
28813042 this . emitHelperFunctions ( ) ;
28823043 }
28833044
3045+ if ( iterableSome ( this . namedUnions , ( u ) => this . isRecursiveUnion ( u ) ) ) {
3046+ this . forEachUnion ( "none" , ( u : UnionType , unionName : Name ) => {
3047+ if ( this . isRecursiveUnion ( u ) ) {
3048+ this . emitLine ( "struct " , unionName , ";" ) ;
3049+ }
3050+ } ) ;
3051+ this . ensureBlankLine ( ) ;
3052+ }
3053+
28843054 this . forEachDeclaration ( "interposing" , ( decl ) =>
28853055 this . emitDeclaration ( decl ) ,
28863056 ) ;
@@ -2904,6 +3074,12 @@ export class CPlusPlusRenderer extends ConvenienceRenderer {
29043074 "leading-and-interposing" ,
29053075 ( _ : unknown , enumName : Name ) => this . emitEnumHeaders ( enumName ) ,
29063076 ) ;
3077+
3078+ this . forEachUnion (
3079+ "leading-and-interposing" ,
3080+ ( u : UnionType , unionName : Name ) =>
3081+ this . emitUnionWrapperHeaders ( u , unionName ) ,
3082+ ) ;
29073083 } ) ;
29083084 }
29093085
@@ -2933,6 +3109,12 @@ export class CPlusPlusRenderer extends ConvenienceRenderer {
29333109 ( e : EnumType , enumName : Name ) =>
29343110 this . emitEnumFunctions ( e , enumName ) ,
29353111 ) ;
3112+
3113+ this . forEachUnion (
3114+ "leading-and-interposing" ,
3115+ ( u : UnionType , unionName : Name ) =>
3116+ this . emitUnionWrapperFunctions ( u , unionName ) ,
3117+ ) ;
29363118 }
29373119
29383120 protected emitNlohmannNamespaceImpls ( ) : void {
0 commit comments