Skip to content

Commit

Permalink
Make ReflectionAttribute generic
Browse files Browse the repository at this point in the history
  • Loading branch information
vhenzl committed Aug 24, 2021
1 parent 5010ef4 commit 4c4a9bf
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 0 deletions.
6 changes: 6 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ parameters:
bootstrap: null
bootstrapFiles:
- ../stubs/runtime/ReflectionUnionType.php
- ../stubs/runtime/ReflectionAttribute.php
- ../stubs/runtime/Attribute.php
excludes_analyse: []
excludePaths: null
Expand Down Expand Up @@ -1492,6 +1493,11 @@ services:
- phpstan.broker.dynamicMethodReturnTypeExtension
- phpstan.broker.dynamicStaticMethodReturnTypeExtension

-
class: PHPStan\Type\Php\ReflectionClassGetAttributesMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

exceptionTypeResolver:
class: PHPStan\Rules\Exceptions\ExceptionTypeResolver
factory: @PHPStan\Rules\Exceptions\DefaultExceptionTypeResolver
Expand Down
1 change: 1 addition & 0 deletions conf/config.stubFiles.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
parameters:
stubFiles:
- ../stubs/ReflectionAttribute.stub
- ../stubs/ReflectionClass.stub
- ../stubs/iterable.stub
- ../stubs/ArrayObject.stub
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;

class ReflectionClassGetAttributesMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension
{

public function getClass(): string
{
return \ReflectionClass::class;
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'getAttributes';
}

public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
if (count($methodCall->args) === 0) {
return $this->getDefaultReturnType($scope, $methodCall, $methodReflection);
}
$argType = $scope->getType($methodCall->args[0]->value);

if ($argType instanceof ConstantStringType) {
$classType = new ObjectType($argType->getValue());
} elseif ($argType instanceof GenericClassStringType) {
$classType = $argType->getGenericType();
} else {
return $this->getDefaultReturnType($scope, $methodCall, $methodReflection);
}

return new ArrayType(new MixedType(), new GenericObjectType(\ReflectionAttribute::class, [$classType]));
}

private function getDefaultReturnType(Scope $scope, MethodCall $methodCall, MethodReflection $methodReflection): Type
{
return ParametersAcceptorSelector::selectFromArgs(
$scope,
$methodCall->args,
$methodReflection->getVariants()
)->getReturnType();
}

}
21 changes: 21 additions & 0 deletions stubs/ReflectionAttribute.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/**
* @template-covariant T of object
*/
class ReflectionAttribute
{
/**
* @return class-string<T>
*/
public function getName() : string
{
}

/**
* @return T
*/
public function newInstance() : object
{
}
}
6 changes: 6 additions & 0 deletions stubs/ReflectionClass.stub
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,10 @@ class ReflectionClass
*/
public function newInstanceWithoutConstructor();

/**
* @return array<ReflectionAttribute<object>>
*/
public function getAttributes(?string $name = null, int $flags = 0)
{
}
}
38 changes: 38 additions & 0 deletions stubs/runtime/ReflectionAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

if (\PHP_VERSION_ID < 80000) {
if (class_exists('ReflectionAttribute', false)) {
return;
}

final class ReflectionAttribute
{
public function getName(): string
{
}

public function getTarget(): int
{
}

public function isRepeated(): bool
{
}

public function getArguments(): array
{
}

public function newInstance(): object
{
}

private function __clone()
{
}

private function __construct()
{
}
}
}
4 changes: 4 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,10 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3379.php');
}

if (PHP_VERSION_ID >= 80000) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/reflectionclass-issue-5511-php8.php');
}

yield from $this->gatherAssertTypes(__DIR__ . '/data/modulo-operator.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/literal-string.php');
Expand Down
51 changes: 51 additions & 0 deletions tests/PHPStan/Analyser/data/reflectionclass-issue-5511-php8.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php declare(strict_types=1);

namespace Issue5511;

use function PHPStan\Testing\assertType;

#[\Attribute]
class Abc
{
}

#[Abc]
class X
{
}

/**
* @param string $str
* @param class-string $className
* @param class-string<Abc> $genericClassName
*/
function testGetAttributes(string $str, string $className, string $genericClassName): void
{
$class = new \ReflectionClass(X::class);

$attrsAll = $class->getAttributes();
$attrsAbc1 = $class->getAttributes(Abc::class);
$attrsAbc2 = $class->getAttributes(Abc::class, \ReflectionAttribute::IS_INSTANCEOF);
$attrsGCN = $class->getAttributes($genericClassName);
$attrsCN = $class->getAttributes($className);
$attrsStr = $class->getAttributes($str);
$attrsNonsense = $class->getAttributes("some random string");

assertType('array<ReflectionAttribute<object>>', $attrsAll);
assertType('array<ReflectionAttribute<Issue5511\Abc>>', $attrsAbc1);
assertType('array<ReflectionAttribute<Issue5511\Abc>>', $attrsAbc2);
assertType('array<ReflectionAttribute<Issue5511\Abc>>', $attrsGCN);
assertType('array<ReflectionAttribute<object>>', $attrsCN);
assertType('array<ReflectionAttribute<object>>', $attrsStr);
assertType('array<ReflectionAttribute<some random string>>', $attrsNonsense);
}

/**
* @param \ReflectionAttribute<Abc> $ra
*/
function testNewInstance(\ReflectionAttribute $ra): void
{
assertType('ReflectionAttribute<Issue5511\\Abc>', $ra);
$abc = $ra->newInstance();
assertType(Abc::class, $abc);
}
1 change: 1 addition & 0 deletions tests/PHPStan/Command/CommandHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ public function dataParameters(): array
'bootstrap' => __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php',
'bootstrapFiles' => [
realpath(__DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php'),
realpath(__DIR__ . '/../../../stubs/runtime/ReflectionAttribute.php'),
realpath(__DIR__ . '/../../../stubs/runtime/Attribute.php'),
__DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php',
],
Expand Down

0 comments on commit 4c4a9bf

Please sign in to comment.