Skip to content

Commit 4c4a9bf

Browse files
authored
Make ReflectionAttribute generic
1 parent 5010ef4 commit 4c4a9bf

9 files changed

+186
-0
lines changed

conf/config.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ parameters:
44
bootstrap: null
55
bootstrapFiles:
66
- ../stubs/runtime/ReflectionUnionType.php
7+
- ../stubs/runtime/ReflectionAttribute.php
78
- ../stubs/runtime/Attribute.php
89
excludes_analyse: []
910
excludePaths: null
@@ -1492,6 +1493,11 @@ services:
14921493
- phpstan.broker.dynamicMethodReturnTypeExtension
14931494
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
14941495

1496+
-
1497+
class: PHPStan\Type\Php\ReflectionClassGetAttributesMethodReturnTypeExtension
1498+
tags:
1499+
- phpstan.broker.dynamicMethodReturnTypeExtension
1500+
14951501
exceptionTypeResolver:
14961502
class: PHPStan\Rules\Exceptions\ExceptionTypeResolver
14971503
factory: @PHPStan\Rules\Exceptions\DefaultExceptionTypeResolver

conf/config.stubFiles.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
parameters:
22
stubFiles:
3+
- ../stubs/ReflectionAttribute.stub
34
- ../stubs/ReflectionClass.stub
45
- ../stubs/iterable.stub
56
- ../stubs/ArrayObject.stub
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PhpParser\Node\Expr\MethodCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\MethodReflection;
8+
use PHPStan\Reflection\ParametersAcceptorSelector;
9+
use PHPStan\Type\ArrayType;
10+
use PHPStan\Type\Constant\ConstantStringType;
11+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
12+
use PHPStan\Type\Generic\GenericClassStringType;
13+
use PHPStan\Type\Generic\GenericObjectType;
14+
use PHPStan\Type\MixedType;
15+
use PHPStan\Type\ObjectType;
16+
use PHPStan\Type\Type;
17+
18+
class ReflectionClassGetAttributesMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension
19+
{
20+
21+
public function getClass(): string
22+
{
23+
return \ReflectionClass::class;
24+
}
25+
26+
public function isMethodSupported(MethodReflection $methodReflection): bool
27+
{
28+
return $methodReflection->getName() === 'getAttributes';
29+
}
30+
31+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
32+
{
33+
if (count($methodCall->args) === 0) {
34+
return $this->getDefaultReturnType($scope, $methodCall, $methodReflection);
35+
}
36+
$argType = $scope->getType($methodCall->args[0]->value);
37+
38+
if ($argType instanceof ConstantStringType) {
39+
$classType = new ObjectType($argType->getValue());
40+
} elseif ($argType instanceof GenericClassStringType) {
41+
$classType = $argType->getGenericType();
42+
} else {
43+
return $this->getDefaultReturnType($scope, $methodCall, $methodReflection);
44+
}
45+
46+
return new ArrayType(new MixedType(), new GenericObjectType(\ReflectionAttribute::class, [$classType]));
47+
}
48+
49+
private function getDefaultReturnType(Scope $scope, MethodCall $methodCall, MethodReflection $methodReflection): Type
50+
{
51+
return ParametersAcceptorSelector::selectFromArgs(
52+
$scope,
53+
$methodCall->args,
54+
$methodReflection->getVariants()
55+
)->getReturnType();
56+
}
57+
58+
}

stubs/ReflectionAttribute.stub

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/**
4+
* @template-covariant T of object
5+
*/
6+
class ReflectionAttribute
7+
{
8+
/**
9+
* @return class-string<T>
10+
*/
11+
public function getName() : string
12+
{
13+
}
14+
15+
/**
16+
* @return T
17+
*/
18+
public function newInstance() : object
19+
{
20+
}
21+
}

stubs/ReflectionClass.stub

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,10 @@ class ReflectionClass
4242
*/
4343
public function newInstanceWithoutConstructor();
4444

