From 57db278625edf788149d233143e97a3b6e11c7c1 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 3 Sep 2019 08:41:22 +0200 Subject: [PATCH] add mixed type --- .../src/StaticTypeToStringResolver.php | 5 ++- .../PhpDocParser/ParamPhpDocNodeFactory.php | 14 +++++- .../AddArrayParamDocTypeRector.php | 10 ++--- .../Closure/AddClosureReturnTypeRector.php | 2 +- .../ReturnTypeDeclarationRector.php | 24 +++++----- .../PropertyTypeDeclarationRector.php | 13 +----- .../src/TypeInferer/PropertyTypeInferer.php | 6 +-- .../GetterPropertyTypeInferer.php | 4 +- .../src/TypeInferer/ReturnTypeInferer.php | 45 +++++++++++++------ .../ReturnedNodesReturnTypeInferer.php | 7 ++- .../Fixture/skip_mixed_and_string.php.inc | 25 +++++++++++ .../ReturnTypeDeclarationRectorTest.php | 1 + ...structor_array_param_with_nullable.php.inc | 2 +- 13 files changed, 102 insertions(+), 56 deletions(-) create mode 100644 packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_mixed_and_string.php.inc diff --git a/packages/NodeTypeResolver/src/StaticTypeToStringResolver.php b/packages/NodeTypeResolver/src/StaticTypeToStringResolver.php index 90e791f7513..a0ab69f392e 100644 --- a/packages/NodeTypeResolver/src/StaticTypeToStringResolver.php +++ b/packages/NodeTypeResolver/src/StaticTypeToStringResolver.php @@ -10,6 +10,7 @@ use PHPStan\Type\ClosureType; 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; @@ -36,6 +37,7 @@ final class StaticTypeToStringResolver BooleanType::class => ['bool'], StringType::class => ['string'], NullType::class => ['null'], + MixedType::class => ['mixed'], // more complex callables function (ArrayType $arrayType): array { @@ -69,8 +71,7 @@ final class StaticTypeToStringResolver 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()]; }, ]; diff --git a/packages/TypeDeclaration/src/PhpDocParser/ParamPhpDocNodeFactory.php b/packages/TypeDeclaration/src/PhpDocParser/ParamPhpDocNodeFactory.php index 0d2de5ef4f7..2799bf0047b 100644 --- a/packages/TypeDeclaration/src/PhpDocParser/ParamPhpDocNodeFactory.php +++ b/packages/TypeDeclaration/src/PhpDocParser/ParamPhpDocNodeFactory.php @@ -31,12 +31,12 @@ final class ParamPhpDocNodeFactory 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__); } @@ -53,4 +53,14 @@ final class ParamPhpDocNodeFactory return new AttributeAwarePhpDocTagNode('@param', $paramTagValueNode); } + + private function createIdentifierTypeNode(string $type): IdentifierTypeNode + { + if (class_exists($type)) { + // FQN class name + $type = '\\' . $type; + } + + return new IdentifierTypeNode($type); + } } diff --git a/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayParamDocTypeRector.php b/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayParamDocTypeRector.php index cc085825cd4..36c47b62781 100644 --- a/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayParamDocTypeRector.php +++ b/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayParamDocTypeRector.php @@ -99,14 +99,11 @@ CODE_SAMPLE */ 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; } @@ -116,7 +113,8 @@ CODE_SAMPLE return null; } - $this->docBlockManipulator->addTag($node, $this->paramPhpDocNodeFactory->create($types, $param)); + $paramTagNode = $this->paramPhpDocNodeFactory->create($types, $param); + $this->docBlockManipulator->addTag($node, $paramTagNode); } return $node; diff --git a/packages/TypeDeclaration/src/Rector/Closure/AddClosureReturnTypeRector.php b/packages/TypeDeclaration/src/Rector/Closure/AddClosureReturnTypeRector.php index 34c7686ffa3..9cb7c3aec34 100644 --- a/packages/TypeDeclaration/src/Rector/Closure/AddClosureReturnTypeRector.php +++ b/packages/TypeDeclaration/src/Rector/Closure/AddClosureReturnTypeRector.php @@ -90,8 +90,8 @@ CODE_SAMPLE } $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; diff --git a/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php b/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php index 05eb3be9a15..cb20952d123 100644 --- a/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php +++ b/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php @@ -115,23 +115,27 @@ CODE_SAMPLE 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) { - $node->returnType = $returnTypeInfo->getFqnTypeNode(); - } - } else { - if ($returnTypeInfo->getFqnTypeNode() !== null) { + } 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; } @@ -200,16 +204,16 @@ CODE_SAMPLE */ private function shouldSkip(Node $node): bool { - if (! $node instanceof ClassMethod) { - return false; - } - if ($this->overrideExistingReturnTypes === false) { if ($node->returnType) { return true; } } + if (! $node instanceof ClassMethod) { + return false; + } + return $this->isNames($node, self::EXCLUDED_METHOD_NAMES); } diff --git a/packages/TypeDeclaration/src/Rector/Property/PropertyTypeDeclarationRector.php b/packages/TypeDeclaration/src/Rector/Property/PropertyTypeDeclarationRector.php index d185d27beda..f662317c61b 100644 --- a/packages/TypeDeclaration/src/Rector/Property/PropertyTypeDeclarationRector.php +++ b/packages/TypeDeclaration/src/Rector/Property/PropertyTypeDeclarationRector.php @@ -10,7 +10,6 @@ use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; 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 @@ -61,20 +60,10 @@ final class PropertyTypeDeclarationRector extends AbstractRector $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; - } } diff --git a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer.php b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer.php index c7f90271b40..6dac5bdee35 100644 --- a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer.php @@ -26,9 +26,9 @@ final class PropertyTypeInferer extends AbstractPriorityAwareTypeInferer */ 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; } } diff --git a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/GetterPropertyTypeInferer.php b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/GetterPropertyTypeInferer.php index 86ce53f0318..6aea7f05c2f 100644 --- a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/GetterPropertyTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/GetterPropertyTypeInferer.php @@ -58,7 +58,7 @@ final class GetterPropertyTypeInferer extends AbstractTypeInferer implements Pro } $returnTypes = $this->inferClassMethodReturnTypes($classMethod); - if ($returnTypes !== []) { + if ($returnTypes !== [] && $returnTypes !== ['mixed']) { return $returnTypes; } } @@ -107,7 +107,7 @@ final class GetterPropertyTypeInferer extends AbstractTypeInferer implements Pro } $inferedTypes = $this->returnedNodesReturnTypeInferer->inferFunctionLike($classMethod); - if ($inferedTypes) { + if ($inferedTypes !== [] && $inferedTypes !== ['mixed']) { return $inferedTypes; } diff --git a/packages/TypeDeclaration/src/TypeInferer/ReturnTypeInferer.php b/packages/TypeDeclaration/src/TypeInferer/ReturnTypeInferer.php index 76dd045d082..a3edd583328 100644 --- a/packages/TypeDeclaration/src/TypeInferer/ReturnTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/ReturnTypeInferer.php @@ -3,6 +3,7 @@ namespace Rector\TypeDeclaration\TypeInferer; use PhpParser\Node\FunctionLike; +use Rector\Exception\ShouldNotHappenException; use Rector\TypeDeclaration\Contract\TypeInferer\ReturnTypeInfererInterface; final class ReturnTypeInferer extends AbstractPriorityAwareTypeInferer @@ -25,14 +26,7 @@ final class ReturnTypeInferer extends AbstractPriorityAwareTypeInferer */ public function inferFunctionLike(FunctionLike $functionLike): array { - foreach ($this->returnTypeInferers as $returnTypeInferer) { - $types = $returnTypeInferer->inferFunctionLike($functionLike); - if ($types !== []) { - return $types; - } - } - - return []; + return $this->inferFunctionLikeWithExcludedInferers($functionLike, []); } /** @@ -42,18 +36,43 @@ final class ReturnTypeInferer extends AbstractPriorityAwareTypeInferer public function inferFunctionLikeWithExcludedInferers(FunctionLike $functionLike, array $excludedInferers): array { foreach ($this->returnTypeInferers as $returnTypeInferer) { - foreach ($excludedInferers as $excludedInferer) { - if (is_a($returnTypeInferer, $excludedInferer, true)) { - continue 2; - } + if ($this->shouldSkipExcludedTypeInferer($returnTypeInferer, $excludedInferers)) { + continue; } $types = $returnTypeInferer->inferFunctionLike($functionLike); - if ($types !== []) { + if ($types !== [] && $types !== ['mixed']) { return $types; } } return []; } + + /** + * @param string[] $excludedInferers + */ + private function shouldSkipExcludedTypeInferer( + ReturnTypeInfererInterface $returnTypeInferer, + array $excludedInferers + ): bool { + foreach ($excludedInferers as $excludedInferer) { + $this->ensureIsTypeInferer($excludedInferer); + + if (is_a($returnTypeInferer, $excludedInferer)) { + return true; + } + } + + return false; + } + + private function ensureIsTypeInferer(string $excludedInferer): void + { + if (is_a($excludedInferer, ReturnTypeInfererInterface::class, true)) { + return; + } + + throw new ShouldNotHappenException(); + } } diff --git a/packages/TypeDeclaration/src/TypeInferer/ReturnTypeInferer/ReturnedNodesReturnTypeInferer.php b/packages/TypeDeclaration/src/TypeInferer/ReturnTypeInferer/ReturnedNodesReturnTypeInferer.php index d5d154c8980..6b03d37d057 100644 --- a/packages/TypeDeclaration/src/TypeInferer/ReturnTypeInferer/ReturnedNodesReturnTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/ReturnTypeInferer/ReturnedNodesReturnTypeInferer.php @@ -58,16 +58,15 @@ final class ReturnedNodesReturnTypeInferer extends AbstractTypeInferer implement } /** - * @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 { + $this->callableNodeTraverser->traverseNodesWithCallable((array) $functionLike->getStmts(), 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; diff --git a/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_mixed_and_string.php.inc b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_mixed_and_string.php.inc new file mode 100644 index 00000000000..29db67e19f0 --- /dev/null +++ b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_mixed_and_string.php.inc @@ -0,0 +1,25 @@ +value instanceof stdClass) { + return $this->getStringValue(); + } + + return $this->value; + } + + public function getStringValue(): string + { + return 'abc'; + } +} diff --git a/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/ReturnTypeDeclarationRectorTest.php b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/ReturnTypeDeclarationRectorTest.php index 448c875d15c..c5a31a5d7cd 100644 --- a/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/ReturnTypeDeclarationRectorTest.php +++ b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/ReturnTypeDeclarationRectorTest.php @@ -10,6 +10,7 @@ final class ReturnTypeDeclarationRectorTest extends AbstractRectorTestCase public function test(): void { $files = [ + __DIR__ . '/Fixture/skip_mixed_and_string.php.inc', __DIR__ . '/Fixture/skip_self.php.inc', // static types __DIR__ . '/Fixture/void_type.php.inc', diff --git a/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/constructor_array_param_with_nullable.php.inc b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/constructor_array_param_with_nullable.php.inc index d0a5b73fed9..fbee723d8e5 100644 --- a/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/constructor_array_param_with_nullable.php.inc +++ b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/constructor_array_param_with_nullable.php.inc @@ -21,7 +21,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\PropertyTypeDeclarati class ConstructorArrayParamWithNullable { /** - * @var array|null + * @var mixed[]|null */ private $data;