Handle Fragment expansion with variables fully
jlouis opened this issue · comments
Background
Suppose we have a fragment
fragment M on Monster {
...
hitPoints(above: $foo)
...
}
Then this fragment is valid as a spread in any query Q
which defines
query Q($foo : Int) { ... }
But not in a query
query Q($foo: String) { ... }
Also, note that if we have another fragment
fragment R on Room {
description(language: $foo)
}
Then if language : Locale
you are not allowed to mix fragments M
and R
in the same query.
Other observations
- This holds transitively. If
M
refers to a subfragmentI on Iventory
and that fragment refers to$bar
, thenM
also refers to$bar
. - These tests must happen in the type checking phase.
Implementation
The reason this has been held off for a while is that it isn't that simple to implement:
- Introduce the notion of a type signature for
fragment
. This allows us to ask if a fragment "fits" when it is called by a fragment spread. - Inline spreads can just produce their signature and we can then check it afterwards.
- A fragment is implemented as a function from its signature to its expansion. Thus, the function signature is verified by checking if we can "call" the fragment from the spread with a valid type.
- When handling fragments, they can refer to other fragments. When this happens, we must run fragception and DFS the fragment world. We track already handled fragments in order to detect if we have a cycle in the fragment expansion. This runs in linear time over the fragments and is going to be reasonably fast. Also, it moves the cycle validation into the type checker where it belongs.
- Value coercion still happens at the execution phase for variables. So this will still work as expected.
Note: The right solution is to invert the checking:
Currently, we build a variable context and we use this to check that each variable use is valid in that context. In the new system, we "infer" a variable set and their types for each fragment, query, or mutation. Once we have a set of the variables which are referenced, the top level verification checks that the type is indeed a valid type by walking over the inferred set and checking it against the actual variable context. This allows for far simpler solution of issues #70, #71, #109 and #120 since these are now handled directly by the type checker.