Skip to content

Commit a3111e2

Browse files
committed
feat: add support for phpdoc return types
1 parent 7558829 commit a3111e2

3 files changed

Lines changed: 117 additions & 25 deletions

File tree

rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/final_class.php.inc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Rector\Tests\DeadCode\Rector\FunctionLike\NarrowTooWideReturnTypeRector\Fixture;
44

5-
final class ClassMethodCase
5+
final class FinalClass
66
{
77
public function getData(): string|int|\DateTime
88
{
@@ -46,7 +46,7 @@ final class ClassMethodCase
4646

4747
namespace Rector\Tests\DeadCode\Rector\FunctionLike\NarrowTooWideReturnTypeRector\Fixture;
4848

49-
final class ClassMethodCase
49+
final class FinalClass
5050
{
5151
public function getData(): string|int
5252
{
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\FunctionLike\NarrowTooWideReturnTypeRector\Fixture;
4+
5+
use Rector\Tests\DeadCode\Rector\FunctionLike\NarrowTooWideReturnTypeRector\Source\SomeInterface;
6+
7+
final class PhpDocs
8+
{
9+
/** @return string|int|float */
10+
public function foo(): string|int|float
11+
{
12+
if (rand(0, 1)) {
13+
return 'text';
14+
}
15+
16+
return 1000;
17+
}
18+
19+
/**
20+
* @param class-string<SomeInterface> $class
21+
* @return class-string<SomeInterface>|int
22+
*/
23+
public function bar($class): string|int
24+
{
25+
return $class;
26+
}
27+
28+
/** @return class-string<SomeInterface>|int */
29+
public function baz($class): string|int
30+
{
31+
return SomeInterface::class;
32+
}
33+
34+
/** @return \Iterator<int, int>|string */
35+
function qux(): \Iterator|string
36+
{
37+
return new \ArrayIterator([1]);
38+
}
39+
}
40+
41+
?>
42+
-----
43+
<?php
44+
45+
namespace Rector\Tests\DeadCode\Rector\FunctionLike\NarrowTooWideReturnTypeRector\Fixture;
46+
47+
use Rector\Tests\DeadCode\Rector\FunctionLike\NarrowTooWideReturnTypeRector\Source\SomeInterface;
48+
49+
final class PhpDocs
50+
{
51+
/** @return int|string */
52+
public function foo(): string|int
53+
{
54+
if (rand(0, 1)) {
55+
return 'text';
56+
}
57+
58+
return 1000;
59+
}
60+
61+
/**
62+
* @param class-string<SomeInterface> $class
63+
* @return class-string<Rector\Tests\DeadCode\Rector\FunctionLike\NarrowTooWideReturnTypeRector\Source\SomeInterface>
64+
*/
65+
public function bar($class): string
66+
{
67+
return $class;
68+
}
69+
70+
/** @return class-string<Rector\Tests\DeadCode\Rector\FunctionLike\NarrowTooWideReturnTypeRector\Source\SomeInterface> */
71+
public function baz($class): string
72+
{
73+
return SomeInterface::class;
74+
}
75+
76+
/** @return \Iterator<int, int> */
77+
function qux(): \Iterator
78+
{
79+
return new \ArrayIterator([1]);
80+
}
81+
}
82+
83+
?>

rules/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector.php

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Rector\DeadCode\Rector\FunctionLike;
66

7+
use PHPStan\Type\UnionType as PHPStanUnionType;
78
use PhpParser\Node;
89
use PhpParser\Node\ComplexType;
910
use PhpParser\Node\Expr;
@@ -23,6 +24,9 @@
2324
use PHPStan\Type\NullType;
2425
use PHPStan\Type\Type;
2526
use PHPStan\Type\TypeCombinator;
27+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
28+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
29+
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
2630
use Rector\PhpParser\Node\BetterNodeFinder;
2731
use Rector\PHPStan\ScopeFetcher;
2832
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
@@ -46,6 +50,8 @@ public function __construct(
4650
private readonly StaticTypeMapper $staticTypeMapper,
4751
private readonly ReflectionResolver $reflectionResolver,
4852
private readonly SilentVoidResolver $silentVoidResolver,
53+
private readonly PhpDocTypeChanger $phpDocTypeChanger,
54+
private readonly PhpDocInfoFactory $phpDocInfoFactory,
4955
) {
5056
}
5157

@@ -120,17 +126,31 @@ public function refactor(Node $node): ?Node
120126
return null;
121127
}
122128

123-
$returnType = $node->returnType;
124-
Assert::isInstanceOfAny($returnType, [UnionType::class, NullableType::class]);
129+
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
130+
131+
if ($phpDocInfo?->hasByName('@return')) {
132+
$returnType = $phpDocInfo->getReturnType();
133+
} else {
134+
$returnType = $node->returnType;
135+
Assert::isInstanceOfAny($returnType, [UnionType::class, NullableType::class]);
136+
$returnType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($returnType);
137+
}
125138

126139
$actualReturnTypes = $this->collectActualReturnTypes($node, $returnStatements, $isAlwaysTerminating);
127-
$newReturnType = $this->narrowUnionReturnType($returnType, $actualReturnTypes);
140+
$newReturnType = $this->narrowReturnType($returnType, $actualReturnTypes);
128141

129142
if ($newReturnType === null) {
130143
return null;
131144
}
132145

133-
$node->returnType = $newReturnType;
146+
$node->returnType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
147+
$newReturnType,
148+
TypeKind::RETURN
149+
);
150+
151+
if ($phpDocInfo instanceof PhpDocInfo) {
152+
$this->phpDocTypeChanger->changeReturnType($node, $phpDocInfo, $newReturnType);
153+
}
134154

135155
return $node;
136156
}
@@ -182,7 +202,7 @@ private function collectActualReturnTypes(
182202
bool $isAlwaysTerminating,
183203
): array {
184204
if ($node instanceof ArrowFunction) {
185-
return [$this->nodeTypeResolver->getNativeType($node->expr)];
205+
return [$this->getType($node->expr)];
186206
}
187207

188208
$returnTypes = [];
@@ -192,7 +212,7 @@ private function collectActualReturnTypes(
192212
continue;
193213
}
194214

195-
$returnTypes[] = $this->nodeTypeResolver->getNativeType($returnStatement->expr);
215+
$returnTypes[] = $this->getType($returnStatement->expr);
196216
}
197217

198218
if (! $isAlwaysTerminating) {
@@ -205,23 +225,15 @@ private function collectActualReturnTypes(
205225
/**
206226
* @param Type[] $actualReturnTypes
207227
*/
208-
private function narrowUnionReturnType(
209-
UnionType|NullableType $returnType,
210-
array $actualReturnTypes
211-
): ComplexType|Identifier|Name|null {
212-
$types = $returnType instanceof UnionType
213-
? $returnType->types
214-
: [$returnType->type, new Identifier('null')];
228+
private function narrowReturnType(Type $returnType, array $actualReturnTypes): Type|null
229+
{
230+
$types = $returnType instanceof PHPStanUnionType ? $returnType->getTypes() : [$returnType];
215231
$usedTypes = [];
216232

217233
foreach ($types as $type) {
218-
$declaredType = $type instanceof Expr
219-
? $this->nodeTypeResolver->getNativeType($type)
220-
: $this->getType($type);
221-
222234
foreach ($actualReturnTypes as $actualType) {
223-
if (! $declaredType->isSuperTypeOf($actualType)->no()) {
224-
$usedTypes[] = $declaredType;
235+
if (! $type->isSuperTypeOf($actualType)->no()) {
236+
$usedTypes[] = $type;
225237
break;
226238
}
227239
}
@@ -233,10 +245,7 @@ private function narrowUnionReturnType(
233245
return null;
234246
}
235247

236-
return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
237-
TypeCombinator::union(...$usedTypes),
238-
TypeKind::RETURN
239-
);
248+
return TypeCombinator::union(...$usedTypes);
240249
}
241250

242251
private function hasYield(ClassMethod|Function_|Closure|ArrowFunction $node): bool

0 commit comments

Comments
 (0)