cleanup type resolving

This commit is contained in:
Tomas Votruba 2019-09-02 11:55:24 +02:00
parent a8643b7576
commit ac08e7c320
10 changed files with 161 additions and 303 deletions

View File

@ -14,10 +14,10 @@ use Rector\NetteToSymfony\Route\RouteInfo;
use Rector\NetteToSymfony\Route\RouteInfoFactory;
use Rector\NodeContainer\ParsedNodesByType;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
use Rector\PhpParser\Node\Manipulator\ClassMethodManipulator;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
use Rector\Util\RectorStrings;
use ReflectionMethod;
@ -58,15 +58,15 @@ final class RouterListToControllerAnnotationsRector extends AbstractRector
private $routeInfoFactory;
/**
* @var ClassMethodManipulator
* @var ReturnTypeInferer
*/
private $classMethodManipulator;
private $returnTypeInferer;
public function __construct(
ParsedNodesByType $parsedNodesByType,
ClassMethodManipulator $classMethodManipulator,
DocBlockManipulator $docBlockManipulator,
RouteInfoFactory $routeInfoFactory,
ReturnTypeInferer $returnTypeInferer,
string $routeListClass = 'Nette\Application\Routers\RouteList',
string $routerClass = 'Nette\Application\IRouter',
string $routeAnnotationClass = 'Symfony\Component\Routing\Annotation\Route'
@ -77,7 +77,7 @@ final class RouterListToControllerAnnotationsRector extends AbstractRector
$this->docBlockManipulator = $docBlockManipulator;
$this->routeAnnotationClass = $routeAnnotationClass;
$this->routeInfoFactory = $routeInfoFactory;
$this->classMethodManipulator = $classMethodManipulator;
$this->returnTypeInferer = $returnTypeInferer;
}
public function getDefinition(): RectorDefinition
@ -153,12 +153,8 @@ CODE_SAMPLE
return null;
}
$nodeReturnTypes = $this->classMethodManipulator->resolveReturnType($node);
if ($nodeReturnTypes === []) {
return null;
}
if (! in_array($this->routeListClass, $nodeReturnTypes, true)) {
$inferedReturnTypes = $this->returnTypeInferer->inferFunctionLike($node);
if (! in_array($this->routeListClass, $inferedReturnTypes, true)) {
return null;
}
@ -176,13 +172,7 @@ CODE_SAMPLE
continue;
}
$phpDocTagNode = new SymfonyRoutePhpDocTagNode(
$this->routeAnnotationClass,
$routeInfo->getPath(),
null,
$routeInfo->getHttpMethods()
);
$phpDocTagNode = $this->createSymfonyRoutePhpDocTagNode($routeInfo);
$this->docBlockManipulator->addTag($classMethod, $phpDocTagNode);
}
@ -338,4 +328,14 @@ CODE_SAMPLE
return $presenterPart . '/' . $actionPart;
}
private function createSymfonyRoutePhpDocTagNode(RouteInfo $routeInfo): SymfonyRoutePhpDocTagNode
{
return new SymfonyRoutePhpDocTagNode(
$this->routeAnnotationClass,
$routeInfo->getPath(),
null,
$routeInfo->getHttpMethods()
);
}
}

View File

@ -9,6 +9,7 @@ use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer\ReturnTypeDeclarationReturnTypeInferer;
/**
* @sponsor Thanks https://spaceflow.io/ for sponsoring this rule - visit them on https://github.com/SpaceFlow-app
@ -89,16 +90,22 @@ CODE_SAMPLE
return null;
}
$inferedTypes = $this->returnTypeInferer->inferFunctionLike($node);
$inferedTypes = $this->returnTypeInferer->inferFunctionLikeWithExcludedInferers(
$node,
[ReturnTypeDeclarationReturnTypeInferer::class]
);
if ($inferedTypes === ['void']) {
return null;
}
if ($inferedTypes !== []) {
$docType = implode('|', $inferedTypes);
$this->docBlockManipulator->addReturnTag($node, $docType);
if ($inferedTypes === []) {
return null;
}
$docType = implode('|', $inferedTypes);
$this->docBlockManipulator->addReturnTag($node, $docType);
return $node;
}

View File

@ -14,6 +14,7 @@ use Rector\Php\TypeAnalyzer;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer\ReturnTypeDeclarationReturnTypeInferer;
final class ReturnTypeDeclarationRector extends AbstractTypeDeclarationRector
{
@ -94,7 +95,11 @@ CODE_SAMPLE
$hasNewType = $node->returnType->getAttribute(self::HAS_NEW_INHERITED_TYPE, false);
}
$inferedTypes = $this->returnTypeInferer->inferFunctionLike($node);
$inferedTypes = $this->returnTypeInferer->inferFunctionLikeWithExcludedInferers(
$node,
[ReturnTypeDeclarationReturnTypeInferer::class]
);
if ($inferedTypes === []) {
return null;
}

View File

@ -0,0 +1,46 @@
<?php declare(strict_types=1);
namespace Rector\TypeDeclaration;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\NullableType;
use Rector\PhpParser\Node\Resolver\NameResolver;
final class TypeDeclarationToStringConverter
{
/**
* @var NameResolver
*/
private $nameResolver;
public function __construct(NameResolver $nameResolver)
{
$this->nameResolver = $nameResolver;
}
/**
* @return string[]
*/
public function resolveFunctionLikeReturnTypeToString(FunctionLike $functionLike): array
{
if ($functionLike->getReturnType() === null) {
return [];
}
$returnType = $functionLike->getReturnType();
$types = [];
$type = $returnType instanceof NullableType ? $returnType->type : $returnType;
$result = $this->nameResolver->getName($type);
if ($result !== null) {
$types[] = $result;
}
if ($returnType instanceof NullableType) {
$types[] = 'null';
}
return $types;
}
}

