Skip to content

Commit

Permalink
refactor: Fragment types in IR (#3174)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Miller <[email protected]>
  • Loading branch information
calvincestari and AnthonyMDev authored Aug 3, 2023
1 parent 1e0f8dd commit 4147f78
Show file tree
Hide file tree
Showing 14 changed files with 275 additions and 164 deletions.
12 changes: 6 additions & 6 deletions Sources/ApolloCodegenLib/ApolloCodegen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,10 @@ public class ApolloCodegen {
)
}

var fragments: [IR.NamedFragment] = selectionSet.selections.direct?.fragments.values.map { $0.fragment } ?? []
fragments.append(contentsOf: selectionSet.selections.merged.fragments.values.map { $0.fragment })
var namedFragments: [IR.NamedFragment] = selectionSet.selections.direct?.namedFragments.values.map(\.fragment) ?? []
namedFragments.append(contentsOf: selectionSet.selections.merged.namedFragments.values.map(\.fragment))

try fragments.forEach { fragment in
try namedFragments.forEach { fragment in
if let existingTypeName = combinedTypeNames[fragment.generatedDefinitionName] {
throw Error.typeNameConflict(
name: existingTypeName,
Expand All @@ -305,9 +305,9 @@ public class ApolloCodegen {
}
}

// gather nested fragments to loop through and check as well
var nestedSelectionSets: [IR.SelectionSet] = selectionSet.selections.direct?.inlineFragments.values.elements ?? []
nestedSelectionSets.append(contentsOf: selectionSet.selections.merged.inlineFragments.values)
// gather nested fragments to loop through and check as well
var nestedSelectionSets: [IR.SelectionSet] = selectionSet.selections.direct?.inlineFragments.values.map(\.selectionSet) ?? []
nestedSelectionSets.append(contentsOf: selectionSet.selections.merged.inlineFragments.values.map(\.selectionSet))

try nestedSelectionSets.forEach { nestedSet in
try validateTypeConflicts(
Expand Down
51 changes: 28 additions & 23 deletions Sources/ApolloCodegenLib/IR/IR+EntitySelectionTree.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ extension IR {
}

private func mergeIn(selections: DirectSelections.ReadOnly, from source: MergedSelections.MergedSource) {
guard (!selections.fields.isEmpty || !selections.fragments.isEmpty) else {
guard (!selections.fields.isEmpty || !selections.namedFragments.isEmpty) else {
return
}

Expand Down Expand Up @@ -255,7 +255,8 @@ extension IR {
fileprivate func scopeConditionNode(for condition: ScopeCondition) -> EntityNode {
let nodeCondition = ScopeCondition(
type: condition.type == self.type ? nil : condition.type,
conditions: condition.conditions
conditions: condition.conditions,
isDeferred: condition.isDeferred
)

func createNode() -> EntityNode {
Expand Down Expand Up @@ -291,20 +292,20 @@ extension IR {
class EntityTreeScopeSelections: Equatable {

fileprivate(set) var fields: OrderedDictionary<String, Field> = [:]
fileprivate(set) var fragments: OrderedDictionary<String, FragmentSpread> = [:]
fileprivate(set) var namedFragments: OrderedDictionary<String, NamedFragmentSpread> = [:]

init() {}

fileprivate init(
fields: OrderedDictionary<String, Field>,
fragments: OrderedDictionary<String, FragmentSpread>
namedFragments: OrderedDictionary<String, NamedFragmentSpread>
) {
self.fields = fields
self.fragments = fragments
self.namedFragments = namedFragments
}

var isEmpty: Bool {
fields.isEmpty && fragments.isEmpty
fields.isEmpty && namedFragments.isEmpty
}

private func mergeIn(_ field: Field) {
Expand All @@ -315,27 +316,27 @@ extension IR {
fields.forEach { mergeIn($0) }
}

private func mergeIn(_ fragment: FragmentSpread) {
fragments[fragment.hashForSelectionSetScope] = fragment
private func mergeIn(_ fragment: NamedFragmentSpread) {
namedFragments[fragment.hashForSelectionSetScope] = fragment
}

private func mergeIn<T: Sequence>(_ fragments: T) where T.Element == FragmentSpread {
private func mergeIn<T: Sequence>(_ fragments: T) where T.Element == NamedFragmentSpread {
fragments.forEach { mergeIn($0) }
}

func mergeIn(_ selections: DirectSelections.ReadOnly) {
mergeIn(selections.fields.values)
mergeIn(selections.fragments.values)
mergeIn(selections.namedFragments.values)
}

func mergeIn(_ selections: EntityTreeScopeSelections) {
mergeIn(selections.fields.values)
mergeIn(selections.fragments.values)
mergeIn(selections.namedFragments.values)
}

static func == (lhs: IR.EntityTreeScopeSelections, rhs: IR.EntityTreeScopeSelections) -> Bool {
lhs.fields == rhs.fields &&
lhs.fragments == rhs.fragments
lhs.namedFragments == rhs.namedFragments
}
}
}
Expand All @@ -352,7 +353,7 @@ extension IR.EntitySelectionTree {
/// programming error and will result in undefined behavior.
func mergeIn(
_ otherTree: IR.EntitySelectionTree,
from fragment: IR.FragmentSpread,
from fragment: IR.NamedFragmentSpread,
using entityStorage: IR.RootFieldEntityStorage
) {
let otherTreeCount = otherTree.rootTypePath.count
Expand Down Expand Up @@ -391,7 +392,7 @@ extension IR.EntitySelectionTree.EntityNode {

fileprivate func mergeIn(
_ fragmentTree: IR.EntitySelectionTree,
from fragment: IR.FragmentSpread,
from fragment: IR.NamedFragmentSpread,
with inclusionConditions: AnyOf<IR.InclusionConditions>?,
using entityStorage: IR.RootFieldEntityStorage
) {
Expand All @@ -407,7 +408,8 @@ extension IR.EntitySelectionTree.EntityNode {
for conditionGroup in inclusionConditions.elements {
let scope = IR.ScopeCondition(
type: rootTypesMatch ? nil : fragmentType,
conditions: conditionGroup
conditions: conditionGroup,
isDeferred: fragment.isDeferred
)
let nextNode = rootNodeToStartMerge.scopeConditionNode(for: scope)

Expand All @@ -421,7 +423,9 @@ extension IR.EntitySelectionTree.EntityNode {
} else {
let nextNode = rootTypesMatch ?
rootNodeToStartMerge :
rootNodeToStartMerge.scopeConditionNode(for: IR.ScopeCondition(type: fragmentType))
rootNodeToStartMerge.scopeConditionNode(
for: IR.ScopeCondition(type: fragmentType, isDeferred: fragment.isDeferred)
)

nextNode.mergeIn(
fragmentTree.rootNode,
Expand All @@ -433,7 +437,7 @@ extension IR.EntitySelectionTree.EntityNode {

fileprivate func mergeIn(
_ otherNode: IR.EntitySelectionTree.EntityNode,
from fragment: IR.FragmentSpread,
from fragment: IR.NamedFragmentSpread,
using entityStorage: IR.RootFieldEntityStorage
) {
switch otherNode.child {
Expand Down Expand Up @@ -470,7 +474,7 @@ extension IR.EntitySelectionTree.EntityNode {

fileprivate func mergeIn(
_ selections: Selections,
from fragment: IR.FragmentSpread,
from fragment: IR.NamedFragmentSpread,
using entityStorage: IR.RootFieldEntityStorage
) {
for (source, selections) in selections {
Expand Down Expand Up @@ -501,23 +505,24 @@ extension IR.EntitySelectionTree.EntityNode {
}
}

let fragments = selections.fragments.mapValues { oldFragment -> IR.FragmentSpread in
let fragments = selections.namedFragments.mapValues { oldFragment -> IR.NamedFragmentSpread in
let entity = entityStorage.entity(
for: oldFragment.typeInfo.entity,
inFragmentSpreadAtTypePath: fragment.typeInfo
)
return IR.FragmentSpread(
return IR.NamedFragmentSpread(
fragment: oldFragment.fragment,
typeInfo: IR.SelectionSet.TypeInfo(
entity: entity,
scopePath: oldFragment.typeInfo.scopePath
),
inclusionConditions: oldFragment.inclusionConditions
inclusionConditions: oldFragment.inclusionConditions,
isDeferred: oldFragment.isDeferred
)
}

self.mergeIn(
IR.EntityTreeScopeSelections(fields: fields, fragments: fragments),
IR.EntityTreeScopeSelections(fields: fields, namedFragments: fragments),
from: newSource
)
}
Expand Down Expand Up @@ -577,7 +582,7 @@ extension IR.EntityTreeScopeSelections: CustomDebugStringConvertible {
var debugDescription: String {
"""
Fields: \(fields.values.elements)
Fragments: \(fragments.values.elements.description)
Fragments: \(namedFragments.values.elements.description)
"""
}
}
2 changes: 1 addition & 1 deletion Sources/ApolloCodegenLib/IR/IR+InclusionConditions.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import OrderedCollections

extension IR {

/// A condition representing an `@include` or `@skip` directive to determine if a field
/// or fragment should be included.
struct InclusionCondition: Hashable, CustomDebugStringConvertible {
Expand Down
33 changes: 19 additions & 14 deletions Sources/ApolloCodegenLib/IR/IR+RootFieldBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ extension IR {
return entity
}

fileprivate func mergeAllSelectionsIntoEntitySelectionTrees(from fragmentSpread: FragmentSpread) {
fileprivate func mergeAllSelectionsIntoEntitySelectionTrees(from fragmentSpread: NamedFragmentSpread) {
for (_, fragmentEntity) in fragmentSpread.fragment.entities {
let entity = entity(for: fragmentEntity, inFragmentSpreadAtTypePath: fragmentSpread.typeInfo)
entity.selectionTree.mergeIn(fragmentEntity.selectionTree, from: fragmentSpread, using: self)
Expand Down Expand Up @@ -179,7 +179,7 @@ extension IR {
)

} else {
let irTypeCase = buildConditionalSelectionSet(
let irTypeCase = buildInlineFragmentSpread(
from: inlineSelectionSet,
with: scope,
inParentTypePath: typeInfo
Expand All @@ -200,15 +200,15 @@ extension IR {
let matchesScope = selectionSetScope.matches(scope)

if matchesScope {
let irFragmentSpread = buildFragmentSpread(
let irFragmentSpread = buildNamedFragmentSpread(
fromFragment: fragmentSpread,
with: scope,
spreadIntoParentWithTypePath: typeInfo
)
target.mergeIn(irFragmentSpread)

} else {
let irTypeCaseEnclosingFragment = buildConditionalSelectionSet(
let irTypeCaseEnclosingFragment = buildInlineFragmentSpread(
from: CompilationResult.SelectionSet(
parentType: fragmentSpread.parentType,
selections: [selection]
Expand All @@ -221,7 +221,7 @@ extension IR {

if matchesType {
typeInfo.entity.selectionTree.mergeIn(
selections: irTypeCaseEnclosingFragment.selections.direct.unsafelyUnwrapped.readOnlyView,
selections: irTypeCaseEnclosingFragment.selectionSet.selections.direct.unsafelyUnwrapped.readOnlyView,
with: typeInfo
)
}
Expand Down Expand Up @@ -313,11 +313,11 @@ extension IR {
return irSelectionSet
}

private func buildConditionalSelectionSet(
private func buildInlineFragmentSpread(
from selectionSet: CompilationResult.SelectionSet?,
with scopeCondition: ScopeCondition,
inParentTypePath enclosingTypeInfo: SelectionSet.TypeInfo
) -> SelectionSet {
) -> InlineFragmentSpread {
let typePath = enclosingTypeInfo.scopePath.mutatingLast {
$0.appending(scopeCondition)
}
Expand All @@ -334,14 +334,18 @@ extension IR {
from: selectionSet
)
}
return irSelectionSet
}

private func buildFragmentSpread(
return InlineFragmentSpread(
selectionSet: irSelectionSet,
isDeferred: scopeCondition.isDeferred
)
}

private func buildNamedFragmentSpread(
fromFragment fragmentSpread: CompilationResult.FragmentSpread,
with scopeCondition: ScopeCondition,
spreadIntoParentWithTypePath parentTypeInfo: SelectionSet.TypeInfo
) -> FragmentSpread {
) -> NamedFragmentSpread {
let fragment = ir.build(fragment: fragmentSpread.fragment)
referencedFragments.append(fragment)
referencedFragments.append(contentsOf: fragment.referencedFragments)
Expand All @@ -357,14 +361,15 @@ extension IR {
scopePath: scopePath
)

let fragmentSpread = FragmentSpread(
let fragmentSpread = NamedFragmentSpread(
fragment: fragment,
typeInfo: typeInfo,
inclusionConditions: AnyOf(scopeCondition.conditions)
inclusionConditions: AnyOf(scopeCondition.conditions),
isDeferred: scopeCondition.isDeferred
)

entityStorage.mergeAllSelectionsIntoEntitySelectionTrees(from: fragmentSpread)

return fragmentSpread
}

Expand Down
14 changes: 10 additions & 4 deletions Sources/ApolloCodegenLib/IR/IR+SelectionSet.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
extension IR {
@dynamicMemberLookup
class SelectionSet: Equatable, CustomDebugStringConvertible {
class SelectionSet: Hashable, CustomDebugStringConvertible {
class TypeInfo: Hashable, CustomDebugStringConvertible {
/// The entity that the `selections` are being selected on.
///
Expand Down Expand Up @@ -141,9 +141,15 @@ extension IR {
}

static func ==(lhs: IR.SelectionSet, rhs: IR.SelectionSet) -> Bool {
lhs.typeInfo.entity === rhs.typeInfo.entity &&
lhs.typeInfo.scopePath == rhs.typeInfo.scopePath &&
lhs.selections.direct == rhs.selections.direct
lhs.typeInfo === rhs.typeInfo &&
lhs.selections.direct === rhs.selections.direct
}

func hash(into hasher: inout Hasher) {
hasher.combine(typeInfo)
if let directSelections = selections.direct {
hasher.combine(ObjectIdentifier(directSelections))
}
}

subscript<T>(dynamicMember keyPath: KeyPath<TypeInfo, T>) -> T {
Expand Down
Loading

0 comments on commit 4147f78

Please sign in to comment.