add AbstarctPriorityAwareTypeInferer

This commit is contained in:
Tomas Votruba 2019-09-01 22:38:31 +02:00
parent 8af87e51c9
commit 8b6331bc01
39 changed files with 441 additions and 209 deletions

View File

@ -10,7 +10,12 @@ use Rector\NodeTypeResolver\Contract\PerNodeTypeResolver\PerNodeTypeResolverInte
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
use Rector\PhpParser\Node\Resolver\NameResolver;
use Rector\TypeDeclaration\TypeInferer\ParamTypeInferer;
/**
* @todo move to
* @see ParamTypeInferer
*/
final class ParamTypeResolver implements PerNodeTypeResolverInterface
{
/**

View File

@ -18,6 +18,11 @@ abstract class AbstractTypeInfo
*/
protected $isNullable = false;
/**
* @var bool
*/
protected $isAlias = false;
/**
* @var string[]
*/
@ -112,7 +117,11 @@ abstract class AbstractTypeInfo
$type = ltrim($type, '\\');
$name = $forceFqn ? new FullyQualified($type) : new Name($type);
if ($this->isAlias || $forceFqn === false) {
$name = new Name($type);
} else {
$name = new FullyQualified($type);
}
if ($this->isNullable) {
return new NullableType($name);

View File

@ -11,6 +11,11 @@ final class ParamTypeInfo extends AbstractTypeInfo
*/
protected $typesToRemove = ['void', 'real'];
/**
* @var bool
*/
protected $isAlias = false;
/**
* @var string
*/
@ -20,9 +25,15 @@ final class ParamTypeInfo extends AbstractTypeInfo
* @param string[] $types
* @param string[] $fqnTypes
*/
public function __construct(string $name, TypeAnalyzer $typeAnalyzer, array $types, array $fqnTypes = [])
{
public function __construct(
string $name,
TypeAnalyzer $typeAnalyzer,
array $types,
array $fqnTypes = [],
bool $isAlias = false
) {
$this->name = $this->normalizeName($name);
$this->isAlias = $isAlias;
parent::__construct($types, $typeAnalyzer, $fqnTypes);
}

View File

@ -5,7 +5,11 @@ namespace Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer;
use Nette\Utils\Strings;
use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode;
@ -231,15 +235,16 @@ final class DocBlockManipulator
/**
* With "name" as key
*
* @param Function_|ClassMethod|Closure $functionLike
* @return ParamTypeInfo[]
*/
public function getParamTypeInfos(Node $node): array
public function getParamTypeInfos(FunctionLike $functionLike): array
{
if ($node->getDocComment() === null) {
if ($functionLike->getDocComment() === null) {
return [];
}
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
$phpDocInfo = $this->createPhpDocInfoFromNode($functionLike);
$types = $phpDocInfo->getParamTagValues();
if ($types === []) {
return [];
@ -251,12 +256,14 @@ final class DocBlockManipulator
/** @var AttributeAwareParamTagValueNode $paramTagValueNode */
foreach ($types as $i => $paramTagValueNode) {
$fqnParamTagValueNode = $fqnTypes[$i];
$isAlias = $this->isAlias((string) $paramTagValueNode->type, $functionLike);
$paramTypeInfo = new ParamTypeInfo(
$paramTagValueNode->parameterName,
$this->typeAnalyzer,
$paramTagValueNode->getAttribute(Attribute::TYPE_AS_ARRAY),
$fqnParamTagValueNode->getAttribute(Attribute::RESOLVED_NAMES)
$fqnParamTagValueNode->getAttribute(Attribute::RESOLVED_NAMES),
$isAlias
);
$paramTypeInfos[$paramTypeInfo->getName()] = $paramTypeInfo;
@ -300,6 +307,11 @@ final class DocBlockManipulator
}
}
$returnTypeInfo = new ReturnTypeInfo(explode('|', $type), $this->typeAnalyzer);
if ($returnTypeInfo) {
$type = implode('|', $returnTypeInfo->getDocTypes());
}
$this->removeTagFromNode($node, 'return');
$this->addTypeSpecificTag($node, 'return', $type);
}
@ -824,4 +836,27 @@ final class DocBlockManipulator
return $isShortClassUsed;
}
private function isAlias(string $paramType, Node $node): bool
{
/** @var Node\Stmt\Use_[]|null $useNodes */
$useNodes = $node->getAttribute(AttributeKey::USE_NODES);
if ($useNodes === null) {
return false;
}
foreach ($useNodes as $useNode) {
foreach ($useNode->uses as $useUse) {
if ($useUse->alias === null) {
continue;
}
if ((string) $useUse->alias === $paramType) {
return true;
}
}
}
return false;
}
}

View File

@ -0,0 +1,11 @@
<?php declare(strict_types=1);
namespace Rector\TypeDeclaration\Contract\TypeInferer;
interface PriorityAwareTypeInfererInterface
{
/**
* Higher priority goes first.
*/
public function getPriority(): int;
}

View File

@ -5,15 +5,10 @@ namespace Rector\TypeDeclaration\Contract\TypeInferer;
use PhpParser\Node\Stmt\Property;
use Rector\TypeDeclaration\ValueObject\IdentifierValueObject;
interface PropertyTypeInfererInterface
interface PropertyTypeInfererInterface extends PriorityAwareTypeInfererInterface
{
/**
* @return string[]|IdentifierValueObject[]
*/
public function inferProperty(Property $property): array;
/**
* Higher priority goes first.
*/
public function getPriority(): int;
}

View File

@ -4,7 +4,7 @@ namespace Rector\TypeDeclaration\Contract\TypeInferer;
use PhpParser\Node\FunctionLike;
interface ReturnTypeInfererInterface
interface ReturnTypeInfererInterface extends PriorityAwareTypeInfererInterface
{
/**
* @return string[]

View File

@ -3,21 +3,21 @@
namespace Rector\TypeDeclaration\Exception;
use Exception;
use Rector\TypeDeclaration\Contract\TypeInferer\PropertyTypeInfererInterface;
use Rector\TypeDeclaration\Contract\TypeInferer\PriorityAwareTypeInfererInterface;
final class ConflictingPriorityException extends Exception
{
public function __construct(
PropertyTypeInfererInterface $firstPropertyTypeInfererInterface,
PropertyTypeInfererInterface $secondPropertyTypeInfererInterface
PriorityAwareTypeInfererInterface $firstPriorityAwareTypeInferer,
PriorityAwareTypeInfererInterface $secondPriorityAwareTypeInferer
) {
$message = sprintf(
'There are 2 property type inferers with %d priority:%s- %s%s- %s.%sChange value in "getPriority()" method in one of them to different value',
$firstPropertyTypeInfererInterface->getPriority(),
'There are 2 type inferers with %d priority:%s- %s%s- %s.%sChange value in "getPriority()" method in one of them to different value',
$firstPriorityAwareTypeInferer->getPriority(),
PHP_EOL,
get_class($firstPropertyTypeInfererInterface),
get_class($firstPriorityAwareTypeInferer),
PHP_EOL,
get_class($secondPropertyTypeInfererInterface),
get_class($secondPriorityAwareTypeInferer),
PHP_EOL
);

View File

@ -89,10 +89,13 @@ CODE_SAMPLE
return null;
}
$docTypes = $this->returnTypeInferer->inferFunctionLike($node);
if ($docTypes !== []) {
$docType = implode('|', $docTypes);
$inferedTypes = $this->returnTypeInferer->inferFunctionLike($node);
if ($inferedTypes === ['void']) {
return null;
}
if ($inferedTypes !== []) {
$docType = implode('|', $inferedTypes);
$this->docBlockManipulator->addReturnTag($node, $docType);
}

View File

@ -86,7 +86,7 @@ CODE_SAMPLE
return null;
}
$returnTypeNode = $staticReturnType->getTypeNode();
$returnTypeNode = $staticReturnType->getFqnTypeNode();
if ($returnTypeNode === null) {
return null;
}

View File

@ -18,7 +18,6 @@ use Rector\NodeContainer\ParsedNodesByType;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\Php\AbstractTypeInfo;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
use Rector\PhpParser\Node\Manipulator\FunctionLikeManipulator;
use Rector\Rector\AbstractRector;
/**
@ -44,22 +43,15 @@ abstract class AbstractTypeDeclarationRector extends AbstractRector
*/
protected $parsedNodesByType;
/**
* @var FunctionLikeManipulator
*/
protected $functionLikeManipulator;
/**
* @required
*/
public function autowireAbstractTypeDeclarationRector(
DocBlockManipulator $docBlockManipulator,
ParsedNodesByType $parsedNodesByType,
FunctionLikeManipulator $functionLikeManipulator
ParsedNodesByType $parsedNodesByType
): void {
$this->docBlockManipulator = $docBlockManipulator;
$this->parsedNodesByType = $parsedNodesByType;
$this->functionLikeManipulator = $functionLikeManipulator;
}
/**
@ -136,7 +128,7 @@ abstract class AbstractTypeDeclarationRector extends AbstractRector
* @param Name|NullableType|Identifier $possibleSubtype
* @param Name|NullableType|Identifier $type
*/
protected function isSubtypeOf(Node $possibleSubtype, Node $type): bool
protected function isSubtypeOf(Node $possibleSubtype, Node $type, string $kind): bool
{
$type = $type instanceof NullableType ? $type->type : $type;
@ -147,8 +139,17 @@ abstract class AbstractTypeDeclarationRector extends AbstractRector
$possibleSubtype = $possibleSubtype->toString();
$type = $type->toString();
if (is_a($possibleSubtype, $type, true)) {
return true;
if ($kind === 'return') {
if ($this->isAtLeastPhpVersion('7.4')) {
// @see https://wiki.php.net/rfc/covariant-returns-and-contravariant-parameters
if (is_a($possibleSubtype, $type, true)) {
return true;
}
}
} elseif ($kind === 'param') {
if (is_a($possibleSubtype, $type, true)) {
return true;
}
}
if (in_array($possibleSubtype, ['array', 'Traversable'], true) && $type === 'iterable') {
@ -167,14 +168,11 @@ abstract class AbstractTypeDeclarationRector extends AbstractRector
}
/**
* @param ClassMethod|Param $childClassMethodOrParam
* @param ClassMethod|Param $node
* @return Name|NullableType|Identifier|null
*/
protected function resolveChildType(
AbstractTypeInfo $returnTypeInfo,
Node $node,
Node $childClassMethodOrParam
): ?Node {
protected function resolveChildType(AbstractTypeInfo $returnTypeInfo, Node $node): ?Node
{
$nakedType = $returnTypeInfo->getTypeNode() instanceof NullableType ? $returnTypeInfo->getTypeNode()->type : $returnTypeInfo->getTypeNode();
if ($nakedType === null) {
@ -203,11 +201,6 @@ abstract class AbstractTypeDeclarationRector extends AbstractRector
return $returnTypeInfo->isNullable() ? new NullableType($type) : $type;
}
// is namespace the same? use short name
if ($this->haveSameNamespace($node, $childClassMethodOrParam)) {
return $returnTypeInfo->getTypeNode();
}
// are namespaces different? → FQN name
return $returnTypeInfo->getFqnTypeNode();
}
@ -264,10 +257,4 @@ abstract class AbstractTypeDeclarationRector extends AbstractRector
return $interfaces;
}
private function haveSameNamespace(Node $firstNode, Node $secondNode): bool
{
return $firstNode->getAttribute(AttributeKey::NAMESPACE_NAME)
=== $secondNode->getAttribute(AttributeKey::NAMESPACE_NAME);
}
}

View File

@ -140,19 +140,19 @@ CODE_SAMPLE
if ($hasNewType) {
// should override - is it subtype?
$possibleOverrideNewReturnType = $paramTypeInfo->getTypeNode();
$possibleOverrideNewReturnType = $paramTypeInfo->getFqnTypeNode();
if ($possibleOverrideNewReturnType !== null) {
if ($paramNode->type !== null) {
if ($this->isSubtypeOf($possibleOverrideNewReturnType, $paramNode->type)) {
if ($this->isSubtypeOf($possibleOverrideNewReturnType, $paramNode->type, 'param')) {
// allow override
$paramNode->type = $paramTypeInfo->getTypeNode();
$paramNode->type = $paramTypeInfo->getFqnTypeNode();
}
} else {
$paramNode->type = $paramTypeInfo->getTypeNode();
}
}
} else {
$paramNode->type = $paramTypeInfo->getTypeNode();
$paramNode->type = $paramTypeInfo->getFqnTypeNode();
$paramNodeType = $paramNode->type instanceof NullableType ? $paramNode->type->type : $paramNode->type;
// "resource" is valid phpdoc type, but it's not implemented in PHP
@ -171,6 +171,7 @@ CODE_SAMPLE
/**
* Add typehint to all children
* @param ClassMethod|Function_ $node
*/
private function populateChildren(Node $node, int $position, ParamTypeInfo $paramTypeInfo): void
{
@ -178,9 +179,6 @@ CODE_SAMPLE
return;
}
/** @var string $methodName */
$methodName = $this->getName($node);
/** @var string $className */
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
// anonymous class
@ -196,38 +194,39 @@ CODE_SAMPLE
$usedTraits = $this->parsedNodesByType->findUsedTraitsInClass($childClassLike);
foreach ($usedTraits as $trait) {
$this->addParamTypeToMethod($trait, $methodName, $position, $node, $paramTypeInfo);
$this->addParamTypeToMethod($trait, $position, $node, $paramTypeInfo);
}
}
$this->addParamTypeToMethod($childClassLike, $methodName, $position, $node, $paramTypeInfo);
$this->addParamTypeToMethod($childClassLike, $position, $node, $paramTypeInfo);
}
}
private function addParamTypeToMethod(
ClassLike $classLike,
string $methodName,
int $position,
Node $node,
ClassMethod $classMethod,
ParamTypeInfo $paramTypeInfo
): void {
$classMethod = $classLike->getMethod($methodName);
if ($classMethod === null) {
$methodName = $this->getName($classMethod);
$currentClassMethod = $classLike->getMethod($methodName);
if ($currentClassMethod === null) {
return;
}
if (! isset($classMethod->params[$position])) {
if (! isset($currentClassMethod->params[$position])) {
return;
}
$paramNode = $classMethod->params[$position];
$paramNode = $currentClassMethod->params[$position];
// already has a type
if ($paramNode->type !== null) {
return;
}
$resolvedChildType = $this->resolveChildType($paramTypeInfo, $node, $classMethod);
$resolvedChildType = $this->resolveChildType($paramTypeInfo, $classMethod);
if ($resolvedChildType === null) {
return;
}

View File

@ -13,7 +13,6 @@ use Rector\NodeTypeResolver\Php\ReturnTypeInfo;
use Rector\Php\TypeAnalyzer;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\TypeDeclaration\ReturnTypeResolver\ReturnTypeResolver;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
final class ReturnTypeDeclarationRector extends AbstractTypeDeclarationRector
@ -23,11 +22,6 @@ final class ReturnTypeDeclarationRector extends AbstractTypeDeclarationRector
*/
private const EXCLUDED_METHOD_NAMES = ['__construct', '__destruct', '__clone'];
/**
* @var ReturnTypeResolver
*/
private $returnTypeResolver;
/**
* @var ReturnTypeInferer
*/
@ -38,12 +32,8 @@ final class ReturnTypeDeclarationRector extends AbstractTypeDeclarationRector
*/
private $typeAnalyzer;
public function __construct(
ReturnTypeResolver $returnTypeResolver,
ReturnTypeInferer $returnTypeInferer,
TypeAnalyzer $typeAnalyzer
) {
$this->returnTypeResolver = $returnTypeResolver;
public function __construct(ReturnTypeInferer $returnTypeInferer, TypeAnalyzer $typeAnalyzer)
{
$this->returnTypeInferer = $returnTypeInferer;
$this->typeAnalyzer = $typeAnalyzer;
}
@ -104,50 +94,28 @@ CODE_SAMPLE
$hasNewType = $node->returnType->getAttribute(self::HAS_NEW_INHERITED_TYPE, false);
}
$returnTypeInfo = $this->returnTypeResolver->resolveFunctionLikeReturnType($node);
if ($returnTypeInfo === null) {
$inferedTypes = $this->returnTypeInferer->inferFunctionLike($node);
if ($inferedTypes === []) {
return null;
}
// this could be void
if ($returnTypeInfo->getTypeNode() === null) {
if ($returnTypeInfo->getTypeCount() !== 0) {
return null;
}
// use
$returnTypeInfo = $this->functionLikeManipulator->resolveStaticReturnTypeInfo($node);
if ($returnTypeInfo === null) {
return null;
}
}
// $inferedTypes = $this->returnTypeInferer->inferFunctionLike($node);
// $inferedReturnTypeInfo = new ReturnTypeInfo($inferedTypes, $this->typeAnalyzer, $inferedTypes);
$returnTypeInfo = new ReturnTypeInfo($inferedTypes, $this->typeAnalyzer, $inferedTypes);
// @todo is it violation?
if ($hasNewType) {
// should override - is it subtype?
$possibleOverrideNewReturnType = $returnTypeInfo->getTypeNode();
$possibleOverrideNewReturnType = $returnTypeInfo->getFqnTypeNode();
if ($possibleOverrideNewReturnType !== null) {
if ($node->returnType !== null) {
if ($this->isSubtypeOf($possibleOverrideNewReturnType, $node->returnType)) {
if ($this->isSubtypeOf($possibleOverrideNewReturnType, $node->returnType, 'return')) {
// allow override
$node->returnType = $returnTypeInfo->getTypeNode();
$node->returnType = $returnTypeInfo->getFqnTypeNode();
}
} else {
$node->returnType = $returnTypeInfo->getTypeNode();
$node->returnType = $returnTypeInfo->getFqnTypeNode();
}
}
} else {
if ($returnTypeInfo->getTypeNode() === null) {
$inferedTypes = $this->returnTypeInferer->inferFunctionLike($node);
if ($inferedTypes) {
$returnTypeInfo = new ReturnTypeInfo($inferedTypes, $this->typeAnalyzer, $inferedTypes);
}
}
if ($this->isReturnTypeAlreadyAdded($node, $returnTypeInfo)) {
return null;
}
@ -189,45 +157,41 @@ CODE_SAMPLE
$usedTraits = $this->parsedNodesByType->findUsedTraitsInClass($childClassLike);
foreach ($usedTraits as $trait) {
$this->addReturnTypeToMethod($trait, $methodName, $node, $returnTypeInfo);
$this->addReturnTypeToMethod($trait, $node, $returnTypeInfo);
}
$this->addReturnTypeToMethod($childClassLike, $methodName, $node, $returnTypeInfo);
$this->addReturnTypeToMethod($childClassLike, $node, $returnTypeInfo);
}
}
private function addReturnTypeToMethod(
ClassLike $classLike,
string $methodName,
Node $node,
ClassMethod $classMethod,
ReturnTypeInfo $returnTypeInfo
): void {
$classMethod = $classLike->getMethod($methodName);
if ($classMethod === null) {
$methodName = $this->getName($classMethod);
$currentClassMethod = $classLike->getMethod($methodName);
if ($currentClassMethod === null) {
return;
}
// already has a type
if ($classMethod->returnType !== null) {
if ($currentClassMethod->returnType !== null) {
return;
}
$resolvedChildType = $this->resolveChildType($returnTypeInfo, $node, $classMethod);
$resolvedChildType = $this->resolveChildType($returnTypeInfo, $classMethod);
if ($resolvedChildType === null) {
return;
}
$resolvedChildType = $this->resolveChildType($returnTypeInfo, $node, $classMethod);
if ($resolvedChildType === null) {
return;
}
$classMethod->returnType = $resolvedChildType;
$currentClassMethod->returnType = $resolvedChildType;
// let the method now it was changed now
$classMethod->returnType->setAttribute(self::HAS_NEW_INHERITED_TYPE, true);
$currentClassMethod->returnType->setAttribute(self::HAS_NEW_INHERITED_TYPE, true);
$this->notifyNodeChangeFileInfo($classMethod);
$this->notifyNodeChangeFileInfo($currentClassMethod);
}
/**
@ -247,9 +211,6 @@ CODE_SAMPLE
*/
private function isReturnTypeAlreadyAdded(Node $node, ReturnTypeInfo $returnTypeInfo): bool
{
// dump($this->print($node->returnType), '\\');
// dump($this->print($returnTypeInfo->getTypeNode()));
if (ltrim($this->print($node->returnType), '\\') === $this->print($returnTypeInfo->getTypeNode())) {
return true;
}

View File

@ -0,0 +1,43 @@
<?php declare(strict_types=1);
namespace Rector\TypeDeclaration\TypeInferer;
use Rector\TypeDeclaration\Contract\TypeInferer\PriorityAwareTypeInfererInterface;
use Rector\TypeDeclaration\Exception\ConflictingPriorityException;
abstract class AbstractPriorityAwareTypeInferer
{
/**
* @var PriorityAwareTypeInfererInterface[]
*/
private $sortedTypeInferers = [];
/**
* @param PriorityAwareTypeInfererInterface[] $priorityAwareTypeInferers
* @return PriorityAwareTypeInfererInterface[]
*/
protected function sortTypeInferersByPriority(array $priorityAwareTypeInferers): array
{
$this->sortedTypeInferers = [];
foreach ($priorityAwareTypeInferers as $propertyTypeInferer) {
$this->ensurePriorityIsUnique($propertyTypeInferer);
$this->sortedTypeInferers[$propertyTypeInferer->getPriority()] = $propertyTypeInferer;
}
krsort($this->sortedTypeInferers);
return $this->sortedTypeInferers;
}
private function ensurePriorityIsUnique(PriorityAwareTypeInfererInterface $priorityAwareTypeInferer): void
{
if (! isset($this->sortedTypeInferers[$priorityAwareTypeInferer->getPriority()])) {
return;
}
$alreadySetPropertyTypeInferer = $this->sortedTypeInferers[$priorityAwareTypeInferer->getPriority()];
throw new ConflictingPriorityException($priorityAwareTypeInferer, $alreadySetPropertyTypeInferer);
}
}

View File

@ -4,10 +4,9 @@ namespace Rector\TypeDeclaration\TypeInferer;
use PhpParser\Node\Stmt\Property;
use Rector\TypeDeclaration\Contract\TypeInferer\PropertyTypeInfererInterface;
use Rector\TypeDeclaration\Exception\ConflictingPriorityException;
use Rector\TypeDeclaration\ValueObject\IdentifierValueObject;
final class PropertyTypeInferer
final class PropertyTypeInferer extends AbstractPriorityAwareTypeInferer
{
/**
* @var PropertyTypeInfererInterface[]
@ -19,7 +18,7 @@ final class PropertyTypeInferer
*/
public function __construct(array $propertyTypeInferers)
{
$this->sortAndSetPropertyTypeInferers($propertyTypeInferers);
$this->propertyTypeInferers = $this->sortTypeInferersByPriority($propertyTypeInferers);
}
/**
@ -36,28 +35,4 @@ final class PropertyTypeInferer
return [];
}
/**
* @param PropertyTypeInfererInterface[] $propertyTypeInferers
*/
private function sortAndSetPropertyTypeInferers(array $propertyTypeInferers): void
{
foreach ($propertyTypeInferers as $propertyTypeInferer) {
$this->ensurePriorityIsUnique($propertyTypeInferer);
$this->propertyTypeInferers[$propertyTypeInferer->getPriority()] = $propertyTypeInferer;
}
krsort($this->propertyTypeInferers);
}
private function ensurePriorityIsUnique(PropertyTypeInfererInterface $propertyTypeInferer): void
{
if (! isset($this->propertyTypeInferers[$propertyTypeInferer->getPriority()])) {
return;
}
$alreadySetPropertyTypeInferer = $this->propertyTypeInferers[$propertyTypeInferer->getPriority()];
throw new ConflictingPriorityException($propertyTypeInferer, $alreadySetPropertyTypeInferer);
}
}

View File

@ -5,7 +5,7 @@ namespace Rector\TypeDeclaration\TypeInferer;
use PhpParser\Node\FunctionLike;
use Rector\TypeDeclaration\Contract\TypeInferer\ReturnTypeInfererInterface;
final class ReturnTypeInferer
final class ReturnTypeInferer extends AbstractPriorityAwareTypeInferer
{
/**
* @var ReturnTypeInfererInterface[]
@ -17,7 +17,7 @@ final class ReturnTypeInferer
*/
public function __construct(array $returnTypeInferers)
{
$this->returnTypeInferers = $returnTypeInferers;
$this->returnTypeInferers = $this->sortTypeInferersByPriority($returnTypeInferers);
}
/**

View File

@ -0,0 +1,48 @@
<?php declare(strict_types=1);
namespace Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
use Rector\TypeDeclaration\Contract\TypeInferer\ReturnTypeInfererInterface;
use Rector\TypeDeclaration\TypeInferer\AbstractTypeInferer;
final class ReturnTagReturnTypeInferer extends AbstractTypeInferer implements ReturnTypeInfererInterface
{
/**
* @var DocBlockManipulator
*/
private $docBlockManipulator;
public function __construct(DocBlockManipulator $docBlockManipulator)
{
$this->docBlockManipulator = $docBlockManipulator;
}
/**
* @param ClassMethod|Closure|Function_ $functionLike
* @return string[]
*/
public function inferFunctionLike(FunctionLike $functionLike): array
{
$returnTypeInfo = $this->docBlockManipulator->getReturnTypeInfo($functionLike);
if ($returnTypeInfo === null) {
return [];
}
$fqnTypes = $returnTypeInfo->getFqnTypes();
if ($returnTypeInfo->isNullable()) {
$fqnTypes[] = 'null';
}
return $fqnTypes;
}
public function getPriority(): int
{
return 400;
}
}

View File

@ -32,4 +32,9 @@ final class ReturnedNodeReturnTypeInferer extends AbstractTypeInferer implements
return $resolvedReturnTypeInfo ? $resolvedReturnTypeInfo->getDocTypes() : [];
}
public function getPriority(): int
{
return 500;
}
}

View File

@ -6,10 +6,14 @@ use PhpParser\Node;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\NodeTraverser;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\TypeDeclaration\Contract\TypeInferer\ReturnTypeInfererInterface;
use Rector\TypeDeclaration\TypeInferer\AbstractTypeInferer;
@ -21,8 +25,21 @@ final class ReturnedNodesReturnTypeInferer extends AbstractTypeInferer implement
*/
public function inferFunctionLike(FunctionLike $functionLike): array
{
/** @var Class_|Trait_|Interface_|null $classLike */
$classLike = $functionLike->getAttribute(AttributeKey::CLASS_NODE);
if ($functionLike instanceof ClassMethod) {
if ($classLike instanceof Interface_) {
return [];
}
}
$localReturnNodes = $this->collectReturns($functionLike);
if ($localReturnNodes === []) {
// void type
if ($functionLike instanceof ClassMethod && ! $functionLike->isAbstract()) {
return ['void'];
}
return [];
}
@ -35,8 +52,13 @@ final class ReturnedNodesReturnTypeInferer extends AbstractTypeInferer implement
return $types;
}
public function getPriority(): int
{
return 1000;
}
/**
* @param ClassMethod|Function_ $functionLike
* @param ClassMethod|Closure|Function_ $functionLike
* @return Return_[]
*/
private function collectReturns(FunctionLike $functionLike): array

View File

@ -57,6 +57,11 @@ final class ReturnedPropertyReturnTypeInferer extends AbstractTypeInferer implem
return $this->propertyTypeInferer->inferProperty($property);
}
public function getPriority(): int
{
return 700;
}
private function matchSingleStmtReturnPropertyFetch(ClassMethod $classMethod): ?PropertyFetch
{
if (count((array) $classMethod->stmts) !== 1) {

View File

@ -55,4 +55,9 @@ final class SetterNodeReturnTypeInferer extends AbstractTypeInferer implements R
return $this->staticTypeToStringResolver->resolveObjectType($assignedExprStaticType);
}
public function getPriority(): int
{
return 600;
}
}

View File

@ -0,0 +1,71 @@
<?php declare(strict_types=1);
namespace Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
use Iterator;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\Yield_;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use Rector\Php\PhpVersionProvider;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\TypeDeclaration\Contract\TypeInferer\ReturnTypeInfererInterface;
use Rector\TypeDeclaration\TypeInferer\AbstractTypeInferer;
final class YieldNodesReturnTypeInferer extends AbstractTypeInferer implements ReturnTypeInfererInterface
{
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
/**
* @var PhpVersionProvider
*/
private $phpVersionProvider;
public function __construct(BetterNodeFinder $betterNodeFinder, PhpVersionProvider $phpVersionProvider)
{
$this->betterNodeFinder = $betterNodeFinder;
$this->phpVersionProvider = $phpVersionProvider;
}
/**
* @param ClassMethod|Function_|Closure $functionLike
* @return string[]
*/
public function inferFunctionLike(FunctionLike $functionLike): array
{
/** @var Yield_[] $yieldNodes */
$yieldNodes = $this->betterNodeFinder->findInstanceOf((array) $functionLike->stmts, Yield_::class);
$types = [];
if (count($yieldNodes)) {
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);
}
public function getPriority(): int
{
return 1200;
}
}

View File

@ -11,7 +11,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRe
* @param void $void
* @param mixed $mixed
* @param unknown $unknown
* @param Class $class
* @param AnyClass $class
*/
function aliases($integer, $boolean, $real, $double, $callback, $void, $mixed, $unkown, $class) {
}
@ -30,8 +30,8 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRe
* @param void $void
* @param mixed $mixed
* @param unknown $unknown
* @param Class $class
* @param AnyClass $class
*/
function aliases(int $integer, bool $boolean, float $real, float $double, callable $callback, $void, $mixed, $unkown, Class $class) {
function aliases(int $integer, bool $boolean, float $real, float $double, callable $callback, $void, $mixed, $unkown, \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\Dunglas\AnyClass $class) {
}
?>

View File

@ -31,9 +31,9 @@ function foo() {
function foo2($a) {}
/** @param null|A $a */
function foo3(?A $a = null) {}
function foo3(?\Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\Nikic\Null_\A $a = null) {}
/** @param null|A $a */
function foo4(?A $a) {}
function foo4(?\Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\Nikic\Null_\A $a) {}
?>

View File

@ -18,6 +18,6 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRe
* @param Foo|null $a
* @param Bar|null $b
*/
function test2(?Foo $a, ?Bar $b = null) {}
function test2(?\Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\Nikic\Nullable\Foo $a, ?\Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\Nikic\Nullable\Bar $b = null) {}
?>

View File

@ -19,11 +19,11 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRe
class A {
/** @param Foo $a */
public function test2(Foo $a) {}
public function test2(\Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\NullableInheritance\Foo $a) {}
}
class B extends A {
/** @param null|Foo $a */
public function test2(?Foo $a) {}
public function test2(?\Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\NullableInheritance\Foo $a) {}
}
?>

View File

@ -9,5 +9,5 @@ interface Foo { /** @param Bar $bar */ function my_foo($bar); }
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\PhpCsFixerParam\Interface_;
interface Foo { /** @param Bar $bar */ function my_foo(Bar $bar); }
interface Foo { /** @param Bar $bar */ function my_foo(\Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\PhpCsFixerParam\Interface_\Bar $bar); }
?>

View File

@ -24,6 +24,6 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRe
*/
function my_foo(string $bar, int $baz, float $tab) {}
/** @param My\Bar $foo */ function my_foo2(My\Bar $foo) {}
/** @param My\Bar $foo */ function my_foo2(\Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\PhpCsFixerParam\TypehintAlreadyDefinedWithWrongPhpdocTypehint\My\Bar $foo) {}
?>

View File

@ -15,7 +15,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRe
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\PhpCsFixerParam\Nullable;
/** @param null|bool $foo */ function my_foo(?bool $foo) {}
/** @param null|Foo $foo */ function my_foo2(?Foo $foo) {}
/** @param null|Foo $foo */ function my_foo2(?\Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\PhpCsFixerParam\Nullable\Foo $foo) {}
/** @param null|callable $foo */ function my_foo3(?callable $foo) {}
/** @param null|Foo[] $foo */ function my_foo4(?array $foo) {}
/** @param null|iterable $foo */ function my_foo5(?iterable $foo) {}

View File

@ -23,5 +23,5 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRe
* @param float $tab
* @param bool $baz
*/
function my_foo(string $bar, int $foo, bool $baz, float $tab, Baz $hey) {}
function my_foo(string $bar, int $foo, bool $baz, float $tab, \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\PhpCsFixerParam\Unsorted\Baz $hey) {}
?>

View File

@ -22,7 +22,6 @@ final class ParamTypeDeclarationRectorTest extends AbstractRectorTestCase
__DIR__ . '/Fixture/this.php.inc',
__DIR__ . '/Fixture/false.php.inc',
__DIR__ . '/Fixture/undesired.php.inc',
__DIR__ . '/Fixture/aliased.php.inc',
__DIR__ . '/Fixture/external_scope.php.inc',
__DIR__ . '/Fixture/local_and_external_scope.php.inc',
__DIR__ . '/Fixture/local_scope_with_parent_interface.php.inc',
@ -38,7 +37,6 @@ final class ParamTypeDeclarationRectorTest extends AbstractRectorTestCase
__DIR__ . '/Fixture/php-cs-fixer-param/non_root_class_with_different_types_of_params.php.inc',
__DIR__ . '/Fixture/php-cs-fixer-param/nullable.php.inc',
__DIR__ . '/Fixture/php-cs-fixer-param/number.php.inc',
__DIR__ . '/Fixture/php-cs-fixer-param/root_class.php.inc',
__DIR__ . '/Fixture/php-cs-fixer-param/self_accessor.php.inc',
__DIR__ . '/Fixture/php-cs-fixer-param/skip.php.inc',
__DIR__ . '/Fixture/php-cs-fixer-param/unsorted.php.inc',
@ -48,7 +46,6 @@ final class ParamTypeDeclarationRectorTest extends AbstractRectorTestCase
__DIR__ . '/Fixture/nikic/iterable.php.inc',
__DIR__ . '/Fixture/nikic/null.php.inc',
__DIR__ . '/Fixture/nikic/nullable.php.inc',
__DIR__ . '/Fixture/nikic/nullable_inheritance.php.inc',
__DIR__ . '/Fixture/nikic/rename.php.inc',
// dunglas set - https://github.com/dunglas/phpdoc-to-typehint/
__DIR__ . '/Fixture/dunglas/array_no_types.php.inc',
@ -59,9 +56,12 @@ final class ParamTypeDeclarationRectorTest extends AbstractRectorTestCase
__DIR__ . '/Fixture/dunglas/Foo.php.inc',
__DIR__ . '/Fixture/dunglas/functions.php.inc',
__DIR__ . '/Fixture/dunglas/functions2.php.inc',
__DIR__ . '/Fixture/dunglas/nullable_types.php.inc',
__DIR__ . '/Fixture/dunglas/param_no_type.php.inc',
__DIR__ . '/Fixture/dunglas/type_aliases_and_whitelisting.php.inc',
__DIR__ . '/Fixture/php-cs-fixer-param/root_class.php.inc',
__DIR__ . '/Fixture/dunglas/nullable_types.php.inc',
__DIR__ . '/Fixture/aliased.php.inc',
__DIR__ . '/Fixture/nikic/nullable_inheritance.php.inc',
];
$this->doTestFiles($integrationFiles);

View File

@ -24,7 +24,7 @@ use Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector
class SkipOverrideOfTheSameClass
{
public function getReturnedClass(): RealReturnedClass
public function getReturnedClass(): \Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Source\RealReturnedClass
{
return new RealReturnedClass();
}

View File

@ -15,18 +15,6 @@ class B extends A {
}
}
class C extends B {
/**
* Technically valid return type, but against PHP's variance restrictions.
* We use "A" instead, which is less accurate but valid.
*
* @return C
*/
public function test() {
return $this;
}
}
?>
-----
<?php
@ -41,19 +29,7 @@ class A {
}
class B extends A {
public function test(): A {
return $this;
}
}
class C extends B {
/**
* Technically valid return type, but against PHP's variance restrictions.
* We use "A" instead, which is less accurate but valid.
*
* @return C
*/
public function test(): A {
public function test(): \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Inheritance\A {
return $this;
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Inheritance;
class ACovariance {
/** @return ACovariance */
public function test() {
return $this;
}
}
class CCovariance extends ACovariance {
/**
* Technically valid return type, but against PHP's variance restrictions.
* We use "ACovariance" instead, which is less accurate but valid.
*
* @return CCovariance
*/
public function test() {
return $this;
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Inheritance;
class ACovariance {
/** @return ACovariance */
public function test(): \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Inheritance\ACovariance {
return $this;
}
}
class CCovariance extends ACovariance {
/**
* Technically valid return type, but against PHP's variance restrictions.
* We use "ACovariance" instead, which is less accurate but valid.
*
* @return CCovariance
*/
public function test(): \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Inheritance\CCovariance {
return $this;
}
}
?>

View File

@ -30,7 +30,7 @@ class A {
class B extends A {
/** @return Foo */
public function test($value): Foo {
public function test($value): \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\NullableInheritance\Foo {
return $value;
}
}

View File

@ -28,7 +28,7 @@ class A {
}
class B extends A {
/** @return Foo */
public function getObject($value): Foo {
public function getObject($value): \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Object\Foo {
return $value;
}
}

View File

@ -28,7 +28,7 @@ class A {
}
class B extends A {
/** @return Foo */
public function getObject($value): Foo {
public function getObject($value): \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\ObjectPhp72\Foo {
return $value;
}
}

View File

@ -5,11 +5,14 @@ namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclaration
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector;
/**
* @sponsor Thanks https://spaceflow.io/ for sponsoring this rule - visit them on https://github.com/SpaceFlow-app
*/
final class ReturnTypeDeclarationRectorTest extends AbstractRectorTestCase
{
public function test(): void
{
$integrationFiles = [
$files = [
// static types
__DIR__ . '/Fixture/void_type.php.inc',
__DIR__ . '/Fixture/no_void_abstract.php.inc',
@ -38,12 +41,11 @@ final class ReturnTypeDeclarationRectorTest extends AbstractRectorTestCase
__DIR__ . '/Fixture/php-cs-fixer-return/skip.php.inc',
__DIR__ . '/Fixture/php-cs-fixer-return/nullables.php.inc',
// nikic set - https://github.com/nikic/TypeUtil/
__DIR__ . '/Fixture/nikic/inheritance.php.inc',
__DIR__ . '/Fixture/nikic/iterable.php.inc',
__DIR__ . '/Fixture/nikic/name_resolution.php.inc',
__DIR__ . '/Fixture/nikic/null.php.inc',
__DIR__ . '/Fixture/nikic/nullable.php.inc',
__DIR__ . '/Fixture/nikic/nullable_inheritance.php.inc',
__DIR__ . '/Fixture/nikic/object.php.inc',
__DIR__ . '/Fixture/nikic/return_type_position.php.inc',
__DIR__ . '/Fixture/nikic/self_inheritance.php.inc',
@ -58,7 +60,18 @@ final class ReturnTypeDeclarationRectorTest extends AbstractRectorTestCase
__DIR__ . '/Fixture/a_new_class.php.inc',
];
$this->doTestFiles($integrationFiles);
$this->doTestFiles($files);
}
public function testInheritance(): void
{
$files = [
__DIR__ . '/Fixture/nikic/inheritance.php.inc',
__DIR__ . '/Fixture/nikic/inheritance_covariance.php.inc',
__DIR__ . '/Fixture/nikic/nullable_inheritance.php.inc',
];
$this->doTestFiles($files);
}
protected function getRectorClass(): string

View File

@ -194,6 +194,10 @@ parameters:
- '#PHPDoc tag @param for parameter \$nodeWithStatements with type PhpParser\\Builder\\FunctionLike\|PhpParser\\Node\\Stmt\\ClassLike is not subtype of native type PhpParser\\Node#'
- '#Access to an undefined property PhpParser\\Node\\FunctionLike\|PhpParser\\Node\\Stmt\\ClassLike\:\:\$stmts#'
- '#Property Rector\\TypeDeclaration\\TypeInferer\\(.*?)\:\:\$(.*?)TypeInferers \(array<Rector\\TypeDeclaration\\Contract\\TypeInferer\\(.*?)TypeInfererInterface\>\) does not accept array<Rector\\TypeDeclaration\\Contract\\TypeInferer\\PriorityAwareTypeInfererInterface\>#'
# sense-less errors
- '#Parameter \#1 \$functionLike of method Rector\\NodeTypeResolver\\PhpDoc\\NodeAnalyzer\\DocBlockManipulator\:\:getParamTypeInfos\(\) expects PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Stmt\\ClassMethod\|PhpParser\\Node\\Stmt\\Function_, PhpParser\\Node\\FunctionLike given#'
# PHP 7.4 1_000 support
- '#Property PhpParser\\Node\\Scalar\\DNumber\:\:\$value \(float\) does not accept string#'
- '#Call to function is_string\(\) with float will always evaluate to false#'