Skip to content

Commit 9a2ddc6

Browse files
authored
[DowngradePhp82] Add DowngradeArrowFunctionNeverReturnTypeRector (#399)
PHP 8.1 accepts the "never" return type on functions, methods and closures, but rejects it on arrow functions: the implicit return of the body expression trips "A never-returning function must not return" at compile time (php/php-src#7900, fixed in 8.2). #397 taught DowngradeNeverTypeDeclarationRector to strip it from arrow functions, but that rule sits in the 8.1 set, which removes 8.1 features when downgrading *from* 8.1 to 8.0. Code downgraded *to* 8.1 never reaches it, so `fn (): never => throw ...` survived and still failed to compile on 8.1. The arrow-function case is an 8.2 feature and belongs in the 8.2 set; this rule puts it there and leaves every other function-like to the 8.1 set. Assisted-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 8186856 commit 9a2ddc6

8 files changed

Lines changed: 163 additions & 0 deletions

File tree

‎config/set/downgrade-php82.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
declare(strict_types=1);
44

55
use Rector\Config\RectorConfig;
6+
use Rector\DowngradePhp82\Rector\ArrowFunction\DowngradeArrowFunctionNeverReturnTypeRector;
67
use Rector\DowngradePhp82\Rector\Class_\DowngradeReadonlyClassRector;
78
use Rector\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector;
89
use Rector\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector;
@@ -18,5 +19,6 @@
1819
DowngradeIteratorCountToArrayRector::class,
1920
DowngradeUnionIntersectionRector::class,
2021
DowngradeReflectionMethodHasPrototypeRector::class,
22+
DowngradeArrowFunctionNeverReturnTypeRector::class,
2123
]);
2224
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\DowngradePhp82\Rector\ArrowFunction\DowngradeArrowFunctionNeverReturnTypeRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class DowngradeArrowFunctionNeverReturnTypeRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Rector\Tests\DowngradePhp82\Rector\ArrowFunction\DowngradeArrowFunctionNeverReturnTypeRector\Fixture;
4+
5+
$callable = fn (): never => throw new \RuntimeException();
6+
7+
?>
8+
-----
9+
<?php
10+
11+
namespace Rector\Tests\DowngradePhp82\Rector\ArrowFunction\DowngradeArrowFunctionNeverReturnTypeRector\Fixture;
12+
13+
$callable = fn () => throw new \RuntimeException();
14+
15+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Rector\Tests\DowngradePhp82\Rector\ArrowFunction\DowngradeArrowFunctionNeverReturnTypeRector\Fixture;
4+
5+
$callable = function (): never {
6+
throw new \RuntimeException();
7+
};
8+
9+
function fail(): never
10+
{
11+
throw new \RuntimeException();
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
namespace Rector\Tests\DowngradePhp82\Rector\ArrowFunction\DowngradeArrowFunctionNeverReturnTypeRector\Fixture;
4+
5+
$callable = fn (): int => 1;
6+
$untyped = fn () => throw new \RuntimeException();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Rector\Tests\DowngradePhp82\Rector\ArrowFunction\DowngradeArrowFunctionNeverReturnTypeRector\Fixture;
4+
5+
function run(int $code)
6+
{
7+
return array_map(static fn (int $item): never => throw new \LogicException('boom', $code), [1]);
8+
}
9+
10+
?>
11+
-----
12+
<?php
13+
14+
namespace Rector\Tests\DowngradePhp82\Rector\ArrowFunction\DowngradeArrowFunctionNeverReturnTypeRector\Fixture;
15+
16+
function run(int $code)
17+
{
18+
return array_map(static fn (int $item) => throw new \LogicException('boom', $code), [1]);
19+
}
20+
21+
?>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\DowngradePhp82\Rector\ArrowFunction\DowngradeArrowFunctionNeverReturnTypeRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->rule(DowngradeArrowFunctionNeverReturnTypeRector::class);
10+
};
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\DowngradePhp82\Rector\ArrowFunction;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\ArrowFunction;
9+
use PHPStan\Type\NeverType;
10+
use Rector\PhpDocDecorator\PhpDocFromTypeDeclarationDecorator;
11+
use Rector\Rector\AbstractRector;
12+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
13+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
14+
15+
/**
16+
* PHP 8.1 accepts the "never" return type everywhere except on arrow functions, where the implicit
17+
* return of the body expression trips "A never-returning function must not return" at compile time.
18+
* PHP 8.2 fixed that, so only the arrow-function case has to go when targeting 8.1; other
19+
* function-likes keep "never" until the 8.1 set removes it.
20+
*
21+
* @changelog https://github.com/php/php-src/issues/7900
22+
*
23+
* @see \Rector\Tests\DowngradePhp82\Rector\ArrowFunction\DowngradeArrowFunctionNeverReturnTypeRector\DowngradeArrowFunctionNeverReturnTypeRectorTest
24+
*/
25+
final class DowngradeArrowFunctionNeverReturnTypeRector extends AbstractRector
26+
{
27+
public function __construct(
28+
private readonly PhpDocFromTypeDeclarationDecorator $phpDocFromTypeDeclarationDecorator
29+
) {
30+
}
31+
32+
/**
33+
* @return array<class-string<Node>>
34+
*/
35+
public function getNodeTypes(): array
36+
{
37+
return [ArrowFunction::class];
38+
}
39+
40+
public function getRuleDefinition(): RuleDefinition
41+
{
42+
return new RuleDefinition(
43+
'Remove "never" return type from arrow functions, which PHP 8.1 rejects at compile time',
44+
[
45+
new CodeSample(
46+
<<<'CODE_SAMPLE'
47+
$callable = fn (): never => throw new \RuntimeException();
48+
CODE_SAMPLE
49+
,
50+
<<<'CODE_SAMPLE'
51+
$callable = fn () => throw new \RuntimeException();
52+
CODE_SAMPLE
53+
),
54+
]
55+
);
56+
}
57+
58+
/**
59+
* @param ArrowFunction $node
60+
*/
61+
public function refactor(Node $node): ?Node
62+
{
63+
if (! $this->phpDocFromTypeDeclarationDecorator->decorateReturnWithSpecificType($node, new NeverType())) {
64+
return null;
65+
}
66+
67+
return $node;
68+
}
69+
}

0 commit comments

Comments
 (0)