Skip to content

Make ReflectionAttribute generic #640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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