From c2c6da79c14f8ddd45540176607ddc9792c189b6 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 14 Sep 2026 16:09:04 +0400 Subject: [PATCH] [DowngradePhp82] Add DowngradeArrowFunctionNeverReturnTypeRector 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 --- config/set/downgrade-php82.php | 2 + ...ArrowFunctionNeverReturnTypeRectorTest.php | 28 ++++++++ .../Fixture/arrow_function_never.php.inc | 15 ++++ .../Fixture/skip_closure_never.php.inc | 12 ++++ .../Fixture/skip_other_return_type.php.inc | 6 ++ .../static_arrow_function_as_argument.php.inc | 21 ++++++ .../config/configured_rule.php | 10 +++ ...radeArrowFunctionNeverReturnTypeRector.php | 69 +++++++++++++++++++ 8 files changed, 163 insertions(+) create mode 100644 rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/DowngradeArrowFunctionNeverReturnTypeRectorTest.php create mode 100644 rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/Fixture/arrow_function_never.php.inc create mode 100644 rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/Fixture/skip_closure_never.php.inc create mode 100644 rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/Fixture/skip_other_return_type.php.inc create mode 100644 rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/Fixture/static_arrow_function_as_argument.php.inc create mode 100644 rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/config/configured_rule.php create mode 100644 rules/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector.php diff --git a/config/set/downgrade-php82.php b/config/set/downgrade-php82.php index 18138f6e..3e388282 100644 --- a/config/set/downgrade-php82.php +++ b/config/set/downgrade-php82.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Rector\Config\RectorConfig; +use Rector\DowngradePhp82\Rector\ArrowFunction\DowngradeArrowFunctionNeverReturnTypeRector; use Rector\DowngradePhp82\Rector\Class_\DowngradeReadonlyClassRector; use Rector\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector; use Rector\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector; @@ -18,5 +19,6 @@ DowngradeIteratorCountToArrayRector::class, DowngradeUnionIntersectionRector::class, DowngradeReflectionMethodHasPrototypeRector::class, + DowngradeArrowFunctionNeverReturnTypeRector::class, ]); }; diff --git a/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/DowngradeArrowFunctionNeverReturnTypeRectorTest.php b/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/DowngradeArrowFunctionNeverReturnTypeRectorTest.php new file mode 100644 index 00000000..3848a224 --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/DowngradeArrowFunctionNeverReturnTypeRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/Fixture/arrow_function_never.php.inc b/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/Fixture/arrow_function_never.php.inc new file mode 100644 index 00000000..1e9ff70f --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/Fixture/arrow_function_never.php.inc @@ -0,0 +1,15 @@ + throw new \RuntimeException(); + +?> +----- + throw new \RuntimeException(); + +?> diff --git a/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/Fixture/skip_closure_never.php.inc b/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/Fixture/skip_closure_never.php.inc new file mode 100644 index 00000000..a16a4370 --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/Fixture/skip_closure_never.php.inc @@ -0,0 +1,12 @@ + 1; +$untyped = fn () => throw new \RuntimeException(); diff --git a/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/Fixture/static_arrow_function_as_argument.php.inc b/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/Fixture/static_arrow_function_as_argument.php.inc new file mode 100644 index 00000000..e4b8f782 --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/Fixture/static_arrow_function_as_argument.php.inc @@ -0,0 +1,21 @@ + throw new \LogicException('boom', $code), [1]); +} + +?> +----- + throw new \LogicException('boom', $code), [1]); +} + +?> diff --git a/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/config/configured_rule.php b/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/config/configured_rule.php new file mode 100644 index 00000000..354f06f3 --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(DowngradeArrowFunctionNeverReturnTypeRector::class); +}; diff --git a/rules/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector.php b/rules/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector.php new file mode 100644 index 00000000..83de98a8 --- /dev/null +++ b/rules/DowngradePhp82/Rector/ArrowFunction/DowngradeArrowFunctionNeverReturnTypeRector.php @@ -0,0 +1,69 @@ +> + */ + public function getNodeTypes(): array + { + return [ArrowFunction::class]; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Remove "never" return type from arrow functions, which PHP 8.1 rejects at compile time', + [ + new CodeSample( + <<<'CODE_SAMPLE' +$callable = fn (): never => throw new \RuntimeException(); +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +$callable = fn () => throw new \RuntimeException(); +CODE_SAMPLE + ), + ] + ); + } + + /** + * @param ArrowFunction $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->phpDocFromTypeDeclarationDecorator->decorateReturnWithSpecificType($node, new NeverType())) { + return null; + } + + return $node; + } +}