mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-02-11 10:09:24 +03:00
Implemented covariant return types for interface fields (#23)
This commit is contained in:
parent
f1ddc98390
commit
8ab7a9a438
@ -14,6 +14,7 @@ use GraphQL\Type\Definition\Type;
|
|||||||
use GraphQL\Type\Definition\UnionType;
|
use GraphQL\Type\Definition\UnionType;
|
||||||
use GraphQL\Type\Definition\WrappingType;
|
use GraphQL\Type\Definition\WrappingType;
|
||||||
use GraphQL\Type\Introspection;
|
use GraphQL\Type\Introspection;
|
||||||
|
use GraphQL\Utils\TypeInfo;
|
||||||
|
|
||||||
class Schema
|
class Schema
|
||||||
{
|
{
|
||||||
@ -165,9 +166,9 @@ class Schema
|
|||||||
$objectField = $objectFieldMap[$fieldName];
|
$objectField = $objectFieldMap[$fieldName];
|
||||||
|
|
||||||
Utils::invariant(
|
Utils::invariant(
|
||||||
$this->_isEqualType($ifaceField->getType(), $objectField->getType()),
|
TypeInfo::isTypeSubTypeOf($this, $objectField->getType(), $ifaceField->getType()),
|
||||||
"$iface.$fieldName expects type \"{$ifaceField->getType()}\" but " .
|
"$iface.$fieldName expects type \"{$ifaceField->getType()}\" but " .
|
||||||
"$object.$fieldName provides type \"{$objectField->getType()}"
|
"$object.$fieldName provides type \"{$objectField->getType()}\"."
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($ifaceField->args as $ifaceArg) {
|
foreach ($ifaceField->args as $ifaceArg) {
|
||||||
@ -185,7 +186,7 @@ class Schema
|
|||||||
// Assert interface field arg type matches object field arg type.
|
// Assert interface field arg type matches object field arg type.
|
||||||
// (invariant)
|
// (invariant)
|
||||||
Utils::invariant(
|
Utils::invariant(
|
||||||
$this->_isEqualType($ifaceArg->getType(), $objectArg->getType()),
|
TypeInfo::isEqualType($ifaceArg->getType(), $objectArg->getType()),
|
||||||
"$iface.$fieldName($argName:) expects type \"{$ifaceArg->getType()}\" " .
|
"$iface.$fieldName($argName:) expects type \"{$ifaceArg->getType()}\" " .
|
||||||
"but $object.$fieldName($argName:) provides " .
|
"but $object.$fieldName($argName:) provides " .
|
||||||
"type \"{$objectArg->getType()}\""
|
"type \"{$objectArg->getType()}\""
|
||||||
@ -205,22 +206,6 @@ class Schema
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $typeA
|
|
||||||
* @param $typeB
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
protected function _isEqualType($typeA, $typeB)
|
|
||||||
{
|
|
||||||
if ($typeA instanceof NonNull && $typeB instanceof NonNull) {
|
|
||||||
return $this->_isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
|
||||||
}
|
|
||||||
if ($typeA instanceof ListOfType && $typeB instanceof ListOfType) {
|
|
||||||
return $this->_isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
|
||||||
}
|
|
||||||
return $typeA === $typeB;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ObjectType
|
* @return ObjectType
|
||||||
*/
|
*/
|
||||||
|
@ -8,6 +8,7 @@ use GraphQL\Language\AST\NamedType;
|
|||||||
use GraphQL\Language\AST\Node;
|
use GraphQL\Language\AST\Node;
|
||||||
use GraphQL\Language\AST\NonNullType;
|
use GraphQL\Language\AST\NonNullType;
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
|
use GraphQL\Type\Definition\CompositeType;
|
||||||
use GraphQL\Type\Definition\Directive;
|
use GraphQL\Type\Definition\Directive;
|
||||||
use GraphQL\Type\Definition\FieldArgument;
|
use GraphQL\Type\Definition\FieldArgument;
|
||||||
use GraphQL\Type\Definition\FieldDefinition;
|
use GraphQL\Type\Definition\FieldDefinition;
|
||||||
@ -24,6 +25,116 @@ use GraphQL\Utils;
|
|||||||
|
|
||||||
class TypeInfo
|
class TypeInfo
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Provided two types, return true if the types are equal (invariant).
|
||||||
|
*/
|
||||||
|
public static function isEqualType(Type $typeA, Type $typeB)
|
||||||
|
{
|
||||||
|
// Equivalent types are equal.
|
||||||
|
if ($typeA === $typeB) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If either type is non-null, the other must also be non-null.
|
||||||
|
if ($typeA instanceof NonNull && $typeB instanceof NonNull) {
|
||||||
|
return self::isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If either type is a list, the other must also be a list.
|
||||||
|
if ($typeA instanceof ListOfType && $typeB instanceof ListOfType) {
|
||||||
|
return self::isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise the types are not equal.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provided a type and a super type, return true if the first type is either
|
||||||
|
* equal or a subset of the second super type (covariant).
|
||||||
|
*/
|
||||||
|
static function isTypeSubTypeOf(Schema $schema, Type $maybeSubType, Type $superType)
|
||||||
|
{
|
||||||
|
// Equivalent type is a valid subtype
|
||||||
|
if ($maybeSubType === $superType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If superType is non-null, maybeSubType must also be nullable.
|
||||||
|
if ($superType instanceof NonNull) {
|
||||||
|
if ($maybeSubType instanceof NonNull) {
|
||||||
|
return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType->getWrappedType());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else if ($maybeSubType instanceof NonNull) {
|
||||||
|
// If superType is nullable, maybeSubType may be non-null.
|
||||||
|
return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If superType type is a list, maybeSubType type must also be a list.
|
||||||
|
if ($superType instanceof ListOfType) {
|
||||||
|
if ($maybeSubType instanceof ListOfType) {
|
||||||
|
return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType->getWrappedType());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else if ($maybeSubType instanceof ListOfType) {
|
||||||
|
// If superType is not a list, maybeSubType must also be not a list.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If superType type is an abstract type, maybeSubType type may be a currently
|
||||||
|
// possible object type.
|
||||||
|
if (Type::isAbstractType($superType) && $maybeSubType instanceof ObjectType && $schema->isPossibleType($superType, $maybeSubType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, the child type is not a valid subtype of the parent type.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provided two composite types, determine if they "overlap". Two composite
|
||||||
|
* types overlap when the Sets of possible concrete types for each intersect.
|
||||||
|
*
|
||||||
|
* This is often used to determine if a fragment of a given type could possibly
|
||||||
|
* be visited in a context of another type.
|
||||||
|
*
|
||||||
|
* This function is commutative.
|
||||||
|
*/
|
||||||
|
static function doTypesOverlap(Schema $schema, CompositeType $typeA, CompositeType $typeB)
|
||||||
|
{
|
||||||
|
// Equivalent types overlap
|
||||||
|
if ($typeA === $typeB) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($typeA instanceof InterfaceType || $typeA instanceof UnionType) {
|
||||||
|
if ($typeB instanceof InterfaceType || $typeB instanceof UnionType) {
|
||||||
|
// If both types are abstract, then determine if there is any intersection
|
||||||
|
// between possible concrete types of each.
|
||||||
|
foreach ($schema->getPossibleTypes($typeA) as $type) {
|
||||||
|
if ($schema->isPossibleType($typeB, $type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if the latter type is a possible concrete type of the former.
|
||||||
|
return $schema->isPossibleType($typeA, $typeB);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($typeB instanceof InterfaceType || $typeB instanceof UnionType) {
|
||||||
|
// Determine if the former type is a possible concrete type of the latter.
|
||||||
|
return $schema->isPossibleType($typeB, $typeA);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise the types do not overlap.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Schema $schema
|
* @param Schema $schema
|
||||||
* @param $inputTypeAst
|
* @param $inputTypeAst
|
||||||
|
Loading…
x
Reference in New Issue
Block a user