From 8d80b5b63741475207482358a87d2a4087136f91 Mon Sep 17 00:00:00 2001 From: Lctrs Date: Tue, 14 Jan 2020 14:31:54 +0100 Subject: [PATCH] Introduce a CallReflectionResolver --- config/services.yaml | 1 + ...VariableToVariableOnFunctionCallRector.php | 25 ++- .../Php70/src/Reflection/CallReflection.php | 110 ------------- .../FuncCall/RemoveExtraParametersRector.php | 103 +++---------- .../Reflection/CallReflectionResolver.php | 145 ++++++++++++++++++ .../Php/ClosureInvokeMethodReflection.php | 110 +++++++++++++ 6 files changed, 293 insertions(+), 201 deletions(-) delete mode 100644 packages/Php70/src/Reflection/CallReflection.php create mode 100644 src/PHPStan/Reflection/CallReflectionResolver.php create mode 100644 src/PHPStan/Reflection/Php/ClosureInvokeMethodReflection.php diff --git a/config/services.yaml b/config/services.yaml index fa2761468881..e0bc218b2534 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -15,6 +15,7 @@ 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/src/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector.php b/packages/Php70/src/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector.php index c74e39daff05..85bc5ad72be3 100644 --- a/packages/Php70/src/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector.php +++ b/packages/Php70/src/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector.php @@ -25,8 +25,8 @@ use PHPStan\Reflection\ParameterReflection; use Rector\CodingStyle\Naming\ClassNaming; use Rector\NodeTypeResolver\Node\AttributeKey; -use Rector\Php70\Reflection\CallReflection; use Rector\Php70\ValueObject\VariableAssignPair; +use Rector\PHPStan\Reflection\CallReflectionResolver; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -44,18 +44,18 @@ final class NonVariableToVariableOnFunctionCallRector extends AbstractRector private const DEFAULT_VARIABLE_NAME = 'tmp'; /** - * @var CallReflection + * @var CallReflectionResolver */ - private $callReflection; + private $callReflectionResolver; /** * @var ClassNaming */ private $classNaming; - public function __construct(CallReflection $callReflection, ClassNaming $classNaming) + public function __construct(CallReflectionResolver $callReflectionResolver, ClassNaming $classNaming) { - $this->callReflection = $callReflection; + $this->callReflectionResolver = $callReflectionResolver; $this->classNaming = $classNaming; } @@ -85,7 +85,7 @@ public function refactor(Node $node): ?Node return null; } - $arguments = $this->getNonVariableArguments($node, $scope); + $arguments = $this->getNonVariableArguments($node); if ($arguments === []) { return null; @@ -111,12 +111,21 @@ public function refactor(Node $node): ?Node * * @return array */ - private function getNonVariableArguments(Node $node, Scope $scope): array + private function getNonVariableArguments(Node $node): array { $arguments = []; + $parametersAcceptor = $this->callReflectionResolver->resolveParametersAcceptor( + $this->callReflectionResolver->resolveCall($node), + $node + ); + + if ($parametersAcceptor === null) { + return []; + } + /** @var ParameterReflection $parameter */ - foreach ($this->callReflection->getParameterReflections($node, $scope) as $key => $parameter) { + foreach ($parametersAcceptor->getParameters() as $key => $parameter) { // omitted optional parameter if (! isset($node->args[$key])) { continue; diff --git a/packages/Php70/src/Reflection/CallReflection.php b/packages/Php70/src/Reflection/CallReflection.php deleted file mode 100644 index 500f1f287f73..000000000000 --- a/packages/Php70/src/Reflection/CallReflection.php +++ /dev/null @@ -1,110 +0,0 @@ -reflectionProvider = $reflectionProvider; - $this->nodeTypeResolver = $nodeTypeResolver; - $this->nameResolver = $nameResolver; - } - - /** - * @param FuncCall|MethodCall|StaticCall $node - * - * @return array - */ - public function getParameterReflections(Node $node, Scope $scope): array - { - if ($node instanceof MethodCall || $node instanceof StaticCall) { - $classType = $this->nodeTypeResolver->getObjectType( - $node instanceof MethodCall ? $node->var : $node->class - ); - $methodName = $this->nameResolver->getName($node->name); - - if ($methodName === null || ! $classType->hasMethod($methodName)->yes()) { - return []; - } - - return ParametersAcceptorSelector::selectSingle( - $classType->getMethod($methodName, $scope)->getVariants() - )->getParameters(); - } - - if ($node->name instanceof Expr) { - return $this->getParametersForExpr($node->name, $scope); - } - - return $this->getParametersForName($node->name, $node->args, $scope); - } - - /** - * @return array - */ - private function getParametersForExpr(Expr $expr, Scope $scope): array - { - $type = $scope->getType($expr); - - if (! $type instanceof ParametersAcceptor) { - return []; - } - - return $type->getParameters(); - } - - /** - * @param Arg[] $args - * - * @return array - */ - private function getParametersForName(Name $name, array $args, Scope $scope): array - { - try { - return ParametersAcceptorSelector::selectFromArgs( - $scope, - $args, - $this->reflectionProvider->getFunction($name, $scope)->getVariants() - )->getParameters(); - } catch (FunctionNotFoundException $functionNotFoundException) { - return []; - } - } -} diff --git a/packages/Php71/src/Rector/FuncCall/RemoveExtraParametersRector.php b/packages/Php71/src/Rector/FuncCall/RemoveExtraParametersRector.php index 74e96e39d900..12df206f79b1 100644 --- a/packages/Php71/src/Rector/FuncCall/RemoveExtraParametersRector.php +++ b/packages/Php71/src/Rector/FuncCall/RemoveExtraParametersRector.php @@ -4,22 +4,17 @@ namespace Rector\Php71\Rector\FuncCall; +use function count; use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Name; -use PHPStan\Type\ObjectType; -use PHPStan\Type\Type; -use PHPStan\Type\TypeUtils; -use PHPStan\Type\UnionType; -use Rector\PhpParser\Node\Manipulator\CallManipulator; +use PHPStan\Reflection\ParametersAcceptor; +use Rector\PHPStan\Reflection\CallReflectionResolver; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; -use ReflectionFunction; -use ReflectionFunctionAbstract; -use ReflectionMethod; /** * @see https://www.reddit.com/r/PHP/comments/a1ie7g/is_there_a_linter_for_argumentcounterror_for_php/ @@ -30,13 +25,13 @@ final class RemoveExtraParametersRector extends AbstractRector { /** - * @var CallManipulator + * @var CallReflectionResolver */ - private $callManipulator; + private $callReflectionResolver; - public function __construct(CallManipulator $callManipulator) + public function __construct(CallReflectionResolver $callReflectionResolver) { - $this->callManipulator = $callManipulator; + $this->callReflectionResolver = $callReflectionResolver; } public function getDefinition(): RectorDefinition @@ -63,10 +58,13 @@ public function refactor(Node $node): ?Node return null; } - /** @var ReflectionFunction $reflectionFunctionLike */ - $reflectionFunctionLike = $this->resolveReflectionFunctionLike($node); + /** @var ParametersAcceptor $parametersAcceptor */ + $parametersAcceptor = $this->callReflectionResolver->resolveParametersAcceptor( + $this->callReflectionResolver->resolveCall($node), + $node + ); - $numberOfParameters = $reflectionFunctionLike->getNumberOfParameters(); + $numberOfParameters = count($parametersAcceptor->getParameters()); $numberOfArguments = count($node->args); for ($i = $numberOfParameters; $i <= $numberOfArguments; $i++) { @@ -95,80 +93,19 @@ private function shouldSkip(Node $node): bool } } - $reflectionFunctionLike = $this->resolveReflectionFunctionLike($node); - if ($reflectionFunctionLike === null) { + $parametersAcceptor = $this->callReflectionResolver->resolveParametersAcceptor( + $this->callReflectionResolver->resolveCall($node), + $node + ); + if ($parametersAcceptor === null) { return true; } // can be any number of arguments → nothing to limit here - if ($this->callManipulator->isVariadic($reflectionFunctionLike, $node)) { + if ($parametersAcceptor->isVariadic()) { return true; } - return $reflectionFunctionLike->getNumberOfParameters() >= count($node->args); - } - - /** - * @param FuncCall|MethodCall|StaticCall $node - * @return ReflectionFunction|ReflectionMethod - */ - private function resolveReflectionFunctionLike(Node $node): ?ReflectionFunctionAbstract - { - if ($node instanceof FuncCall) { - $functionName = $this->getName($node); - if ($functionName === null) { - return null; - } - - if (! function_exists($functionName)) { - return null; - } - - return new ReflectionFunction($functionName); - } - - if ($node instanceof StaticCall) { - $objectType = $this->getObjectType($node->class); - } elseif ($node instanceof MethodCall) { - $objectType = $this->getObjectType($node->var); - } else { - return null; - } - - // unable to resolve - if (! $objectType instanceof ObjectType && ! $objectType instanceof UnionType) { - return null; - } - - $methodName = $this->getName($node); - if ($methodName === null) { - return null; - } - - $class = $this->resolveClassOverIntefaceFromType($objectType); - if ($class === null) { - return null; - } - - if (! method_exists($class, $methodName)) { - return null; - } - - return new ReflectionMethod($class, $methodName); - } - - /** - * Give class priority over interface, because it can change the interface signature - * @see https://github.com/rectorphp/rector/issues/1593#issuecomment-502404580 - */ - private function resolveClassOverIntefaceFromType(Type $type): ?string - { - $classNames = TypeUtils::getDirectClassNames($type); - foreach ($classNames as $className) { - if (class_exists($className)) { - return $className; - } - } - return null; + return count($parametersAcceptor->getParameters()) >= count($node->args); } } diff --git a/src/PHPStan/Reflection/CallReflectionResolver.php b/src/PHPStan/Reflection/CallReflectionResolver.php new file mode 100644 index 000000000000..e9428487ff32 --- /dev/null +++ b/src/PHPStan/Reflection/CallReflectionResolver.php @@ -0,0 +1,145 @@ +reflectionProvider = $reflectionProvider; + $this->nodeTypeResolver = $nodeTypeResolver; + $this->nameResolver = $nameResolver; + } + + /** + * @param FuncCall|MethodCall|StaticCall $node + * @return FunctionReflection|MethodReflection|null + */ + public function resolveCall(Node $node) + { + if ($node instanceof FuncCall) { + return $this->resolveFunctionCall($node); + } + + return $this->resolveMethodCall($node); + } + + /** + * @return FunctionReflection|MethodReflection|null + */ + public function resolveFunctionCall(FuncCall $funcCall) + { + /** @var Scope|null $scope */ + $scope = $funcCall->getAttribute(AttributeKey::SCOPE); + + if ($funcCall->name instanceof Name) { + try { + return $this->reflectionProvider->getFunction($funcCall->name, $scope); + } catch (FunctionNotFoundException $functionNotFoundException) { + return null; + } + } + + if ($scope === null) { + return null; + } + + $type = $scope->getType($funcCall->name); + if (! $type instanceof ClosureType) { + return null; + } + + return new ClosureInvokeMethodReflection($type->getMethod('__invoke', $scope), $type); + } + + /** + * @param MethodCall|StaticCall $node + */ + public function resolveMethodCall(Node $node): ?MethodReflection + { + /** @var Scope|null $scope */ + $scope = $node->getAttribute(AttributeKey::SCOPE); + if ($scope === null) { + return null; + } + + $classType = $this->nodeTypeResolver->getObjectType( + $node instanceof MethodCall ? $node->var : $node->class + ); + $methodName = $this->nameResolver->getName($node->name); + + if ($methodName === null || ! $classType->hasMethod($methodName)->yes()) { + return null; + } + + return $classType->getMethod($methodName, $scope); + } + + /** + * @param FunctionReflection|MethodReflection|null $reflection + * @param FuncCall|MethodCall|StaticCall $node + */ + public function resolveParametersAcceptor($reflection, Node $node): ?ParametersAcceptor + { + if ($reflection === null) { + return null; + } + + $variants = $reflection->getVariants(); + $nbVariants = count($variants); + + if ($nbVariants === 0) { + return null; + } elseif ($nbVariants === 1) { + $parametersAcceptor = ParametersAcceptorSelector::selectSingle($variants); + } else { + /** @var Scope|null $scope */ + $scope = $node->getAttribute(AttributeKey::SCOPE); + if ($scope === null) { + return null; + } + + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $node->args, $variants); + } + + return $parametersAcceptor; + } +} diff --git a/src/PHPStan/Reflection/Php/ClosureInvokeMethodReflection.php b/src/PHPStan/Reflection/Php/ClosureInvokeMethodReflection.php new file mode 100644 index 000000000000..ce97c0974e8e --- /dev/null +++ b/src/PHPStan/Reflection/Php/ClosureInvokeMethodReflection.php @@ -0,0 +1,110 @@ +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(); + } +}