Skip to content

Commit

Permalink
Reflection: getReturnType(), getParameterType(), getPropertyType() re…
Browse files Browse the repository at this point in the history
…turn objects Type (BC break)
  • Loading branch information
dg committed Sep 24, 2021
1 parent abce05c commit 2680b18
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 146 deletions.
33 changes: 6 additions & 27 deletions src/Utils/Reflection.php
Expand Up @@ -51,51 +51,30 @@ public static function isClassKeyword(string $name): bool
/**
* Returns the type of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names.
* If the function does not have a return type, it returns null.
* If the function has union or intersection type, it throws Nette\InvalidStateException.
*/
public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
public static function getReturnType(\ReflectionFunctionAbstract $func): ?Type
{
$type = $func->getReturnType() ?? (PHP_VERSION_ID >= 80100 && $func instanceof \ReflectionMethod ? $func->getTentativeReturnType() : null);
return self::getType($func, $type);
return Type::fromReflection($func);
}


/**
* Returns the type of given parameter and normalizes `self` and `parent` to the actual class names.
* If the parameter does not have a type, it returns null.
* If the parameter has union or intersection type, it throws Nette\InvalidStateException.
*/
public static function getParameterType(\ReflectionParameter $param): ?string
public static function getParameterType(\ReflectionParameter $param): ?Type
{
return self::getType($param, $param->getType());
return Type::fromReflection($param);
}


/**
* Returns the type of given property and normalizes `self` and `parent` to the actual class names.
* If the property does not have a type, it returns null.
* If the property has union or intersection type, it throws Nette\InvalidStateException.
*/
public static function getPropertyType(\ReflectionProperty $prop): ?string
public static function getPropertyType(\ReflectionProperty $prop): ?Type
{
return self::getType($prop, $prop->getType());
}


private static function getType(\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection, ?\ReflectionType $type): ?string
{
if ($type === null) {
return null;

} elseif ($type instanceof \ReflectionNamedType) {
return Type::resolve($type->getName(), $reflection);

} elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
throw new Nette\InvalidStateException('The ' . self::toString($reflection) . ' is not expected to have a union or intersection type.');

} else {
throw new Nette\InvalidStateException('Unexpected type of ' . self::toString($reflection));
}
return Type::fromReflection($prop);
}


Expand Down
31 changes: 10 additions & 21 deletions tests/Utils/Reflection.getParameterType.81.phpt
Expand Up @@ -43,28 +43,17 @@ class AExt extends A
$method = new ReflectionMethod('A', 'method');
$params = $method->getParameters();

Assert::same('Undeclared', Reflection::getParameterType($params[0]));
Assert::same('Test\B', Reflection::getParameterType($params[1]));
Assert::same('array', Reflection::getParameterType($params[2]));
Assert::same('callable', Reflection::getParameterType($params[3]));
Assert::same('A', Reflection::getParameterType($params[4]));
Assert::same('Undeclared', (string) Reflection::getParameterType($params[0]));
Assert::same('Test\B', (string) Reflection::getParameterType($params[1]));
Assert::same('array', (string) Reflection::getParameterType($params[2]));
Assert::same('callable', (string) Reflection::getParameterType($params[3]));
Assert::same('A', (string) Reflection::getParameterType($params[4]));
Assert::null(Reflection::getParameterType($params[5]));
Assert::same('Test\B', Reflection::getParameterType($params[6]));
Assert::same('mixed', Reflection::getParameterType($params[7]));

Assert::exception(function () use ($params) {
Reflection::getParameterType($params[8]);
}, Nette\InvalidStateException::class, 'The $union in A::method() is not expected to have a union or intersection type.');

Assert::exception(function () use ($params) {
Reflection::getParameterType($params[9]);
}, Nette\InvalidStateException::class, 'The $nullableUnion in A::method() is not expected to have a union or intersection type.');

Assert::exception(function () use ($params) {
Reflection::getParameterType($params[10]);
}, Nette\InvalidStateException::class, 'The $intersection in A::method() is not expected to have a union or intersection type.');

