Skip to content

Commit 93fa96f

Browse files
committed
Avoid complex nesting in ConstantArrayTypeGeneralizer
1 parent ad61748 commit 93fa96f

3 files changed

Lines changed: 175 additions & 86 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector\Fixture;
4+
5+
final class AvoidExtremeNesting
6+
{
7+
public function run(): array
8+
{
9+
return [
10+
'data' => [
11+
'data' => [
12+
'data' => [
13+
'data' => [
14+
'data' => [
15+
'name' => 'John',
16+
],
17+
],
18+
],
19+
],
20+
],
21+
];
22+
}
23+
}
24+
25+
?>
26+
-----
27+
<?php
28+
29+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector\Fixture;
30+
31+
final class AvoidExtremeNesting
32+
{
33+
/**
34+
* @return array<string, array<string, array<string, array<string, mixed>>>>
35+
*/
36+
public function run(): array
37+
{
38+
return [
39+
'data' => [
40+
'data' => [
41+
'data' => [
42+
'data' => [
43+
'data' => [
44+
'name' => 'John',
45+
],
46+
],
47+
],
48+
],
49+
],
50+
];
51+
}
52+
}
53+
54+
?>

rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php

Lines changed: 3 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,11 @@
1010
use PhpParser\Node\Stmt\Function_;
1111
use PhpParser\Node\Stmt\Return_;
1212
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
13-
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
14-
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
15-
use PHPStan\Type\BooleanType;
1613
use PHPStan\Type\Constant\ConstantArrayType;
17-
use PHPStan\Type\FloatType;
18-
use PHPStan\Type\IntegerType;
19-
use PHPStan\Type\IntersectionType;
20-
use PHPStan\Type\MixedType;
21-
use PHPStan\Type\NeverType;
22-
use PHPStan\Type\StringType;
23-
use PHPStan\Type\Type;
24-
use PHPStan\Type\UnionType;
2514
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
2615
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
27-
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
2816
use Rector\Rector\AbstractRector;
29-
use Rector\StaticTypeMapper\StaticTypeMapper;
17+
use Rector\TypeDeclarationDocblocks\TypeResolver\ConstantArrayTypeGeneralizer;
3018
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
3119
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
3220

@@ -38,8 +26,7 @@ final class DocblockReturnArrayFromDirectArrayInstanceRector extends AbstractRec
3826
public function __construct(
3927
private readonly PhpDocInfoFactory $phpDocInfoFactory,
4028
private readonly DocBlockUpdater $docBlockUpdater,
41-
private readonly StaticTypeMapper $staticTypeMapper,
42-
private readonly TypeFactory $typeFactory
29+
private readonly ConstantArrayTypeGeneralizer $constantArrayTypeGeneralizer,
4330
) {
4431
}
4532

@@ -118,7 +105,7 @@ public function refactor(Node $node): ?Node
118105
return null;
119106
}
120107

121-
$genericTypeNode = $this->createGenericArrayTypeFromConstantArrayType($returnedType);
108+
$genericTypeNode = $this->constantArrayTypeGeneralizer->generalize($returnedType);
122109

123110
$returnTagValueNode = new ReturnTagValueNode($genericTypeNode, '');
124111
$phpDocInfo->addTagValueNode($returnTagValueNode);
@@ -127,74 +114,4 @@ public function refactor(Node $node): ?Node
127114

