Merge pull request #1939 from rectorphp/correct-types

Add ReturnedNodesReturnTypeInferer + big *TypeDeclarationRector refactoring
This commit is contained in:
Tomáš Votruba 2019-09-02 11:50:17 +02:00 committed by GitHub
commit a8643b7576
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 806 additions and 333 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[]
*/
@ -110,7 +115,13 @@ abstract class AbstractTypeInfo
return new Identifier($type);
}
$name = $forceFqn ? new FullyQualified($type) : new Name($type);
$type = ltrim($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

@ -6,21 +6,29 @@ use PhpParser\Node;
use PhpParser\Node\Expr\Closure;
use PHPStan\Analyser\Scope;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PhpParser\Node\Manipulator\FunctionLikeManipulator;
use Rector\NodeTypeResolver\Php\ReturnTypeInfo;
use Rector\Php\TypeAnalyzer;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
final class AddClosureReturnTypeRector extends AbstractRector
{
/**
* @var FunctionLikeManipulator
* @var ReturnTypeInferer
*/
private $functionLikeManipulator;
private $returnTypeInferer;
public function __construct(FunctionLikeManipulator $functionLikeManipulator)
/**
* @var TypeAnalyzer
*/
private $typeAnalyzer;
public function __construct(ReturnTypeInferer $returnTypeInferer, TypeAnalyzer $typeAnalyzer)
{
$this->functionLikeManipulator = $functionLikeManipulator;
$this->returnTypeInferer = $returnTypeInferer;
$this->typeAnalyzer = $typeAnalyzer;
}
public function getDefinition(): RectorDefinition
@ -81,12 +89,10 @@ CODE_SAMPLE
return null;
}
$staticReturnType = $this->functionLikeManipulator->resolveStaticReturnTypeInfo($node);
if ($staticReturnType === null) {
return null;
}
$inferedReturnTypes = $this->returnTypeInferer->inferFunctionLike($node);
$returnTypeInfo = new ReturnTypeInfo($inferedReturnTypes, $this->typeAnalyzer, $inferedReturnTypes);
$returnTypeNode = $staticReturnType->getTypeNode();
$returnTypeNode = $returnTypeInfo->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,48 +94,33 @@ 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;
}
}
$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;
}
$node->returnType = $returnTypeInfo->getTypeNode();
$node->returnType = $returnTypeInfo->getFqnTypeNode();
}
$this->populateChildren($node, $returnTypeInfo);
@ -154,7 +129,7 @@ CODE_SAMPLE
}
/**
* Add typehint to all children
* Add typehint to all children class methods
*/
private function populateChildren(Node $node, ReturnTypeInfo $returnTypeInfo): void
{
@ -182,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);
}
/**
@ -234,4 +205,16 @@ CODE_SAMPLE
return $this->isNames($node, self::EXCLUDED_METHOD_NAMES);
}
/**
* @param ClassMethod|Function_ $node
*/
private function isReturnTypeAlreadyAdded(Node $node, ReturnTypeInfo $returnTypeInfo): bool
{
if (ltrim($this->print($node->returnType), '\\') === $this->print($returnTypeInfo->getTypeNode())) {
return true;
}
return false;
}
}

View File

@ -1,51 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\TypeDeclaration\ReturnTypeResolver;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use Rector\NodeTypeResolver\Php\ReturnTypeInfo;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
use Rector\PhpParser\Node\Manipulator\FunctionLikeManipulator;
final class ReturnTypeResolver
{
/**
* @var DocBlockManipulator
*/
private $docBlockManipulator;
/**
* @var FunctionLikeManipulator
*/
private $functionLikeManipulator;
public function __construct(
DocBlockManipulator $docBlockManipulator,
FunctionLikeManipulator $functionLikeManipulator
) {
$this->docBlockManipulator = $docBlockManipulator;
$this->functionLikeManipulator = $functionLikeManipulator;
}
/**
* @param ClassMethod|Function_ $functionLike
*/
public function resolveFunctionLikeReturnType(FunctionLike $functionLike): ?ReturnTypeInfo
{
$docReturnTypeInfo = $this->docBlockManipulator->getReturnTypeInfo($functionLike);
$codeReturnTypeInfo = $this->functionLikeManipulator->resolveStaticReturnTypeInfo($functionLike);
// code has priority over docblock
if ($docReturnTypeInfo === null) {
return $codeReturnTypeInfo;
}
if ($codeReturnTypeInfo && $codeReturnTypeInfo->getTypeNode()) {
return $codeReturnTypeInfo;
}
return $docReturnTypeInfo;
}
}

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