Assert::same('?Test\B', (string) Reflection::getParameterType($params[6]));
Assert::same('mixed', (string) Reflection::getParameterType($params[7]));
Assert::same('A|array', (string) Reflection::getParameterType($params[8], false));
Assert::same('A|array|null', (string) Reflection::getParameterType($params[9], false));
$method = new ReflectionMethod('AExt', 'methodExt');
$params = $method->getParameters();

Assert::same('A', Reflection::getParameterType($params[0]));
Assert::same('A', (string) Reflection::getParameterType($params[0]));
25 changes: 8 additions & 17 deletions tests/Utils/Reflection.getParameterType.phpt
Expand Up @@ -41,25 +41,16 @@ class AExt extends A
$method = new ReflectionMethod('A', 'method');
$params = $method->getParameters();

Assert::same('Undeclared', Reflection::getParameterType($params[0]));
Assert::same('Test\B', Reflection::getParameterType($params[1]));
Assert::same('array', Reflection::getParameterType($params[2]));
Assert::same('callable', Reflection::getParameterType($params[3]));
Assert::same('A', Reflection::getParameterType($params[4]));
Assert::same('Undeclared', (string) Reflection::getParameterType($params[0]));
Assert::same('Test\B', (string) Reflection::getParameterType($params[1]));
Assert::same('array', (string) Reflection::getParameterType($params[2]));
Assert::same('callable', (string) Reflection::getParameterType($params[3]));
Assert::same('A', (string) Reflection::getParameterType($params[4]));
Assert::null(Reflection::getParameterType($params[5]));
Assert::same('Test\B', Reflection::getParameterType($params[6]));
Assert::same('mixed', Reflection::getParameterType($params[7]));

Assert::exception(function () use ($params) {
Reflection::getParameterType($params[8]);
}, Nette\InvalidStateException::class, 'The $union in A::method() is not expected to have a union or intersection type.');

Assert::exception(function () use ($params) {
Reflection::getParameterType($params[9]);
}, Nette\InvalidStateException::class, 'The $nullableUnion in A::method() is not expected to have a union or intersection type.');

Assert::same('?Test\B', (string) Reflection::getParameterType($params[6]));
Assert::same('mixed', (string) Reflection::getParameterType($params[7]));

$method = new ReflectionMethod('AExt', 'methodExt');
$params = $method->getParameters();

Assert::same('A', Reflection::getParameterType($params[0]));
Assert::same('A', (string) Reflection::getParameterType($params[0]));
29 changes: 9 additions & 20 deletions tests/Utils/Reflection.getPropertyType.81.phpt
Expand Up @@ -37,27 +37,16 @@ class AExt extends A
$class = new ReflectionClass('A');
$props = $class->getProperties();

Assert::same('Undeclared', Reflection::getPropertyType($props[0]));
Assert::same('Test\B', Reflection::getPropertyType($props[1]));
Assert::same('array', Reflection::getPropertyType($props[2]));
Assert::same('A', Reflection::getPropertyType($props[3]));
Assert::same('Undeclared', (string) Reflection::getPropertyType($props[0]));
Assert::same('Test\B', (string) Reflection::getPropertyType($props[1]));
Assert::same('array', (string) Reflection::getPropertyType($props[2]));
Assert::same('A', (string) Reflection::getPropertyType($props[3]));
Assert::null(Reflection::getPropertyType($props[4]));
Assert::same('Test\B', Reflection::getPropertyType($props[5]));
Assert::same('mixed', Reflection::getPropertyType($props[6]));

Assert::exception(function () use ($props) {
Reflection::getPropertyType($props[7]);
}, Nette\InvalidStateException::class, 'The A::$union is not expected to have a union or intersection type.');

Assert::exception(function () use ($props) {
Reflection::getPropertyType($props[8]);
}, Nette\InvalidStateException::class, 'The A::$nullableUnion is not expected to have a union or intersection type.');

Assert::exception(function () use ($props) {
Reflection::getPropertyType($props[9]);
}, Nette\InvalidStateException::class, 'The A::$intersection is not expected to have a union or intersection type.');

