diff --git a/packages/NodeTypeResolver/src/Php/AbstractTypeInfo.php b/packages/NodeTypeResolver/src/Php/AbstractTypeInfo.php index 138f9efc901a..8449bec28377 100644 --- a/packages/NodeTypeResolver/src/Php/AbstractTypeInfo.php +++ b/packages/NodeTypeResolver/src/Php/AbstractTypeInfo.php @@ -147,6 +147,10 @@ public function getDocTypes(): array { $allTypes = array_merge($this->types, $this->removedTypes); + if ($this->isNullable) { + $allTypes[] = 'null'; + } + return array_filter(array_unique($allTypes)); } diff --git a/packages/Php/tests/Rector/Property/CompleteVarDocTypePropertyRector/Fixture/symfony_console_command.php.inc b/packages/Php/tests/Rector/Property/CompleteVarDocTypePropertyRector/Fixture/symfony_console_command.php.inc index beeb1308c185..c074e739ff3b 100644 --- a/packages/Php/tests/Rector/Property/CompleteVarDocTypePropertyRector/Fixture/symfony_console_command.php.inc +++ b/packages/Php/tests/Rector/Property/CompleteVarDocTypePropertyRector/Fixture/symfony_console_command.php.inc @@ -586,7 +586,7 @@ class Command */ protected static $defaultName; /** - * @var \Symfony\Component\Console\Application + * @var \Symfony\Component\Console\Application|null */ private $application; /** @@ -642,7 +642,7 @@ class Command */ private $usages = array(); /** - * @var \Symfony\Component\Console\Helper\HelperSet + * @var \Symfony\Component\Console\Helper\HelperSet|null */ private $helperSet; /** diff --git a/packages/TypeDeclaration/src/PropertyTypeInferer/GetterOrSetterPropertyTypeInferer.php b/packages/TypeDeclaration/src/PropertyTypeInferer/GetterOrSetterPropertyTypeInferer.php index 99bbad9aa7f4..371483597aef 100644 --- a/packages/TypeDeclaration/src/PropertyTypeInferer/GetterOrSetterPropertyTypeInferer.php +++ b/packages/TypeDeclaration/src/PropertyTypeInferer/GetterOrSetterPropertyTypeInferer.php @@ -3,26 +3,24 @@ namespace Rector\TypeDeclaration\PropertyTypeInferer; use PhpParser\Node\Expr\PropertyFetch; -use PhpParser\Node\NullableType; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\Return_; -use Rector\Exception\ShouldNotHappenException; use Rector\NodeTypeResolver\Node\AttributeKey; -use Rector\PhpParser\Printer\BetterStandardPrinter; use Rector\TypeDeclaration\Contract\PropertyTypeInfererInterface; +use Rector\TypeDeclaration\ReturnTypeResolver\ReturnTypeResolver; final class GetterOrSetterPropertyTypeInferer extends AbstractPropertyTypeInferer implements PropertyTypeInfererInterface { /** - * @var BetterStandardPrinter + * @var ReturnTypeResolver */ - private $betterStandardPrinter; + private $returnTypeResolver; - public function __construct(BetterStandardPrinter $betterStandardPrinter) + public function __construct(ReturnTypeResolver $returnTypeResolver) { - $this->betterStandardPrinter = $betterStandardPrinter; + $this->returnTypeResolver = $returnTypeResolver; } /** @@ -40,16 +38,11 @@ public function inferProperty(Property $property): array if (! $this->hasClassMethodOnlyStatementReturnOfPropertyFetch($classMethod, $propertyName)) { continue; } + $returnTypes = $this->resolveClassMethodReturnTypes($classMethod); if ($returnTypes !== []) { return $returnTypes; } - - throw new ShouldNotHappenException(sprintf( - '"%s" for "%s" type', - __METHOD__, - $this->betterStandardPrinter->print($classMethod->returnType) - )); } return []; @@ -88,28 +81,11 @@ private function hasClassMethodOnlyStatementReturnOfPropertyFetch( */ private function resolveClassMethodReturnTypes(ClassMethod $classMethod): array { - // @todo resolve from doc? - if (! $classMethod->returnType) { + $returnType = $this->returnTypeResolver->resolveFunctionLikeReturnType($classMethod); + if ($returnType === null) { return []; } - if ($classMethod->returnType instanceof NullableType) { - $type = $classMethod->returnType->type; - } else { - $type = $classMethod->returnType; - } - - $result = $this->nameResolver->resolve($type); - if ($result !== null) { - $types = [$result]; - - if ($classMethod->returnType instanceof NullableType) { - $types[] = 'null'; - } - - return $types; - } - - return []; + return $returnType->getDocTypes(); } } diff --git a/packages/TypeDeclaration/src/Rector/FunctionLike/AbstractTypeDeclarationRector.php b/packages/TypeDeclaration/src/Rector/FunctionLike/AbstractTypeDeclarationRector.php index e5aee020ae85..16410cc3c5e7 100644 --- a/packages/TypeDeclaration/src/Rector/FunctionLike/AbstractTypeDeclarationRector.php +++ b/packages/TypeDeclaration/src/Rector/FunctionLike/AbstractTypeDeclarationRector.php @@ -32,7 +32,7 @@ abstract class AbstractTypeDeclarationRector extends AbstractRector /** * @var string */ - protected const HAS_NEW_INHERITED_TYPE = 'has_new_inherited_return_type'; + public const HAS_NEW_INHERITED_TYPE = 'has_new_inherited_return_type'; /** * @var DocBlockManipulator @@ -49,11 +49,14 @@ abstract class AbstractTypeDeclarationRector extends AbstractRector */ protected $functionLikeManipulator; - public function __construct( + /** + * @required + */ + public function autowireAbstractTypeDeclarationRector( DocBlockManipulator $docBlockManipulator, ParsedNodesByType $parsedNodesByType, FunctionLikeManipulator $functionLikeManipulator - ) { + ): void { $this->docBlockManipulator = $docBlockManipulator; $this->parsedNodesByType = $parsedNodesByType; $this->functionLikeManipulator = $functionLikeManipulator; diff --git a/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php b/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php index e9fe153487a9..dda8778fbf59 100644 --- a/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php +++ b/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php @@ -3,7 +3,6 @@ namespace Rector\TypeDeclaration\Rector\FunctionLike; use PhpParser\Node; -use PhpParser\Node\FunctionLike; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; @@ -13,6 +12,7 @@ use Rector\NodeTypeResolver\Php\ReturnTypeInfo; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; +use Rector\TypeDeclaration\ReturnTypeResolver\ReturnTypeResolver; final class ReturnTypeDeclarationRector extends AbstractTypeDeclarationRector { @@ -21,6 +21,16 @@ final class ReturnTypeDeclarationRector extends AbstractTypeDeclarationRector */ private const EXCLUDED_METHOD_NAMES = ['__construct', '__destruct', '__clone']; + /** + * @var ReturnTypeResolver + */ + private $returnTypeResolver; + + public function __construct(ReturnTypeResolver $returnTypeResolver) + { + $this->returnTypeResolver = $returnTypeResolver; + } + public function getDefinition(): RectorDefinition { return new RectorDefinition( @@ -82,7 +92,7 @@ public function refactor(Node $node): ?Node } } - $returnTypeInfo = $this->resolveReturnType($node); + $returnTypeInfo = $this->returnTypeResolver->resolveFunctionLikeReturnType($node); if ($returnTypeInfo === null) { return null; } @@ -125,26 +135,6 @@ public function refactor(Node $node): ?Node return $node; } - /** - * @param ClassMethod|Function_ $functionLike - */ - private function resolveReturnType(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; - } - /** * Add typehint to all children */ diff --git a/packages/TypeDeclaration/src/ReturnTypeResolver/ReturnTypeResolver.php b/packages/TypeDeclaration/src/ReturnTypeResolver/ReturnTypeResolver.php new file mode 100644 index 000000000000..2864cbef1239 --- /dev/null +++ b/packages/TypeDeclaration/src/ReturnTypeResolver/ReturnTypeResolver.php @@ -0,0 +1,51 @@ +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; + } +} diff --git a/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/nikic/iterable.php.inc b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/nikic/iterable.php.inc index 0510f91c4554..0d03f327f727 100644 --- a/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/nikic/iterable.php.inc +++ b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/nikic/iterable.php.inc @@ -2,13 +2,13 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Iterable; -class A { +class AnIterableClass { /** @return iterable */ public function getIterable($value) { return $value; } } -class B extends A { +class BuildOnThePreviousClass extends AnIterableClass { /** @return array */ public function getIterable($value) { return $value; @@ -21,13 +21,13 @@ class B extends A { namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture\Iterable; -class A { +class AnIterableClass { /** @return iterable */ public function getIterable($value): iterable { return $value; } } -class B extends A { +class BuildOnThePreviousClass extends AnIterableClass { /** @return array */ public function getIterable($value): array { return $value; diff --git a/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/getter_type.php.inc b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/getter_type.php.inc new file mode 100644 index 000000000000..acf94b45b48a --- /dev/null +++ b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/getter_type.php.inc @@ -0,0 +1,151 @@ +email; + } + + public function setEmail(string $email): void + { + $this->email = $email; + } + + public function hasLanguage(): bool + { + return $this->language !== null; + } + + public function getLanguage() + { + return $this->language; + } + + public function setLanguage(string $language): void + { + $this->language = $language; + } + + public function getPassword(): string + { + return $this->password; + } + + public function setPassword(string $password): void + { + $this->password = $password; + } + + /** + * @return string + */ + public function getSurname() + { + return $this->surname; + } +} + +?> +----- +email; + } + + public function setEmail(string $email): void + { + $this->email = $email; + } + + public function hasLanguage(): bool + { + return $this->language !== null; + } + + public function getLanguage() + { + return $this->language; + } + + public function setLanguage(string $language): void + { + $this->language = $language; + } + + public function getPassword(): string + { + return $this->password; + } + + public function setPassword(string $password): void + { + $this->password = $password; + } + + /** + * @return string + */ + public function getSurname() + { + return $this->surname; + } +} + +?> diff --git a/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/setter_type.php.inc b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/setter_type.php.inc new file mode 100644 index 000000000000..e8fe51eb6833 --- /dev/null +++ b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/setter_type.php.inc @@ -0,0 +1,59 @@ +email = $email; + } + + /** + * @param string $name + */ + public function setName($name) + { + return $this->name = $name; + } +} + +?> +----- +email = $email; + } + + /** + * @param string $name + */ + public function setName($name) + { + return $this->name = $name; + } +} + +?> diff --git a/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/PropertyTypeDeclarationRectorTest.php b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/PropertyTypeDeclarationRectorTest.php index a1ddef75fdee..5743d2fd9583 100644 --- a/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/PropertyTypeDeclarationRectorTest.php +++ b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/PropertyTypeDeclarationRectorTest.php @@ -15,13 +15,13 @@ public function test(): void __DIR__ . '/Fixture/constructor_assign.php.inc', __DIR__ . '/Fixture/phpunit_setup.php.inc', __DIR__ . '/Fixture/default_value.php.inc', - __DIR__ . '/Fixture/doctrine_column.php.inc', __DIR__ . '/Fixture/doctrine_relation.php.inc', - + // get and set __DIR__ . '/Fixture/complex.php.inc', - __DIR__ . '/Fixture/single_nullable_return.php.inc', + __DIR__ . '/Fixture/getter_type.php.inc', + __DIR__ . '/Fixture/setter_type.php.inc', // skip __DIR__ . '/Fixture/skip_multi_vars.php.inc', ]); diff --git a/src/PhpParser/Node/Manipulator/FunctionLikeManipulator.php b/src/PhpParser/Node/Manipulator/FunctionLikeManipulator.php index 3e2941875a5f..881db49d043f 100644 --- a/src/PhpParser/Node/Manipulator/FunctionLikeManipulator.php +++ b/src/PhpParser/Node/Manipulator/FunctionLikeManipulator.php @@ -2,8 +2,12 @@ namespace Rector\PhpParser\Node\Manipulator; +use PhpParser\Node; use PhpParser\Node\Expr\Closure; use PhpParser\Node\FunctionLike; +use PhpParser\Node\Identifier; +use PhpParser\Node\Name; +use PhpParser\Node\NullableType; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Interface_; @@ -13,6 +17,8 @@ use Rector\NodeTypeResolver\Php\ReturnTypeInfo; use Rector\Php\TypeAnalyzer; use Rector\PhpParser\Node\BetterNodeFinder; +use Rector\PhpParser\Node\Resolver\NameResolver; +use Rector\TypeDeclaration\Rector\FunctionLike\AbstractTypeDeclarationRector; final class FunctionLikeManipulator { @@ -31,14 +37,21 @@ final class FunctionLikeManipulator */ private $nodeTypeResolver; + /** + * @var NameResolver + */ + private $nameResolver; + public function __construct( BetterNodeFinder $betterNodeFinder, TypeAnalyzer $typeAnalyzer, - NodeTypeResolver $nodeTypeResolver + NodeTypeResolver $nodeTypeResolver, + NameResolver $nameResolver ) { $this->betterNodeFinder = $betterNodeFinder; $this->typeAnalyzer = $typeAnalyzer; $this->nodeTypeResolver = $nodeTypeResolver; + $this->nameResolver = $nameResolver; } /** @@ -51,6 +64,19 @@ public function resolveStaticReturnTypeInfo(FunctionLike $functionLike): ?Return return null; } + // A. resolve from function return type + if ($functionLike->returnType !== null) { + $types = $this->resolveReturnTypeToString($functionLike->returnType); + + // do not override freshly added type declaration + if (! $functionLike->returnType->getAttribute( + AbstractTypeDeclarationRector::HAS_NEW_INHERITED_TYPE + ) && $types !== []) { + return new ReturnTypeInfo($types, $this->typeAnalyzer); + } + } + + // B. resolve from return $x nodes /** @var Return_[] $returnNodes */ $returnNodes = $this->betterNodeFinder->findInstanceOf((array) $functionLike->stmts, Return_::class); @@ -90,4 +116,25 @@ private function shouldSkip(FunctionLike $functionLike): bool // only methods that are not abstract can be analyzed for returns return $functionLike->isAbstract(); } + + /** + * @param Identifier|Name|NullableType $node + * @return string[] + */ + private function resolveReturnTypeToString(Node $node): array + { + $types = []; + + $type = $node instanceof NullableType ? $node->type : $node; + $result = $this->nameResolver->resolve($type); + if ($result !== null) { + $types[] = $result; + } + + if ($node instanceof NullableType) { + $types[] = 'null'; + } + + return $types; + } }