Skip to content

Commit 758e5f1

Browse files
committed
It's okay to have always-throwing expression in arrow function
1 parent f449d98 commit 758e5f1

File tree

7 files changed

+72
-4
lines changed

7 files changed

+72
-4
lines changed

src/Php/PhpVersion.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,4 +272,9 @@ public function supportsReadOnlyAnonymousClasses(): bool
272272
return $this->versionId >= 80300;
273273
}
274274

275+
public function supportsNeverReturnTypeInArrowFunction(): bool
276+
{
277+
return $this->versionId >= 80200;
278+
}
279+
275280
}

src/Rules/Functions/ArrowFunctionReturnTypeRule.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHPStan\Rules\FunctionReturnTypeCheck;
1010
use PHPStan\Rules\Rule;
1111
use PHPStan\ShouldNotHappenException;
12+
use PHPStan\Type\NeverType;
1213
use PHPStan\Type\ObjectType;
1314
use PHPStan\Type\Type;
1415

@@ -43,6 +44,16 @@ public function processNode(Node $node, Scope $scope): array
4344
return [];
4445
}
4546

47+
$exprType = $scope->getType($originalNode->expr);
48+
if (
49+
$returnType instanceof NeverType
50+
&& $returnType->isExplicit()
51+
&& $exprType instanceof NeverType
52+
&& $exprType->isExplicit()
53+
) {
54+
return [];
55+
}
56+
4657
return $this->returnTypeCheck->checkReturnType(
4758
$scope,
4859
$returnType,

src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@
44

55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
7+
use PHPStan\Php\PhpVersion;
78
use PHPStan\Rules\FunctionDefinitionCheck;
89
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
use PHPStan\Type\NonAcceptingNeverType;
12+
use PHPStan\Type\ParserNodeTypeToPHPStanType;
13+
use function array_merge;
914

1015
/**
1116
* @implements Rule<Node\Expr\ArrowFunction>
1217
*/
1318
class ExistingClassesInArrowFunctionTypehintsRule implements Rule
1419
{
1520

16-
public function __construct(private FunctionDefinitionCheck $check)
21+
public function __construct(private FunctionDefinitionCheck $check, private PhpVersion $phpVersion)
1722
{
1823
}
1924

@@ -24,7 +29,17 @@ public function getNodeType(): string
2429

2530
public function processNode(Node $node, Scope $scope): array
2631
{
27-
return $this->check->checkAnonymousFunction(
32+
$messages = [];
33+
if ($node->returnType !== null && !$this->phpVersion->supportsNeverReturnTypeInArrowFunction()) {
34+
$returnType = ParserNodeTypeToPHPStanType::resolve($node->returnType, $scope->isInClass() ? $scope->getClassReflection() : null);
35+
if ($returnType instanceof NonAcceptingNeverType) {
36+
$messages[] = RuleErrorBuilder::message('Never return type in arrow function is supported only on PHP 8.2 and later.')
37+
->nonIgnorable()
38+
->build();
39+
}
40+
}
41+
42+
return array_merge($messages, $this->check->checkAnonymousFunction(
2843
$scope,
2944
$node->getParams(),
3045
$node->getReturnType(),
@@ -33,7 +48,7 @@ public function processNode(Node $node, Scope $scope): array
3348
'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.',
3449
'Parameter $%s of anonymous function has unresolvable native type.',
3550
'Anonymous function has unresolvable native return type.',
36-
);
51+
));
3752
}
3853

3954
}

tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ public function testRule(): void
3939
'Anonymous function should return int but returns string.',
4040
14,
4141
],
42+
[
43+
'Anonymous function should never return but return statement found.',
44+
44,
45+
],
4246
]);
4347
}
4448

tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class ExistingClassesInArrowFunctionTypehintsRuleTest extends RuleTestCase
2121
protected function getRule(): Rule
2222
{
2323
$broker = $this->createReflectionProvider();
24-
return new ExistingClassesInArrowFunctionTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new UnresolvableTypeHelper(), new PhpVersion($this->phpVersionId), true, false));
24+
return new ExistingClassesInArrowFunctionTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new UnresolvableTypeHelper(), new PhpVersion($this->phpVersionId), true, false), new PhpVersion(PHP_VERSION_ID));
2525
}
2626

2727
public function testRule(): void
@@ -147,4 +147,18 @@ public function testIntersectionTypes(int $phpVersion, array $errors): void
147147
$this->analyse([__DIR__ . '/data/arrow-function-intersection-types.php'], $errors);
148148
}
149149

150+
public function testNever(): void
151+
{
152+
$errors = [];
153+
if (PHP_VERSION_ID < 80200) {
154+
$errors = [
155+
[
156+
'Never return type in arrow function is supported only on PHP 8.2 and later.',
157+
6,
158+
],
159+
];
160+
}
161+
$this->analyse([__DIR__ . '/data/arrow-function-never.php'], $errors);
162+
}
163+
150164
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php // lint >= 7.4
2+
3+
namespace ArrowFunctionNever;
4+
5+
function (): void {
6+
$g = fn (): never => throw new \Exception();
7+
};

tests/PHPStan/Rules/Functions/data/arrow-functions-return-type.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,15 @@ public function doBar(): void
3333
}
3434

3535
static fn (int $value): iterable => yield $value;
36+
37+
class Baz
38+
{
39+
40+
public function doFoo(): void
41+
{
42+
$f = fn () => throw new \Exception();
43+
$g = fn (): never => throw new \Exception();
44+
$g = fn (): never => 1;
45+
}
46+
47+
}

0 commit comments

Comments
 (0)