Skip to content

Commit

Permalink
PhpDocTypeUtils: generic subtype of checking refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
JanTvrdik committed Aug 5, 2023
1 parent a0fb401 commit 3992166
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 27 deletions.
53 changes: 26 additions & 27 deletions src/Compiler/Type/PhpDocTypeUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -450,49 +450,43 @@ public static function isSubTypeOf(TypeNode $a, TypeNode $b): bool

private static function isSubTypeOfGeneric(GenericTypeNode $a, GenericTypeNode $b): bool
{
$def = self::getGenericTypeDefinition($a);
$typeDef = self::getGenericTypeDefinition($a);

if (strcasecmp($a->type->name, $b->type->name) === 0) {
return Arrays::every($b->genericTypes, static function (TypeNode $genericTypeB, int $idx) use ($a, $def): bool {
$genericTypeA = $a->genericTypes[$idx] ?? null;
$variance = $def['parameters'][$idx]['variance'] ?? null;
return Arrays::every($typeDef['parameters'] ?? [], static function (array $parameter, int $idx) use ($a, $b): bool {
$genericTypeA = $a->genericTypes[$idx] ?? $parameter['bound'] ?? new IdentifierTypeNode('mixed');
$genericTypeB = $b->genericTypes[$idx] ?? $parameter['bound'] ?? new IdentifierTypeNode('mixed');

if ($genericTypeA === null || $variance === null) {
return false;
}

return match ($variance) {
return match ($parameter['variance']) {
'in' => self::isSubTypeOf($genericTypeB, $genericTypeA),
'out' => self::isSubTypeOf($genericTypeA, $genericTypeB),
'inout' => self::isSubTypeOf($genericTypeA, $genericTypeB) && self::isSubTypeOf($genericTypeB, $genericTypeA),
default => throw new LogicException("Invalid variance {$parameter['variance']}"),
};
});
}

return Arrays::some($def['superTypes'] ?? [], static function (array $parameters, string $identifier) use ($a, $b): bool {
$resolvedParameters = Arrays::map($parameters, static function (int|TypeNode $typeOrPosition) use ($a): TypeNode {
return $typeOrPosition instanceof TypeNode
? $typeOrPosition
: $a->genericTypes[$typeOrPosition] ?? new IdentifierTypeNode('mixed');
});

$superType = new GenericTypeNode(new IdentifierTypeNode($identifier), $resolvedParameters);
return self::isSubTypeOfGeneric($superType, $b);
$superTypes = isset($typeDef['superTypes']) ? $typeDef['superTypes']($a->genericTypes) : [];
return Arrays::some($superTypes, static function (TypeNode $superType) use ($b): bool {
return self::isSubTypeOf($superType, $b);
});
}

/**
* @return array{
* superTypes?: array<string, list<int | TypeNode>>,
* superTypes?: callable(array<TypeNode>): list<TypeNode>,
* parameters?: list<array{variance: 'in' | 'out' | 'inout', bound?: TypeNode}>,
* }
*/
private static function getGenericTypeDefinition(GenericTypeNode $type): array
{
return match ($type->type->name) {
'array' => [
'superTypes' => [
'iterable' => [0, 1],
'superTypes' => static fn (array $types): array => [
new GenericTypeNode(new IdentifierTypeNode('iterable'), [
$types[0] ?? new IdentifierTypeNode('mixed'),
$types[1] ?? new IdentifierTypeNode('mixed'),
]),
],
'parameters' => [
['variance' => 'out'],
Expand All @@ -501,8 +495,11 @@ private static function getGenericTypeDefinition(GenericTypeNode $type): array
],

'list' => [
'superTypes' => [
'array' => [new IdentifierTypeNode('int'), 0],
'superTypes' => static fn (array $types): array => [
new GenericTypeNode(new IdentifierTypeNode('array'), [
new IdentifierTypeNode('int'),
$types[0] ?? new IdentifierTypeNode('mixed'),
]),
],
'parameters' => [
['variance' => 'out'],
Expand All @@ -516,17 +513,19 @@ private static function getGenericTypeDefinition(GenericTypeNode $type): array
],

OptionalSome::class => [
'superTypes' => [
Optional::class => [0],
'superTypes' => static fn (array $types): array => [
new GenericTypeNode(new IdentifierTypeNode(Optional::class), [
$types[0] ?? new IdentifierTypeNode('mixed'),
]),
],
'parameters' => [
['variance' => 'out'],
],
],

OptionalNone::class => [
'superTypes' => [
Optional::class => [new IdentifierTypeNode('never')],
'superTypes' => static fn (): array => [
new GenericTypeNode(new IdentifierTypeNode(Optional::class), [new IdentifierTypeNode('never')]),
],
],

Expand Down
14 changes: 14 additions & 0 deletions tests/Compiler/Type/PhpDocTypeUtilsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,20 @@ private static function provideIsSubTypeOfDataInner(): iterable
],
];

yield 'array<mixed>' => [
'true' => [
'array<mixed>',
'array<int>',
'array',
'never',
],

'false' => [
'int',
'iterable<mixed>',
],
];

yield 'array<string, int>' => [
'true' => [
'array<string, int>',
Expand Down

0 comments on commit 3992166

Please sign in to comment.