Skip to content
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
175 changes: 10 additions & 165 deletions src/PHPStan/Reflection/CallReflectionResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Rector\Core\PHPStan\Reflection;

use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
Expand All @@ -14,32 +13,16 @@
use PHPStan\Broker\FunctionNotFoundException;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\Native\NativeFunctionReflection;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantArrayTypeAndMethod;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use Rector\Core\PHPStan\Reflection\TypeToCallReflectionResolver\TypeToCallReflectionResolverRegistry;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;

final class CallReflectionResolver
{
/**
* Took from https://github.com/phpstan/phpstan-src/blob/8376548f76e2c845ae047e3010e873015b796818/src/Type/Constant/ConstantStringType.php#L158
*
* @see https://regex101.com/r/IE6lcM/4
*
* @var string
*/
private const STATIC_METHOD_REGEXP = '#^([a-zA-Z_\\x7f-\\xff\\\\][a-zA-Z0-9_\\x7f-\\xff\\\\]*)::([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)\\z#';

/**
* @var ReflectionProvider
*/
Expand All @@ -55,14 +38,21 @@ final class CallReflectionResolver
*/
private $nodeNameResolver;

/**
* @var TypeToCallReflectionResolverRegistry
*/
private $typeToCallReflectionResolverRegistry;

public function __construct(
ReflectionProvider $reflectionProvider,
NodeTypeResolver $nodeTypeResolver,
NodeNameResolver $nodeNameResolver
NodeNameResolver $nodeNameResolver,
TypeToCallReflectionResolverRegistry $typeToCallReflectionResolverRegistry
) {
$this->reflectionProvider = $reflectionProvider;
$this->nodeTypeResolver = $nodeTypeResolver;
$this->nodeNameResolver = $nodeNameResolver;
$this->typeToCallReflectionResolverRegistry = $typeToCallReflectionResolverRegistry;
}

/**
Expand Down Expand Up @@ -98,26 +88,7 @@ public function resolveFunctionCall(FuncCall $funcCall)
return null;
}

$type = $scope->getType($funcCall->name);

if ($type instanceof ObjectType) {
return $this->resolveInvokable($type);
}

if ($type instanceof ConstantStringType) {
return $this->resolveConstantString($type, $scope);
}

if ($type instanceof ConstantArrayType) {
return $this->resolveConstantArray($type, $scope);
}

return new NativeFunctionReflection(
'{closure}',
$type->getCallableParametersAcceptors($scope),
null,
TrinaryLogic::createMaybe()
);
return $this->typeToCallReflectionResolverRegistry->resolve($scope->getType($funcCall->name), $scope);
}

/**
Expand Down Expand Up @@ -170,130 +141,4 @@ public function resolveParametersAcceptor($reflection, Node $node): ?ParametersA

return $parametersAcceptor;
}

/**
* @see https://github.com/phpstan/phpstan-src/blob/b1fd47bda2a7a7d25091197b125c0adf82af6757/src/Type/ObjectType.php#L705
*/
private function resolveInvokable(ObjectType $objectType): ?MethodReflection
{
$className = $objectType->getClassName();
if (! $this->reflectionProvider->hasClass($className)) {
return null;
}

$classReflection = $this->reflectionProvider->getClass($className);

if (! $classReflection->hasNativeMethod('__invoke')) {
return null;
}

return $classReflection->getNativeMethod('__invoke');
}

/**
* @see https://github.com/phpstan/phpstan-src/blob/b1fd47bda2a7a7d25091197b125c0adf82af6757/src/Type/Constant/ConstantStringType.php#L147
*
* @return FunctionReflection|MethodReflection|null
*/
private function resolveConstantString(ConstantStringType $constantStringType, Scope $scope)
{
$value = $constantStringType->getValue();

// 'my_function'
$functionName = new Name($value);
if ($this->reflectionProvider->hasFunction($functionName, null)) {
return $this->reflectionProvider->getFunction($functionName, null);
}

// 'MyClass::myStaticFunction'
$matches = Strings::match($value, self::STATIC_METHOD_REGEXP);
if ($matches === null) {
return null;
}

if (! $this->reflectionProvider->hasClass($matches[1])) {
return null;
}

$classReflection = $this->reflectionProvider->getClass($matches[1]);
if (! $classReflection->hasMethod($matches[2])) {
return null;
}

return $classReflection->getMethod($matches[2], $scope);
}

/**
* @see https://github.com/phpstan/phpstan-src/blob/b1fd47bda2a7a7d25091197b125c0adf82af6757/src/Type/Constant/ConstantArrayType.php#L188
*/
private function resolveConstantArray(ConstantArrayType $constantArrayType, Scope $scope): ?MethodReflection
{
$typeAndMethodName = $this->findTypeAndMethodName($constantArrayType);
if ($typeAndMethodName === null) {
return null;
}

if ($typeAndMethodName->isUnknown() || ! $typeAndMethodName->getCertainty()->yes()) {
return null;
}

$method = $typeAndMethodName
->getType()
->getMethod($typeAndMethodName->getMethod(), $scope);

if (! $scope->canCallMethod($method)) {
return null;
}

return $method;
}

/**
* @see https://github.com/phpstan/phpstan-src/blob/b1fd47bda2a7a7d25091197b125c0adf82af6757/src/Type/Constant/ConstantArrayType.php#L209
*/
private function findTypeAndMethodName(ConstantArrayType $constantArrayType): ?ConstantArrayTypeAndMethod
{
if (! $this->areKeyTypesValid($constantArrayType)) {
return null;
}

[$classOrObject, $method] = $constantArrayType->getValueTypes();

if (! $method instanceof ConstantStringType) {
return ConstantArrayTypeAndMethod::createUnknown();
}

if ($classOrObject instanceof ConstantStringType) {
if (! $this->reflectionProvider->hasClass($classOrObject->getValue())) {
return ConstantArrayTypeAndMethod::createUnknown();
}

$type = new ObjectType($this->reflectionProvider->getClass($classOrObject->getValue())->getName());
} elseif ((new ObjectWithoutClassType())->isSuperTypeOf($classOrObject)->yes()) {
$type = $classOrObject;
} else {
return ConstantArrayTypeAndMethod::createUnknown();
}

$has = $type->hasMethod($method->getValue());
if (! $has->no()) {
return ConstantArrayTypeAndMethod::createConcrete($type, $method->getValue(), $has);
}

return null;
}

private function areKeyTypesValid(ConstantArrayType $constantArrayType): bool
{
$keyTypes = $constantArrayType->getKeyTypes();

if (count($keyTypes) !== 2) {
return false;
}

if ($keyTypes[0]->isSuperTypeOf(new ConstantIntegerType(0))->no()) {
return false;
}
return ! $keyTypes[1]->isSuperTypeOf(new ConstantIntegerType(1))->no();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Rector\Core\PHPStan\Reflection\TypeToCallReflectionResolver;

use PHPStan\Reflection\ClassMemberAccessAnswerer;
use PHPStan\Reflection\Native\NativeFunctionReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\ClosureType;
use PHPStan\Type\Type;

final class ClosureTypeToCallReflectionResolver implements TypeToCallReflectionResolverInterface
{
public function supports(Type $type): bool
{
return $type instanceof ClosureType;
}

/**
* @param ClosureType $type
*/
public function resolve(Type $type, ClassMemberAccessAnswerer $classMemberAccessAnswerer)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be return type, in interface and it's implementations

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be return type, in interface and it's implementations

{
return new NativeFunctionReflection(
'{closure}',
$type->getCallableParametersAcceptors($classMemberAccessAnswerer),
null,
TrinaryLogic::createMaybe()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

namespace Rector\Core\PHPStan\Reflection\TypeToCallReflectionResolver;

use PHPStan\Reflection\ClassMemberAccessAnswerer;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantArrayTypeAndMethod;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\Type;

/**
* @see https://github.com/phpstan/phpstan-src/blob/b1fd47bda2a7a7d25091197b125c0adf82af6757/src/Type/Constant/ConstantArrayType.php#L188
*/
final class ConstantArrayTypeToCallReflectionResolver implements TypeToCallReflectionResolverInterface
{
/**
* @var ReflectionProvider
*/
private $reflectionProvider;

public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}

public function supports(Type $type): bool
{
return $type instanceof ConstantArrayType;
}

/**
* @param ConstantArrayType $type
*/
public function resolve(Type $type, ClassMemberAccessAnswerer $classMemberAccessAnswerer)
{
$typeAndMethodName = $this->findTypeAndMethodName($type);
if ($typeAndMethodName === null) {
return null;
}

if ($typeAndMethodName->isUnknown() || ! $typeAndMethodName->getCertainty()->yes()) {
return null;
}

$method = $typeAndMethodName
->getType()
->getMethod($typeAndMethodName->getMethod(), $classMemberAccessAnswerer);

if (! $classMemberAccessAnswerer->canCallMethod($method)) {
return null;
}

return $method;
}

/**
* @see https://github.com/phpstan/phpstan-src/blob/b1fd47bda2a7a7d25091197b125c0adf82af6757/src/Type/Constant/ConstantArrayType.php#L209
*/
private function findTypeAndMethodName(ConstantArrayType $constantArrayType): ?ConstantArrayTypeAndMethod
{
if (! $this->areKeyTypesValid($constantArrayType)) {
return null;
}

[$classOrObject, $method] = $constantArrayType->getValueTypes();

if (! $method instanceof ConstantStringType) {
return ConstantArrayTypeAndMethod::createUnknown();
}

if ($classOrObject instanceof ConstantStringType) {
if (! $this->reflectionProvider->hasClass($classOrObject->getValue())) {
return ConstantArrayTypeAndMethod::createUnknown();
}

$type = new ObjectType($this->reflectionProvider->getClass($classOrObject->getValue())->getName());
} elseif ((new ObjectWithoutClassType())->isSuperTypeOf($classOrObject)->yes()) {
$type = $classOrObject;
} else {
return ConstantArrayTypeAndMethod::createUnknown();
}

$has = $type->hasMethod($method->getValue());
if (! $has->no()) {
return ConstantArrayTypeAndMethod::createConcrete($type, $method->getValue(), $has);
}

return null;
}

private function areKeyTypesValid(ConstantArrayType $constantArrayType): bool
{
$keyTypes = $constantArrayType->getKeyTypes();

if (count($keyTypes) !== 2) {
return false;
}

if ($keyTypes[0]->isSuperTypeOf(new ConstantIntegerType(0))->no()) {
return false;
}

return ! $keyTypes[1]->isSuperTypeOf(new ConstantIntegerType(1))->no();
}
}
Loading