Skip to content

Commit

Permalink
Use self for member access/assignment in all initializers, decoders, …
Browse files Browse the repository at this point in the history
…and encoders (#700)

### Motivation

In order to address #695 we made some targeted fixes (#696, #699) to use
explicit-self to avoid clashing with schema property names. This
addressed a clash with a specific variable with properties of only
certain schemas, but there are more places where we might encounter
future issues. Specifically, we have other variable names which might
cause a clash (e.g. `storage` and `discriminator`), and there are other
flavours of initializers, encoders, and decoders that were not updated
in the targeted fix.

In this PR, we update all access and assignment in the generated code
dealing with schemas to use explicit-self.


### Modifications

- Use self for member access and assignment in all initializers,
decoders, and encoders.
- Update the reference tests and snippet tests.

### Result

Removed some cases where valid OpenAPI docs would produce non-compiling
code.

### Test Plan

Snippet and reference tests. The latter is actually compiled during the
CI.
  • Loading branch information
simonjbeaumont authored Dec 11, 2024
1 parent 2e1b6fc commit 5dd8d18
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,16 @@ extension Expression {
/// - Returns: A new expression representing member access with a dot prefix.
static func dot(_ member: String) -> Self { Self.memberAccess(.init(right: member)) }

/// Returns a new member access expression with `self` as the receiver.
///
/// For example: `self.foo`, where `member` is `foo`.
///
/// - Parameter member: The name of the member to access on the expression.
/// - Returns: A new expression representing member access.
static func selfDot(_ member: String) -> Expression {
.memberAccess(.init(left: .identifier(.pattern("self")), right: member))
}

/// Returns a new identifier expression for the provided pattern, such
/// as a variable or function name.
/// - Parameter name: The name of the identifier.
Expand Down Expand Up @@ -1555,6 +1565,7 @@ extension Expression {
/// - Parameter expressions: The member expressions.
/// - Returns: A tuple expression.
static func tuple(_ expressions: [Expression]) -> Self { .tuple(.init(members: expressions)) }

}

extension MemberAccessDescription {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ extension FileTranslator {
let assignExprs: [Expression] = properties.map { property in
let typeUsage = property.typeUsage
return .assignment(
left: .identifierPattern("self").dot(property.swiftSafeName),
left: .selfDot(property.swiftSafeName),
right: .try(
.identifierPattern("container").dot("decode\(typeUsage.isOptional ? "IfPresent" : "")")
.call([
Expand Down Expand Up @@ -156,7 +156,7 @@ extension FileTranslator {
.try(
.identifierPattern("container").dot("encode\(property.typeUsage.isOptional ? "IfPresent" : "")")
.call([
.init(label: nil, expression: .identifierPattern("self").dot(property.swiftSafeName)),
.init(label: nil, expression: .selfDot(property.swiftSafeName)),
.init(label: "forKey", expression: .dot(property.swiftSafeName)),
])
)
Expand All @@ -181,7 +181,7 @@ extension FileTranslator {
let assignExprs: [Expression] = properties.map { property, isKeyValuePair in
let decoderExpr: Expression =
isKeyValuePair ? .initFromDecoderExpr() : .decodeFromSingleValueContainerExpr()
return .assignment(left: .identifierPattern(property.swiftSafeName), right: .try(decoderExpr))
return .assignment(left: .selfDot(property.swiftSafeName), right: .try(decoderExpr))
}
return decoderInitializer(body: assignExprs.map { .expression($0) })
}
Expand All @@ -194,12 +194,11 @@ extension FileTranslator {
{
let exprs: [Expression]
if let firstSingleValue = properties.first(where: { !$0.isKeyValuePair }) {
let expr: Expression = .identifierPattern(firstSingleValue.property.swiftSafeName)
let expr: Expression = .selfDot(firstSingleValue.property.swiftSafeName)
.encodeToSingleValueContainerExpr(gracefully: false)
exprs = [expr]
} else {
exprs = properties.filter { $0.isKeyValuePair }.map(\.property.swiftSafeName)
.map { name in .identifierPattern(name).encodeExpr() }
exprs = properties.filter(\.isKeyValuePair).map { .selfDot($0.property.swiftSafeName).encodeExpr() }
}
return encoderFunction(body: exprs.map { .expression($0) })
}
Expand All @@ -216,18 +215,15 @@ extension FileTranslator {
let assignBlocks: [CodeBlock] = properties.map { (property, isKeyValuePair) in
let decoderExpr: Expression =
isKeyValuePair ? .initFromDecoderExpr() : .decodeFromSingleValueContainerExpr()
let assignExpr: Expression = .assignment(
left: .identifierPattern(property.swiftSafeName),
right: .try(decoderExpr)
)
let assignExpr: Expression = .assignment(left: .selfDot(property.swiftSafeName), right: .try(decoderExpr))
return .expression(assignExpr.wrapInDoCatchAppendArrayExpr())
}
let atLeastOneNotNilCheckExpr: Expression = .try(
.identifierType(TypeName.decodingError).dot("verifyAtLeastOneSchemaIsNotNil")
.call([
.init(
label: nil,
expression: .literal(.array(properties.map { .identifierPattern($0.property.swiftSafeName) }))
expression: .literal(.array(properties.map { .selfDot($0.property.swiftSafeName) }))
), .init(label: "type", expression: .identifierPattern("Self").dot("self")),
.init(label: "codingPath", expression: .identifierPattern("decoder").dot("codingPath")),
.init(label: "errors", expression: .identifierPattern("errors")),
Expand All @@ -250,14 +246,12 @@ extension FileTranslator {
? nil
: .try(
.identifierPattern("encoder").dot("encodeFirstNonNilValueToSingleValueContainer")
.call([
.init(label: nil, expression: .literal(.array(singleValueNames.map { .identifierPattern($0) })))
])
.call([.init(label: nil, expression: .literal(.array(singleValueNames.map { .selfDot($0) })))])
)
let encodeExprs: [Expression] =
(encodeSingleValuesExpr.flatMap { [$0] } ?? [])
+ properties.filter { $0.isKeyValuePair }.map(\.property)
.map { property in .identifierPattern(property.swiftSafeName).optionallyChained().encodeExpr() }
.map { property in .selfDot(property.swiftSafeName).optionallyChained().encodeExpr() }
return encoderFunction(body: encodeExprs.map { .expression($0) })
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,8 @@ extension TypesFileTranslator {
case .variable(var variableDescription) = commented
else { return member }
let name = TextBasedRenderer.renderedExpressionAsString(variableDescription.left)
variableDescription.getter = [.expression(.identifierPattern("storage").dot("value").dot(name))]
variableDescription.modify = [
.expression(.yield(.inOut(.identifierPattern("storage").dot("value").dot(name))))
]
variableDescription.getter = [.expression(.selfDot("storage").dot("value").dot(name))]
variableDescription.modify = [.expression(.yield(.inOut(.selfDot("storage").dot("value").dot(name))))]
return .commentable(comment, .variable(variableDescription))
}

Expand All @@ -127,7 +125,7 @@ extension TypesFileTranslator {
funcDesc.body = [
.expression(
.assignment(
left: .identifierPattern("storage"),
left: .selfDot("storage"),
right: .dot("init")
.call([
.init(
Expand Down Expand Up @@ -167,7 +165,7 @@ extension TypesFileTranslator {
body: [
.expression(
.assignment(
left: .identifierPattern("storage"),
left: .selfDot("storage"),
right: .try(
.dot("init").call([.init(label: "from", expression: .identifierPattern("decoder"))])
)
Expand All @@ -185,7 +183,7 @@ extension TypesFileTranslator {
body: [
.expression(
.try(
.identifierPattern("storage").dot("encode")
.selfDot("storage").dot("encode")
.call([.init(label: "to", expression: .identifierPattern("encoder"))])
)
)
Expand Down
Loading

0 comments on commit 5dd8d18

Please sign in to comment.