Assert::same('?Test\B', (string) Reflection::getPropertyType($props[5]));
Assert::same('mixed', (string) Reflection::getPropertyType($props[6]));
Assert::same('A|array', (string) Reflection::getPropertyType($props[7], false));
Assert::same('A|array|null', (string) Reflection::getPropertyType($props[8], false));
$class = new ReflectionClass('AExt');
$props = $class->getProperties();

Assert::same('A', Reflection::getPropertyType($props[0]));
Assert::same('A', (string) Reflection::getPropertyType($props[0]));
23 changes: 7 additions & 16 deletions tests/Utils/Reflection.getPropertyType.phpt
Expand Up @@ -35,23 +35,14 @@ class AExt extends A
$class = new ReflectionClass('A');
$props = $class->getProperties();

Assert::same('Undeclared', Reflection::getPropertyType($props[0]));
Assert::same('Test\B', Reflection::getPropertyType($props[1]));
Assert::same('array', Reflection::getPropertyType($props[2]));
Assert::same('A', Reflection::getPropertyType($props[3]));
Assert::same('Undeclared', (string) Reflection::getPropertyType($props[0]));
Assert::same('Test\B', (string) Reflection::getPropertyType($props[1]));
Assert::same('array', (string) Reflection::getPropertyType($props[2]));
Assert::same('A', (string) Reflection::getPropertyType($props[3]));
Assert::null(Reflection::getPropertyType($props[4]));
Assert::same('Test\B', Reflection::getPropertyType($props[5]));
Assert::same('mixed', Reflection::getPropertyType($props[6]));

Assert::exception(function () use ($props) {
Reflection::getPropertyType($props[7]);
}, Nette\InvalidStateException::class, 'The A::$union is not expected to have a union or intersection type.');

Assert::exception(function () use ($props) {
Reflection::getPropertyType($props[8]);
}, Nette\InvalidStateException::class, 'The A::$nullableUnion is not expected to have a union or intersection type.');

Assert::same('?Test\B', (string) Reflection::getPropertyType($props[5]));
Assert::same('mixed', (string) Reflection::getPropertyType($props[6]));
$class = new ReflectionClass('AExt');
$props = $class->getProperties();

Assert::same('A', Reflection::getPropertyType($props[0]));
Assert::same('A', (string) Reflection::getPropertyType($props[0]));
42 changes: 16 additions & 26 deletions tests/Utils/Reflection.getReturnType.81.phpt
Expand Up @@ -106,48 +106,38 @@ function intersectionType(): AExt&A

Assert::null(Reflection::getReturnType(new \ReflectionMethod(A::class, 'noType')));

Assert::same('Test\B', Reflection::getReturnType(new \ReflectionMethod(A::class, 'classType')));
Assert::same('Test\B', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'classType')));

