Skip to content

Commit 5f2d42c

Browse files
committed
Support for throw expression (PHP 8.0)
https://wiki.php.net/rfc/throw_expression
1 parent 7a38424 commit 5f2d42c

File tree

8 files changed

+146
-1
lines changed

8 files changed

+146
-1
lines changed

conf/config.level0.neon

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ rules:
2626
- PHPStan\Rules\Classes\ExistingClassInTraitUseRule
2727
- PHPStan\Rules\Classes\InstantiationRule
2828
- PHPStan\Rules\Classes\NewStaticRule
29+
- PHPStan\Rules\Exceptions\ThrowExpressionRule
2930
- PHPStan\Rules\Functions\CallToFunctionParametersRule
3031
- PHPStan\Rules\Functions\ExistingClassesInArrowFunctionTypehintsRule
3132
- PHPStan\Rules\Functions\ExistingClassesInClosureTypehintsRule

src/Analyser/MutatingScope.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ private function getNodeKey(Expr $node): string
494494

495495
private function resolveType(Expr $node): Type
496496
{
497-
if ($node instanceof Expr\Exit_) {
497+
if ($node instanceof Expr\Exit_ || $node instanceof Expr\Throw_) {
498498
return new NeverType();
499499
}
500500

src/Php/PhpVersion.php

+5
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,9 @@ public function requiresParenthesesForNestedTernaries(): bool
6161
return $this->versionId >= 80000;
6262
}
6363

64+
public function supportsThrowExpression(): bool
65+
{
66+
return $this->versionId >= 80000;
67+
}
68+
6469
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Exceptions;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Php\PhpVersion;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Rules\RuleErrorBuilder;
10+
11+
/**
12+
* @implements Rule<Node\Expr\Throw_>
13+
*/
14+
class ThrowExpressionRule implements Rule
15+
{
16+
17+
private PhpVersion $phpVersion;
18+
19+
public function __construct(PhpVersion $phpVersion)
20+
{
21+
$this->phpVersion = $phpVersion;
22+
}
23+
24+
public function getNodeType(): string
25+
{
26+
return Node\Expr\Throw_::class;
27+
}
28+
29+
public function processNode(Node $node, Scope $scope): array
30+
{
31+
if ($this->phpVersion->supportsThrowExpression()) {
32+
return [];
33+
}
34+
35+
return [
36+
RuleErrorBuilder::message('Throw expression is supported only on PHP 8.0 and later.')->nonIgnorable()->build(),
37+
];
38+
}
39+
40+
}

tests/PHPStan/Analyser/NodeScopeResolverTest.php

+10
Original file line numberDiff line numberDiff line change
@@ -10146,6 +10146,15 @@ public function dataPow(): array
1014610146
return $this->gatherAssertTypes(__DIR__ . '/data/pow.php');
1014710147
}
1014810148

10149+
public function dataThrowExpression(): array
10150+
{
10151+
if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) {
10152+
return [];
10153+
}
10154+
10155+
return $this->gatherAssertTypes(__DIR__ . '/data/throw-expr.php');
10156+
}
10157+
1014910158
/**
1015010159
* @dataProvider dataBug2574
1015110160
* @dataProvider dataBug2577
@@ -10223,6 +10232,7 @@ public function dataPow(): array
1022310232
* @dataProvider dataBug1014
1022410233
* @dataProvider dataBugFromPr339
1022510234
* @dataProvider dataPow
10235+
* @dataProvider dataThrowExpression
1022610236
* @param string $assertType
1022710237
* @param string $file
1022810238
* @param mixed ...$args
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php // lint >= 8.0
2+
3+
namespace ThrowExpr;
4+
5+
use function PHPStan\Analyser\assertType;
6+
7+
class Foo
8+
{
9+
10+
public function doFoo(bool $b): void
11+
{
12+
$result = $b ? true : throw new \Exception();
13+
assertType('true', $result);
14+
}
15+
16+
public function doBar(): void
17+
{
18+
assertType('*NEVER*', throw new \Exception());
19+
}
20+
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Exceptions;
4+
5+
use PHPStan\Php\PhpVersion;
6+
use PHPStan\Rules\Rule;
7+
use PHPStan\Testing\RuleTestCase;
8+
9+
/**
10+
* @extends RuleTestCase<ThrowExpressionRule>
11+
*/
12+
class ThrowExpressionRuleTest extends RuleTestCase
13+
{
14+
15+
/** @var PhpVersion */
16+
private $phpVersion;
17+
18+
protected function getRule(): Rule
19+
{
20+
return new ThrowExpressionRule($this->phpVersion);
21+
}
22+
23+
public function dataRule(): array
24+
{
25+
return [
26+
[
27+
70400,
28+
[
29+
[
30+
'Throw expression is supported only on PHP 8.0 and later.',
31+
10,
32+
],
33+
],
34+
],
35+
[
36+
80000,
37+
[],
38+
],
39+
];
40+
}
41+
42+
/**
43+
* @dataProvider dataRule
44+
* @param int $phpVersion
45+
* @param mixed[] $expectedErrors
46+
*/
47+
public function testRule(int $phpVersion, array $expectedErrors): void
48+
{
49+
$this->phpVersion = new PhpVersion($phpVersion);
50+
$this->analyse([__DIR__ . '/data/throw-expr.php'], $expectedErrors);
51+
}
52+
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace ThrowExpr;
4+
5+
class Bar
6+
{
7+
8+
public function doFoo(bool $b): void
9+
{
10+
$b ? true : throw new \Exception();
11+
12+
throw new \Exception();
13+
}
14+
15+
}

0 commit comments

Comments
 (0)