Introduce a CallReflectionResolver

This commit is contained in:
Lctrs 2020-01-14 14:31:54 +01:00
parent 8429a8e9f2
commit 8d80b5b637
6 changed files with 293 additions and 201 deletions

View File

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

View File

@ -25,8 +25,8 @@ use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParameterReflection;
use Rector\CodingStyle\Naming\ClassNaming; use Rector\CodingStyle\Naming\ClassNaming;
use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Php70\Reflection\CallReflection;
use Rector\Php70\ValueObject\VariableAssignPair; use Rector\Php70\ValueObject\VariableAssignPair;
use Rector\PHPStan\Reflection\CallReflectionResolver;
use Rector\Rector\AbstractRector; use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition; use Rector\RectorDefinition\RectorDefinition;
@ -44,18 +44,18 @@ final class NonVariableToVariableOnFunctionCallRector extends AbstractRector
private const DEFAULT_VARIABLE_NAME = 'tmp'; private const DEFAULT_VARIABLE_NAME = 'tmp';
/** /**
* @var CallReflection * @var CallReflectionResolver
*/ */
private $callReflection; private $callReflectionResolver;
/** /**
* @var ClassNaming * @var ClassNaming
*/ */
private $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; $this->classNaming = $classNaming;
} }
@ -85,7 +85,7 @@ final class NonVariableToVariableOnFunctionCallRector extends AbstractRector
return null; return null;
} }
$arguments = $this->getNonVariableArguments($node, $scope); $arguments = $this->getNonVariableArguments($node);
if ($arguments === []) { if ($arguments === []) {
return null; return null;
@ -111,12 +111,21 @@ final class NonVariableToVariableOnFunctionCallRector extends AbstractRector
* *
* @return array<int, Expr> * @return array<int, Expr>
*/ */
private function getNonVariableArguments(Node $node, Scope $scope): array private function getNonVariableArguments(Node $node): array
{ {
$arguments = []; $arguments = [];
$parametersAcceptor = $this->callReflectionResolver->resolveParametersAcceptor(
$this->callReflectionResolver->resolveCall($node),
$node
);
if ($parametersAcceptor === null) {
return [];
}
/** @var ParameterReflection $parameter */ /** @var ParameterReflection $parameter */
foreach ($this->callReflection->getParameterReflections($node, $scope) as $key => $parameter) { foreach ($parametersAcceptor->getParameters() as $key => $parameter) {
// omitted optional parameter // omitted optional parameter
if (! isset($node->args[$key])) { if (! isset($node->args[$key])) {
continue; continue;

View File

@ -1,110 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Php70\Reflection;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
use PHPStan\Broker\FunctionNotFoundException;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\PhpParser\Node\Resolver\NameResolver;
final class CallReflection
{
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
/**
* @var NodeTypeResolver
*/
private $nodeTypeResolver;
/**
* @var NameResolver
*/
private $nameResolver;
public function __construct(
ReflectionProvider $reflectionProvider,
NodeTypeResolver $nodeTypeResolver,
NameResolver $nameResolver
) {
$this->reflectionProvider = $reflectionProvider;
$this->nodeTypeResolver = $nodeTypeResolver;
$this->nameResolver = $nameResolver;
}
/**
* @param FuncCall|MethodCall|StaticCall $node
*
* @return array<int, ParameterReflection>
*/
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<int, ParameterReflection>
*/
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<int, ParameterReflection>
*/
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 [];
}
}
}

View File

@ -4,22 +4,17 @@ declare(strict_types=1);
namespace Rector\Php71\Rector\FuncCall; namespace Rector\Php71\Rector\FuncCall;
use function count;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name; use PhpParser\Node\Name;
use PHPStan\Type\ObjectType; use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Type\Type; use Rector\PHPStan\Reflection\CallReflectionResolver;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\UnionType;
use Rector\PhpParser\Node\Manipulator\CallManipulator;
use Rector\Rector\AbstractRector; use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition; 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/ * @see https://www.reddit.com/r/PHP/comments/a1ie7g/is_there_a_linter_for_argumentcounterror_for_php/
@ -30,13 +25,13 @@ use ReflectionMethod;
final class RemoveExtraParametersRector extends AbstractRector 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 public function getDefinition(): RectorDefinition
@ -63,10 +58,13 @@ final class RemoveExtraParametersRector extends AbstractRector
return null; return null;
} }
/** @var ReflectionFunction $reflectionFunctionLike */ /** @var ParametersAcceptor $parametersAcceptor */
$reflectionFunctionLike = $this->resolveReflectionFunctionLike($node); $parametersAcceptor = $this->callReflectionResolver->resolveParametersAcceptor(
$this->callReflectionResolver->resolveCall($node),
$node
);
$numberOfParameters = $reflectionFunctionLike->getNumberOfParameters(); $numberOfParameters = count($parametersAcceptor->getParameters());
$numberOfArguments = count($node->args); $numberOfArguments = count($node->args);
for ($i = $numberOfParameters; $i <= $numberOfArguments; $i++) { for ($i = $numberOfParameters; $i <= $numberOfArguments; $i++) {
@ -95,80 +93,19 @@ final class RemoveExtraParametersRector extends AbstractRector
} }
} }
$reflectionFunctionLike = $this->resolveReflectionFunctionLike($node); $parametersAcceptor = $this->callReflectionResolver->resolveParametersAcceptor(
if ($reflectionFunctionLike === null) { $this->callReflectionResolver->resolveCall($node),
$node
);
if ($parametersAcceptor === null) {
return true; return true;
} }
// can be any number of arguments → nothing to limit here // can be any number of arguments → nothing to limit here
if ($this->callManipulator->isVariadic($reflectionFunctionLike, $node)) { if ($parametersAcceptor->isVariadic()) {
return true; return true;
} }
return $reflectionFunctionLike->getNumberOfParameters() >= count($node->args);
}
/** return count($parametersAcceptor->getParameters()) >= 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;
} }
} }

View File

@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
namespace Rector\PHPStan\Reflection;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
use PHPStan\Broker\FunctionNotFoundException;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ClosureType;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\PhpParser\Node\Resolver\NameResolver;
use Rector\PHPStan\Reflection\Php\ClosureInvokeMethodReflection;
final class CallReflectionResolver
{
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
/**
* @var NodeTypeResolver
*/
private $nodeTypeResolver;
/**
* @var NameResolver
*/
private $nameResolver;
public function __construct(
ReflectionProvider $reflectionProvider,
NodeTypeResolver $nodeTypeResolver,
NameResolver $nameResolver
) {
$this->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;
}
}

View File

@ -0,0 +1,110 @@
<?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();
}
}