Skip to content

Commit

Permalink
Leveraging improved ReflectionType API from Roave/BetterReflection#415
Browse files Browse the repository at this point in the history
  • Loading branch information
uzibhalepu authored and Ocramius committed Apr 16, 2018
1 parent 7b736ed commit 8d7c35a
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 165 deletions.
10 changes: 2 additions & 8 deletions src/Comparator/Variance/TypeIsContravariant.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

namespace Roave\ApiCompare\Comparator\Variance;

use Roave\BetterReflection\Reflection\ReflectionClass;
use Roave\BetterReflection\Reflection\ReflectionType;
use Roave\BetterReflection\Reflector\ClassReflector;
use function in_array;
use function strtolower;

Expand All @@ -19,7 +17,6 @@
final class TypeIsContravariant
{
public function __invoke(
ClassReflector $reflector,
?ReflectionType $type,
?ReflectionType $comparedType
) : bool {
Expand Down Expand Up @@ -66,12 +63,9 @@ public function __invoke(
return false;
}

/** @var ReflectionClass $typeReflectionClass */
$typeReflectionClass = $reflector->reflect($typeAsString);
/** @var ReflectionClass $comparedTypeReflectionClass */
$comparedTypeReflectionClass = $reflector->reflect($comparedTypeAsString);
$typeReflectionClass = $type->targetReflectionClass();

if ($comparedTypeReflectionClass->isInterface()) {
if ($comparedType->targetReflectionClass()->isInterface()) {
return $typeReflectionClass->implementsInterface($comparedTypeAsString);
}

Expand Down
13 changes: 3 additions & 10 deletions src/Comparator/Variance/TypeIsCovariant.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
final class TypeIsCovariant
{
public function __invoke(
ClassReflector $reflector,
?ReflectionType $type,
?ReflectionType $comparedType
) : bool {
Expand Down Expand Up @@ -60,10 +59,7 @@ public function __invoke(
}

if (strtolower($typeAsString) === 'iterable' && ! $comparedType->isBuiltin()) {
/** @var ReflectionClass $comparedTypeReflectionClass */
$comparedTypeReflectionClass = $reflector->reflect($comparedTypeAsString);

if ($comparedTypeReflectionClass->implementsInterface(\Traversable::class)) {
if ($comparedType->targetReflectionClass()->implementsInterface(\Traversable::class)) {
// `iterable` can be restricted via any `Iterator` implementation
return true;
}
Expand All @@ -79,12 +75,9 @@ public function __invoke(
return false;
}

/** @var ReflectionClass $typeReflectionClass */
$typeReflectionClass = $reflector->reflect($typeAsString);
/** @var ReflectionClass $comparedTypeReflectionClass */
$comparedTypeReflectionClass = $reflector->reflect($comparedTypeAsString);
$comparedTypeReflectionClass = $comparedType->targetReflectionClass();

if ($typeReflectionClass->isInterface()) {
if ($type->targetReflectionClass()->isInterface()) {
return $comparedTypeReflectionClass->implementsInterface($typeAsString);
}

Expand Down
150 changes: 75 additions & 75 deletions test/unit/Comparator/Variance/TypeIsContravariantTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ public function testContravariance(
?ReflectionType $newType,
bool $expectedToBeContravariant
) : void {
self::assertSame(
$expectedToBeContravariant,
(new TypeIsContravariant())
->__invoke($type, $newType)
);
}

public function checkedTypes() : array
{
$reflector = new ClassReflector(new StringSourceLocator(
<<<'PHP'
<?php
Expand All @@ -36,16 +45,7 @@ class CClass extends BClass {}
,
(new BetterReflection())->astLocator()
));

self::assertSame(
$expectedToBeContravariant,
(new TypeIsContravariant())
->__invoke($reflector, $type, $newType)
);
}

public function checkedTypes() : array
{

return [
'no type to no type is contravariant with itself' => [
null,
Expand All @@ -54,149 +54,157 @@ public function checkedTypes() : array
],
'no type to void type is not contravariant' => [
null,
ReflectionType::createFromType('void', false),
ReflectionType::createFromTypeAndReflector('void', false, $reflector),
false,
],
'void type to no type is contravariant' => [
ReflectionType::createFromType('void', false),
ReflectionType::createFromTypeAndReflector('void', false, $reflector),
null,
true,
],
'void type to scalar type is contravariant' => [
ReflectionType::createFromType('void', false),
ReflectionType::createFromType('string', false),
ReflectionType::createFromTypeAndReflector('void', false, $reflector),
ReflectionType::createFromTypeAndReflector('string', false, $reflector),
true,
],
'void type to class type is contravariant' => [
ReflectionType::createFromType('void', false),
ReflectionType::createFromType('AClass', false),
ReflectionType::createFromTypeAndReflector('void', false, $reflector),
ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
true,
],
'scalar type to no type is contravariant' => [
ReflectionType::createFromType('string', false),
ReflectionType::createFromTypeAndReflector('string', false, $reflector),
null,
true,
],
'no type to scalar type is not contravariant' => [
null,
ReflectionType::createFromType('string', false),
ReflectionType::createFromTypeAndReflector('string', false, $reflector),
false,
],
'class type to no type is contravariant' => [
ReflectionType::createFromType('AClass', false),
ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
null,
true,
],
'no type to class type is not contravariant' => [
ReflectionType::createFromType('AClass', false),
ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
null,
true,
],
'iterable to array is not contravariant' => [
ReflectionType::createFromType('iterable', false),
ReflectionType::createFromType('array', false),
ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
ReflectionType::createFromTypeAndReflector('array', false, $reflector),
false,
],
'array to iterable is contravariant' => [
ReflectionType::createFromType('array', false),
ReflectionType::createFromType('iterable', false),
ReflectionType::createFromTypeAndReflector('array', false, $reflector),
ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
true,
],
'iterable to non-iterable class type is not contravariant' => [
ReflectionType::createFromType('iterable', false),
ReflectionType::createFromType('AnotherClassWithMultipleInterfaces', false),
ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
false,
],
'iterable to iterable class type is not contravariant' => [
ReflectionType::createFromType('iterable', false),
ReflectionType::createFromType('Iterator', false),
ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
false,
],
'non-iterable class to iterable type is not contravariant' => [
ReflectionType::createFromType('iterable', false),
ReflectionType::createFromType('AnotherClassWithMultipleInterfaces', false),
ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
false,
],
'iterable class type to iterable is not contravariant' => [
ReflectionType::createFromType('Iterator', false),
ReflectionType::createFromType('iterable', false),
ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
false,
],
'object to class type is not contravariant' => [
ReflectionType::createFromType('object', false),
ReflectionType::createFromType('AClass', false),
ReflectionType::createFromTypeAndReflector('object', false, $reflector),
ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
false,
],
'class type to object is contravariant' => [
ReflectionType::createFromType('AClass', false),
ReflectionType::createFromType('object', false),
ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
ReflectionType::createFromTypeAndReflector('object', false, $reflector),
true,
],
'class type to scalar type is not contravariant' => [
ReflectionType::createFromType('AClass', false),
ReflectionType::createFromType('string', false),
ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
ReflectionType::createFromTypeAndReflector('string', false, $reflector),
false,
],
'scalar type to class type is not contravariant' => [
ReflectionType::createFromType('string', false),
ReflectionType::createFromType('AClass', false),
ReflectionType::createFromTypeAndReflector('string', false, $reflector),
ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
false,
],
'scalar type (string) to different scalar type (int) is not contravariant' => [
ReflectionType::createFromType('string', false),
ReflectionType::createFromType('int', false),
ReflectionType::createFromTypeAndReflector('string', false, $reflector),
ReflectionType::createFromTypeAndReflector('int', false, $reflector),
false,
],
'scalar type (int) to different scalar type (float) is not contravariant' => [
ReflectionType::createFromType('int', false),
ReflectionType::createFromType('float', false),
ReflectionType::createFromTypeAndReflector('int', false, $reflector),
ReflectionType::createFromTypeAndReflector('float', false, $reflector),
false,
],
'object type to scalar type is not contravariant' => [
ReflectionType::createFromType('object', false),
ReflectionType::createFromType('string', false),
ReflectionType::createFromTypeAndReflector('object', false, $reflector),
ReflectionType::createFromTypeAndReflector('string', false, $reflector),
false,
],
'scalar type to object type is not contravariant' => [
ReflectionType::createFromType('string', false),
ReflectionType::createFromType('object', false),
ReflectionType::createFromTypeAndReflector('string', false, $reflector),
ReflectionType::createFromTypeAndReflector('object', false, $reflector),
false,
],
'class to superclass is contravariant' => [
ReflectionType::createFromType('BClass', false),
ReflectionType::createFromType('AClass', false),
ReflectionType::createFromTypeAndReflector('BClass', false, $reflector),
ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
true,
],
'class to subclass is not contravariant' => [
ReflectionType::createFromType('BClass', false),
ReflectionType::createFromType('CClass', false),
ReflectionType::createFromTypeAndReflector('BClass', false, $reflector),
ReflectionType::createFromTypeAndReflector('CClass', false, $reflector),
false,
],
'class to implemented interface is contravariant' => [
ReflectionType::createFromType('AnotherClassWithMultipleInterfaces', false),
ReflectionType::createFromType('AnInterface', false),
ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
ReflectionType::createFromTypeAndReflector('AnInterface', false, $reflector),
true,
],
'class to not implemented interface is not contravariant' => [
ReflectionType::createFromType('AnotherClassWithMultipleInterfaces', false),
ReflectionType::createFromType('Traversable', false),
ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
ReflectionType::createFromTypeAndReflector('Traversable', false, $reflector),
false,
],
'interface to parent interface is contravariant' => [
ReflectionType::createFromType('Iterator', false),
ReflectionType::createFromType('Traversable', false),
ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
ReflectionType::createFromTypeAndReflector('Traversable', false, $reflector),
true,
],
'interface to child interface is contravariant' => [
ReflectionType::createFromType('Traversable', false),
ReflectionType::createFromType('Iterator', false),
ReflectionType::createFromTypeAndReflector('Traversable', false, $reflector),
ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
false,
],
];
}

/** @dataProvider existingTypes */
public function testContravarianceConsidersSameTypeAlwaysContravariant(?ReflectionType $type) : void
{
self::assertTrue(
(new TypeIsContravariant())
->__invoke($type, $type)
);
}

public function existingTypes() : array
{
$reflector = new ClassReflector(new StringSourceLocator(
<<<'PHP'
Expand All @@ -209,21 +217,13 @@ class AClass {}
(new BetterReflection())->astLocator()
));

self::assertTrue(
(new TypeIsContravariant())
->__invoke($reflector, $type, $type)
);
}

public function existingTypes() : array
{
return array_merge(
[[null]],
array_merge(...array_map(
function (string $type) : array {
function (string $type) use ($reflector) : array {
return [
[ReflectionType::createFromType($type, false)],
[ReflectionType::createFromType($type, true)],
[ReflectionType::createFromTypeAndReflector($type, false, $reflector)],
[ReflectionType::createFromTypeAndReflector($type, true, $reflector)],
];
},
[
Expand All @@ -244,8 +244,6 @@ function (string $type) : array {
/** @dataProvider existingNullableTypeStrings */
public function testContravarianceConsidersNullability(string $type) : void
{
$nullable = ReflectionType::createFromType($type, true);
$notNullable = ReflectionType::createFromType($type, false);
$reflector = new ClassReflector(new StringSourceLocator(
<<<'PHP'
<?php
Expand All @@ -256,11 +254,13 @@ class AClass {}
,
(new BetterReflection())->astLocator()
));
$nullable = ReflectionType::createFromTypeAndReflector($type, true, $reflector);
$notNullable = ReflectionType::createFromTypeAndReflector($type, false, $reflector);

$isContravariant = new TypeIsContravariant();

self::assertFalse($isContravariant->__invoke($reflector, $nullable, $notNullable));
self::assertTrue($isContravariant->__invoke($reflector, $notNullable, $nullable));
self::assertFalse($isContravariant->__invoke($nullable, $notNullable));
self::assertTrue($isContravariant->__invoke($notNullable, $nullable));
}

/** @return string[][] */
Expand Down
Loading

0 comments on commit 8d7c35a

Please sign in to comment.