Skip to content

Commit 3e23c43

Browse files
Resolve Static throw points
1 parent 5878035 commit 3e23c43

File tree

4 files changed

+183
-0
lines changed

4 files changed

+183
-0
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4260,6 +4260,18 @@ private function getMethodThrowPoint(MethodReflection $methodReflection, Paramet
42604260

42614261
if ($throwType !== null) {
42624262
if (!$throwType->isVoid()->yes()) {
4263+
if ($throwType instanceof StaticType) {
4264+
$classReflections = $scope->getType($methodCall->var)->getObjectClassReflections();
4265+
if (count($classReflections) > 0) {
4266+
$types = [];
4267+
foreach ($classReflections as $classReflection) {
4268+
$types[] = $throwType->changeBaseClass($classReflection);
4269+
}
4270+
4271+
$throwType = TypeCombinator::union(...$types);
4272+
}
4273+
}
4274+
42634275
return ThrowPoint::createExplicit($scope, $throwType, $methodCall, true);
42644276
}
42654277
} elseif ($this->implicitThrows) {
@@ -4297,6 +4309,10 @@ private function getConstructorThrowPoint(MethodReflection $constructorReflectio
42974309
if ($constructorReflection->getThrowType() !== null) {
42984310
$throwType = $constructorReflection->getThrowType();
42994311
if (!$throwType->isVoid()->yes()) {
4312+
if ($throwType instanceof StaticType && $this->reflectionProvider->hasClass($className->name)) {
4313+
$throwType = $throwType->changeBaseClass($this->reflectionProvider->getClass($className->name));
4314+
}
4315+
43004316
return ThrowPoint::createExplicit($scope, $throwType, $new, true);
43014317
}
43024318
} elseif ($this->implicitThrows) {
@@ -4329,6 +4345,25 @@ private function getStaticMethodThrowPoint(MethodReflection $methodReflection, P
43294345
if ($methodReflection->getThrowType() !== null) {
43304346
$throwType = $methodReflection->getThrowType();
43314347
if (!$throwType->isVoid()->yes()) {
4348+
if ($throwType instanceof StaticType) {
4349+
if ($methodCall->class instanceof Name) {
4350+
if ($this->reflectionProvider->hasClass($methodCall->class->name)) {
4351+
$throwType = $throwType->changeBaseClass($this->reflectionProvider->getClass($methodCall->class->name));
4352+
}
4353+
} else {
4354+
$classReflections = $scope->getType($methodCall->class)->getObjectClassReflections();
4355+
4356+
if (count($classReflections) > 0) {
4357+
$types = [];
4358+
foreach ($classReflections as $classReflection) {
4359+
$types[] = $throwType->changeBaseClass($classReflection);
4360+
}
4361+
4362+
$throwType = TypeCombinator::union(...$types);
4363+
}
4364+
}
4365+
}
4366+
43324367
return ThrowPoint::createExplicit($scope, $throwType, $methodCall, true);
43334368
}
43344369
} elseif ($this->implicitThrows) {
@@ -4409,6 +4444,18 @@ private function getThrowPointsFromPropertyHook(
44094444

44104445
if ($throwType !== null) {
44114446
if (!$throwType->isVoid()->yes()) {
4447+
if ($throwType instanceof StaticType) {
4448+
$classReflections = $scope->getType($propertyFetch->var)->getObjectClassReflections();
4449+
if (count($classReflections) > 0) {
4450+
$types = [];
4451+
foreach ($classReflections as $classReflection) {
4452+
$types[] = $throwType->changeBaseClass($classReflection);
4453+
}
4454+
4455+
$throwType = TypeCombinator::union(...$types);
4456+
}
4457+
}
4458+
44124459
return [ThrowPoint::createExplicit($scope, $throwType, $propertyFetch, true)];
44134460
}
44144461
} elseif ($this->implicitThrows) {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Exceptions;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<MissingCheckedExceptionInMethodThrowsRule>
10+
*/
11+
class Bug11900Test extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new MissingCheckedExceptionInMethodThrowsRule(
17+
new MissingCheckedExceptionInThrowsCheck(new DefaultExceptionTypeResolver(
18+
self::createReflectionProvider(),
19+
[],
20+
[],
21+
[],
22+
[],
23+
)),
24+
);
25+
}
26+
27+
public function testRule(): void
28+
{
29+
if (PHP_VERSION_ID < 80400) {
30+
self::markTestSkipped('Test requires PHP 8.4.');
31+
}
32+
33+
$this->analyse([__DIR__ . '/data/bug-11900.php'], []);
34+
}
35+
36+
public static function getAdditionalConfigFiles(): array
37+
{
38+
return [
39+
__DIR__ . '/bug-11900.neon',
40+
];
41+
}
42+
43+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
parameters:
2+
exceptions:
3+
implicitThrows: false
4+
check:
5+
missingCheckedExceptionInThrows: true
6+
tooWideThrowType: true
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php // lint >= 8.4
2+
3+
namespace Bug11900;
4+
5+
use Exception;
6+
use Throwable;
7+
8+
abstract class ADataException extends Exception
9+
{
10+
public int $i {
11+
/** @throws static */
12+
get {
13+
if (rand(0, 1)) {
14+
throw new static();
15+
}
16+
17+
return 42;
18+
}
19+
}
20+
21+
/** @throws static */
22+
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
23+
{
24+
if (rand(0, 1)) {
25+
throw new static();
26+
}
27+
28+
parent::__construct($message, $code, $previous);
29+
}
30+
31+
/**
32+
* @return void
33+
* @throws static
34+
*/
35+
public function throw1(): void
36+
{
37+
throw $this;
38+
}
39+
40+
/**
41+
* @return void
42+
* @throws static
43+
*/
44+
public static function throw2(): void
45+
{
46+
throw new static();
47+
}
48+
}
49+
50+
final class TestDataException extends ADataException
51+
{
52+
}
53+
54+
class TestPhpStan
55+
{
56+
/**
57+
* @throws TestDataException
58+
*/
59+
public function validate(TestDataException $e): void
60+
{
61+
$e->throw1();
62+
}
63+
64+
/**
65+
* @throws TestDataException
66+
*/
67+
public function validate2(): void
68+
{
69+
TestDataException::throw2();
70+
}
71+
72+
/**
73+
* @throws TestDataException
74+
*/
75+
public function validate3(TestDataException $e): void
76+
{
77+
$e->i;
78+
}
79+
80+
/**
81+
* @throws TestDataException
82+
*/
83+
public function validate4(): void
84+
{
85+
new TestDataException();
86+
}
87+
}

0 commit comments

Comments
 (0)