Add support for stringy calls in CallReflectionResolver (#2663)

Add support for stringy calls in CallReflectionResolver
This commit is contained in:
Tomas Votruba 2020-01-15 14:14:37 +01:00 committed by GitHub
commit 54c92ebfec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 151 additions and 139 deletions

View File

@ -15,7 +15,6 @@ services:
- '../src/DependencyInjection/Loader/*'
- '../src/HttpKernel/*'
- '../src/ValueObject/*'
- '../src/PHPStan/Reflection/Php/*'
# extra services
Rector\Symfony\Rector\Form\Helper\FormTypeStringToTypeProvider: null

View File

@ -0,0 +1,28 @@
<?php
namespace Rector\Php70\Tests\Rector\FuncCall\NonVariableToVariableOnFunctionCallRector\Fixture;
function anonymousClass()
{
$anonymousClass = new class {
public function bar(&$baz) {}
};
$anonymousClass->bar(baz());
}
?>
-----
<?php
namespace Rector\Php70\Tests\Rector\FuncCall\NonVariableToVariableOnFunctionCallRector\Fixture;
function anonymousClass()
{
$anonymousClass = new class {
public function bar(&$baz) {}
};
$baz = baz();
$anonymousClass->bar($baz);
}
?>

View File

@ -0,0 +1,29 @@
<?php
namespace Rector\Php70\Tests\Rector\FuncCall\NonVariableToVariableOnFunctionCallRector\Fixture;
function anonymousFunction()
{
$anonymousFunction = function (&$bar) {};
$anonymousFunction(bar());
$staticAnonymousFunction = static function (&$bar) {};
$staticAnonymousFunction(bar());
}
?>
-----
<?php
namespace Rector\Php70\Tests\Rector\FuncCall\NonVariableToVariableOnFunctionCallRector\Fixture;
function anonymousFunction()
{
$anonymousFunction = function (&$bar) {};
$bar = bar();
$anonymousFunction($bar);
$staticAnonymousFunction = static function (&$bar) {};
$bar = bar();
$staticAnonymousFunction($bar);
}
?>

View File

@ -13,11 +13,6 @@ function funcCalls()
allByRef(bar(), baz());
allByRef(1, 2);
$anonymousFunction = function (&$bar) {};
$staticAnonymousFunction = static function (&$bar) {};
$anonymousFunction(bar());
$staticAnonymousFunction(bar());
return byRef(1, bar());
}
@ -43,13 +38,6 @@ function funcCalls()
$tmp = 1;
$tmp2 = 2;
allByRef($tmp, $tmp2);
$anonymousFunction = function (&$bar) {};
$staticAnonymousFunction = static function (&$bar) {};
$bar = bar();
$anonymousFunction($bar);
$bar = bar();
$staticAnonymousFunction($bar);
$bar = bar();
return byRef(1, $bar);

View File

@ -25,11 +25,6 @@ function methodCalls()
$aClass = new AClass();
$aClass->baz(baz());
$aClass->child()->bar(bar());
$anonymousClass = new class {
public function bar(&$baz) {}
};
$anonymousClass->bar(baz());
}
?>
@ -64,12 +59,6 @@ function methodCalls()
$aClass->baz($baz);
$bar = bar();
$aClass->child()->bar($bar);
$anonymousClass = new class {
public function bar(&$baz) {}
};
$baz = baz();
$anonymousClass->bar($baz);
}
?>

View File

@ -0,0 +1,41 @@
<?php
namespace Rector\Php70\Tests\Rector\FuncCall\NonVariableToVariableOnFunctionCallRector\Fixture;
class MyClass
{
public static function staticMethod(&$bar) {}
}
function stringyCalls()
{
$functionName = 'reset';
$functionName(bar());
$methodName = MyClass::class.'::staticMethod';
$methodName(bar());
}
?>
-----
<?php
namespace Rector\Php70\Tests\Rector\FuncCall\NonVariableToVariableOnFunctionCallRector\Fixture;
class MyClass
{
public static function staticMethod(&$bar) {}
}
function stringyCalls()
{
$functionName = 'reset';
$bar = bar();
$functionName($bar);
$methodName = MyClass::class.'::staticMethod';
$bar = bar();
$methodName($bar);
}
?>

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Rector\PHPStan\Reflection;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
@ -13,17 +14,27 @@ 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\Type\ClosureType;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Constant\ConstantStringType;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\PhpParser\Node\Resolver\NameResolver;
use Rector\PHPStan\Reflection\Php\ClosureInvokeMethodReflection;
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
*/
@ -83,11 +94,17 @@ final class CallReflectionResolver
}
$type = $scope->getType($funcCall->name);
if (! $type instanceof ClosureType) {
return null;
if (! $type instanceof ConstantStringType) {
return new NativeFunctionReflection(
'{closure}',
$type->getCallableParametersAcceptors($scope),
null,
TrinaryLogic::createMaybe()
);
}
return new ClosureInvokeMethodReflection($type->getMethod('__invoke', $scope), $type);
return $this->resolveConstantString($type, $scope);
}
/**
@ -142,4 +159,35 @@ final class CallReflectionResolver
return $parametersAcceptor;
}
/**
* @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);
}
}

View File

@ -1,110 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\PHPStan\Reflection\Php;
use PHPStan\Reflection\ClassMemberReflection;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\FunctionVariant;
use PHPStan\Reflection\MethodReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\ClosureType;
use PHPStan\Type\Type;
final class ClosureInvokeMethodReflection implements MethodReflection
{
/**
* @var MethodReflection
*/
private $nativeMethodReflection;
/**
* @var ClosureType
*/
private $closureType;
public function __construct(MethodReflection $nativeMethodReflection, ClosureType $closureType)
{
$this->nativeMethodReflection = $nativeMethodReflection;
$this->closureType = $closureType;
}
public function getDeclaringClass(): ClassReflection
{
return $this->nativeMethodReflection->getDeclaringClass();
}
public function isStatic(): bool
{
return $this->nativeMethodReflection->isStatic();
}
public function isPrivate(): bool
{
return $this->nativeMethodReflection->isPrivate();
}
public function isPublic(): bool
{
return $this->nativeMethodReflection->isPublic();
}
public function getDocComment(): ?string
{
return $this->nativeMethodReflection->getDocComment();
}
public function getName(): string
{
return $this->nativeMethodReflection->getName();
}
public function getPrototype(): ClassMemberReflection
{
return $this->nativeMethodReflection->getPrototype();
}
public function getVariants(): array
{
return [
new FunctionVariant(
$this->closureType->getTemplateTypeMap(),
$this->closureType->getResolvedTemplateTypeMap(),
$this->closureType->getParameters(),
$this->closureType->isVariadic(),
$this->closureType->getReturnType()
),
];
}
public function isDeprecated(): TrinaryLogic
{
return $this->nativeMethodReflection->isDeprecated();
}
public function getDeprecatedDescription(): ?string
{
return $this->nativeMethodReflection->getDeprecatedDescription();
}
public function isFinal(): TrinaryLogic
{
return $this->nativeMethodReflection->isFinal();
}
public function isInternal(): TrinaryLogic
{
return $this->nativeMethodReflection->isInternal();
}
public function getThrowType(): ?Type
{
return $this->nativeMethodReflection->getThrowType();
}
public function hasSideEffects(): TrinaryLogic
{
return $this->nativeMethodReflection->hasSideEffects();
}
}