Skip to content

Commit ff296fa

Browse files
authored
Override existing dummy @return array with more precise one (#7362)
* add fixture * change existing dummy array * update DocblockGetterReturnArrayFromPropertyDocblockVarRector too * add same support to AddReturnDocblockForCommonObjectDenominatorRector
1 parent 43d1eae commit ff296fa

File tree

12 files changed

+333
-76
lines changed

12 files changed

+333
-76
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForArrayDimAssignedObjectRector\Fixture;
4+
5+
use Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForArrayDimAssignedObjectRector\Source\SomeItem;
6+
7+
final class OverrideDummyArray
8+
{
9+
/**
10+
* @return array
11+
*/
12+
public function provide(array $keys): array
13+
{
14+
$items = [];
15+
foreach ($keys as $key) {
16+
$items[] = new SomeItem($key);
17+
}
18+
19+
return $items;
20+
}
21+
}
22+
23+
?>
24+
-----
25+
<?php
26+
27+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForArrayDimAssignedObjectRector\Fixture;
28+
29+
use Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForArrayDimAssignedObjectRector\Source\SomeItem;
30+
31+
final class OverrideDummyArray
32+
{
33+
/**
34+
* @return \Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForArrayDimAssignedObjectRector\Source\SomeItem[]
35+
*/
36+
public function provide(array $keys): array
37+
{
38+
$items = [];
39+
foreach ($keys as $key) {
40+
$items[] = new SomeItem($key);
41+
}
42+
43+
return $items;
44+
}
45+
}
46+
47+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector\Fixture;
4+
5+
use Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector\Source\FirstExtension;
6+
use Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector\Source\SecondExtension;
7+
8+
final class OverrideDummyArray
9+
{
10+
/**
11+
* @return array
12+
*/
13+
public function getExtensions(): array
14+
{
15+
return [
16+
new FirstExtension(),
17+
new SecondExtension()
18+
];
19+
}
20+
}
21+
22+
?>
23+
-----
24+
<?php
25+
26+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector\Fixture;
27+
28+
use Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector\Source\FirstExtension;
29+
use Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector\Source\SecondExtension;
30+
31+
final class OverrideDummyArray
32+
{
33+
/**
34+
* @return \Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector\Source\Contract\ExtensionInterface[]
35+
*/
36+
public function getExtensions(): array
37+
{
38+
return [
39+
new FirstExtension(),
40+
new SecondExtension()
41+
];
42+
}
43+
}
44+
45+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector\Fixture;
6+
7+
final class OverrideDummyArray
8+
{
9+
/**
10+
* @return array
11+
*/
12+
public function provide(string $contents): array
13+
{
14+
return json_decode($contents, true);
15+
}
16+
}
17+
18+
?>
19+
-----
20+
<?php
21+
22+
declare(strict_types=1);
23+
24+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector\Fixture;
25+
26+
final class OverrideDummyArray
27+
{
28+
/**
29+
* @return array<string, mixed>
30+
*/
31+
public function provide(string $contents): array
32+
{
33+
return json_decode($contents, true);
34+
}
35+
}
36+
37+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector\Fixture;
4+
5+
final class OverrideDummyArray
6+
{
7+
/**
8+
* @var string[]
9+
*/
10+
private array $names = [];
11+
12+
/**
13+
* @return array
14+
*/
15+
public function getNames(): array
16+
{
17+
return $this->names;
18+
}
19+
}
20+
21+
?>
22+
-----
23+
<?php
24+
25+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector\Fixture;
26+
27+
final class OverrideDummyArray
28+
{
29+
/**
30+
* @var string[]
31+
*/
32+
private array $names = [];
33+
34+
/**
35+
* @return string[]
36+
*/
37+
public function getNames(): array
38+
{
39+
return $this->names;
40+
}
41+
}
42+
43+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector\Fixture;
4+
5+
/**
6+
* @return array
7+
*/
8+
function overrideBareArray()
9+
{
10+
return [
11+
'first' => [true],
12+
'second' => [false],
13+
];
14+
}
15+
16+
?>
17+
-----
18+
<?php
19+
20+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector\Fixture;
21+
22+
/**
23+
* @return array<string, bool[]>
24+
*/
25+
function overrideBareArray()
26+
{
27+
return [
28+
'first' => [true],
29+
'second' => [false],
30+
];
31+
}
32+
33+
?>

rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector.php

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
2424
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
2525
use Rector\Rector\AbstractRector;
26+
use Rector\StaticTypeMapper\StaticTypeMapper;
2627
use Rector\StaticTypeMapper\ValueObject\Type\NonExistingObjectType;
2728
use Rector\TypeDeclarationDocblocks\NodeFinder\ReturnNodeFinder;
29+
use Rector\TypeDeclarationDocblocks\TagNodeAnalyzer\UsefulArrayTagNodeAnalyzer;
2830
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
2931
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
3032

@@ -37,6 +39,8 @@ public function __construct(
3739
private readonly PhpDocInfoFactory $phpDocInfoFactory,
3840
private readonly ReturnNodeFinder $returnNodeFinder,
3941
private readonly PhpDocTypeChanger $phpDocTypeChanger,
42+
private readonly StaticTypeMapper $staticTypeMapper,
43+
private readonly UsefulArrayTagNodeAnalyzer $usefulArrayTagNodeAnalyzer
4044
) {
4145
}
4246

@@ -101,7 +105,11 @@ public function refactor(Node $node): ?Node
101105
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
102106
$returnType = $phpDocInfo->getReturnType();
103107

104-
if (! $returnType instanceof MixedType || $returnType->isExplicitMixed()) {
108+
if ($returnType instanceof ArrayType && ! $returnType->getItemType() instanceof MixedType) {
109+
return null;
110+
}
111+
112+
if ($this->usefulArrayTagNodeAnalyzer->isUsefulArrayTag($phpDocInfo->getReturnTagValue())) {
105113
return null;
106114
}
107115

@@ -124,10 +132,49 @@ public function refactor(Node $node): ?Node
124132
return null;
125133
}
126134

135+
if ($this->isVariableExclusivelyArrayDimAssigned($node, $returnedVariableName) === false) {
136+
return null;
137+
}
138+
139+
$arrayObjectType = $this->matchArrayObjectType($returnedType);
140+
if (! $arrayObjectType instanceof ObjectType) {
141+
return null;
142+
}
143+
144+
$objectTypeArrayType = new ArrayType(new MixedType(), $arrayObjectType);
145+
$returnTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($objectTypeArrayType);
146+
$this->phpDocTypeChanger->changeReturnTypeNode($node, $phpDocInfo, $returnTypeNode);
147+
148+
return $node;
149+
}
150+
151+
private function matchArrayObjectType(Type $returnedType): ?Type
152+
{
153+
if ($returnedType instanceof IntersectionType) {
154+
foreach ($returnedType->getTypes() as $intersectionedType) {
155+
if ($intersectionedType instanceof AccessoryArrayListType) {
156+
continue;
157+
}
158+
159+
if ($intersectionedType instanceof ArrayType && $intersectionedType->getItemType() instanceof ObjectType) {
160+
return $intersectionedType->getItemType();
161+
}
162+
163+
return null;
164+
}
165+
}
166+
167+
return null;
168+
}
169+
170+
private function isVariableExclusivelyArrayDimAssigned(
171+
ClassMethod|Function_ $functionLike,
172+
string $variableName
173+
): bool {
127174
$isVariableExclusivelyArrayDimAssigned = true;
128175

129-
$this->traverseNodesWithCallable((array) $node->stmts, function ($node) use (
130-
$returnedVariableName,
176+
$this->traverseNodesWithCallable((array) $functionLike->stmts, function ($node) use (
177+
$variableName,
131178
&$isVariableExclusivelyArrayDimAssigned
132179
): ?int {
133180
if ($node instanceof Assign) {
@@ -139,7 +186,7 @@ public function refactor(Node $node): ?Node
139186
return null;
140187
}
141188

142-
if ($this->isName($arrayDimFetch->var, $returnedVariableName)) {
189+
if ($this->isName($arrayDimFetch->var, $variableName)) {
143190
if ($arrayDimFetch->dim instanceof Expr) {
144191
$isVariableExclusivelyArrayDimAssigned = false;
145192
}
@@ -160,7 +207,7 @@ public function refactor(Node $node): ?Node
160207

161208
if ($node->var instanceof Variable && $this->isName(
162209
$node->var,
163-
$returnedVariableName
210+
$variableName
164211
) && $node->expr instanceof Array_) {
165212
if ($node->expr->items === []) {
166213
// ignore empty array assignment
@@ -172,57 +219,21 @@ public function refactor(Node $node): ?Node
172219
}
173220

174221
if ($node instanceof Return_ && $node->expr instanceof Variable) {
175-
if ($this->isName($node->expr, $returnedVariableName)) {
222+
if ($this->isName($node->expr, $variableName)) {
176223
// ignore lower value
177224
return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
178225
}
179226

180227
$isVariableExclusivelyArrayDimAssigned = false;
181228
}
182229

183-
if ($node instanceof Variable && $this->isName($node, $returnedVariableName)) {
230+
if ($node instanceof Variable && $this->isName($node, $variableName)) {
184231
$isVariableExclusivelyArrayDimAssigned = false;
185232
}
186233

187234
return null;
188235
});
189236

190-
if ($isVariableExclusivelyArrayDimAssigned === false) {
191-
return null;
192-
}
193-
194-
$arrayObjectType = $this->matchArrayObjectType($returnedType);
195-
if (! $arrayObjectType instanceof ObjectType) {
196-
return null;
197-
}
198-
199-
//$arrayReturnDocType = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($arrayObjectType);
200-
201-
$objectTypeArrayType = new ArrayType(new MixedType(), $arrayObjectType);
202-
$hasChanged = $this->phpDocTypeChanger->changeReturnType($node, $phpDocInfo, $objectTypeArrayType);
203-
if (! $hasChanged) {
204-
return null;
205-
}
206-
207-
return $node;
208-
}
209-
210-
private function matchArrayObjectType(Type $returnedType): ?Type
211-
{
212-
if ($returnedType instanceof IntersectionType) {
213-
foreach ($returnedType->getTypes() as $intersectionedType) {
214-
if ($intersectionedType instanceof AccessoryArrayListType) {
215-
continue;
216-
}
217-
218-
if ($intersectionedType instanceof ArrayType && $intersectionedType->getItemType() instanceof ObjectType) {
219-
return $intersectionedType->getItemType();
220-
}
221-
222-
return null;
223-
}
224-
}
225-
226-
return null;
237+
return $isVariableExclusivelyArrayDimAssigned;
227238
}
228239
}

rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public function refactor(Node $node): ?Node
110110
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
111111
$returnType = $phpDocInfo->getReturnType();
112112

113-
if (! $returnType instanceof MixedType || $returnType->isExplicitMixed()) {
113+
if ($returnType instanceof ArrayType && ! $returnType->getItemType() instanceof MixedType) {
114114
return null;
115115
}
116116

@@ -175,6 +175,7 @@ public function refactor(Node $node): ?Node
175175
$this->resolveKeyType($returnedType),
176176
new FullyQualifiedObjectType($firstSharedType)
177177
);
178+
178179
$hasChanged = $this->phpDocTypeChanger->changeReturnType($node, $phpDocInfo, $objectTypeArrayType);
179180
if (! $hasChanged) {
180181
return null;

0 commit comments

Comments
 (0)