128115
return $node;
129116
}
130-
131-
/**
132-
* covers constant types too and makes them more generic
133-
*/
134-
private function constantToGenericType(Type $type): Type
135-
{
136-
if ($type->isString()->yes()) {
137-
return new StringType();
138-
}
139-
140-
if ($type->isInteger()->yes()) {
141-
return new IntegerType();
142-
}
143-
144-
if ($type->isBoolean()->yes()) {
145-
return new BooleanType();
146-
}
147-
148-
if ($type->isFloat()->yes()) {
149-
return new FloatType();
150-
}
151-
152-
if ($type instanceof UnionType || $type instanceof IntersectionType) {
153-
$genericComplexTypes = [];
154-
foreach ($type->getTypes() as $splitType) {
155-
$genericComplexTypes[] = $this->constantToGenericType($splitType);
156-
}
157-
158-
$genericComplexTypes = $this->typeFactory->uniquateTypes($genericComplexTypes);
159-
if (count($genericComplexTypes) > 1) {
160-
return new UnionType($genericComplexTypes);
161-
}
162-
163-
return $genericComplexTypes[0];
164-
}
165-
166-
// unclear
167-
return new MixedType();
168-
}
169-
170-
private function createGenericArrayTypeFromConstantArrayType(ConstantArrayType $constantArrayType): GenericTypeNode
171-
{
172-
$genericKeyType = $this->constantToGenericType($constantArrayType->getKeyType());
173-
174-
if ($constantArrayType->getItemType() instanceof NeverType) {
175-
$genericKeyType = new IntegerType();
176-
}
177-
178-
$itemType = $constantArrayType->getItemType();
179-
if ($itemType instanceof ConstantArrayType) {
180-
$genericItemType = $this->createGenericArrayTypeFromConstantArrayType($itemType);
181-
} else {
182-
$genericItemType = $this->constantToGenericType($itemType);
183-
}
184-
185-
return $this->createArrayGenericTypeNode($genericKeyType, $genericItemType);
186-
}
187-
188-
private function createArrayGenericTypeNode(Type $keyType, Type|GenericTypeNode $itemType): GenericTypeNode
189-
{
190-
$keyDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($keyType);
191-
192-
if ($itemType instanceof Type) {
193-
$itemDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($itemType);
194-
} else {
195-
$itemDocTypeNode = $itemType;
196-
}
197-
198-
return new GenericTypeNode(new IdentifierTypeNode('array'), [$keyDocTypeNode, $itemDocTypeNode]);
199-
}
200117
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclarationDocblocks\TypeResolver;
6+
7+
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
8+
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
9+
use PHPStan\Type\BooleanType;
10+
use PHPStan\Type\Constant\ConstantArrayType;
11+
use PHPStan\Type\FloatType;
12+
use PHPStan\Type\IntegerType;
13+
use PHPStan\Type\IntersectionType;
14+
use PHPStan\Type\MixedType;
15+
use PHPStan\Type\NeverType;
16+
use PHPStan\Type\StringType;
17+
use PHPStan\Type\Type;
18+
use PHPStan\Type\UnionType;
19+
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
20+
use Rector\StaticTypeMapper\StaticTypeMapper;
21+
22+
final class ConstantArrayTypeGeneralizer
23+
{
24+
/**
25+
* Using 10-level array @return docblocks makes code very hard to read,
26+
* lets limit it to reasonable level
27+
*/
28+
private const MAX_NESTING = 3;
29+
30+
private int $currentNesting = 0;
31+
32+
public function __construct(
33+
private readonly TypeFactory $typeFactory,
34+
private readonly StaticTypeMapper $staticTypeMapper,
35+
) {
36+
}
37+
38+
public function generalize(ConstantArrayType $constantArrayType, bool $isFresh = true): GenericTypeNode
39+
{
40+
if ($isFresh) {
41+
$this->currentNesting = 0;
42+
} else {
43+
++$this->currentNesting;
44+
}
45+
46+
$genericKeyType = $this->constantToGenericType($constantArrayType->getKeyType());
47+
48+
if ($constantArrayType->getItemType() instanceof NeverType) {
49+
$genericKeyType = new IntegerType();
50+
}
51+
52+
$itemType = $constantArrayType->getItemType();
53+
54+
if ($itemType instanceof ConstantArrayType) {
55+
if ($this->currentNesting >= self::MAX_NESTING) {
56+
$genericItemType = new MixedType();
57+
} else {
58+
$genericItemType = $this->generalize($itemType, false);
59+
}
60+
} else {
61+
$genericItemType = $this->constantToGenericType($itemType);
62+
}
63+
64+
return $this->createArrayGenericTypeNode($genericKeyType, $genericItemType);
65+
}
66+
67+
private function createArrayGenericTypeNode(Type $keyType, Type|GenericTypeNode $itemType): GenericTypeNode
68+
{
69+
$keyDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($keyType);
70+
71+
if ($itemType instanceof Type) {
72+
$itemDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($itemType);
73+
} else {
74+
$itemDocTypeNode = $itemType;
75+
}
76+
77+
return new GenericTypeNode(new IdentifierTypeNode('array'), [$keyDocTypeNode, $itemDocTypeNode]);
78+
}
79+
80+
/**
81+
* Covers constant types too and makes them more generic
82+
*/
83+
private function constantToGenericType(Type $type): Type
84+
{
85+
if ($type->isString()->yes()) {
86+
return new StringType();
87+
}
88+
89+
if ($type->isInteger()->yes()) {
90+
return new IntegerType();
91+
}
92+
93+
if ($type->isBoolean()->yes()) {
94+
return new BooleanType();
95+
}
96+
97+
if ($type->isFloat()->yes()) {
98+
return new FloatType();
99+
}
100+
101+
if ($type instanceof UnionType || $type instanceof IntersectionType) {
102+
$genericComplexTypes = [];
103+
foreach ($type->getTypes() as $splitType) {
104+
$genericComplexTypes[] = $this->constantToGenericType($splitType);
105+
}
106+
107+
$genericComplexTypes = $this->typeFactory->uniquateTypes($genericComplexTypes);
108+
if (count($genericComplexTypes) > 1) {
109+
return new UnionType($genericComplexTypes);
110+
}
111+
112+
return $genericComplexTypes[0];
113+
}
114+
115+
// unclear
116+
return new MixedType();
117+
}
118+
}

0 commit comments

Comments
 (0)