Merge pull request #2949 from rectorphp/complex-property-fetch

Improve PropertyFetchManipulator
This commit is contained in:
Tomas Votruba 2020-02-28 00:30:46 +01:00 committed by GitHub
commit 3650ad07c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 191 additions and 72 deletions

View File

@ -13,6 +13,7 @@ use PhpParser\NodeTraverser;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Core\PhpParser\Node\Manipulator\PropertyFetchAssignManipulator;
use Rector\Core\PhpParser\Node\Manipulator\PropertyFetchManipulator;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\TypeDeclaration\Contract\TypeInferer\ParamTypeInfererInterface;
@ -25,9 +26,17 @@ final class GetterNodeParamTypeInferer extends AbstractTypeInferer implements Pa
*/
private $propertyFetchManipulator;
public function __construct(PropertyFetchManipulator $propertyFetchManipulator)
{
/**
* @var PropertyFetchAssignManipulator
*/
private $propertyFetchAssignManipulator;
public function __construct(
PropertyFetchManipulator $propertyFetchManipulator,
PropertyFetchAssignManipulator $propertyFetchAssignManipulator
) {
$this->propertyFetchManipulator = $propertyFetchManipulator;
$this->propertyFetchAssignManipulator = $propertyFetchAssignManipulator;
}
public function inferParam(Param $param): Type
@ -44,7 +53,10 @@ final class GetterNodeParamTypeInferer extends AbstractTypeInferer implements Pa
/** @var string $paramName */
$paramName = $this->nodeNameResolver->getName($param);
$propertyNames = $this->propertyFetchManipulator->getPropertyNamesOfAssignOfVariable($classMethod, $paramName);
$propertyNames = $this->propertyFetchAssignManipulator->getPropertyNamesOfAssignOfVariable(
$classMethod,
$paramName
);
if ($propertyNames === []) {
return new MixedType();
}

View File

@ -20,7 +20,7 @@ use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Node\Manipulator\PropertyFetchManipulator;
use Rector\Core\PhpParser\Node\Manipulator\ClassMethodPropertyFetchManipulator;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PHPStan\Type\AliasedObjectType;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
@ -30,13 +30,13 @@ use Rector\TypeDeclaration\TypeInferer\AbstractTypeInferer;
final class ConstructorPropertyTypeInferer extends AbstractTypeInferer implements PropertyTypeInfererInterface
{
/**
* @var PropertyFetchManipulator
* @var ClassMethodPropertyFetchManipulator
*/
private $propertyFetchManipulator;
private $classMethodPropertyFetchManipulator;
public function __construct(PropertyFetchManipulator $propertyFetchManipulator)
public function __construct(ClassMethodPropertyFetchManipulator $classMethodPropertyFetchManipulator)
{
$this->propertyFetchManipulator = $propertyFetchManipulator;
$this->classMethodPropertyFetchManipulator = $classMethodPropertyFetchManipulator;
}
public function inferProperty(Property $property): Type
@ -58,7 +58,7 @@ final class ConstructorPropertyTypeInferer extends AbstractTypeInferer implement
throw new ShouldNotHappenException();
}
$param = $this->propertyFetchManipulator->resolveParamForPropertyFetch($classMethod, $propertyName);
$param = $this->classMethodPropertyFetchManipulator->resolveParamForPropertyFetch($classMethod, $propertyName);
if ($param === null) {
return new MixedType();
}

View File

@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Rector\Core\PhpParser\Node\Manipulator;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\NodeTraverser;
use Rector\Core\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\NodeNameResolver\NodeNameResolver;
final class ClassMethodPropertyFetchManipulator
{
/**
* @var CallableNodeTraverser
*/
private $callableNodeTraverser;
/**
* @var NodeNameResolver
*/
private $nodeNameResolver;
public function __construct(CallableNodeTraverser $callableNodeTraverser, NodeNameResolver $nodeNameResolver)
{
$this->callableNodeTraverser = $callableNodeTraverser;
$this->nodeNameResolver = $nodeNameResolver;
}
/**
* In case the property name is different to param name:
*
* E.g.:
* (SomeType $anotherValue)
* $this->value = $anotherValue;
*
* (SomeType $anotherValue)
*/
public function resolveParamForPropertyFetch(ClassMethod $classMethod, string $propertyName): ?Param
{
$assignedParamName = null;
$this->callableNodeTraverser->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use (
$propertyName,
&$assignedParamName
): ?int {
if (! $node instanceof Assign) {
return null;
}
if (! $this->nodeNameResolver->isName($node->var, $propertyName)) {
return null;
}
$assignedParamName = $this->nodeNameResolver->getName($node->expr);
return NodeTraverser::STOP_TRAVERSAL;
});
/** @var string|null $assignedParamName */
if ($assignedParamName === null) {
return null;
}
/** @var Param $param */
foreach ((array) $classMethod->params as $param) {
if (! $this->nodeNameResolver->isName($param, $assignedParamName)) {
continue;
}
return $param;
}
return null;
}
}

