Skip to content

Commit f118fe1

Browse files
AnthonyMDevgh-action-runner
authored andcommitted
Fix SelectionSet Equality bug with child merged field from fragment (apollographql/apollo-ios-dev#832)
1 parent eb6ddef commit f118fe1

File tree

1 file changed

+57
-47
lines changed

1 file changed

+57
-47
lines changed
Lines changed: 57 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import Foundation
22

33
// MARK: - Equatable & Hashable
4-
public extension SelectionSet {
4+
extension SelectionSet {
55

66
/// Creates a hash using a narrowly scoped algorithm that only combines fields in the underlying data
77
/// that are relevant to the `SelectionSet`. This ensures that hashes for a fragment do not
88
/// consider fields that are not included in the fragment, even if they are present in the data.
9-
func hash(into hasher: inout Hasher) {
9+
public func hash(into hasher: inout Hasher) {
1010
hasher.combine(self.fieldsForEquality())
1111
}
1212

1313
/// Checks for equality using a narrowly scoped algorithm that only compares fields in the underlying data
1414
/// that are relevant to the `SelectionSet`. This ensures that equality checks for a fragment do not
1515
/// consider fields that are not included in the fragment, even if they are present in the data.
16-
static func == (lhs: Self, rhs: Self) -> Bool {
16+
public static func == (lhs: Self, rhs: Self) -> Bool {
1717
return AnySendableHashable.equatableCheck(
1818
lhs.fieldsForEquality(),
1919
rhs.fieldsForEquality()
@@ -22,40 +22,72 @@ public extension SelectionSet {
2222

2323
private func fieldsForEquality() -> [String: DataDict.FieldValue] {
2424
var fields: [String: DataDict.FieldValue] = [:]
25-
if let asTypeCase = self as? any InlineFragment {
26-
self.addFulfilledSelections(of: type(of: asTypeCase.asRootEntityType), to: &fields)
25+
var addedFragments: Set<ObjectIdentifier> = []
2726

28-
} else {
29-
self.addFulfilledSelections(of: type(of: self), to: &fields)
30-
27+
for fragment in type(of: self).__fulfilledFragments {
28+
self.addFulfilledSelections(of: fragment, to: &fields, addedFragments: &addedFragments)
3129
}
3230
return fields
3331
}
3432

3533
private func addFulfilledSelections(
3634
of selectionSetType: any SelectionSet.Type,
37-
to fields: inout [String: DataDict.FieldValue]
35+
to fields: inout [String: DataDict.FieldValue],
36+
addedFragments: inout Set<ObjectIdentifier>
3837
) {
39-
guard self.__data.fragmentIsFulfilled(selectionSetType) else {
38+
let selectionSetTypeId = ObjectIdentifier(selectionSetType)
39+
guard !addedFragments.contains(selectionSetTypeId),
40+
self.__data.fragmentIsFulfilled(selectionSetType) else {
4041
return
4142
}
4243

44+
addedFragments.insert(selectionSetTypeId)
45+
4346
for selection in selectionSetType.__selections {
4447
switch selection {
45-
case let .field(field):
48+
case .field(let field):
4649
add(field: field, to: &fields)
4750

48-
case let .inlineFragment(typeCase):
49-
self.addFulfilledSelections(of: typeCase, to: &fields)
51+
case .inlineFragment(let typeCase):
52+
self.addFulfilledSelections(of: typeCase, to: &fields, addedFragments: &addedFragments)
53+
54+
case .conditional(_, let selections):
55+
self.addConditionalSelections(selections, to: &fields, addedFragments: &addedFragments)
56+
57+
case .fragment(let fragmentType):
58+
self.addFulfilledSelections(of: fragmentType, to: &fields, addedFragments: &addedFragments)
59+
60+
case .deferred(_, let fragmentType, _):
61+
self.addFulfilledSelections(of: fragmentType, to: &fields, addedFragments: &addedFragments)
62+
}
63+
}
64+
65+
for fragment in selectionSetType.__fulfilledFragments {
66+
self.addFulfilledSelections(of: fragment, to: &fields, addedFragments: &addedFragments)
67+
}
68+
}
69+
70+
private func addConditionalSelections(
71+
_ selections: [Selection],
72+
to fields: inout [String: DataDict.FieldValue],
73+
addedFragments: inout Set<ObjectIdentifier>
74+
) {
75+
for selection in selections {
76+
switch selection {
77+
case .inlineFragment(let typeCase):
78+
self.addFulfilledSelections(of: typeCase, to: &fields, addedFragments: &addedFragments)
79+
80+
case .fragment(let fragment):
81+
self.addFulfilledSelections(of: fragment, to: &fields, addedFragments: &addedFragments)
5082

51-
case let .conditional(_, selections):
52-
self.addConditionalSelections(selections, to: &fields)
83+
case .deferred(_, let fragment, _):
84+
self.addFulfilledSelections(of: fragment, to: &fields, addedFragments: &addedFragments)
5385

54-
case let .fragment(fragmentType):
55-
self.addFulfilledSelections(of: fragmentType, to: &fields)
86+
case .conditional(_, let selections):
87+
addConditionalSelections(selections, to: &fields, addedFragments: &addedFragments)
5688

57-
case let .deferred(_, fragmentType, _):
58-
self.addFulfilledSelections(of: fragmentType, to: &fields)
89+
case .field(let field):
90+
add(field: field, to: &fields)
5991
}
6092
}
6193
}
@@ -64,10 +96,12 @@ public extension SelectionSet {
6496
field: Selection.Field,
6597
to fields: inout [String: DataDict.FieldValue]
6698
) {
99+
guard !fields.keys.contains(field.responseKey) else { return }
100+
67101
let nullableFieldData = self.__data._data[field.responseKey].asNullable
68102
let fieldData: DataDict.FieldValue
69103
switch nullableFieldData {
70-
case let .some(value):
104+
case .some(let value):
71105
fieldData = value
72106
case .none, .null:
73107
return
@@ -79,13 +113,13 @@ public extension SelectionSet {
79113
case .scalar, .customScalar:
80114
fields[field.responseKey] = fieldData
81115

82-
case let .nonNull(innerType):
116+
case .nonNull(let innerType):
83117
addData(for: innerType, inList: inList)
84118

85-
case let .list(innerType):
119+
case .list(let innerType):
86120
addData(for: innerType, inList: true)
87121

88-
case let .object(selectionSetType):
122+
case .object(let selectionSetType):
89123
switch inList {
90124
case false:
91125
guard let objectData = fieldData as? DataDict else {
@@ -119,28 +153,4 @@ public extension SelectionSet {
119153
preconditionFailure("Expected list data to contain objects.")
120154
}
121155

122-
private func addConditionalSelections(
123-
_ selections: [Selection],
124-
to fields: inout [String: DataDict.FieldValue]
125-
) {
126-
for selection in selections {
127-
switch selection {
128-
case let .inlineFragment(typeCase):
129-
self.addFulfilledSelections(of: typeCase, to: &fields)
130-
131-
case let .fragment(fragment):
132-
self.addFulfilledSelections(of: fragment, to: &fields)
133-
134-
case let .deferred(_, fragment, _):
135-
self.addFulfilledSelections(of: fragment, to: &fields)
136-
137-
case let .conditional(_, selections):
138-
addConditionalSelections(selections, to: &fields)
139-
140-
case let .field(field):
141-
add(field: field, to: &fields)
142-
}
143-
}
144-
}
145-
146156
}

0 commit comments

Comments
 (0)