45+
/**
46+
* @return array<ReflectionAttribute<object>>
47+
*/
48+
public function getAttributes(?string $name = null, int $flags = 0)
49+
{
50+
}
4551
}

stubs/runtime/ReflectionAttribute.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
if (\PHP_VERSION_ID < 80000) {
4+
if (class_exists('ReflectionAttribute', false)) {
5+
return;
6+
}
7+
8+
final class ReflectionAttribute
9+
{
10+
public function getName(): string
11+
{
12+
}
13+
14+
public function getTarget(): int
15+
{
16+
}
17+
18+
public function isRepeated(): bool
19+
{
20+
}
21+
22+
public function getArguments(): array
23+
{
24+
}
25+
26+
public function newInstance(): object
27+
{
28+
}
29+
30+
private function __clone()
31+
{
32+
}
33+
34+
private function __construct()
35+
{
36+
}
37+
}
38+
}

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,10 @@ public function dataFileAsserts(): iterable
468468
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3379.php');
469469
}
470470

471+
if (PHP_VERSION_ID >= 80000) {
472+
yield from $this->gatherAssertTypes(__DIR__ . '/data/reflectionclass-issue-5511-php8.php');
473+
}
474+
471475
yield from $this->gatherAssertTypes(__DIR__ . '/data/modulo-operator.php');
472476

473477
yield from $this->gatherAssertTypes(__DIR__ . '/data/literal-string.php');
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Issue5511;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
#[\Attribute]
8+
class Abc
9+
{
10+
}
11+
12+
#[Abc]
13+
class X
14+
{
15+
}
16+
17+
/**
18+
* @param string $str
19+
* @param class-string $className
20+
* @param class-string<Abc> $genericClassName
21+
*/
22+
function testGetAttributes(string $str, string $className, string $genericClassName): void
23+
{
24+
$class = new \ReflectionClass(X::class);
25+
26+
$attrsAll = $class->getAttributes();
27+
$attrsAbc1 = $class->getAttributes(Abc::class);
28+
$attrsAbc2 = $class->getAttributes(Abc::class, \ReflectionAttribute::IS_INSTANCEOF);
29+
$attrsGCN = $class->getAttributes($genericClassName);
30+
$attrsCN = $class->getAttributes($className);
31+
$attrsStr = $class->getAttributes($str);
32+
$attrsNonsense = $class->getAttributes("some random string");
33+
34+
assertType('array<ReflectionAttribute<object>>', $attrsAll);
35+
assertType('array<ReflectionAttribute<Issue5511\Abc>>', $attrsAbc1);
36+
assertType('array<ReflectionAttribute<Issue5511\Abc>>', $attrsAbc2);
37+
assertType('array<ReflectionAttribute<Issue5511\Abc>>', $attrsGCN);
38+
assertType('array<ReflectionAttribute<object>>', $attrsCN);
39+
assertType('array<ReflectionAttribute<object>>', $attrsStr);
40+
assertType('array<ReflectionAttribute<some random string>>', $attrsNonsense);
41+
}
42+
43+
/**
44+
* @param \ReflectionAttribute<Abc> $ra
45+
*/
46+
function testNewInstance(\ReflectionAttribute $ra): void
47+
{
48+
assertType('ReflectionAttribute<Issue5511\\Abc>', $ra);
49+
$abc = $ra->newInstance();
50+
assertType(Abc::class, $abc);
51+
}

tests/PHPStan/Command/CommandHelperTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ public function dataParameters(): array
169169
'bootstrap' => __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php',
170170
'bootstrapFiles' => [
171171
realpath(__DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php'),
172+
realpath(__DIR__ . '/../../../stubs/runtime/ReflectionAttribute.php'),
172173
realpath(__DIR__ . '/../../../stubs/runtime/Attribute.php'),
173174
__DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php',
174175
],

0 commit comments

Comments
 (0)