-
Notifications
You must be signed in to change notification settings - Fork 149
Support recursive types #330
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
96ecc2b
WIP on recursive type support
czechboy0 1d3ca43
WIP
czechboy0 4a77a62
Formatting
czechboy0 9d05a58
WIP
czechboy0 e8685d9
WIP
czechboy0 374f3c0
Make response type rendering also more type-safe.
czechboy0 458bddc
More formatting
czechboy0 3365f3b
Tests passing
czechboy0 d85e4dd
Also test nested references
czechboy0 efbdd1b
wip
czechboy0 3366536
Tests passing for all types that need recursion.
czechboy0 158dbf4
Add more conceptual documentation for maintainers about how recursion…
czechboy0 7526219
Add a utility factory method for .member
czechboy0 fd1f7a6
Use the new utility in more places
czechboy0 93809ac
Remove a deprecated computed property
czechboy0 fff0323
Added more doc comments
czechboy0 d96a037
Add more documentation
czechboy0 5ff0e68
More comments
czechboy0 ad12ea1
Remove an obsolete file.
czechboy0 71f74aa
Add more snippet tests
czechboy0 e48c013
Merge branch 'main' into hd-recursive-types-support
czechboy0 624427f
Improve structured Swift representation for recursive type support
czechboy0 c748597
Rename variable to pattern
czechboy0 3656b23
PR feedback, use the _modify accessor instead of set
czechboy0 14b9ec9
Add _modify and yield support
czechboy0 166e048
Merge branch 'hd-recursive-prep' into hd-recursive-types-support
czechboy0 f028ac0
Update docs
czechboy0 da75322
Merge branch 'main' into hd-recursive-types-support
czechboy0 ceb51fa
Move the reference stack into its own file
czechboy0 57782e2
Bump the runtime dependency to 0.3.3
czechboy0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
204 changes: 204 additions & 0 deletions
204
Sources/_OpenAPIGeneratorCore/Translator/Recursion/DeclarationRecursionDetector.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This source file is part of the SwiftOpenAPIGenerator open source project | ||
| // | ||
| // Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors | ||
| // Licensed under Apache License v2.0 | ||
| // | ||
| // See LICENSE.txt for license information | ||
| // See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| /// A set of specialized types for using the recursion detector for | ||
| /// declarations. | ||
| struct DeclarationRecursionDetector { | ||
|
|
||
| /// A node for a pair of a Swift type name and a corresponding declaration. | ||
| struct Node: TypeNode, Equatable { | ||
|
|
||
| /// The type of the name is a string. | ||
| typealias NameType = String | ||
|
|
||
| /// The name of the node. | ||
| var name: NameType | ||
|
|
||
| /// Whether the type can be boxed. | ||
| var isBoxable: Bool | ||
|
|
||
| /// The names of nodes pointed to by this node. | ||
| var edges: [NameType] | ||
|
|
||
| /// The declaration represented by this node. | ||
| var decl: Declaration | ||
|
|
||
| /// Creates a new node. | ||
| /// - Parameters: | ||
| /// - name: The name of the node. | ||
| /// - isBoxable: Whether the type can be boxed. | ||
| /// - edges: The names of nodes pointed to by this node. | ||
| /// - decl: The declaration represented by this node. | ||
| private init(name: NameType, isBoxable: Bool, edges: [NameType], decl: Declaration) { | ||
| self.name = name | ||
| self.isBoxable = isBoxable | ||
| self.edges = edges | ||
| self.decl = decl | ||
| } | ||
|
|
||
| /// Creates a new node from the provided declaration. | ||
| /// | ||
| /// Returns nil when the declaration is missing a name. | ||
| /// - Parameter decl: A declaration. | ||
| init?(_ decl: Declaration) { | ||
| guard let name = decl.name else { | ||
| return nil | ||
| } | ||
| let edges = decl.schemaComponentNamesOfUnbreakableReferences | ||
| self.init( | ||
| name: name, | ||
| isBoxable: decl.isBoxable, | ||
| edges: edges, | ||
| decl: decl | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| /// A container for declarations. | ||
| struct Container: TypeNodeContainer { | ||
|
|
||
| /// The type of the node. | ||
| typealias Node = DeclarationRecursionDetector.Node | ||
|
|
||
| /// An error thrown by the container. | ||
| enum ContainerError: Swift.Error { | ||
|
|
||
| /// The node for the provided name was not found. | ||
| case nodeNotFound(Node.NameType) | ||
| } | ||
|
|
||
| /// The lookup map from the name to the node. | ||
| var lookupMap: [String: Node] | ||
|
|
||
| func lookup(_ name: String) throws -> DeclarationRecursionDetector.Node { | ||
| guard let node = lookupMap[name] else { | ||
| throw ContainerError.nodeNotFound(name) | ||
| } | ||
| return node | ||
| } | ||
| } | ||
| } | ||
|
|
||
| extension Declaration { | ||
|
|
||
| /// A name of the declaration, if it has one. | ||
| var name: String? { | ||
| switch self { | ||
| case .struct(let desc): | ||
| return desc.name | ||
| case .enum(let desc): | ||
| return desc.name | ||
| case .typealias(let desc): | ||
| return desc.name | ||
| case .commentable(_, let decl), .deprecated(_, let decl): | ||
| return decl.name | ||
| case .variable, .extension, .protocol, .function, .enumCase: | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| /// A Boolean value representing whether this declaration can be boxed. | ||
| var isBoxable: Bool { | ||
| switch self { | ||
| case .struct, .enum: | ||
| return true | ||
| case .commentable(_, let decl), .deprecated(_, let decl): | ||
| return decl.isBoxable | ||
| case .typealias, .variable, .extension, .protocol, .function, .enumCase: | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| /// An array of names that can be found in `#/components/schemas` in | ||
| /// the OpenAPI document that represent references that can cause | ||
| /// a reference cycle. | ||
| var schemaComponentNamesOfUnbreakableReferences: [String] { | ||
| switch self { | ||
| case .struct(let desc): | ||
| return desc | ||
| .members | ||
| .compactMap { (member) -> [String]? in | ||
| switch member.strippingTopComment { | ||
| case .variable, // A reference to a reusable type. | ||
| .struct, .enum: // An inline type. | ||
| return member.schemaComponentNamesOfUnbreakableReferences | ||
| default: | ||
| return nil | ||
| } | ||
| } | ||
| .flatMap { $0 } | ||
| case .enum(let desc): | ||
| return desc | ||
| .members | ||
| .compactMap { (member) -> [String]? in | ||
| guard case .enumCase = member.strippingTopComment else { | ||
| return nil | ||
| } | ||
| return member | ||
| .schemaComponentNamesOfUnbreakableReferences | ||
| } | ||
| .flatMap { $0 } | ||
| case .commentable(_, let decl), .deprecated(_, let decl): | ||
| return decl | ||
| .schemaComponentNamesOfUnbreakableReferences | ||
| case .typealias(let desc): | ||
| return desc | ||
| .existingType | ||
| .referencedSchemaComponentName | ||
| .map { [$0] } ?? [] | ||
| case .variable(let desc): | ||
| return desc.type?.referencedSchemaComponentName.map { [$0] } ?? [] | ||
| case .enumCase(let desc): | ||
| switch desc.kind { | ||
| case .nameWithAssociatedValues(let values): | ||
| return values.compactMap { $0.type.referencedSchemaComponentName } | ||
| default: | ||
| return [] | ||
| } | ||
| case .extension, .protocol, .function: | ||
| return [] | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fileprivate extension Array where Element == String { | ||
|
|
||
| /// The name in the `Components.Schemas.` namespace. | ||
| var nameIfTopLevelSchemaComponent: String? { | ||
| let components = self | ||
| guard | ||
| components.count == 3, | ||
| components.starts(with: Constants.Components.Schemas.components) | ||
| else { | ||
| return nil | ||
| } | ||
| return components[2] | ||
| } | ||
| } | ||
|
|
||
| extension ExistingTypeDescription { | ||
|
|
||
| /// The name in the `Components.Schemas.` namespace, if the type can appear | ||
| /// there. Nil otherwise. | ||
| var referencedSchemaComponentName: String? { | ||
| switch self { | ||
| case .member(let components): | ||
| return components.nameIfTopLevelSchemaComponent | ||
| case .array(let desc), .dictionaryValue(let desc), .any(let desc), .optional(let desc): | ||
| return desc.referencedSchemaComponentName | ||
| case .generic: | ||
| return nil | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.