@ -44,6 +44,6 @@ final class AllAssignNodePropertyTypeInferer extends AbstractTypeInferer impleme
public function getPriority(): int
{
return 500;
return 610;
}
}

View File

@ -0,0 +1,150 @@
<?php declare(strict_types=1);
namespace Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer;
use PhpParser\Node;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
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\TypeInferer\AbstractTypeInferer;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer\ReturnedNodesReturnTypeInferer;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer\ReturnTagReturnTypeInferer;
final class GetterPropertyTypeInferer extends AbstractTypeInferer implements PropertyTypeInfererInterface
{
/**
* @var ReturnedNodesReturnTypeInferer
*/
private $returnedNodesReturnTypeInferer;
/**
* @var ReturnTagReturnTypeInferer
*/
private $returnTagReturnTypeInferer;
public function __construct(
ReturnedNodesReturnTypeInferer $returnedNodesReturnTypeInferer,
ReturnTagReturnTypeInferer $returnTagReturnTypeInferer
) {
$this->returnedNodesReturnTypeInferer = $returnedNodesReturnTypeInferer;
$this->returnTagReturnTypeInferer = $returnTagReturnTypeInferer;
}
/**
* @return string[]
*/
public function inferProperty(Property $property): array
{
/** @var Class_ $class */
$class = $property->getAttribute(AttributeKey::CLASS_NODE);
/** @var string $propertyName */
$propertyName = $this->nameResolver->getName($property);
foreach ($class->getMethods() as $classMethod) {
if (! $this->hasClassMethodOnlyStatementReturnOfPropertyFetch($classMethod, $propertyName)) {
continue;
}
$returnTypes = $this->inferClassMethodReturnTypes($classMethod);
if ($returnTypes !== []) {
return $returnTypes;
}
}
return [];
}
public function getPriority(): int
{
return 600;
}
private function hasClassMethodOnlyStatementReturnOfPropertyFetch(
ClassMethod $classMethod,
string $propertyName
): bool {
if (count((array) $classMethod->stmts) !== 1) {
return false;
}
$onlyClassMethodStmt = $classMethod->stmts[0];
if (! $onlyClassMethodStmt instanceof Return_) {
return false;
}
/** @var Return_ $return */
$return = $onlyClassMethodStmt;
if (! $return->expr instanceof PropertyFetch) {
return false;
}
return $this->nameResolver->isName($return->expr, $propertyName);
}
/**
* Intentionally local method
* @todo possible move to ReturnTypeInferer, but allow to disable/enable in case of override returnType (99 %)
*
* @param ClassMethod|Function_|Closure $functionLike
* @return string[]
*/
private function resolveFunctionLikeReturnTypeDeclaration(FunctionLike $functionLike): array
{
if ($functionLike->returnType === null) {
return [];
}
return $this->resolveReturnTypeToString($functionLike->returnType);
}
/**
* @return string[]
*/
private function inferClassMethodReturnTypes(ClassMethod $classMethod): array
{
$returnTypeDeclarationTypes = $this->resolveFunctionLikeReturnTypeDeclaration($classMethod);
if ($returnTypeDeclarationTypes) {
return $returnTypeDeclarationTypes;
}
$inferedTypes = $this->returnedNodesReturnTypeInferer->inferFunctionLike($classMethod);
if ($inferedTypes) {
return $inferedTypes;
}
return $this->returnTagReturnTypeInferer->inferFunctionLike($classMethod);
}
/**
* @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;
}
}

View File

@ -2,28 +2,21 @@
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\ReturnTypeResolver\ReturnTypeResolver;
use Rector\TypeDeclaration\TypeInferer\AbstractTypeInferer;
final class GetterOrSetterPropertyTypeInferer extends AbstractTypeInferer implements PropertyTypeInfererInterface
final class GetterTypeDeclarationPropertyTypeInferer extends AbstractTypeInferer implements PropertyTypeInfererInterface
{
/**
* @var ReturnTypeResolver
*/
private $returnTypeResolver;
public function __construct(ReturnTypeResolver $returnTypeResolver)
{
$this->returnTypeResolver = $returnTypeResolver;
}
/**
* @return string[]
*/
@ -40,7 +33,12 @@ final class GetterOrSetterPropertyTypeInferer extends AbstractTypeInferer implem
continue;
}
$returnTypes = $this->resolveClassMethodReturnTypes($classMethod);
$returnTypes = $this->resolveReturnTypeToString($classMethod);
// let PhpDoc solve that later for more precise type
if ($returnTypes === ['array']) {
return [];
}
if ($returnTypes !== []) {
return $returnTypes;
}
@ -51,7 +49,7 @@ final class GetterOrSetterPropertyTypeInferer extends AbstractTypeInferer implem
public function getPriority(): int
{
return 600;
return 630;
}
private function hasClassMethodOnlyStatementReturnOfPropertyFetch(
@ -78,15 +76,29 @@ final class GetterOrSetterPropertyTypeInferer extends AbstractTypeInferer implem
}
/**
* @param Function_|ClassMethod|Closure $functionLike
* @return string[]
*/
private function resolveClassMethodReturnTypes(ClassMethod $classMethod): array
private function resolveReturnTypeToString(FunctionLike $functionLike): array
{
$returnType = $this->returnTypeResolver->resolveFunctionLikeReturnType($classMethod);
if ($returnType === null) {
if ($functionLike->getReturnType() === null) {
return [];
}
return $returnType->getDocTypes();
$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

@ -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

@ -1,35 +0,0 @@
<?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\PhpParser\Node\Manipulator\FunctionLikeManipulator;
use Rector\TypeDeclaration\Contract\TypeInferer\ReturnTypeInfererInterface;
use Rector\TypeDeclaration\TypeInferer\AbstractTypeInferer;
final class ReturnedNodeReturnTypeInferer extends AbstractTypeInferer implements ReturnTypeInfererInterface
{
/**
* @var FunctionLikeManipulator
*/
private $functionLikeManipulator;
public function __construct(FunctionLikeManipulator $functionLikeManipulator)
{
$this->functionLikeManipulator = $functionLikeManipulator;
}
/**
* @param ClassMethod|Closure|Function_ $functionLike
* @return string[]
*/
public function inferFunctionLike(FunctionLike $functionLike): array
{
$resolvedReturnTypeInfo = $this->functionLikeManipulator->resolveStaticReturnTypeInfo($functionLike);
return $resolvedReturnTypeInfo ? $resolvedReturnTypeInfo->getDocTypes() : [];
}
}

View File

@ -0,0 +1,92 @@
<?php declare(strict_types=1);
namespace Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
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;
final class ReturnedNodesReturnTypeInferer extends AbstractTypeInferer implements ReturnTypeInfererInterface
{
/**
* @param ClassMethod|Closure|Function_ $functionLike
* @return string[]
*/
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 [];
}
$types = [];
foreach ($localReturnNodes as $localReturnNode) {
$types = array_merge($types, $this->nodeTypeResolver->resolveSingleTypeToStrings($localReturnNode->expr));
}
// @todo add priority, because this gets last :)
return $types;
}
public function getPriority(): int
{
return 1000;
}
/**
* @param ClassMethod|Closure|Function_ $functionLike
* @return Return_[]
*/
private function collectReturns(FunctionLike $functionLike): array
{
$returns = [];
$this->callableNodeTraverser->traverseNodesWithCallable((array) $functionLike->stmts, function (Node $node) use (
&$returns
): ?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;
}
$returns[] = $node;
return null;
});
return $returns;
}
}

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