Assert::same('string', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nativeType')));
Assert::same('string', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nativeType')));

Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(A::class, 'selfType')));
Assert::same('A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'selfType')));

Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(A::class, 'staticType')));
Assert::same('A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'staticType')));

Assert::same('Test\B', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableClassType')));
Assert::same('?Test\B', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableClassType')));

Assert::same('string', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableNativeType')));
Assert::same('?string', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableNativeType')));

Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableSelfType')));
Assert::same('?A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableSelfType')));

Assert::exception(function () {
Reflection::getReturnType(new \ReflectionMethod(A::class, 'unionType'));
}, Nette\InvalidStateException::class, 'The A::unionType() is not expected to have a union or intersection type.');
Assert::same('A|array', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'unionType')));

Assert::exception(function () {
Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableUnionType'));
}, Nette\InvalidStateException::class, 'The A::nullableUnionType() is not expected to have a union or intersection type.');
Assert::same('A|array|null', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableUnionType')));

Assert::exception(function () {
Reflection::getReturnType(new \ReflectionMethod(A::class, 'intersectionType'));
}, Nette\InvalidStateException::class, 'The A::intersectionType() is not expected to have a union or intersection type.');
Assert::same('AExt&A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'intersectionType')));

Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(AExt::class, 'parentTypeExt')));
Assert::same('A', (string) Reflection::getReturnType(new \ReflectionMethod(AExt::class, 'parentTypeExt')));

Assert::null(Reflection::getReturnType(new \ReflectionFunction('noType')));

Assert::same('Test\B', Reflection::getReturnType(new \ReflectionFunction('classType')));
Assert::same('Test\B', (string) Reflection::getReturnType(new \ReflectionFunction('classType')));

Assert::same('string', Reflection::getReturnType(new \ReflectionFunction('nativeType')));
Assert::same('string', (string) Reflection::getReturnType(new \ReflectionFunction('nativeType')));

Assert::exception(function () {
Reflection::getReturnType(new \ReflectionFunction('unionType'));
}, Nette\InvalidStateException::class, 'The unionType() is not expected to have a union or intersection type.');
Assert::same('A|array', (string) Reflection::getReturnType(new \ReflectionFunction('unionType')));

Assert::exception(function () {
Reflection::getReturnType(new \ReflectionFunction('intersectionType'));
}, Nette\InvalidStateException::class, 'The intersectionType() is not expected to have a union or intersection type.');
Assert::same('AExt&A', (string) Reflection::getReturnType(new \ReflectionFunction('intersectionType')));


// tentative type
Assert::same('int', Reflection::getReturnType(new \ReflectionMethod(\ArrayObject::class, 'count')));
Assert::same('int', (string) Reflection::getReturnType(new \ReflectionMethod(\ArrayObject::class, 'count')));
32 changes: 13 additions & 19 deletions tests/Utils/Reflection.getReturnType.phpt
Expand Up @@ -95,36 +95,30 @@ function unionType(): array|A

Assert::null(Reflection::getReturnType(new \ReflectionMethod(A::class, 'noType')));

Assert::same('Test\B', Reflection::getReturnType(new \ReflectionMethod(A::class, 'classType')));
Assert::same('Test\B', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'classType')));

Assert::same('string', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nativeType')));
Assert::same('string', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nativeType')));

Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(A::class, 'selfType')));
Assert::same('A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'selfType')));

Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(A::class, 'staticType')));
Assert::same('A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'staticType')));

Assert::same('Test\B', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableClassType')));
Assert::same('?Test\B', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableClassType')));

Assert::same('string', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableNativeType')));
Assert::same('?string', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableNativeType')));

Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableSelfType')));
Assert::same('?A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableSelfType')));

Assert::exception(function () {
Reflection::getReturnType(new \ReflectionMethod(A::class, 'unionType'));
}, Nette\InvalidStateException::class, 'The A::unionType() is not expected to have a union or intersection type.');
Assert::same('A|array', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'unionType')));

Assert::exception(function () {
Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableUnionType'));
}, Nette\InvalidStateException::class, 'The A::nullableUnionType() is not expected to have a union or intersection type.');
Assert::same('A|array|null', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableUnionType')));

Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(AExt::class, 'parentTypeExt')));
Assert::same('A', (string) Reflection::getReturnType(new \ReflectionMethod(AExt::class, 'parentTypeExt')));

Assert::null(Reflection::getReturnType(new \ReflectionFunction('noType')));

Assert::same('Test\B', Reflection::getReturnType(new \ReflectionFunction('classType')));
Assert::same('Test\B', (string) Reflection::getReturnType(new \ReflectionFunction('classType')));

Assert::same('string', Reflection::getReturnType(new \ReflectionFunction('nativeType')));
Assert::same('string', (string) Reflection::getReturnType(new \ReflectionFunction('nativeType')));

Assert::exception(function () {
Reflection::getReturnType(new \ReflectionFunction('unionType'));
}, Nette\InvalidStateException::class, 'The unionType() is not expected to have a union or intersection type.');
Assert::same('A|array', (string) Reflection::getReturnType(new \ReflectionFunction('unionType')));

0 comments on commit 2680b18

Please sign in to comment.