View File

@ -2,21 +2,28 @@
namespace Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Return_;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\TypeDeclaration\Contract\TypeInferer\PropertyTypeInfererInterface;
use Rector\TypeDeclaration\TypeDeclarationToStringConverter;
use Rector\TypeDeclaration\TypeInferer\AbstractTypeInferer;
final class GetterTypeDeclarationPropertyTypeInferer extends AbstractTypeInferer implements PropertyTypeInfererInterface
{
/**
* @var TypeDeclarationToStringConverter
*/
private $typeDeclarationToStringConverter;
public function __construct(TypeDeclarationToStringConverter $typeDeclarationToStringConverter)
{
$this->typeDeclarationToStringConverter = $typeDeclarationToStringConverter;
}
/**
* @return string[]
*/
@ -33,7 +40,7 @@ final class GetterTypeDeclarationPropertyTypeInferer extends AbstractTypeInferer
continue;
}
$returnTypes = $this->resolveReturnTypeToString($classMethod);
$returnTypes = $this->typeDeclarationToStringConverter->resolveFunctionLikeReturnTypeToString($classMethod);
// let PhpDoc solve that later for more precise type
if ($returnTypes === ['array']) {
return [];
@ -74,31 +81,4 @@ final class GetterTypeDeclarationPropertyTypeInferer extends AbstractTypeInferer
return $this->nameResolver->isName($return->expr, $propertyName);
}
/**
* @param Function_|ClassMethod|Closure $functionLike
* @return string[]
*/
private function resolveReturnTypeToString(FunctionLike $functionLike): array
{
if ($functionLike->getReturnType() === null) {
return [];
}
$returnType = $functionLike->getReturnType();
$types = [];
$type = $returnType instanceof NullableType ? $returnType->type : $returnType;
$result = $this->nameResolver->getName($type);
if ($result !== null) {
$types[] = $result;
}
if ($returnType instanceof NullableType) {
$types[] = 'null';
}
return $types;
}
}

View File

