From 1dd780335997c7c14c440b9322df742573605800 Mon Sep 17 00:00:00 2001 From: Wiebren Braakman Date: Tue, 8 Sep 2026 11:03:02 +0200 Subject: [PATCH 1/2] [swift] fix: models on a reference cycle become classes so they compile A struct that stores itself inline - through any chain of model-typed properties, Optional included - has infinite size: "value type cannot have a stored property that recursively contains it", and every struct that embeds it is infinite in turn. The generators emitted structs unconditionally, so any self- or mutually-referencing schema produced a client that does not compile; the only escape was useClasses=true, which turns every model into a class. Detect inline reference cycles in postProcessAllModels (containers break recursion on the heap and are not edges) and render only the models on a cycle as final classes - heap allocation provides the indirection, the wire format is unchanged, and everything else stays a struct. In swift6 a recursion-breaking class is @unchecked Sendable, the way readonlyProperties classes already are, so the Sendable structs embedding it still conform. The per-model rendering flag also carries the global useClasses value, and the useClasses samples (urlsession, vapor, swift5 and swift6 alike) regenerate byte-identical. Fixes #15240 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GcwZ1arjLZNpetHz2a3TJz --- .../languages/Swift5ClientCodegen.java | 68 +++++++++++++++++ .../languages/Swift6ClientCodegen.java | 74 +++++++++++++++++++ .../resources/swift5/modelObject.mustache | 6 +- .../resources/swift6/modelObject.mustache | 6 +- .../swift5/Swift5ClientCodegenTest.java | 22 ++++++ .../swift6/Swift6ClientCodegenTest.java | 22 ++++++ .../resources/3_0/swift/recursive-models.yaml | 50 +++++++++++++ 7 files changed, 242 insertions(+), 6 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_0/swift/recursive-models.yaml diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java index 92224fc5a338..1109d9826107 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java @@ -746,6 +746,7 @@ private static List splitAdditionalModelOption(String value) { @Override public Map postProcessAllModels(Map objs) { objs = super.postProcessAllModels(objs); + markModelClassRendering(objs); if (additionalModelObjectAttributes.isEmpty() && additionalModelEnumAttributes.isEmpty() && additionalModelImports.isEmpty()) { @@ -766,6 +767,73 @@ public Map postProcessAllModels(Map objs) return objs; } + + /** + * A struct that stores itself inline - through any chain of model-typed properties, + * Optional included - has infinite size and does not compile ("value type cannot have a + * stored property that recursively contains it"). Containers store their elements on the + * heap and break the recursion, so only bare model-to-model properties form the edges. + * Every model on such a reference cycle is generated as a final class instead: heap + * allocation provides the indirection the struct cannot have, and the wire format is + * unchanged. See https://github.com/OpenAPITools/openapi-generator/issues/15240. + * + * @param objs the models + */ + private void markModelClassRendering(Map objs) { + Map modelsByClassname = new HashMap<>(); + for (ModelsMap modelsMap : objs.values()) { + for (ModelMap modelMap : modelsMap.getModels()) { + CodegenModel cm = modelMap.getModel(); + modelsByClassname.put(cm.classname, cm); + } + } + + Map> inlineRefs = new HashMap<>(); + for (CodegenModel cm : modelsByClassname.values()) { + Set refs = new LinkedHashSet<>(); + collectInlineModelRefs(cm.allVars, modelsByClassname, refs); + if (cm.getComposedSchemas() != null) { + collectInlineModelRefs(cm.getComposedSchemas().getOneOf(), modelsByClassname, refs); + collectInlineModelRefs(cm.getComposedSchemas().getAnyOf(), modelsByClassname, refs); + collectInlineModelRefs(cm.getComposedSchemas().getAllOf(), modelsByClassname, refs); + } + inlineRefs.put(cm.classname, refs); + } + + for (CodegenModel cm : modelsByClassname.values()) { + boolean recursive = !useClasses && isOnInlineReferenceCycle(cm.classname, inlineRefs); + if (useClasses || recursive) { + cm.vendorExtensions.put("x-swift-use-class", true); + } + } + } + + private void collectInlineModelRefs(List vars, Map modelsByClassname, Set refs) { + if (vars == null) { + return; + } + for (CodegenProperty var : vars) { + if (!var.isContainer && var.complexType != null && modelsByClassname.containsKey(var.complexType)) { + refs.add(var.complexType); + } + } + } + + private boolean isOnInlineReferenceCycle(String classname, Map> inlineRefs) { + Deque toVisit = new ArrayDeque<>(inlineRefs.getOrDefault(classname, Collections.emptySet())); + Set visited = new HashSet<>(); + while (!toVisit.isEmpty()) { + String current = toVisit.pop(); + if (classname.equals(current)) { + return true; + } + if (visited.add(current)) { + toVisit.addAll(inlineRefs.getOrDefault(current, Collections.emptySet())); + } + } + return false; + } + @Override protected boolean isReservedWord(String word) { return word != null && reservedWords.contains(word); //don't lowercase as super does diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift6ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift6ClientCodegen.java index 77394f42b70f..32353a647e09 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift6ClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift6ClientCodegen.java @@ -794,6 +794,7 @@ private static List splitAdditionalModelOption(String value) { @Override public Map postProcessAllModels(Map objs) { objs = super.postProcessAllModels(objs); + markModelClassRendering(objs); if (additionalModelObjectAttributes.isEmpty() && additionalModelEnumAttributes.isEmpty() && additionalModelImports.isEmpty()) { @@ -814,6 +815,79 @@ public Map postProcessAllModels(Map objs) return objs; } + + /** + * A struct that stores itself inline - through any chain of model-typed properties, + * Optional included - has infinite size and does not compile ("value type cannot have a + * stored property that recursively contains it"). Containers store their elements on the + * heap and break the recursion, so only bare model-to-model properties form the edges. + * Every model on such a reference cycle is generated as a final class instead: heap + * allocation provides the indirection the struct cannot have, and the wire format is + * unchanged. See https://github.com/OpenAPITools/openapi-generator/issues/15240. + * + * @param objs the models + */ + private void markModelClassRendering(Map objs) { + Map modelsByClassname = new HashMap<>(); + for (ModelsMap modelsMap : objs.values()) { + for (ModelMap modelMap : modelsMap.getModels()) { + CodegenModel cm = modelMap.getModel(); + modelsByClassname.put(cm.classname, cm); + } + } + + Map> inlineRefs = new HashMap<>(); + for (CodegenModel cm : modelsByClassname.values()) { + Set refs = new LinkedHashSet<>(); + collectInlineModelRefs(cm.allVars, modelsByClassname, refs); + if (cm.getComposedSchemas() != null) { + collectInlineModelRefs(cm.getComposedSchemas().getOneOf(), modelsByClassname, refs); + collectInlineModelRefs(cm.getComposedSchemas().getAnyOf(), modelsByClassname, refs); + collectInlineModelRefs(cm.getComposedSchemas().getAllOf(), modelsByClassname, refs); + } + inlineRefs.put(cm.classname, refs); + } + + for (CodegenModel cm : modelsByClassname.values()) { + boolean recursive = !useClasses && isOnInlineReferenceCycle(cm.classname, inlineRefs); + if (useClasses || recursive) { + cm.vendorExtensions.put("x-swift-use-class", true); + } + if ((useClasses && readonlyProperties) || recursive) { + // a struct embedding one of these classes is still declared Sendable, so the + // class must conform; a recursion-breaking class is treated as unchecked the + // way readonlyProperties classes already are + cm.vendorExtensions.put("x-swift-unchecked-sendable", true); + } + } + } + + private void collectInlineModelRefs(List vars, Map modelsByClassname, Set refs) { + if (vars == null) { + return; + } + for (CodegenProperty var : vars) { + if (!var.isContainer && var.complexType != null && modelsByClassname.containsKey(var.complexType)) { + refs.add(var.complexType); + } + } + } + + private boolean isOnInlineReferenceCycle(String classname, Map> inlineRefs) { + Deque toVisit = new ArrayDeque<>(inlineRefs.getOrDefault(classname, Collections.emptySet())); + Set visited = new HashSet<>(); + while (!toVisit.isEmpty()) { + String current = toVisit.pop(); + if (classname.equals(current)) { + return true; + } + if (visited.add(current)) { + toVisit.addAll(inlineRefs.getOrDefault(current, Collections.emptySet())); + } + } + return false; + } + @Override protected boolean isReservedWord(String word) { return word != null && reservedWords.contains(word); //don't lowercase as super does diff --git a/modules/openapi-generator/src/main/resources/swift5/modelObject.mustache b/modules/openapi-generator/src/main/resources/swift5/modelObject.mustache index b6759a6a0e4b..7e107cb08f5c 100644 --- a/modules/openapi-generator/src/main/resources/swift5/modelObject.mustache +++ b/modules/openapi-generator/src/main/resources/swift5/modelObject.mustache @@ -1,5 +1,5 @@ {{#additionalModelObjectAttributes}}{{{.}}} -{{/additionalModelObjectAttributes}}{{^objcCompatible}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#useClasses}}final class{{/useClasses}}{{^useClasses}}struct{{/useClasses}} {{{classname}}}: {{#useVapor}}Content{{/useVapor}}{{^useVapor}}Codable{{#useJsonEncodable}}, JSONEncodable{{/useJsonEncodable}}{{/useVapor}}{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}} { +{{/additionalModelObjectAttributes}}{{^objcCompatible}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#vendorExtensions.x-swift-use-class}}final class{{/vendorExtensions.x-swift-use-class}}{{^vendorExtensions.x-swift-use-class}}struct{{/vendorExtensions.x-swift-use-class}} {{{classname}}}: {{#useVapor}}Content{{/useVapor}}{{^useVapor}}Codable{{#useJsonEncodable}}, JSONEncodable{{/useJsonEncodable}}{{/useVapor}}{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}} { {{/objcCompatible}}{{#objcCompatible}}@objcMembers {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} class {{classname}}: NSObject, Codable{{#useJsonEncodable}}, JSONEncodable{{/useJsonEncodable}} { {{/objcCompatible}} @@ -121,7 +121,7 @@ {{/allVars}} let additionalPropertiesContainer = try decoder.container(keyedBy: String.self) additionalProperties = try additionalPropertiesContainer.decodeMap({{{additionalPropertiesType}}}.self, excludedKeys: nonAdditionalPropertyKeys) - }{{/additionalPropertiesType}}{{/generateModelAdditionalProperties}}{{^objcCompatible}}{{#useClasses}}{{#vendorExtensions.x-swift-hashable}} + }{{/additionalPropertiesType}}{{/generateModelAdditionalProperties}}{{^objcCompatible}}{{#vendorExtensions.x-swift-use-class}}{{#vendorExtensions.x-swift-hashable}} {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func == (lhs: {{classname}}, rhs: {{classname}}) -> Bool { {{#allVars}} @@ -135,5 +135,5 @@ hasher.combine({{{name}}}{{^vendorExtensions.x-null-encodable}}{{^required}}?{{/required}}{{/vendorExtensions.x-null-encodable}}.hashValue) {{/allVars}} {{#generateModelAdditionalProperties}}{{#additionalPropertiesType}}hasher.combine(additionalProperties.hashValue){{/additionalPropertiesType}}{{/generateModelAdditionalProperties}} - }{{/vendorExtensions.x-swift-hashable}}{{/useClasses}}{{/objcCompatible}} + }{{/vendorExtensions.x-swift-hashable}}{{/vendorExtensions.x-swift-use-class}}{{/objcCompatible}} } diff --git a/modules/openapi-generator/src/main/resources/swift6/modelObject.mustache b/modules/openapi-generator/src/main/resources/swift6/modelObject.mustache index c4677923979d..445826d40146 100644 --- a/modules/openapi-generator/src/main/resources/swift6/modelObject.mustache +++ b/modules/openapi-generator/src/main/resources/swift6/modelObject.mustache @@ -1,5 +1,5 @@ {{#additionalModelObjectAttributes}}{{{.}}} -{{/additionalModelObjectAttributes}}{{^objcCompatible}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#useClasses}}final class{{/useClasses}}{{^useClasses}}struct{{/useClasses}} {{{classname}}}: {{^useClasses}}Sendable, {{/useClasses}}{{#useClasses}}{{#readonlyProperties}}@unchecked Sendable, {{/readonlyProperties}}{{/useClasses}}{{#useVapor}}Content{{/useVapor}}{{^useVapor}}Codable{{/useVapor}}{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}} { +{{/additionalModelObjectAttributes}}{{^objcCompatible}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#vendorExtensions.x-swift-use-class}}final class{{/vendorExtensions.x-swift-use-class}}{{^vendorExtensions.x-swift-use-class}}struct{{/vendorExtensions.x-swift-use-class}} {{{classname}}}: {{^vendorExtensions.x-swift-use-class}}Sendable, {{/vendorExtensions.x-swift-use-class}}{{#vendorExtensions.x-swift-unchecked-sendable}}@unchecked Sendable, {{/vendorExtensions.x-swift-unchecked-sendable}}{{#useVapor}}Content{{/useVapor}}{{^useVapor}}Codable{{/useVapor}}{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}} { {{/objcCompatible}}{{#objcCompatible}}@objcMembers {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} final class {{classname}}: NSObject, Codable, @unchecked Sendable { {{/objcCompatible}} @@ -121,7 +121,7 @@ {{/allVars}} let additionalPropertiesContainer = try decoder.container(keyedBy: String.self) additionalProperties = try additionalPropertiesContainer.decodeMap({{{additionalPropertiesType}}}.self, excludedKeys: nonAdditionalPropertyKeys) - }{{/additionalPropertiesType}}{{/generateModelAdditionalProperties}}{{^objcCompatible}}{{#useClasses}}{{#vendorExtensions.x-swift-hashable}} + }{{/additionalPropertiesType}}{{/generateModelAdditionalProperties}}{{^objcCompatible}}{{#vendorExtensions.x-swift-use-class}}{{#vendorExtensions.x-swift-hashable}} {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func == (lhs: {{classname}}, rhs: {{classname}}) -> Bool { {{#allVars}} @@ -135,5 +135,5 @@ hasher.combine({{{name}}}{{^vendorExtensions.x-null-encodable}}{{^required}}?{{/required}}{{/vendorExtensions.x-null-encodable}}.hashValue) {{/allVars}} {{#generateModelAdditionalProperties}}{{#additionalPropertiesType}}hasher.combine(additionalProperties.hashValue){{/additionalPropertiesType}}{{/generateModelAdditionalProperties}} - }{{/vendorExtensions.x-swift-hashable}}{{/useClasses}}{{/objcCompatible}} + }{{/vendorExtensions.x-swift-hashable}}{{/vendorExtensions.x-swift-use-class}}{{/objcCompatible}} } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/swift5/Swift5ClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/swift5/Swift5ClientCodegenTest.java index 782a5d95d57e..a9e64c40716c 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/swift5/Swift5ClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/swift5/Swift5ClientCodegenTest.java @@ -266,6 +266,28 @@ public void testPodAuthors() throws Exception { Assert.assertEquals(podAuthors, openAPIDevs); } + @Test(description = "models on an inline reference cycle become classes, everything else stays a struct") + public void testRecursiveModelsBecomeClasses() throws IOException { + Path target = Files.createTempDirectory("test"); + target.toFile().deleteOnExit(); + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("swift5") + .setInputSpec("src/test/resources/3_0/swift/recursive-models.yaml") + .setOutputDir(target.toAbsolutePath().toString()); + new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); + + Path models = target.resolve("OpenAPIClient/Classes/OpenAPIs/Models"); + // a struct that stores itself inline has infinite size and does not compile (#15240): + // the self-referencing model and both halves of the mutual cycle become final classes + TestUtils.assertFileContains(models.resolve("ContactInfo.swift"), "public final class ContactInfo:"); + TestUtils.assertFileContains(models.resolve("NodeA.swift"), "public final class NodeA:"); + TestUtils.assertFileContains(models.resolve("NodeB.swift"), "public final class NodeB:"); + // embedding a cyclic class costs nothing, and containers already give heap + // indirection - these stay structs + TestUtils.assertFileContains(models.resolve("DomainInfo.swift"), "public struct DomainInfo:"); + TestUtils.assertFileContains(models.resolve("Category.swift"), "public struct Category:"); + } + @Test(description = "Bug example code generation", enabled = true) public void crashSwift5ExampleCodeGenerationStackOverflowTest() throws IOException { //final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/bugs/Swift5CodeGenerationStackOverflow#2966.yaml"); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/swift6/Swift6ClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/swift6/Swift6ClientCodegenTest.java index 3fe2f07d3b0f..6c94b5b730ad 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/swift6/Swift6ClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/swift6/Swift6ClientCodegenTest.java @@ -52,6 +52,28 @@ public void testToRegularExpressionRemainsValidInSwiftStringLiteral() throws Exc Assert.assertEquals(swiftCodegen.toRegularExpression("/[a-z]/i"), "/[a-z]/i"); } + @Test(description = "models on an inline reference cycle become classes, everything else stays a struct") + public void testRecursiveModelsBecomeClasses() throws IOException { + Path target = Files.createTempDirectory("test"); + target.toFile().deleteOnExit(); + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("swift6") + .setInputSpec("src/test/resources/3_0/swift/recursive-models.yaml") + .setOutputDir(target.toAbsolutePath().toString()); + new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); + + Path models = target.resolve("Sources/OpenAPIClient/Models"); + // a struct that stores itself inline has infinite size and does not compile (#15240): + // the self-referencing model and both halves of the mutual cycle become final classes + TestUtils.assertFileContains(models.resolve("ContactInfo.swift"), "public final class ContactInfo: @unchecked Sendable,"); + TestUtils.assertFileContains(models.resolve("NodeA.swift"), "public final class NodeA: @unchecked Sendable,"); + TestUtils.assertFileContains(models.resolve("NodeB.swift"), "public final class NodeB: @unchecked Sendable,"); + // embedding a cyclic class costs nothing, and containers already give heap + // indirection - these stay structs + TestUtils.assertFileContains(models.resolve("DomainInfo.swift"), "public struct DomainInfo: Sendable,"); + TestUtils.assertFileContains(models.resolve("Category.swift"), "public struct Category: Sendable,"); + } + @Test(enabled = true) public void testCapitalizedReservedWord() throws Exception { Assert.assertEquals(swiftCodegen.toEnumVarName("AS", null), "_as"); diff --git a/modules/openapi-generator/src/test/resources/3_0/swift/recursive-models.yaml b/modules/openapi-generator/src/test/resources/3_0/swift/recursive-models.yaml new file mode 100644 index 000000000000..f917b4a32296 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/swift/recursive-models.yaml @@ -0,0 +1,50 @@ +openapi: 3.0.3 +info: + title: recursive models + version: 1.0.0 +paths: + /contacts: + get: + operationId: getContact + responses: + '200': + description: ok + content: + application/json: + schema: + $ref: '#/components/schemas/DomainInfo' +components: + schemas: + ContactInfo: + type: object + properties: + name: + type: string + internationalizedPostalInfo: + $ref: '#/components/schemas/ContactInfo' + DomainInfo: + type: object + properties: + domainName: + type: string + registrant: + $ref: '#/components/schemas/ContactInfo' + NodeA: + type: object + properties: + b: + $ref: '#/components/schemas/NodeB' + NodeB: + type: object + properties: + a: + $ref: '#/components/schemas/NodeA' + Category: + type: object + properties: + name: + type: string + children: + type: array + items: + $ref: '#/components/schemas/Category' From bb5244e02bcfbefe4aa5f605a0486ad20fca0868 Mon Sep 17 00:00:00 2001 From: Wiebren Braakman Date: Tue, 8 Sep 2026 11:41:47 +0200 Subject: [PATCH 2/2] [swift] fix: allOf composition is not an inline-storage edge Review pointed out that allOf parents are flattened into allVars rather than stored inline, so treating the composed reference as an edge could mark models that store nothing recursively. Real cycles introduced by the flattening still surface through the allVars properties themselves; oneOf/anyOf keep their edges, since those render as enums with inline associated values. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GcwZ1arjLZNpetHz2a3TJz --- .../openapitools/codegen/languages/Swift5ClientCodegen.java | 3 ++- .../openapitools/codegen/languages/Swift6ClientCodegen.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java index 1109d9826107..d4bf79eb06ba 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java @@ -793,9 +793,10 @@ private void markModelClassRendering(Map objs) { Set refs = new LinkedHashSet<>(); collectInlineModelRefs(cm.allVars, modelsByClassname, refs); if (cm.getComposedSchemas() != null) { + // oneOf/anyOf render as enums with inline associated values, so they carry the + // recursion; allOf is flattened into allVars and is deliberately not an edge collectInlineModelRefs(cm.getComposedSchemas().getOneOf(), modelsByClassname, refs); collectInlineModelRefs(cm.getComposedSchemas().getAnyOf(), modelsByClassname, refs); - collectInlineModelRefs(cm.getComposedSchemas().getAllOf(), modelsByClassname, refs); } inlineRefs.put(cm.classname, refs); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift6ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift6ClientCodegen.java index 32353a647e09..99b8f014c2c7 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift6ClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift6ClientCodegen.java @@ -841,9 +841,10 @@ private void markModelClassRendering(Map objs) { Set refs = new LinkedHashSet<>(); collectInlineModelRefs(cm.allVars, modelsByClassname, refs); if (cm.getComposedSchemas() != null) { + // oneOf/anyOf render as enums with inline associated values, so they carry the + // recursion; allOf is flattened into allVars and is deliberately not an edge collectInlineModelRefs(cm.getComposedSchemas().getOneOf(), modelsByClassname, refs); collectInlineModelRefs(cm.getComposedSchemas().getAnyOf(), modelsByClassname, refs); - collectInlineModelRefs(cm.getComposedSchemas().getAllOf(), modelsByClassname, refs); } inlineRefs.put(cm.classname, refs); }