Skip to content

Commit 62b69a9

Browse files
authored
Improve structured Swift representation for recursive type support (#334)
Improve structured Swift representation for recursive type support ### Motivation The recursive type support required some improvements to our structured Swift representation layer, so I split out just these noisy, mechanical, but not very interesting changes separate from the actual recursive type support here, into this PR. ### Modifications - Improves `IdentifierDescription` to be an enum of a "pattern" (usually a variable) and "type" (represents a type, like `Foo`, `any Foo`, `Foo?`, `[Foo]`, etc.) The lattern required more logic, so we propagate the distinction all the way to the renderer. - Added `trailingClosure` support to a `FunctionCallDescription`. - Introduced `ExistingTypeDescription`, kind of like the equivalent of `TypeUsage`, but on the structured Swift layer side. Moved to this type in a bunch of other types, most of which previously just had a `String`. - Allowed defining a setter on a computed variable. - Allowed marking an enum as indirect. ### Result Added the functionality required for the next PR adding recursive type support. But _mostly_ this should be a refactoring and apart from the next point, not change any functionality. The most visible change to the reference tests is that more types are now fully qualified, such as going from `String` -> `Swift.String`. ### Test Plan Adapted all tests, all still pass. Reviewed by: simonjbeaumont Builds: ✔︎ pull request validation (5.10) - Build finished. ✔︎ pull request validation (5.8) - Build finished. ✔︎ pull request validation (5.9) - Build finished. ✔︎ pull request validation (compatibility test) - Build finished. ✔︎ pull request validation (docc test) - Build finished. ✔︎ pull request validation (integration test) - Build finished. ✔︎ pull request validation (nightly) - Build finished. ✔︎ pull request validation (soundness) - Build finished. #334
1 parent d9d5daa commit 62b69a9

34 files changed

+795
-431
lines changed

Sources/_OpenAPIGeneratorCore/Layers/StructuredSwiftRepresentation.swift

Lines changed: 227 additions & 29 deletions
Large diffs are not rendered by default.

Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,12 @@ struct TextBasedRenderer: RendererProtocol {
118118

119119
/// Renders the specified identifier.
120120
func renderedIdentifier(_ identifier: IdentifierDescription) -> String {
121-
return identifier.name
121+
switch identifier {
122+
case .pattern(let string):
123+
return string
124+
case .type(let existingTypeDescription):
125+
return renderedExistingTypeDescription(existingTypeDescription)
126+
}
122127
}
123128

124129
/// Renders the specified member access expression.
@@ -136,8 +141,15 @@ struct TextBasedRenderer: RendererProtocol {
136141
/// Renders the specified function call.
137142
func renderedFunctionCall(_ functionCall: FunctionCallDescription) -> String {
138143
let arguments = functionCall.arguments
144+
let trailingClosureString: String
145+
if let trailingClosure = functionCall.trailingClosure {
146+
trailingClosureString = renderedClosureInvocation(trailingClosure)
147+
} else {
148+
trailingClosureString = ""
149+
}
139150
return
140151
"\(renderedExpression(functionCall.calledExpression))(\(arguments.map(renderedFunctionCallArgument).joined(separator: ", ")))"
152+
+ trailingClosureString
141153
}
142154

143155
/// Renders the specified assignment expression.
@@ -233,6 +245,8 @@ struct TextBasedRenderer: RendererProtocol {
233245
return "await"
234246
case .throw:
235247
return "throw"
248+
case .yield:
249+
return "yield"
236250
}
237251
}
238252

@@ -382,6 +396,24 @@ struct TextBasedRenderer: RendererProtocol {
382396
return lines.joinedLines()
383397
}
384398

399+
/// Renders the specified type reference to an existing type.
400+
func renderedExistingTypeDescription(_ type: ExistingTypeDescription) -> String {
401+
switch type {
402+
case .any(let existingTypeDescription):
403+
return "any \(renderedExistingTypeDescription(existingTypeDescription))"
404+
case .generic(let wrapper, let wrapped):
405+
return "\(renderedExistingTypeDescription(wrapper))<\(renderedExistingTypeDescription(wrapped))>"
406+
case .optional(let existingTypeDescription):
407+
return "\(renderedExistingTypeDescription(existingTypeDescription))?"
408+
case .member(let components):
409+
return components.joined(separator: ".")
410+
case .array(let existingTypeDescription):
411+
return "[\(renderedExistingTypeDescription(existingTypeDescription))]"
412+
case .dictionaryValue(let existingTypeDescription):
413+
return "[String: \(renderedExistingTypeDescription(existingTypeDescription))]"
414+
}
415+
}
416+
385417
/// Renders the specified typealias declaration.
386418
func renderedTypealias(_ alias: TypealiasDescription) -> String {
387419
var words: [String] = []
@@ -392,7 +424,7 @@ struct TextBasedRenderer: RendererProtocol {
392424
"typealias",
393425
alias.name,
394426
"=",
395-
alias.existingType,
427+
renderedExistingTypeDescription(alias.existingType),
396428
])
397429
return words.joinedWords()
398430
}
@@ -419,7 +451,7 @@ struct TextBasedRenderer: RendererProtocol {
419451
words.append(renderedBindingKind(variable.kind))
420452
let labelWithOptionalType: String
421453
if let type = variable.type {
422-
labelWithOptionalType = "\(variable.left): \(type)"
454+
labelWithOptionalType = "\(variable.left): \(renderedExistingTypeDescription(type))"
423455
} else {
424456
labelWithOptionalType = variable.left
425457
}
@@ -432,11 +464,22 @@ struct TextBasedRenderer: RendererProtocol {
432464
var lines: [String] = [words.joinedWords()]
433465
if let body = variable.getter {
434466
lines.append("{")
435-
if !variable.getterEffects.isEmpty {
467+
let hasExplicitGetter = !variable.getterEffects.isEmpty || variable.setter != nil || variable.modify != nil
468+
if hasExplicitGetter {
436469
lines.append("get \(variable.getterEffects.map(renderedFunctionKeyword).joined(separator: " ")) {")
437470
}
438471
lines.append(renderedCodeBlocks(body))
439-
if !variable.getterEffects.isEmpty {
472+
if hasExplicitGetter {
473+
lines.append("}")
474+
}
475+
if let modify = variable.modify {
476+
lines.append("_modify {")
477+
lines.append(renderedCodeBlocks(modify))
478+
lines.append("}")
479+
}
480+
if let setter = variable.setter {
481+
lines.append("set {")
482+
lines.append(renderedCodeBlocks(setter))
440483
lines.append("}")
441484
}
442485
lines.append("}")
@@ -505,6 +548,9 @@ struct TextBasedRenderer: RendererProtocol {
505548
if let accessModifier = enumDesc.accessModifier {
506549
words.append(renderedAccessModifier(accessModifier))
507550
}
551+
if enumDesc.isIndirect {
552+
words.append("indirect")
553+
}
508554
words.append("enum")
509555
words.append(enumDesc.name)
510556
if !enumDesc.conformances.isEmpty {
@@ -532,7 +578,7 @@ struct TextBasedRenderer: RendererProtocol {
532578
words.append(label)
533579
words.append(":")
534580
}
535-
words.append(value.type)
581+
words.append(renderedExistingTypeDescription(value.type))
536582
return words.joinedWords()
537583
}
538584

@@ -663,7 +709,7 @@ struct TextBasedRenderer: RendererProtocol {
663709
}
664710
}
665711
words.append(":")
666-
words.append(parameterDescription.type)
712+
words.append(renderedExistingTypeDescription(parameterDescription.type))
667713
if let defaultValue = parameterDescription.defaultValue {
668714
words.append("=")
669715
words.append(renderedExpression(defaultValue))

Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/ClientTranslator.swift

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ struct ClientFileTranslator: FileTranslator {
5656
accessModifier: .private,
5757
kind: .let,
5858
left: Constants.Client.Universal.propertyName,
59-
type: Constants.Client.Universal.typeName
59+
type: .member(Constants.Client.Universal.typeName)
6060
)
6161
)
6262

@@ -77,43 +77,46 @@ struct ClientFileTranslator: FileTranslator {
7777
accessModifier: config.access,
7878
kind: .initializer,
7979
parameters: [
80-
.init(label: "serverURL", type: Constants.ServerURL.underlyingType),
80+
.init(
81+
label: "serverURL",
82+
type: .init(TypeName.url)
83+
),
8184
.init(
8285
label: "configuration",
83-
type: Constants.Configuration.typeName,
86+
type: .member(Constants.Configuration.typeName),
8487
defaultValue: .dot("init").call([])
8588
),
8689
.init(
8790
label: "transport",
88-
type: Constants.Client.Transport.typeName
91+
type: .member(Constants.Client.Transport.typeName)
8992
),
9093
.init(
9194
label: "middlewares",
92-
type: "[\(Constants.Client.Middleware.typeName)]",
95+
type: .array(.member(Constants.Client.Middleware.typeName)),
9396
defaultValue: .literal(.array([]))
9497
),
9598
],
9699
body: [
97100
.expression(
98101
.assignment(
99-
left: .identifier("self").dot(Constants.Client.Universal.propertyName),
102+
left: .identifierPattern("self").dot(Constants.Client.Universal.propertyName),
100103
right: .dot("init")
101104
.call([
102105
.init(
103106
label: "serverURL",
104-
expression: .identifier("serverURL")
107+
expression: .identifierPattern("serverURL")
105108
),
106109
.init(
107110
label: "configuration",
108-
expression: .identifier("configuration")
111+
expression: .identifierPattern("configuration")
109112
),
110113
.init(
111114
label: "transport",
112-
expression: .identifier("transport")
115+
expression: .identifierPattern("transport")
113116
),
114117
.init(
115118
label: "middlewares",
116-
expression: .identifier("middlewares")
119+
expression: .identifierPattern("middlewares")
117120
),
118121
])
119122
)
@@ -126,10 +129,10 @@ struct ClientFileTranslator: FileTranslator {
126129
accessModifier: .private,
127130
kind: .var,
128131
left: "converter",
129-
type: Constants.Converter.typeName,
132+
type: .member(Constants.Converter.typeName),
130133
getter: [
131134
.expression(
132-
.identifier(Constants.Client.Universal.propertyName)
135+
.identifierPattern(Constants.Client.Universal.propertyName)
133136
.dot("converter")
134137
)
135138
]

Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/translateClientMethod.swift

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ extension ClientFileTranslator {
3131
kind: .let,
3232
left: "path",
3333
right: .try(
34-
.identifier("converter")
34+
.identifierPattern("converter")
3535
.dot("renderedPath")
3636
.call([
3737
.init(label: "template", expression: .literal(pathTemplate)),
@@ -42,10 +42,10 @@ extension ClientFileTranslator {
4242
let requestDecl: Declaration = .variable(
4343
kind: .var,
4444
left: "request",
45-
type: TypeName.request.fullyQualifiedSwiftName,
45+
type: .init(TypeName.request),
4646
right: .dot("init")
4747
.call([
48-
.init(label: "soar_path", expression: .identifier("path")),
48+
.init(label: "soar_path", expression: .identifierPattern("path")),
4949
.init(label: "method", expression: .dot(description.httpMethodLowercased)),
5050
])
5151
)
@@ -74,16 +74,16 @@ extension ClientFileTranslator {
7474
)
7575
if !acceptContent.isEmpty {
7676
let setAcceptHeaderExpr: Expression =
77-
.identifier("converter")
77+
.identifierPattern("converter")
7878
.dot("setAcceptHeader")
7979
.call([
8080
.init(
8181
label: "in",
82-
expression: .inOut(.identifier("request").dot("headerFields"))
82+
expression: .inOut(.identifierPattern("request").dot("headerFields"))
8383
),
8484
.init(
8585
label: "contentTypes",
86-
expression: .identifier("input").dot("headers").dot("accept")
86+
expression: .identifierPattern("input").dot("headers").dot("accept")
8787
),
8888
])
8989
requestBlocks.append(.expression(setAcceptHeaderExpr))
@@ -97,11 +97,11 @@ extension ClientFileTranslator {
9797
.variable(
9898
kind: .let,
9999
left: bodyVariableName,
100-
type: TypeName.body.asUsage.asOptional.fullyQualifiedSwiftName
100+
type: .init(TypeName.body.asUsage.asOptional)
101101
)
102102
)
103103
)
104-
requestBodyReturnExpr = .identifier(bodyVariableName)
104+
requestBodyReturnExpr = .identifierPattern(bodyVariableName)
105105
let requestBodyExpr = try translateRequestBodyInClient(
106106
requestBody,
107107
requestVariableName: "request",
@@ -115,7 +115,7 @@ extension ClientFileTranslator {
115115

116116
let returnRequestExpr: Expression = .return(
117117
.tuple([
118-
.identifier("request"),
118+
.identifierPattern("request"),
119119
requestBodyReturnExpr,
120120
])
121121
)
@@ -156,7 +156,10 @@ extension ClientFileTranslator {
156156
let undocumentedExpr: Expression = .return(
157157
.dot(Constants.Operation.Output.undocumentedCaseName)
158158
.call([
159-
.init(label: "statusCode", expression: .identifier("response").dot("status").dot("code")),
159+
.init(
160+
label: "statusCode",
161+
expression: .identifierPattern("response").dot("status").dot("code")
162+
),
160163
.init(label: nil, expression: .dot("init").call([])),
161164
])
162165
)
@@ -170,7 +173,7 @@ extension ClientFileTranslator {
170173
)
171174
}
172175
let switchStatusCodeExpr: Expression = .switch(
173-
switchedExpression: .identifier("response").dot("status").dot("code"),
176+
switchedExpression: .identifierPattern("response").dot("status").dot("code"),
174177
cases: cases
175178
)
176179
return .closureInvocation(
@@ -192,7 +195,7 @@ extension ClientFileTranslator {
192195

193196
let operationTypeExpr =
194197
Expression
195-
.identifier(Constants.Operations.namespace)
198+
.identifierType(.member(Constants.Operations.namespace))
196199
.dot(description.methodName)
197200

198201
let operationArg = FunctionArgumentDescription(
@@ -201,7 +204,7 @@ extension ClientFileTranslator {
201204
)
202205
let inputArg = FunctionArgumentDescription(
203206
label: "input",
204-
expression: .identifier(Constants.Operation.Input.variableName)
207+
expression: .identifierPattern(Constants.Operation.Input.variableName)
205208
)
206209
let serializerArg = FunctionArgumentDescription(
207210
label: "serializer",
@@ -215,7 +218,7 @@ extension ClientFileTranslator {
215218
let sendExpr: Expression = .try(
216219
.await(
217220
.functionCall(
218-
calledExpression: .identifier("client").dot("send"),
221+
calledExpression: .identifierPattern("client").dot("send"),
219222
arguments: [
220223
inputArg,
221224
operationArg,

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ extension FileTranslator {
214214
kind: .nameWithAssociatedValues([
215215
.init(
216216
label: nil,
217-
type: childType.fullyQualifiedSwiftName
217+
type: .init(childType)
218218
)
219219
])
220220
)

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateArray.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ extension FileTranslator {
6969
.`typealias`(
7070
accessModifier: config.access,
7171
name: typeName.shortSwiftName,
72-
existingType: elementType.typeName.asUsage.asArray.fullyQualifiedSwiftName
72+
existingType: .init(elementType.typeName.asUsage.asArray)
7373
)
7474
)
7575
return inline + [arrayDecl]

0 commit comments

Comments
 (0)