mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-30 06:33:31 +00:00
Decouple PHPStan Type to function resolver logic
This commit is contained in:
parent
1abb9835e5
commit
11c0655718
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\Core\PHPStan\Reflection;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
|
@ -14,32 +13,16 @@ use PHPStan\Analyser\Scope;
|
|||
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
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,26 +88,7 @@ final class CallReflectionResolver
|
|||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -170,130 +141,4 @@ final class CallReflectionResolver
|
|||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
return new NativeFunctionReflection(
|
||||
'{closure}',
|
||||
$type->getCallableParametersAcceptors($classMemberAccessAnswerer),
|
||||
null,
|
||||
TrinaryLogic::createMaybe()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Core\PHPStan\Reflection\TypeToCallReflectionResolver;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node\Name;
|
||||
use PHPStan\Reflection\ClassMemberAccessAnswerer;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
use PHPStan\Type\Constant\ConstantStringType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
/**
|
||||
* @see https://github.com/phpstan/phpstan-src/blob/b1fd47bda2a7a7d25091197b125c0adf82af6757/src/Type/Constant/ConstantStringType.php#L147
|
||||
*/
|
||||
final class ConstantStringTypeToCallReflectionResolver implements TypeToCallReflectionResolverInterface
|
||||
{
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private $reflectionProvider;
|
||||
|
||||
public function __construct(ReflectionProvider $reflectionProvider)
|
||||
{
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
}
|
||||
|
||||
public function supports(Type $type): bool
|
||||
{
|
||||
return $type instanceof ConstantStringType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConstantStringType $type
|
||||
*/
|
||||
public function resolve(Type $type, ClassMemberAccessAnswerer $classMemberAccessAnswerer)
|
||||
{
|
||||
$value = $type->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], $classMemberAccessAnswerer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Core\PHPStan\Reflection\TypeToCallReflectionResolver;
|
||||
|
||||
use PHPStan\Reflection\ClassMemberAccessAnswerer;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
/**
|
||||
* @see https://github.com/phpstan/phpstan-src/blob/b1fd47bda2a7a7d25091197b125c0adf82af6757/src/Type/ObjectType.php#L705
|
||||
*/
|
||||
final class ObjectTypeToCallReflectionResolver implements TypeToCallReflectionResolverInterface
|
||||
{
|
||||
/**
|
||||
* @var ReflectionProvider
|
||||
*/
|
||||
private $reflectionProvider;
|
||||
|
||||
public function __construct(ReflectionProvider $reflectionProvider)
|
||||
{
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
}
|
||||
|
||||
public function supports(Type $type): bool
|
||||
{
|
||||
return $type instanceof ObjectType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectType $type
|
||||
*/
|
||||
public function resolve(Type $type, ClassMemberAccessAnswerer $classMemberAccessAnswerer)
|
||||
{
|
||||
$className = $type->getClassName();
|
||||
if (! $this->reflectionProvider->hasClass($className)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$classReflection = $this->reflectionProvider->getClass($className);
|
||||
|
||||
if (! $classReflection->hasNativeMethod('__invoke')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $classReflection->getNativeMethod('__invoke');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Core\PHPStan\Reflection\TypeToCallReflectionResolver;
|
||||
|
||||
use PHPStan\Reflection\ClassMemberAccessAnswerer;
|
||||
use PHPStan\Reflection\FunctionReflection;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
interface TypeToCallReflectionResolverInterface
|
||||
{
|
||||
public function supports(Type $type): bool;
|
||||
|
||||
/**
|
||||
* @return FunctionReflection|MethodReflection|null
|
||||
*/
|
||||
public function resolve(Type $type, ClassMemberAccessAnswerer $classMemberAccessAnswerer);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Core\PHPStan\Reflection\TypeToCallReflectionResolver;
|
||||
|
||||
use PHPStan\Reflection\ClassMemberAccessAnswerer;
|
||||
use PHPStan\Reflection\FunctionReflection;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
final class TypeToCallReflectionResolverRegistry
|
||||
{
|
||||
/**
|
||||
* @var TypeToCallReflectionResolverInterface[]
|
||||
*/
|
||||
private $resolvers = [];
|
||||
|
||||
/**
|
||||
* @param TypeToCallReflectionResolverInterface[] $resolvers
|
||||
*/
|
||||
public function __construct(array $resolvers)
|
||||
{
|
||||
$this->resolvers = $resolvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FunctionReflection|MethodReflection|null
|
||||
*/
|
||||
public function resolve(Type $type, ClassMemberAccessAnswerer $classMemberAccessAnswerer)
|
||||
{
|
||||
foreach ($this->resolvers as $resolver) {
|
||||
if (! $resolver->supports($type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $resolver->resolve($type, $classMemberAccessAnswerer);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user