View File

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Rector\Core\PhpParser\Node\Manipulator;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use Rector\Core\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\NodeNameResolver\NodeNameResolver;
final class PropertyFetchAssignManipulator
{
/**
* @var NodeNameResolver
*/
private $nodeNameResolver;
/**
* @var CallableNodeTraverser
*/
private $callableNodeTraverser;
public function __construct(NodeNameResolver $nodeNameResolver, CallableNodeTraverser $callableNodeTraverser)
{
$this->nodeNameResolver = $nodeNameResolver;
$this->callableNodeTraverser = $callableNodeTraverser;
}
/**
* @return string[]
*/
public function getPropertyNamesOfAssignOfVariable(Node $node, string $paramName): array
{
$propertyNames = [];
$this->callableNodeTraverser->traverseNodesWithCallable($node, function (Node $node) use (
$paramName,
&$propertyNames
) {
if (! $this->isVariableAssignToThisPropertyFetch($node, $paramName)) {
return null;
}
/** @var Assign $node */
$propertyName = $this->nodeNameResolver->getName($node->expr);
if ($propertyName) {
$propertyNames[] = $propertyName;
}
return null;
});
return $propertyNames;
}
/**
* Matches:
* "$this->someValue = $<variableName>;"
*/
public function isVariableAssignToThisPropertyFetch(Node $node, string $variableName): bool
{
if (! $node instanceof Assign) {
return false;
}
if (! $node->expr instanceof Variable) {
return false;
}
if (! $this->nodeNameResolver->isName($node->expr, $variableName)) {
return false;
}
if (! $node->var instanceof PropertyFetch) {
return false;
}
// must be local property
return $this->nodeNameResolver->isName($node->var->var, 'this');
}
}

View File

@ -12,13 +12,11 @@ use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\NodeTraverser;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ErrorType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\NodeNameResolver\NodeNameResolver;
@ -86,13 +84,9 @@ final class PropertyFetchManipulator
return false;
}
public function isMagicOnType(Node $node, Type $type): bool
public function isMagicOnType(PropertyFetch $propertyFetch, Type $type): bool
{
if (! $node instanceof PropertyFetch) {
return false;
}
$varNodeType = $this->nodeTypeResolver->resolve($node);
$varNodeType = $this->nodeTypeResolver->resolve($propertyFetch);
if ($varNodeType instanceof ErrorType) {
return true;
@ -106,12 +100,12 @@ final class PropertyFetchManipulator
return false;
}
$nodeName = $this->nodeNameResolver->getName($node);
$nodeName = $this->nodeNameResolver->getName($propertyFetch);
if ($nodeName === null) {
return false;
}
return ! $this->hasPublicProperty($node, $nodeName);
return ! $this->hasPublicProperty($propertyFetch, $nodeName);
}
/**
@ -162,6 +156,7 @@ final class PropertyFetchManipulator
if (! $node->var instanceof PropertyFetch) {
return false;
}
// must be local property
return $this->nodeNameResolver->isName($node->var->var, 'this');
}
@ -188,53 +183,6 @@ final class PropertyFetchManipulator
return $this->nodeNameResolver->isName($node->var, 'this');
}
/**
* In case the property name is different to param name:
*
* E.g.:
* (SomeType $anotherValue)
* $this->value = $anotherValue;
*
* (SomeType $anotherValue)
*/
public function resolveParamForPropertyFetch(ClassMethod $classMethod, string $propertyName): ?Param
{
$assignedParamName = null;
$this->callableNodeTraverser->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use (
$propertyName,
&$assignedParamName
): ?int {
if (! $node instanceof Assign) {
return null;
}
if (! $this->nodeNameResolver->isName($node->var, $propertyName)) {
return null;
}
$assignedParamName = $this->nodeNameResolver->getName($node->expr);
return NodeTraverser::STOP_TRAVERSAL;
});
/** @var string|null $assignedParamName */
if ($assignedParamName === null) {
return null;
}
/** @var Param $param */
foreach ((array) $classMethod->params as $param) {
if (! $this->nodeNameResolver->isName($param, $assignedParamName)) {
continue;
}
return $param;
}
return null;
}
/**
* @return PropertyFetch|StaticPropertyFetch|null
*/
@ -290,14 +238,11 @@ final class PropertyFetchManipulator
}
$propertyFetchType = $nodeScope->getType($propertyFetch->var);
if ($propertyFetchType instanceof ObjectType) {
$propertyFetchType = $propertyFetchType->getClassName();
}
if (! is_string($propertyFetchType)) {
if (! $propertyFetchType instanceof TypeWithClassName) {
return false;
}
$propertyFetchType = $propertyFetchType->getClassName();
if (! $this->reflectionProvider->hasClass($propertyFetchType)) {
return false;
}