-
Notifications
You must be signed in to change notification settings - Fork 731
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
refactor: Selection set generation for @defer
#3176
Conversation
@@ -173,6 +181,27 @@ public class CompilationResult: JavaScriptObject { | |||
|
|||
lazy var directives: [Directive]? = self["directives"] | |||
|
|||
lazy var isDeferred: IsDeferred = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is duplicated for both InlineFragment
and FragmentSpread
, I'm not sure if there is a better way to do it; they're both just simple JavaScriptObject
types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protocol Deferrable {
var directives: [Directive]? { get }
}
fileprivate extension Deferrable where Self: JavaScriptObject {
func getIsDeferred() -> IsDeferred {
...
}
}
public class InlineFragment: Deferrable {
...
lazy var isDeferred: IsDeferred = getIsDeferred()
}
Wish we could easily namespace this Deferrable
protocol in CompilationResult
. Until we break CompilationResult
into it's own package, we might want to call the protocol CompilationResult_Deferrable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The protocol and extension can have the fileprivate
scope which is effectively the same as putting it into it's own package for this particular case.
@@ -54,6 +54,21 @@ extension IR { | |||
self.conditions = [condition] | |||
} | |||
|
|||
/// A failable initializer to convert from an array of `CompilationResult.InclusionCondition`. | |||
init?(_ conditions: [CompilationResult.InclusionCondition]?) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AnthonyMDev - I've added this since we determined that ScopeCondition
should match the inclusion conditions as well as the parent type. Problem is that the named fragment passed into the scope condition is from CompilationResult
and the two InclusionCondition
types can't be compared. This is a simple conversion similar to how we treat conversions for the IR.InclusionCondition.Result
type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we could instead have a custom ==
operator that will compare CompilationResult.InclusionConditions
to IR.InclusionConditions
. That might be a better solution than an initializer that should never be used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with including this initializer is that it doesn't have an accurate 1:1 way to map. For @skip
with no variable, we would be omitting the entire fragment. The preconditionFailure
here seems way too easy to accidentally make occur and cause a crash.
I think that a custom matching function that is fileprivate
to the file that is using it is a better approach. It's not an actual ==
, so I would make it a function with a more descriptive name.
@@ -3,9 +3,6 @@ import OrderedCollections | |||
|
|||
extension IR { | |||
|
|||
// TODO: Write tests that two inline fragments with same type and inclusion conditions, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking we should also be adding tests for this behavior to the IR building tests, not just the SelectionSetTemplateTests
. Making sure the actual IR is structured properly is important and having those tests makes it a LOT easier to debug and figure out the cause of bugs than just the template tests.
func DeferredProperties( | ||
_ definition: IR.Definition | ||
) -> TemplateString { | ||
func isDeferred(_ selectionSet: IR.SelectionSet) -> Bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AnthonyMDev - needing your feedback here whether this is the best way to be determining at an operation-level whether any fragments are deferred; I think it should be since we're only doing direct selections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this code should be valid, but I'm wondering if we could prevent another iteration through the entire selection set here.
What if we added hasDeferredFragments
to the RootFieldBuilder.Result
. We could keep track of this as a var
on the RootFieldBuilder
, and while building, if we encounter a deferred fragment, we just set that to true
.
private func scopeCondition( | ||
for conditionalSelectionSet: ConditionallyIncludable, | ||
in parentTypePath: SelectionSet.TypeInfo, | ||
isDeferred: CompilationResult.IsDeferred |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should change the type of this parameter to IR.IsDeferred
and do ``IsDeferred(compilationResult: namedFragment.isDeferred)` on line 247.
.field("__typename", String.self), | ||
.include(if: "filter", [ | ||
.inlineFragment(AsDogIfFilter.self, deferred: true), | ||
.inlineFragment(AsDogIfFilter.self, deferred: .if("other")), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like it's going to generate two separate selection sets both named AsDogIfFilter
, which is not going to compile. We're going to need to handle this more carefully.
@@ -3,9 +3,6 @@ import OrderedCollections | |||
|
|||
extension IR { | |||
|
|||
// TODO: Write tests that two inline fragments with same type and inclusion conditions, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking we should also be adding tests for this behavior to the IR building tests, not just the SelectionSetTemplateTests
. Making sure the actual IR is structured properly is important and having those tests makes it a LOT easier to debug and figure out the cause of bugs than just the template tests.
Sources/ApolloCodegenLib/Templates/RenderingHelpers/OperationTemplateRenderer.swift
Show resolved
Hide resolved
func DeferredProperties( | ||
_ definition: IR.Definition | ||
) -> TemplateString { | ||
func isDeferred(_ selectionSet: IR.SelectionSet) -> Bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this code should be valid, but I'm wondering if we could prevent another iteration through the entire selection set here.
What if we added hasDeferredFragments
to the RootFieldBuilder.Result
. We could keep track of this as a var
on the RootFieldBuilder
, and while building, if we encounter a deferred fragment, we just set that to true
.
type: type, | ||
conditions: inclusionResult.conditions, | ||
isDeferred: IsDeferred(compilationResult: isDeferred) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes also should really be tested in tests on the RootFieldBuilder
, not just the SelectionSetTemplateTests
.
Closes #3141
Introduces
IsDeferred
expression logic which is used in the selection set template to:hasDeferredFragments
propertydeferred
property to inline and named fragment selectionsIf there are other tests you think would be beneficial, I'm sure there are, please let me know and get them added.