From 85fed6766d84d43b360dcaf3e6342e2c52bb4893 Mon Sep 17 00:00:00 2001 From: Lctrs Date: Wed, 15 Jan 2020 09:47:41 +0100 Subject: [PATCH] Add support for stringy calls in CallReflectionResolver eg `$f = 'date'; $f();` --- config/services.yaml | 1 - .../Fixture/anonymous_class.php.inc | 28 +++++ .../Fixture/anonymous_function.php.inc | 29 +++++ .../Fixture/func_calls.php.inc | 12 -- .../Fixture/method_calls.php.inc | 11 -- .../Fixture/stringy_calls.php.inc | 41 +++++++ .../Reflection/CallReflectionResolver.php | 58 ++++++++- .../Php/ClosureInvokeMethodReflection.php | 110 ------------------ 8 files changed, 151 insertions(+), 139 deletions(-) create mode 100644 packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/anonymous_class.php.inc create mode 100644 packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/anonymous_function.php.inc create mode 100644 packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/stringy_calls.php.inc delete mode 100644 src/PHPStan/Reflection/Php/ClosureInvokeMethodReflection.php diff --git a/config/services.yaml b/config/services.yaml index e0bc218b2534..fa2761468881 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -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 diff --git a/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/anonymous_class.php.inc b/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/anonymous_class.php.inc new file mode 100644 index 000000000000..2982c0608381 --- /dev/null +++ b/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/anonymous_class.php.inc @@ -0,0 +1,28 @@ +bar(baz()); +} + +?> +----- +bar($baz); +} + +?> diff --git a/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/anonymous_function.php.inc b/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/anonymous_function.php.inc new file mode 100644 index 000000000000..5f416d726752 --- /dev/null +++ b/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/anonymous_function.php.inc @@ -0,0 +1,29 @@ + +----- + diff --git a/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/func_calls.php.inc b/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/func_calls.php.inc index d599f9224a33..d53795ee138e 100644 --- a/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/func_calls.php.inc +++ b/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/func_calls.php.inc @@ -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); diff --git a/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/method_calls.php.inc b/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/method_calls.php.inc index c34469be3c71..995a82396ffc 100644 --- a/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/method_calls.php.inc +++ b/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/method_calls.php.inc @@ -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); } ?> diff --git a/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/stringy_calls.php.inc b/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/stringy_calls.php.inc new file mode 100644 index 000000000000..f526fc73e319 --- /dev/null +++ b/packages/Php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/stringy_calls.php.inc @@ -0,0 +1,41 @@ + +----- + diff --git a/src/PHPStan/Reflection/CallReflectionResolver.php b/src/PHPStan/Reflection/CallReflectionResolver.php index e9428487ff32..23b7c5b58a08 100644 --- a/src/PHPStan/Reflection/CallReflectionResolver.php +++ b/src/PHPStan/Reflection/CallReflectionResolver.php @@ -4,6 +4,7 @@ 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\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 @@ public function resolveFunctionCall(FuncCall $funcCall) } $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 @@ public function resolveParametersAcceptor($reflection, Node $node): ?ParametersA 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); + } } diff --git a/src/PHPStan/Reflection/Php/ClosureInvokeMethodReflection.php b/src/PHPStan/Reflection/Php/ClosureInvokeMethodReflection.php deleted file mode 100644 index ce97c0974e8e..000000000000 --- a/src/PHPStan/Reflection/Php/ClosureInvokeMethodReflection.php +++ /dev/null @@ -1,110 +0,0 @@ -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(); - } -}