@ -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

@ -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

@ -9,7 +9,12 @@ final class CorrectionTest extends AbstractRectorTestCase
{
public function test(): void
{
$this->doTestFiles([__DIR__ . '/Fixture/Correction/constructor_property_assign_over_getter.php.inc']);
$this->doTestFiles([
// __DIR__ . '/Fixture/Correction/constructor_property_assign_over_getter.php.inc',
__DIR__ . '/Fixture/Correction/prefix_fqn.php.inc',
// skip
// __DIR__ . '/Fixture/Correction/skip_override_of_the_same_class.php.inc',
]);
}
protected function getRectorClass(): string

View File

@ -0,0 +1,33 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Correction;
use Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Source\RealReturnedClass;
use Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Source\ReturnedClass;
class SkipOverrideOfTheSameClass
{
public function getReturnedClass(): ReturnedClass
{
return new RealReturnedClass();
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Correction;
use Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Source\RealReturnedClass;
use Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Source\ReturnedClass;
class SkipOverrideOfTheSameClass
{
public function getReturnedClass(): \Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Source\RealReturnedClass
{
return new RealReturnedClass();
}
}
?>

View File

@ -0,0 +1,13 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Correction;
use Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Source\ReturnedClass;
class SkipOverrideOfTheSameClass
{
public function getReturnedClass(): ReturnedClass
{
return new ReturnedClass();
}
}

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
@ -35,25 +23,13 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationR
class A {
/** @return A */
public function test(): A {
public function test(): \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Inheritance\A {
return $this;
}
}
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

@ -50,27 +50,27 @@ function test1($value): \Bar {
}
/** @return Bar */
function test2($value): Bar {
function test2($value): \Foo\Bar {
return $value;
}
/** @return Bar\Baz */
function test3($value): Bar\Baz {
function test3($value): \Foo\Bar\Baz {
return $value;
}
/** @return Baz */
function test4($value): Baz {
function test4($value): \ABC\Baz {
return $value;
}
/** @return Baz\Foo */
function test5($value): Baz\Foo {
function test5($value): \ABC\Baz\Foo {
return $value;
}
/** @return XYZ */
function test6($value): XYZ {
function test6($value): \Foo\XYZ {
return $value;
}

View File

@ -14,7 +14,7 @@ function test($value) {
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Nikic\Nullable;
/** @return Foo|null */
function test($value): ?Foo {
function test($value): ?\Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Nikic\Nullable\Foo {
return $value;
}

View File

@ -23,14 +23,14 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationR
class A {
/** @return null|Foo */
public function test($value): ?Foo {
public function test($value): ?\Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\NullableInheritance\Foo {
return $value;
}
}
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

@ -14,7 +14,7 @@ function test($a = array()) {
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\ReturnTypePosition;
/** @return A */
function test($a = array()): A {
function test($a = array()): \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\ReturnTypePosition\A {
return $a;
}

View File

@ -34,7 +34,7 @@ class Foo
}
/** @return Bar */
function __construct($value): Bar {
function __construct($value): \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\PhpCsFixerReturn\BlacklistedClassMethods\Bar {
return $value;
}

View File

@ -26,11 +26,11 @@ function my_foo4($value) {
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\PhpCsFixerReturn\Nullables;
/** @return null|Bar */
function my_foo($value): ?Bar {
function my_foo($value): ?\Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\PhpCsFixerReturn\Nullables\Bar {
return $value;
}
/** @return Bar|null */
function my_foo2($value): ?Bar {
function my_foo2($value): ?\Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\PhpCsFixerReturn\Nullables\Bar {
return $value;
}
/** @return null|array */

View File

@ -24,10 +24,10 @@ interface Foo {
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\PhpCsFixerReturn\Various;
/** @return Bar */ function my_foo($value): Bar {
/** @return Bar */ function my_foo($value): \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\PhpCsFixerReturn\Various\Bar {
return $value;
}
/** @return My\Bar */ function my_foo2($value): My\Bar {
/** @return My\Bar */ function my_foo2($value): \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\PhpCsFixerReturn\Various\My\Bar {
return $value;
}
/** @return \My\Bar */ function my_foo3($value): \My\Bar {
@ -35,7 +35,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationR
}
interface Foo {
/** @return Bar */
function my_foo4($value): Bar;
function my_foo4($value): \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\PhpCsFixerReturn\Various\Bar;
}
/** @return void */ function my_foo5(): void {
}

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

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Source;
final class RealReturnedClass
{
}

View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Source;
final class ReturnedClass
{
}

View File

@ -26,7 +26,7 @@ final class PropertyTypeDeclarationRectorTest extends AbstractRectorTestCase
__DIR__ . '/Fixture/single_nullable_return.php.inc',
__DIR__ . '/Fixture/getter_type.php.inc',
__DIR__ . '/Fixture/setter_type.php.inc',
// skip
// skip
__DIR__ . '/Fixture/skip_multi_vars.php.inc',
]);
}

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#'

View File

@ -96,6 +96,8 @@ final class FunctionLikeManipulator
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(
@ -202,6 +204,7 @@ final class FunctionLikeManipulator
}
/**
* @covered
* @param ClassMethod|Function_|Closure $functionLike
* @return string[]
*/