You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
So here's the problem. When putting our inline fragment for NumberLiteral in the value edge. [1] We are shown an error [2] stating the following:
Fragment cannot be spread here as objects of type "Expression" can never be of type "NumberLiteral".
This is a bug because NumberLiteral implements Expression, so it would be entirely correct to convert from an Expression to a NumberLiteral.
However, this check is implemented with this code, inside the InlineFragment function (as this is a syntax tree visitor I believe):
// <snip>{InlineFragment(node){constfragType=context.getType();constparentType=context.getParentType();if(isCompositeType(fragType)&&isCompositeType(parentType)&&!doTypesOverlap(context.getSchema(),fragType,parentType)){constparentTypeStr=inspect(parentType);constfragTypeStr=inspect(fragType);context.reportError(newGraphQLError(`Fragment cannot be spread here as objects of type "${parentTypeStr}" can never be of type "${fragTypeStr}".`,{nodes: node},),);}},}// <snip>
in this function, doTypesOverlap(context.getSchema(), fragType, parentType) is doing the heavy lifting of whether NumberLiteral is a suitable subtype of Expression.
Let's take a look at doTypesOverlap()'s implemenation:
exportfunctiondoTypesOverlap(schema: GraphQLSchema,typeA: GraphQLCompositeType,typeB: GraphQLCompositeType,): boolean{// Equivalent types overlapif(typeA===typeB){returntrue;}if(isAbstractType(typeA)){if(isAbstractType(typeB)){// If both types are abstract, then determine if there is any intersection// between possible concrete types of each.returnschema.getPossibleTypes(typeA).some((type)=>schema.isSubType(typeB,type));}// Determine if the latter type is a possible concrete type of the former.returnschema.isSubType(typeA,typeB);}if(isAbstractType(typeB)){// Determine if the former type is a possible concrete type of the latter.returnschema.isSubType(typeB,typeA);}// Otherwise the types do not overlap.returnfalse;}
To help not paste the entire codebase here, isAbstractType(X) returns true if X is an interface type or a union type, false otherwise.
For further context, NumberLiteral and Expression are both interfaces.
So we would have for if (isAbstractType(typeA)) { and then into the if (isAbstractType(typeB)) { path.
Which brings us to the schema.getPossibleTypes(typeA), so what is schema.getPossibleTypes()? Oh, and typeA will be fragType from the InlineFragment function which ends up being NumberLiteral in our case.
Aha, so we just get the objects, which means the subtypes that are declared with the type keyword. Since NumberLiteral is an interface, we don't return it from this getPossibleTypes() function, so we never have the opportunity to check if NumberLiteral overlaps in the doTypesOverlap(). There is another property on the return type of this.getImplementations(abstractType) called implements which houses all the interfaces, including NumberLiteral, so really this getPossibleTypes() should be returning types and interfaces, when it only returns objects for now.
// If both types are abstract, then determine if there is any intersection
// between possible concrete types of each.
and I am thinking that maybe this behavior is correct, so if someone can confirm or deny this is a bug, I am willing to make a pull request to fix this.
Reference:
1
2
The text was updated successfully, but these errors were encountered:
I think you have encountered this error because within the linked schema, the interface NumberLiteral does not actually have any concrete types which implement it, such that at runtime, there are no object types that intersect both NumberLiteral and Expression, the error message being technically correct.
We do not want to directly simply check to see if one interface implements the other, as we want to allow for a case in which they don't, but there are still concrete runtime types that implement both.
Pull requests improving the error reporting in this area are welcome, but I am closing this for now.
Schema for more reference
So here's the problem. When putting our inline fragment for
NumberLiteral
in thevalue
edge. [1] We are shown an error [2] stating the following:This is a bug because
NumberLiteral
implementsExpression
, so it would be entirely correct to convert from anExpression
to aNumberLiteral
.However, this check is implemented with this code, inside the
InlineFragment
function (as this is a syntax tree visitor I believe):in this function,
doTypesOverlap(context.getSchema(), fragType, parentType)
is doing the heavy lifting of whetherNumberLiteral
is a suitable subtype ofExpression
.Let's take a look at
doTypesOverlap()
's implemenation:To help not paste the entire codebase here,
isAbstractType(X)
returns true ifX
is an interface type or a union type, false otherwise.For further context,
NumberLiteral
andExpression
are both interfaces.So we would have for
if (isAbstractType(typeA)) {
and then into theif (isAbstractType(typeB)) {
path.Which brings us to the
schema.getPossibleTypes(typeA)
, so what isschema.getPossibleTypes()
? Oh, andtypeA
will befragType
from theInlineFragment
function which ends up beingNumberLiteral
in our case.Aha, so we just get the objects, which means the subtypes that are declared with the
type
keyword. SinceNumberLiteral
is an interface, we don't return it from thisgetPossibleTypes()
function, so we never have the opportunity to check ifNumberLiteral
overlaps in thedoTypesOverlap()
. There is another property on the return type ofthis.getImplementations(abstractType)
calledimplements
which houses all the interfaces, includingNumberLiteral
, so really thisgetPossibleTypes()
should be returning types and interfaces, when it only returns objects for now.After writing all this, I read the comment here:
graphql-js/src/utilities/typeComparators.ts
Lines 102 to 103 in 3610786
Reference:
1
2
The text was updated successfully, but these errors were encountered: