[TypeDeclaration] Add array type from returned method call types (#6277)

This commit is contained in:
Tomas Votruba 2021-05-01 14:16:35 +02:00 committed by GitHub
parent f925b62d9b
commit 0203e0fda1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 119 additions and 39 deletions

View File

@ -0,0 +1,55 @@
<?php
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
final class AddFromChild
{
public function getData(Nested $nested)
{
return $nested->getData();
}
}
final class Nested
{
public function getData()
{
return [
'key',
'value'
];
}
}
?>
-----
<?php
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
final class AddFromChild
{
/**
* @return string[]
*/
public function getData(Nested $nested)
{
return $nested->getData();
}
}
final class Nested
{
/**
* @return string[]
*/
public function getData()
{
return [
'key',
'value'
];
}
}
?>

View File

@ -2,7 +2,7 @@
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;
class SkipSwtichReturns
final class SkipSwitchReturns
{
public function someFunction($value)
{

View File

@ -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);
}
if ($hasSilentVoid) {
$this->types[] = new VoidType();
$types[] = $this->splArrayFixedTypeNarrower->narrow($returnedExprType);
}
return $this->typeFactory->createMixedPassedOrUnionType($this->types);
if ($this->silentVoidResolver->hasSilentVoid($functionLike)) {
$types[] = new VoidType();
}
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);
}
}

View File

@ -109,20 +109,22 @@ final class SilentVoidResolver
private function isSwitchWithAlwaysReturn(Switch_ $switch): bool
{
$casesWithReturn = 0;
$hasDefault = false;
foreach ($switch->cases as $case) {
foreach ($case->stmts as $caseStmt) {
if (! $caseStmt instanceof Return_) {
continue;
if ($case->cond === null) {
$hasDefault = true;
}
}
++$casesWithReturn;
break;
}
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;
}
}