mirror of
https://github.com/rectorphp/rector.git
synced 2025-02-16 05:44:59 +01:00
[TypeDeclaration] Add array type from returned method call types (#6277)
This commit is contained in:
parent
f925b62d9b
commit
0203e0fda1
@ -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'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;
|
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;
|
||||||
|
|
||||||
class SkipSwtichReturns
|
final class SkipSwitchReturns
|
||||||
{
|
{
|
||||||
public function someFunction($value)
|
public function someFunction($value)
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,7 @@ namespace Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
|
|||||||
|
|
||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
use PhpParser\Node\Expr\Closure;
|
use PhpParser\Node\Expr\Closure;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
use PhpParser\Node\FunctionLike;
|
use PhpParser\Node\FunctionLike;
|
||||||
use PhpParser\Node\Stmt\Class_;
|
use PhpParser\Node\Stmt\Class_;
|
||||||
use PhpParser\Node\Stmt\ClassLike;
|
use PhpParser\Node\Stmt\ClassLike;
|
||||||
@ -13,12 +14,12 @@ use PhpParser\Node\Stmt\ClassMethod;
|
|||||||
use PhpParser\Node\Stmt\Function_;
|
use PhpParser\Node\Stmt\Function_;
|
||||||
use PhpParser\Node\Stmt\Interface_;
|
use PhpParser\Node\Stmt\Interface_;
|
||||||
use PhpParser\Node\Stmt\Return_;
|
use PhpParser\Node\Stmt\Return_;
|
||||||
use PhpParser\Node\Stmt\Switch_;
|
|
||||||
use PhpParser\Node\Stmt\Trait_;
|
use PhpParser\Node\Stmt\Trait_;
|
||||||
use PhpParser\NodeTraverser;
|
use PhpParser\NodeTraverser;
|
||||||
use PHPStan\Type\MixedType;
|
use PHPStan\Type\MixedType;
|
||||||
use PHPStan\Type\Type;
|
use PHPStan\Type\Type;
|
||||||
use PHPStan\Type\VoidType;
|
use PHPStan\Type\VoidType;
|
||||||
|
use Rector\NodeCollector\NodeCollector\NodeRepository;
|
||||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||||
use Rector\NodeTypeResolver\NodeTypeResolver;
|
use Rector\NodeTypeResolver\NodeTypeResolver;
|
||||||
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
|
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
|
||||||
@ -29,11 +30,6 @@ use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser;
|
|||||||
|
|
||||||
final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface
|
final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var Type[]
|
|
||||||
*/
|
|
||||||
private $types = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var SilentVoidResolver
|
* @var SilentVoidResolver
|
||||||
*/
|
*/
|
||||||
@ -59,18 +55,25 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface
|
|||||||
*/
|
*/
|
||||||
private $splArrayFixedTypeNarrower;
|
private $splArrayFixedTypeNarrower;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var NodeRepository
|
||||||
|
*/
|
||||||
|
private $nodeRepository;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
SilentVoidResolver $silentVoidResolver,
|
SilentVoidResolver $silentVoidResolver,
|
||||||
NodeTypeResolver $nodeTypeResolver,
|
NodeTypeResolver $nodeTypeResolver,
|
||||||
SimpleCallableNodeTraverser $simpleCallableNodeTraverser,
|
SimpleCallableNodeTraverser $simpleCallableNodeTraverser,
|
||||||
TypeFactory $typeFactory,
|
TypeFactory $typeFactory,
|
||||||
SplArrayFixedTypeNarrower $splArrayFixedTypeNarrower
|
SplArrayFixedTypeNarrower $splArrayFixedTypeNarrower,
|
||||||
|
NodeRepository $nodeRepository
|
||||||
) {
|
) {
|
||||||
$this->silentVoidResolver = $silentVoidResolver;
|
$this->silentVoidResolver = $silentVoidResolver;
|
||||||
$this->nodeTypeResolver = $nodeTypeResolver;
|
$this->nodeTypeResolver = $nodeTypeResolver;
|
||||||
$this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser;
|
$this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser;
|
||||||
$this->typeFactory = $typeFactory;
|
$this->typeFactory = $typeFactory;
|
||||||
$this->splArrayFixedTypeNarrower = $splArrayFixedTypeNarrower;
|
$this->splArrayFixedTypeNarrower = $splArrayFixedTypeNarrower;
|
||||||
|
$this->nodeRepository = $nodeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,25 +91,28 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface
|
|||||||
return new MixedType();
|
return new MixedType();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->types = [];
|
$types = [];
|
||||||
|
|
||||||
$localReturnNodes = $this->collectReturns($functionLike);
|
$localReturnNodes = $this->collectReturns($functionLike);
|
||||||
if ($localReturnNodes === []) {
|
if ($localReturnNodes === []) {
|
||||||
return $this->resolveNoLocalReturnNodes($classLike, $functionLike);
|
return $this->resolveNoLocalReturnNodes($classLike, $functionLike);
|
||||||
}
|
}
|
||||||
|
|
||||||
$hasSilentVoid = $this->silentVoidResolver->hasSilentVoid($functionLike);
|
|
||||||
|
|
||||||
foreach ($localReturnNodes as $localReturnNode) {
|
foreach ($localReturnNodes as $localReturnNode) {
|
||||||
$returnedExprType = $this->nodeTypeResolver->getStaticType($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) {
|
if ($this->silentVoidResolver->hasSilentVoid($functionLike)) {
|
||||||
$this->types[] = new VoidType();
|
$types[] = new VoidType();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->typeFactory->createMixedPassedOrUnionType($this->types);
|
return $this->typeFactory->createMixedPassedOrUnionType($types);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPriority(): int
|
public function getPriority(): int
|
||||||
@ -124,10 +130,6 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface
|
|||||||
$this->simpleCallableNodeTraverser->traverseNodesWithCallable((array) $functionLike->getStmts(), function (
|
$this->simpleCallableNodeTraverser->traverseNodesWithCallable((array) $functionLike->getStmts(), function (
|
||||||
Node $node
|
Node $node
|
||||||
) use (&$returns): ?int {
|
) use (&$returns): ?int {
|
||||||
if ($node instanceof Switch_) {
|
|
||||||
$this->processSwitch($node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip Return_ nodes in nested functions or switch statements
|
// skip Return_ nodes in nested functions or switch statements
|
||||||
if ($node instanceof FunctionLike) {
|
if ($node instanceof FunctionLike) {
|
||||||
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
|
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
|
||||||
@ -155,17 +157,6 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface
|
|||||||
return new MixedType();
|
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
|
private function isAbstractMethod(ClassLike $classLike, FunctionLike $functionLike): bool
|
||||||
{
|
{
|
||||||
if ($functionLike instanceof ClassMethod && $functionLike->isAbstract()) {
|
if ($functionLike instanceof ClassMethod && $functionLike->isAbstract()) {
|
||||||
@ -177,4 +168,18 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface
|
|||||||
}
|
}
|
||||||
return $classLike->isAbstract();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,20 +109,22 @@ final class SilentVoidResolver
|
|||||||
|
|
||||||
private function isSwitchWithAlwaysReturn(Switch_ $switch): bool
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
++$casesWithReturn;
|
foreach ($switch->cases as $case) {
|
||||||
break;
|
if ($case->cond === null) {
|
||||||
|
$hasDefault = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $hasDefault) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$casesWithReturnCount = $this->resolveReturnCount($switch);
|
||||||
|
|
||||||
// has same amount of returns as switches
|
// has same amount of returns as switches
|
||||||
return count($switch->cases) === $casesWithReturn;
|
return count($switch->cases) === $casesWithReturnCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isTryCatchAlwaysReturn(TryCatch $tryCatch): bool
|
private function isTryCatchAlwaysReturn(TryCatch $tryCatch): bool
|
||||||
@ -146,4 +148,22 @@ final class SilentVoidResolver
|
|||||||
{
|
{
|
||||||
return $this->betterNodeFinder->hasInstancesOf($functionLike, [Throw_::class]);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user