Skip to content
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

fix: @dynamicMember conflicting field name #2965

Merged
merged 7 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ public extension Mock where O == Bird {
wingspan: Double? = nil
) {
self.init()
self.bodyTemperature = bodyTemperature
self.favoriteToy = favoriteToy
self.height = height
self.humanName = humanName
self.id = id
self.laysEggs = laysEggs
self.owner = owner
self.predators = predators
self.skinCovering = skinCovering
self.species = species
self.wingspan = wingspan
_set(bodyTemperature, for: \.bodyTemperature)
_set(favoriteToy, for: \.favoriteToy)
_set(height, for: \.height)
_set(humanName, for: \.humanName)
_set(id, for: \.id)
_set(laysEggs, for: \.laysEggs)
_set(owner, for: \.owner)
_set(predators, for: \.predators)
_set(skinCovering, for: \.skinCovering)
_set(species, for: \.species)
_set(wingspan, for: \.wingspan)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ public extension Mock where O == Cat {
species: String? = nil
) {
self.init()
self.bodyTemperature = bodyTemperature
self.favoriteToy = favoriteToy
self.height = height
self.humanName = humanName
self.id = id
self.isJellicle = isJellicle
self.laysEggs = laysEggs
self.owner = owner
self.predators = predators
self.skinCovering = skinCovering
self.species = species
_set(bodyTemperature, for: \.bodyTemperature)
_set(favoriteToy, for: \.favoriteToy)
_set(height, for: \.height)
_set(humanName, for: \.humanName)
_set(id, for: \.id)
_set(isJellicle, for: \.isJellicle)
_set(laysEggs, for: \.laysEggs)
_set(owner, for: \.owner)
_set(predators, for: \.predators)
_set(skinCovering, for: \.skinCovering)
_set(species, for: \.species)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ public extension Mock where O == Crocodile {
species: String? = nil
) {
self.init()
self.height = height
self.id = id
self.predators = predators
self.skinCovering = skinCovering
self.species = species
_set(height, for: \.height)
_set(id, for: \.id)
_set(predators, for: \.predators)
_set(skinCovering, for: \.skinCovering)
_set(species, for: \.species)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ public extension Mock where O == Dog {
species: String? = nil
) {
self.init()
self.birthdate = birthdate
self.bodyTemperature = bodyTemperature
self.favoriteToy = favoriteToy
self.height = height
self.humanName = humanName
self.id = id
self.laysEggs = laysEggs
self.owner = owner
self.predators = predators
self.skinCovering = skinCovering
self.species = species
_set(birthdate, for: \.birthdate)
_set(bodyTemperature, for: \.bodyTemperature)
_set(favoriteToy, for: \.favoriteToy)
_set(height, for: \.height)
_set(humanName, for: \.humanName)
_set(id, for: \.id)
_set(laysEggs, for: \.laysEggs)
_set(owner, for: \.owner)
_set(predators, for: \.predators)
_set(skinCovering, for: \.skinCovering)
_set(species, for: \.species)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ public extension Mock where O == Fish {
species: String? = nil
) {
self.init()
self.favoriteToy = favoriteToy
self.height = height
self.humanName = humanName
self.id = id
self.owner = owner
self.predators = predators
self.skinCovering = skinCovering
self.species = species
_set(favoriteToy, for: \.favoriteToy)
_set(height, for: \.height)
_set(humanName, for: \.humanName)
_set(id, for: \.id)
_set(owner, for: \.owner)
_set(predators, for: \.predators)
_set(skinCovering, for: \.skinCovering)
_set(species, for: \.species)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ public extension Mock where O == Height {
relativeSize: GraphQLEnum<AnimalKingdomAPI.RelativeSize>? = nil
) {
self.init()
self.centimeters = centimeters
self.feet = feet
self.inches = inches
self.meters = meters
self.relativeSize = relativeSize
_set(centimeters, for: \.centimeters)
_set(feet, for: \.feet)
_set(inches, for: \.inches)
_set(meters, for: \.meters)
_set(relativeSize, for: \.relativeSize)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ public extension Mock where O == Human {
species: String? = nil
) {
self.init()
self.bodyTemperature = bodyTemperature
self.firstName = firstName
self.height = height
self.id = id
self.laysEggs = laysEggs
self.predators = predators
self.skinCovering = skinCovering
self.species = species
_set(bodyTemperature, for: \.bodyTemperature)
_set(firstName, for: \.firstName)
_set(height, for: \.height)
_set(id, for: \.id)
_set(laysEggs, for: \.laysEggs)
_set(predators, for: \.predators)
_set(skinCovering, for: \.skinCovering)
_set(species, for: \.species)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ public extension Mock where O == Mutation {
adoptPet: AnyMock? = nil
) {
self.init()
self.adoptPet = adoptPet
_set(adoptPet, for: \.adoptPet)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ public extension Mock where O == PetRock {
owner: Mock<Human>? = nil
) {
self.init()
self.favoriteToy = favoriteToy
self.humanName = humanName
self.id = id
self.owner = owner
_set(favoriteToy, for: \.favoriteToy)
_set(humanName, for: \.humanName)
_set(id, for: \.id)
_set(owner, for: \.owner)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ public extension Mock where O == Query {
pets: [AnyMock]? = nil
) {
self.init()
self.allAnimals = allAnimals
self.classroomPets = classroomPets
self.pets = pets
_set(allAnimals, for: \.allAnimals)
_set(classroomPets, for: \.classroomPets)
_set(pets, for: \.pets)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ public extension Mock where O == Rat {
species: String? = nil
) {
self.init()
self.favoriteToy = favoriteToy
self.height = height
self.humanName = humanName
self.id = id
self.owner = owner
self.predators = predators
self.skinCovering = skinCovering
self.species = species
_set(favoriteToy, for: \.favoriteToy)
_set(height, for: \.height)
_set(humanName, for: \.humanName)
_set(id, for: \.id)
_set(owner, for: \.owner)
_set(predators, for: \.predators)
_set(skinCovering, for: \.skinCovering)
_set(species, for: \.species)
}
}
7 changes: 5 additions & 2 deletions Sources/ApolloCodegenLib/TemplateString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,15 @@ struct TemplateString: ExpressibleByStringInterpolation, CustomStringConvertible
terminator: String? = nil
) where T: LazySequenceProtocol, T.Element: CustomStringConvertible {
var iterator = sequence.makeIterator()
guard var elementsString = iterator.next()?.description else {
guard
var elementsString = iterator.next()?.description,
!elementsString.isEmpty
else {
removeLineIfEmpty()
return
}

while let element = iterator.next() {
while let element = iterator.next(), !element.description.isEmpty {
elementsString.append(separator + element.description)
}

Expand Down
16 changes: 15 additions & 1 deletion Sources/ApolloCodegenLib/Templates/MockObjectTemplate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,14 @@ struct MockObjectTemplate: TemplateRenderer {
TemplateString("""

public extension Mock where O == \(objectName) {
\(conflictingFieldNameProperties(fields))
convenience init(
\(fields.map { """
\($0.propertyName)\(ifLet: $0.initializerParameterName, {" \($0)"}): \($0.mockType)? = nil
""" }, separator: ",\n")
) {
self.init()
\(fields.map { "self.\($0.propertyName) = \($0.initializerParameterName ?? $0.propertyName)" }, separator: "\n")
\(fields.map { "_set(\($0.initializerParameterName ?? $0.propertyName), for: \\.\($0.propertyName))" }, separator: "\n")
}
}
""") : TemplateString(stringLiteral: "")
Expand All @@ -68,6 +69,19 @@ struct MockObjectTemplate: TemplateRenderer {
"""
}

private func conflictingFieldNameProperties(_ fields: [TemplateField]) -> TemplateString {
"""
\(fields.map { """
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: might be more performant to compute an intersection with the TestMockConflictingFieldNames set and then just map over those? I could be wrong, but I believe that set intersection computation is very fast b/c of hashing algorithms. This is so so minor though, I'm not really worried about making the change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing about fields nor the FieldCollector is a Set unfortunately, I don't think we're going to get much benefit out of going that route here.

\(if: $0.propertyName.isConflictingTestMockFieldName, """
var \($0.propertyName): \($0.mockType)? {
get { _data["\($0.propertyName)"] as? \($0.mockType) }
set { _set(newValue, for: \\.\($0.propertyName)) }
}
""")
""" }, separator: "\n", terminator: "\n")
"""
}

private func mockTypeName(for type: GraphQLType) -> String {
func nameReplacement(for type: GraphQLType, forceNonNull: Bool) -> String {
switch type {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ extension String {
"\(self)_value" : nil
}

var isConflictingTestMockFieldName: Bool {
SwiftKeywords.TestMockConflictingFieldNames.contains(self)
}

private func escapeIf(in set: Set<String>) -> String {
set.contains(self) ? "`\(self)`" : self
}
Expand Down Expand Up @@ -81,6 +85,13 @@ enum SwiftKeywords {
"Actor"
]

/// There are some field names that conflict with function names due to the @dynamicMember
/// subscripting of `Mock`. This set is used to match those field names and generate properties
/// instead of just relying on the subscript access.
static let TestMockConflictingFieldNames: Set<String> = [
"hash"
]

fileprivate static let FieldAccessorNamesToEscape: Set<String> = [
"associatedtype",
"class",
Expand Down
37 changes: 30 additions & 7 deletions Sources/ApolloTestSupport/TestMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,26 @@ public class Mock<O: MockObject>: AnyMock, Hashable {

public var __typename: String { _data["__typename"] as! String }

public subscript<T: AnyScalarType & Hashable>(dynamicMember keyPath: KeyPath<O.MockFields, Field<T>>) -> T? {
public subscript<T: AnyScalarType & Hashable>(
dynamicMember keyPath: KeyPath<O.MockFields, Field<T>>
) -> T? {
get {
let field = O._mockFields[keyPath: keyPath]
return _data[field.key.description] as? T
}
set {
let field = O._mockFields[keyPath: keyPath]
_data[field.key.description] = newValue
_set(newValue, for: keyPath)
}
}

public func _set<T: AnyScalarType & Hashable>(
_ value: T?,
for keyPath: KeyPath<O.MockFields, Field<T>>
) {
let field = O._mockFields[keyPath: keyPath]
_data[field.key.description] = value
}

public subscript<T: MockFieldValue>(
dynamicMember keyPath: KeyPath<O.MockFields, Field<T>>
) -> T.MockValueCollectionType.Element? {
Expand All @@ -34,11 +43,18 @@ public class Mock<O: MockObject>: AnyMock, Hashable {
return _data[field.key.description] as? T.MockValueCollectionType.Element
}
set {
let field = O._mockFields[keyPath: keyPath]
_data[field.key.description] = (newValue as? AnyHashable)
_set(newValue, for: keyPath)
}
}

public func _set<T: MockFieldValue>(
_ value: T.MockValueCollectionType.Element?,
for keyPath: KeyPath<O.MockFields, Field<T>>
) {
let field = O._mockFields[keyPath: keyPath]
_data[field.key.description] = (value as? AnyHashable)
}

public subscript<T: MockFieldValue>(
dynamicMember keyPath: KeyPath<O.MockFields, Field<Array<T>>>
) -> [T.MockValueCollectionType.Element]? {
Expand All @@ -47,11 +63,18 @@ public class Mock<O: MockObject>: AnyMock, Hashable {
return _data[field.key.description] as? [T.MockValueCollectionType.Element]
}
set {
let field = O._mockFields[keyPath: keyPath]
_data[field.key.description] = newValue?._unsafelyConvertToMockValue()
_set(newValue, for: keyPath)
}
}

public func _set<T: MockFieldValue>(
_ value: [T.MockValueCollectionType.Element]?,
for keyPath: KeyPath<O.MockFields, Field<Array<T>>>
) {
let field = O._mockFields[keyPath: keyPath]
_data[field.key.description] = value?._unsafelyConvertToMockValue()
}

public var _selectionSetMockData: JSONObject {
_data.mapValues {
if let mock = $0 as? AnyMock {
Expand Down
Loading