@ -25,8 +25,30 @@ final class ReturnTypeInferer extends AbstractPriorityAwareTypeInferer
*/
public function inferFunctionLike(FunctionLike $functionLike): array
{
foreach ($this->returnTypeInferers as $returnTypeInferers) {
$types = $returnTypeInferers->inferFunctionLike($functionLike);
foreach ($this->returnTypeInferers as $returnTypeInferer) {
$types = $returnTypeInferer->inferFunctionLike($functionLike);
if ($types !== []) {
return $types;
}
}
return [];
}
/**
* @param string[] $excludedInferers
* @return string[]
*/
public function inferFunctionLikeWithExcludedInferers(FunctionLike $functionLike, array $excludedInferers): array
{
foreach ($this->returnTypeInferers as $returnTypeInferer) {
foreach ($excludedInferers as $excludedInferer) {
if (is_a($returnTypeInferer, $excludedInferer, true)) {
continue 2;
}
}
$types = $returnTypeInferer->inferFunctionLike($functionLike);
if ($types !== []) {
return $types;
}

View File

@ -0,0 +1,43 @@
<?php declare(strict_types=1);
namespace Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
use PhpParser\Node\FunctionLike;
use Rector\TypeDeclaration\Contract\TypeInferer\ReturnTypeInfererInterface;
use Rector\TypeDeclaration\TypeDeclarationToStringConverter;
use Rector\TypeDeclaration\TypeInferer\AbstractTypeInferer;
final class ReturnTypeDeclarationReturnTypeInferer extends AbstractTypeInferer implements ReturnTypeInfererInterface
{
/**
* @var TypeDeclarationToStringConverter
*/
private $typeDeclarationToStringConverter;
public function __construct(TypeDeclarationToStringConverter $typeDeclarationToStringConverter)
{
$this->typeDeclarationToStringConverter = $typeDeclarationToStringConverter;
}
/**
* @return string[]
*/
public function inferFunctionLike(FunctionLike $functionLike): array
{
if ($functionLike->getReturnType() === null) {
return [];
}
// resolve later with more precise type, e.g. Type[]
if ($this->nameResolver->isNames($functionLike->getReturnType(), ['array', 'iterable'])) {
return [];
}
return $this->typeDeclarationToStringConverter->resolveFunctionLikeReturnTypeToString($functionLike);
}
public function getPriority(): int
{
return 2000;
}
}

View File

@ -17,7 +17,7 @@ final class AddArrayReturnDocTypeRectorTest extends AbstractRectorTestCase
__DIR__ . '/Fixture/simple_array.php.inc',
__DIR__ . '/Fixture/add_without_return_type_declaration.php.inc',
__DIR__ . '/Fixture/fix_incorrect_array.php.inc',
// skip
// skip
__DIR__ . '/Fixture/skip_constructor.php.inc',
__DIR__ . '/Fixture/skip_array_after_array_type.php.inc',
__DIR__ . '/Fixture/skip_shorten_class_name.php.inc',

View File

@ -33,11 +33,6 @@ final class ClassMethodManipulator
*/
private $nodeTypeResolver;
/**
* @var FunctionLikeManipulator
*/
private $functionLikeManipulator;
/**
* @var NameResolver
*/
@ -52,14 +47,12 @@ final class ClassMethodManipulator
BetterNodeFinder $betterNodeFinder,
BetterStandardPrinter $betterStandardPrinter,
NodeTypeResolver $nodeTypeResolver,
FunctionLikeManipulator $functionLikeManipulator,
NameResolver $nameResolver,
ValueResolver $valueResolver
) {
$this->betterNodeFinder = $betterNodeFinder;
$this->betterStandardPrinter = $betterStandardPrinter;
$this->nodeTypeResolver = $nodeTypeResolver;
$this->functionLikeManipulator = $functionLikeManipulator;
$this->nameResolver = $nameResolver;
$this->valueResolver = $valueResolver;
}
@ -121,33 +114,6 @@ final class ClassMethodManipulator
return false;
}
/**
* @return string[]
*/
public function resolveReturnType(ClassMethod $classMethod): array
{
if ($classMethod->returnType !== null) {
return $this->nodeTypeResolver->resolve($classMethod->returnType);
}
$staticReturnType = $this->functionLikeManipulator->resolveStaticReturnTypeInfo($classMethod);
if ($staticReturnType === null) {
return [];
}
$getFqnTypeNode = $staticReturnType->getFqnTypeNode();
if ($getFqnTypeNode === null) {
return [];
}
$fqnTypeName = $this->nameResolver->getName($getFqnTypeNode);
if ($fqnTypeName === null) {
return [];
}
return [$fqnTypeName];
}
/**
* Is method actually static, or has some $this-> calls?
*/

View File

@ -2,62 +2,20 @@
namespace Rector\PhpParser\Node\Manipulator;
use Iterator;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\Yield_;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Return_;
use PhpParser\NodeTraverser;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\NodeTypeResolver\Php\ReturnTypeInfo;
use Rector\Php\PhpVersionProvider;
use Rector\Php\TypeAnalyzer;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\PhpParser\Node\Resolver\NameResolver;
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\TypeDeclaration\Rector\FunctionLike\AbstractTypeDeclarationRector;
final class FunctionLikeManipulator
{
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
/**
* @var TypeAnalyzer
*/
private $typeAnalyzer;
/**
* @var NodeTypeResolver
*/
private $nodeTypeResolver;
/**
* @var NameResolver
*/
private $nameResolver;
/**
* @var bool
*/
private $isVoid = false;
/**
* @var PhpVersionProvider
*/
private $phpVersionProvider;
/**
* @var CallableNodeTraverser
*/
@ -69,70 +27,15 @@ final class FunctionLikeManipulator
private $propertyFetchManipulator;
public function __construct(
BetterNodeFinder $betterNodeFinder,
TypeAnalyzer $typeAnalyzer,
NodeTypeResolver $nodeTypeResolver,
NameResolver $nameResolver,
PhpVersionProvider $phpVersionProvider,
CallableNodeTraverser $callableNodeTraverser,
PropertyFetchManipulator $propertyFetchManipulator
) {
$this->betterNodeFinder = $betterNodeFinder;
$this->typeAnalyzer = $typeAnalyzer;
$this->nodeTypeResolver = $nodeTypeResolver;
$this->nameResolver = $nameResolver;
$this->phpVersionProvider = $phpVersionProvider;
$this->callableNodeTraverser = $callableNodeTraverser;
$this->propertyFetchManipulator = $propertyFetchManipulator;
}
/**
* Based on static analysis of code, looking for return types
* @param ClassMethod|Function_|Closure $functionLike
*/
public function resolveStaticReturnTypeInfo(FunctionLike $functionLike): ?ReturnTypeInfo
{
if ($this->shouldSkip($functionLike)) {
return null;
}
// @todo decouple to return type inferer B-)
// A. resolve from function return type
// skip "array" to get more precise types
if ($functionLike->returnType !== null && ! $this->nameResolver->isNames(
$functionLike->returnType,
['array', 'iterable']
)) {
$types = $this->resolveReturnTypeToString($functionLike->returnType);
// do not override freshly added type declaration
if (! $functionLike->returnType->getAttribute(
AbstractTypeDeclarationRector::HAS_NEW_INHERITED_TYPE
) && $types !== []) {
return new ReturnTypeInfo($types, $this->typeAnalyzer, $types);
}
}
$this->isVoid = true;
// B. resolve from return $x nodes
$types = $this->resolveTypesFromReturnNodes($functionLike);
// C. resolve from yields
if ($types === []) {
$types = $this->resolveFromYieldNodes($functionLike);
}
if ($this->isVoid) {
return new ReturnTypeInfo(['void'], $this->typeAnalyzer, ['void']);
}
$types = array_filter($types);
return new ReturnTypeInfo($types, $this->typeAnalyzer, $types);
}
/**
* @return string[]
*/
@ -165,118 +68,4 @@ final class FunctionLikeManipulator
return $returnedLocalPropertyNames;
}
private function shouldSkip(FunctionLike $functionLike): bool
{
if (! $functionLike instanceof ClassMethod) {
return false;
}
$classNode = $functionLike->getAttribute(AttributeKey::CLASS_NODE);
// only class or trait method body can be analyzed for returns
if ($classNode instanceof Interface_) {
return true;
}
// only methods that are not abstract can be analyzed for returns
return $functionLike->isAbstract();
}
/**
* @param Identifier|Name|NullableType $node
* @return string[]
*/
private function resolveReturnTypeToString(Node $node): array
{
$types = [];
$type = $node instanceof NullableType ? $node->type : $node;
$result = $this->nameResolver->getName($type);
if ($result !== null) {
$types[] = $result;
}
if ($node instanceof NullableType) {
$types[] = 'null';
}
return $types;
}
/**
* @covered
* @param ClassMethod|Function_|Closure $functionLike
* @return string[]
*/
private function resolveTypesFromReturnNodes(FunctionLike $functionLike): array
{
// local
/** @var Return_[] $localReturnNodes */
$localReturnNodes = [];
$this->callableNodeTraverser->traverseNodesWithCallable((array) $functionLike->stmts, function (Node $node) use (
&$localReturnNodes
): ?int {
if ($node instanceof Function_ || $node instanceof Closure || $node instanceof ArrowFunction) {
// skip Return_ nodes in nested functions
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
}
if (! $node instanceof Return_) {
return null;
}
// skip void returns
if ($node->expr === null) {
return null;
}
$localReturnNodes[] = $node;
return null;
});
$types = [];
foreach ($localReturnNodes as $localReturnNode) {
$types = array_merge($types, $this->nodeTypeResolver->resolveSingleTypeToStrings($localReturnNode->expr));
$this->isVoid = false;
}
return $types;
}
/**
* @param ClassMethod|Function_|Closure $functionLike
* @return string[]
*/
private function resolveFromYieldNodes(FunctionLike $functionLike): array
{
/** @var Yield_[] $yieldNodes */
$yieldNodes = $this->betterNodeFinder->findInstanceOf((array) $functionLike->stmts, Yield_::class);
$types = [];
if (count($yieldNodes)) {
$this->isVoid = false;
foreach ($yieldNodes as $yieldNode) {
if ($yieldNode->value === null) {
continue;
}
$resolvedTypes = $this->nodeTypeResolver->resolveSingleTypeToStrings($yieldNode->value);
foreach ($resolvedTypes as $resolvedType) {
$types[] = $resolvedType . '[]';
}
}
if ($this->phpVersionProvider->isAtLeast('7.1')) {
// @see https://www.php.net/manual/en/language.types.iterable.php
$types[] = 'iterable';
} else {
$types[] = Iterator::class;
}
}
return array_unique($types);
}
}