Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/NodeTypeResolver/src/StaticTypeToStringResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
Expand All @@ -36,6 +37,7 @@ public function __construct(CallableCollectorPopulator $callableCollectorPopulat
BooleanType::class => ['bool'],
StringType::class => ['string'],
NullType::class => ['null'],
MixedType::class => ['mixed'],

// more complex callables
function (ArrayType $arrayType): array {
Expand Down Expand Up @@ -69,8 +71,7 @@ function (IntersectionType $intersectionType): array {
return $this->removeGenericArrayTypeIfThereIsSpecificArrayType($types);
},
function (ObjectType $objectType): array {
// the must be absolute, since we have no other way to check absolute/local path
return ['\\' . $objectType->getClassName()];
return [$objectType->getClassName()];
},
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ public function create(array $types, Param $param): AttributeAwarePhpDocTagNode
if (count($types) > 1) {
$unionedTypes = [];
foreach ($types as $type) {
$unionedTypes[] = new IdentifierTypeNode($type);
$unionedTypes[] = $this->createIdentifierTypeNode($type);
}

$typeNode = new UnionTypeNode($unionedTypes);
} elseif (count($types) === 1) {
$typeNode = new IdentifierTypeNode($types[0]);
$typeNode = $this->createIdentifierTypeNode($types[0]);
} else {
throw new ShouldNotHappenException(__METHOD__ . '() on line ' . __LINE__);
}
Expand All @@ -53,4 +53,14 @@ public function create(array $types, Param $param): AttributeAwarePhpDocTagNode

return new AttributeAwarePhpDocTagNode('@param', $paramTagValueNode);
}

private function createIdentifierTypeNode(string $type): IdentifierTypeNode
{
if (class_exists($type)) {
// FQN class name
$type = '\\' . $type;
}

return new IdentifierTypeNode($type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,11 @@ public function getNodeTypes(): array
*/
public function refactor(Node $node): ?Node
{
/** @var Param[] $params */
$params = (array) $node->params;

if (count($params) === 0) {
if (count($node->getParams()) === 0) {
return null;
}

foreach ($params as $param) {
foreach ($node->getParams() as $param) {
if ($this->shouldSkipParam($param)) {
return null;
}
Expand All @@ -116,7 +113,8 @@ public function refactor(Node $node): ?Node
return null;
}

$this->docBlockManipulator->addTag($node, $this->paramPhpDocNodeFactory->create($types, $param));
$paramTagNode = $this->paramPhpDocNodeFactory->create($types, $param);
$this->docBlockManipulator->addTag($node, $paramTagNode);
}

return $node;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ public function refactor(Node $node): ?Node
}

$inferedReturnTypes = $this->returnTypeInferer->inferFunctionLike($node);
$returnTypeInfo = new ReturnTypeInfo($inferedReturnTypes, $this->typeAnalyzer, $inferedReturnTypes);

$returnTypeInfo = new ReturnTypeInfo($inferedReturnTypes, $this->typeAnalyzer, $inferedReturnTypes);
$returnTypeNode = $returnTypeInfo->getFqnTypeNode();
if ($returnTypeNode === null) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ protected function isChangeVendorLockedIn(ClassMethod $classMethod, int $paramPo
* @param Name|NullableType|Identifier $possibleSubtype
* @param Name|NullableType|Identifier $type
*/
protected function isSubtypeOf(Node $possibleSubtype, Node $type, string $kind): bool
protected function isSubtypeOf(Node $possibleSubtype, Node $type): bool
{
$type = $type instanceof NullableType ? $type->type : $type;

Expand All @@ -139,17 +139,8 @@ protected function isSubtypeOf(Node $possibleSubtype, Node $type, string $kind):
$possibleSubtype = $possibleSubtype->toString();
$type = $type->toString();

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 (is_a($possibleSubtype, $type, true)) {
return true;
}

if (in_array($possibleSubtype, ['array', 'Traversable'], true) && $type === 'iterable') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public function refactor(Node $node): ?Node
$possibleOverrideNewReturnType = $paramTypeInfo->getFqnTypeNode();
if ($possibleOverrideNewReturnType !== null) {
if ($paramNode->type !== null) {
if ($this->isSubtypeOf($possibleOverrideNewReturnType, $paramNode->type, 'param')) {
if ($this->isSubtypeOf($possibleOverrideNewReturnType, $paramNode->type)) {
// allow override
$paramNode->type = $paramTypeInfo->getFqnTypeNode();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Rector\TypeDeclaration\Rector\FunctionLike;

use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
Expand All @@ -16,6 +15,9 @@
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer\ReturnTypeDeclarationReturnTypeInferer;

/**
* @sponsor Thanks https://spaceflow.io/ for sponsoring this rule - visit them on https://github.com/SpaceFlow-app
*/
final class ReturnTypeDeclarationRector extends AbstractTypeDeclarationRector
{
/**
Expand All @@ -33,10 +35,19 @@ final class ReturnTypeDeclarationRector extends AbstractTypeDeclarationRector
*/
private $typeAnalyzer;

public function __construct(ReturnTypeInferer $returnTypeInferer, TypeAnalyzer $typeAnalyzer)
{
/**
* @var bool
*/
private $overrideExistingReturnTypes = true;

public function __construct(
ReturnTypeInferer $returnTypeInferer,
TypeAnalyzer $typeAnalyzer,
bool $overrideExistingReturnTypes = true
) {
$this->returnTypeInferer = $returnTypeInferer;
$this->typeAnalyzer = $typeAnalyzer;
$this->overrideExistingReturnTypes = $overrideExistingReturnTypes;
}

public function getDefinition(): RectorDefinition
Expand Down Expand Up @@ -90,11 +101,6 @@ public function refactor(Node $node): ?Node
return null;
}

$hasNewType = false;
if ($node->returnType !== null) {
$hasNewType = $node->returnType->getAttribute(self::HAS_NEW_INHERITED_TYPE, false);
}

$inferedTypes = $this->returnTypeInferer->inferFunctionLikeWithExcludedInferers(
$node,
[ReturnTypeDeclarationReturnTypeInferer::class]
Expand All @@ -105,30 +111,31 @@ public function refactor(Node $node): ?Node
}

$returnTypeInfo = new ReturnTypeInfo($inferedTypes, $this->typeAnalyzer, $inferedTypes);
if ($this->isReturnTypeAlreadyAdded($node, $returnTypeInfo)) {
return null;
}

// @todo is it violation?
if ($hasNewType) {
// should override - is it subtype?
$possibleOverrideNewReturnType = $returnTypeInfo->getFqnTypeNode();
if ($possibleOverrideNewReturnType !== null) {
if ($node->returnType !== null) {
if ($this->isSubtypeOf($possibleOverrideNewReturnType, $node->returnType, 'return')) {
// allow override
$node->returnType = $returnTypeInfo->getFqnTypeNode();
}
} else {
$node->returnType = $returnTypeInfo->getFqnTypeNode();
}
}
} else {
if ($this->isReturnTypeAlreadyAdded($node, $returnTypeInfo)) {
return null;
$shouldPopulateChildren = false;
// should be previous overridden?
if ($node->returnType !== null && $returnTypeInfo->getFqnTypeNode() !== null) {
$isSubtype = $this->isSubtypeOf($returnTypeInfo->getFqnTypeNode(), $node->returnType);

// @see https://wiki.php.net/rfc/covariant-returns-and-contravariant-parameters
if ($this->isAtLeastPhpVersion('7.4') && $isSubtype) {
$shouldPopulateChildren = true;
$node->returnType = $returnTypeInfo->getFqnTypeNode();
} elseif ($isSubtype === false) { // type override
$shouldPopulateChildren = true;
$node->returnType = $returnTypeInfo->getFqnTypeNode();
}

} elseif ($returnTypeInfo->getFqnTypeNode() !== null) {
$shouldPopulateChildren = true;
$node->returnType = $returnTypeInfo->getFqnTypeNode();
}

$this->populateChildren($node, $returnTypeInfo);
if ($shouldPopulateChildren) {
$this->populateChildren($node, $returnTypeInfo);
}

return $node;
}
Expand All @@ -152,14 +159,10 @@ private function populateChildren(Node $node, ReturnTypeInfo $returnTypeInfo): v
throw new ShouldNotHappenException(__METHOD__ . '() on line ' . __LINE__);
}

$childrenClassLikes = $this->parsedNodesByType->findClassesAndInterfacesByType($className);
$childrenClassLikes = $this->parsedNodesByType->findChildrenOfClass($className);

// update their methods as well
foreach ($childrenClassLikes as $childClassLike) {
if (! $childClassLike instanceof Class_) {
continue;
}

$usedTraits = $this->parsedNodesByType->findUsedTraitsInClass($childClassLike);
foreach ($usedTraits as $trait) {
$this->addReturnTypeToMethod($trait, $node, $returnTypeInfo);
Expand Down Expand Up @@ -193,9 +196,6 @@ private function addReturnTypeToMethod(

$currentClassMethod->returnType = $resolvedChildType;

// let the method now it was changed now
$currentClassMethod->returnType->setAttribute(self::HAS_NEW_INHERITED_TYPE, true);

$this->notifyNodeChangeFileInfo($currentClassMethod);
}

Expand All @@ -204,6 +204,12 @@ private function addReturnTypeToMethod(
*/
private function shouldSkip(Node $node): bool
{
if ($this->overrideExistingReturnTypes === false) {
if ($node->returnType) {
return true;
}
}

if (! $node instanceof ClassMethod) {
return false;
}
Expand All @@ -220,6 +226,15 @@ private function isReturnTypeAlreadyAdded(Node $node, ReturnTypeInfo $returnType
return true;
}

// prevent overriding self with itself
if ($this->print($node->returnType) === 'self') {
$className = $node->getAttribute(AttributeKey::CLASS_NAME);

if (ltrim($this->print($returnTypeInfo->getFqnTypeNode()), '\\') === $className) {
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\RectorDefinition;
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer;
use Rector\TypeDeclaration\ValueObject\IdentifierValueObject;

/**
* @sponsor Thanks https://spaceflow.io/ for sponsoring this rule - visit them on https://github.com/SpaceFlow-app
Expand Down Expand Up @@ -61,20 +60,10 @@ public function refactor(Node $node): ?Node

$types = $this->propertyTypeInferer->inferProperty($node);
if ($types) {
$this->setNodeVarTypes($node, $types);
$this->docBlockManipulator->changeVarTag($node, $types);
return $node;
}

return null;
}

/**
* @param string[]|IdentifierValueObject[] $varTypes
*/
private function setNodeVarTypes(Node $node, array $varTypes): Node
{
$this->docBlockManipulator->changeVarTag($node, $varTypes);

return $node;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ public function __construct(array $propertyTypeInferers)
*/
public function inferProperty(Property $property): array
{
foreach ($this->propertyTypeInferers as $propertyTypeInferers) {
$types = $propertyTypeInferers->inferProperty($property);
if ($types !== []) {
foreach ($this->propertyTypeInferers as $propertyTypeInferer) {
$types = $propertyTypeInferer->inferProperty($property);
if ($types !== [] && $types !== ['mixed']) {
return $types;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function inferProperty(Property $property): array
}

$returnTypes = $this->inferClassMethodReturnTypes($classMethod);
if ($returnTypes !== []) {
if ($returnTypes !== [] && $returnTypes !== ['mixed']) {
return $returnTypes;
}
}
Expand Down Expand Up @@ -107,7 +107,7 @@ private function inferClassMethodReturnTypes(ClassMethod $classMethod): array
}

$inferedTypes = $this->returnedNodesReturnTypeInferer->inferFunctionLike($classMethod);
if ($inferedTypes) {
if ($inferedTypes !== [] && $inferedTypes !== ['mixed']) {
return $inferedTypes;
}

Expand Down
Loading