diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector/Fixture/add_from_child.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector/Fixture/add_from_child.php.inc new file mode 100644 index 00000000000..575ee8c1d54 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector/Fixture/add_from_child.php.inc @@ -0,0 +1,55 @@ +getData(); + } +} + +final class Nested +{ + public function getData() + { + return [ + 'key', + 'value' + ]; + } +} + +?> +----- +getData(); + } +} + +final class Nested +{ + /** + * @return string[] + */ + public function getData() + { + return [ + 'key', + 'value' + ]; + } +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_switch_returns.php.inc b/rules-tests/TypeDeclaration/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_switch_returns.php.inc index 988a5662f90..c1ad716075f 100644 --- a/rules-tests/TypeDeclaration/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_switch_returns.php.inc +++ b/rules-tests/TypeDeclaration/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_switch_returns.php.inc @@ -2,7 +2,7 @@ namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture; -class SkipSwtichReturns +final class SkipSwitchReturns { public function someFunction($value) { diff --git a/rules/TypeDeclaration/TypeInferer/ReturnTypeInferer/ReturnedNodesReturnTypeInferer.php b/rules/TypeDeclaration/TypeInferer/ReturnTypeInferer/ReturnedNodesReturnTypeInferer.php index 9b6b8612d5e..b559b1a5f11 100644 --- a/rules/TypeDeclaration/TypeInferer/ReturnTypeInferer/ReturnedNodesReturnTypeInferer.php +++ b/rules/TypeDeclaration/TypeInferer/ReturnTypeInferer/ReturnedNodesReturnTypeInferer.php @@ -6,6 +6,7 @@ namespace Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer; use PhpParser\Node; use PhpParser\Node\Expr\Closure; +use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\FunctionLike; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassLike; @@ -13,12 +14,12 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Return_; -use PhpParser\Node\Stmt\Switch_; use PhpParser\Node\Stmt\Trait_; use PhpParser\NodeTraverser; use PHPStan\Type\MixedType; use PHPStan\Type\Type; use PHPStan\Type\VoidType; +use Rector\NodeCollector\NodeCollector\NodeRepository; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeTypeResolver; use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory; @@ -29,11 +30,6 @@ use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser; final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface { - /** - * @var Type[] - */ - private $types = []; - /** * @var SilentVoidResolver */ @@ -59,18 +55,25 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface */ private $splArrayFixedTypeNarrower; + /** + * @var NodeRepository + */ + private $nodeRepository; + public function __construct( SilentVoidResolver $silentVoidResolver, NodeTypeResolver $nodeTypeResolver, SimpleCallableNodeTraverser $simpleCallableNodeTraverser, TypeFactory $typeFactory, - SplArrayFixedTypeNarrower $splArrayFixedTypeNarrower + SplArrayFixedTypeNarrower $splArrayFixedTypeNarrower, + NodeRepository $nodeRepository ) { $this->silentVoidResolver = $silentVoidResolver; $this->nodeTypeResolver = $nodeTypeResolver; $this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser; $this->typeFactory = $typeFactory; $this->splArrayFixedTypeNarrower = $splArrayFixedTypeNarrower; + $this->nodeRepository = $nodeRepository; } /** @@ -88,25 +91,28 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface return new MixedType(); } - $this->types = []; + $types = []; $localReturnNodes = $this->collectReturns($functionLike); if ($localReturnNodes === []) { return $this->resolveNoLocalReturnNodes($classLike, $functionLike); } - $hasSilentVoid = $this->silentVoidResolver->hasSilentVoid($functionLike); - foreach ($localReturnNodes as $localReturnNode) { $returnedExprType = $this->nodeTypeResolver->getStaticType($localReturnNode); - $this->types[] = $this->splArrayFixedTypeNarrower->narrow($returnedExprType); + + if ($returnedExprType instanceof MixedType) { + $returnedExprType = $this->inferFromReturnedMethodCall($localReturnNode); + } + + $types[] = $this->splArrayFixedTypeNarrower->narrow($returnedExprType); } - if ($hasSilentVoid) { - $this->types[] = new VoidType(); + if ($this->silentVoidResolver->hasSilentVoid($functionLike)) { + $types[] = new VoidType(); } - return $this->typeFactory->createMixedPassedOrUnionType($this->types); + return $this->typeFactory->createMixedPassedOrUnionType($types); } public function getPriority(): int @@ -124,10 +130,6 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface $this->simpleCallableNodeTraverser->traverseNodesWithCallable((array) $functionLike->getStmts(), function ( Node $node ) use (&$returns): ?int { - if ($node instanceof Switch_) { - $this->processSwitch($node); - } - // skip Return_ nodes in nested functions or switch statements if ($node instanceof FunctionLike) { return NodeTraverser::DONT_TRAVERSE_CHILDREN; @@ -155,17 +157,6 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface return new MixedType(); } - private function processSwitch(Switch_ $switch): void - { - foreach ($switch->cases as $case) { - if ($case->cond === null) { - return; - } - } - - $this->types[] = new VoidType(); - } - private function isAbstractMethod(ClassLike $classLike, FunctionLike $functionLike): bool { if ($functionLike instanceof ClassMethod && $functionLike->isAbstract()) { @@ -177,4 +168,18 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface } return $classLike->isAbstract(); } + + private function inferFromReturnedMethodCall(Return_ $return): Type + { + if (! $return->expr instanceof MethodCall) { + return new MixedType(); + } + + $classMethod = $this->nodeRepository->findClassMethodByMethodCall($return->expr); + if (! $classMethod instanceof ClassMethod) { + return new MixedType(); + } + + return $this->inferFunctionLike($classMethod); + } } diff --git a/rules/TypeDeclaration/TypeInferer/SilentVoidResolver.php b/rules/TypeDeclaration/TypeInferer/SilentVoidResolver.php index 44893858a10..b5dcc1267a1 100644 --- a/rules/TypeDeclaration/TypeInferer/SilentVoidResolver.php +++ b/rules/TypeDeclaration/TypeInferer/SilentVoidResolver.php @@ -109,20 +109,22 @@ final class SilentVoidResolver private function isSwitchWithAlwaysReturn(Switch_ $switch): bool { - $casesWithReturn = 0; - foreach ($switch->cases as $case) { - foreach ($case->stmts as $caseStmt) { - if (! $caseStmt instanceof Return_) { - continue; - } + $hasDefault = false; - ++$casesWithReturn; - break; + foreach ($switch->cases as $case) { + if ($case->cond === null) { + $hasDefault = true; } } + if (! $hasDefault) { + return false; + } + + $casesWithReturnCount = $this->resolveReturnCount($switch); + // has same amount of returns as switches - return count($switch->cases) === $casesWithReturn; + return count($switch->cases) === $casesWithReturnCount; } private function isTryCatchAlwaysReturn(TryCatch $tryCatch): bool @@ -146,4 +148,22 @@ final class SilentVoidResolver { return $this->betterNodeFinder->hasInstancesOf($functionLike, [Throw_::class]); } + + private function resolveReturnCount(Switch_ $switch): int + { + $casesWithReturnCount = 0; + + foreach ($switch->cases as $case) { + foreach ($case->stmts as $caseStmt) { + if (! $caseStmt instanceof Return_) { + continue; + } + + ++$casesWithReturnCount; + break; + } + } + + return $casesWithReturnCount; + } }