Skip to content

Commit c362fc5

Browse files
committed
Function that returns never is always an explicit throw point
1 parent 24c1eb4 commit c362fc5

File tree

3 files changed

+220
-6
lines changed

3 files changed

+220
-6
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1975,7 +1975,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
19751975
$expr->args,
19761976
$methodReflection->getVariants()
19771977
);
1978-
$methodThrowPoint = $this->getMethodThrowPoint($methodReflection, $expr, $scope);
1978+
$methodThrowPoint = $this->getMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope);
19791979
if ($methodThrowPoint !== null) {
19801980
$throwPoints[] = $methodThrowPoint;
19811981
}
@@ -2642,8 +2642,15 @@ private function getFunctionThrowPoint(
26422642
return ThrowPoint::createExplicit($scope, $throwType, $funcCall, false);
26432643
}
26442644

2645-
if ($functionReflection->getThrowType() !== null) {
2646-
$throwType = $functionReflection->getThrowType();
2645+
$throwType = $functionReflection->getThrowType();
2646+
if ($throwType === null && $parametersAcceptor !== null) {
2647+
$returnType = $parametersAcceptor->getReturnType();
2648+
if ($returnType instanceof NeverType && $returnType->isExplicit()) {
2649+
$throwType = new ObjectType(\Throwable::class);
2650+
}
2651+
}
2652+
2653+
if ($throwType !== null) {
26472654
if (!$throwType instanceof VoidType) {
26482655
return ThrowPoint::createExplicit($scope, $throwType, $funcCall, true);
26492656
}
@@ -2675,7 +2682,7 @@ private function getFunctionThrowPoint(
26752682
return null;
26762683
}
26772684

2678-
private function getMethodThrowPoint(MethodReflection $methodReflection, MethodCall $methodCall, MutatingScope $scope): ?ThrowPoint
2685+
private function getMethodThrowPoint(MethodReflection $methodReflection, ParametersAcceptor $parametersAcceptor, MethodCall $methodCall, MutatingScope $scope): ?ThrowPoint
26792686
{
26802687
foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicMethodThrowTypeExtensions() as $extension) {
26812688
if (!$extension->isMethodSupported($methodReflection)) {
@@ -2690,8 +2697,15 @@ private function getMethodThrowPoint(MethodReflection $methodReflection, MethodC
26902697
return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false);
26912698
}
26922699

2693-
if ($methodReflection->getThrowType() !== null) {
2694-
$throwType = $methodReflection->getThrowType();
2700+
$throwType = $methodReflection->getThrowType();
2701+
if ($throwType === null) {
2702+
$returnType = $parametersAcceptor->getReturnType();
2703+
if ($returnType instanceof NeverType && $returnType->isExplicit()) {
2704+
$throwType = new ObjectType(\Throwable::class);
2705+
}
2706+
}
2707+
2708+
if ($throwType !== null) {
26952709
if (!$throwType instanceof VoidType) {
26962710
return ThrowPoint::createExplicit($scope, $throwType, $methodCall, true);
26972711
}

tests/PHPStan/Rules/Exceptions/OverwrittenExitPointByFinallyRuleTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,94 @@ public function testBug5627(): void
8585
'The overwriting return is on this line.',
8686
51,
8787
],
88+
[
89+
'This throw is overwritten by a different one in the finally block below.',
90+
62,
91+
],
92+
[
93+
'This throw is overwritten by a different one in the finally block below.',
94+
64,
95+
],
96+
[
97+
'The overwriting return is on this line.',
98+
66,
99+
],
100+
[
101+
'This exit point is overwritten by a different one in the finally block below.',
102+
81,
103+
],
104+
[
105+
'This exit point is overwritten by a different one in the finally block below.',
106+
83,
107+
],
108+
[
109+
'The overwriting return is on this line.',
110+
85,
111+
],
112+
[
113+
'This exit point is overwritten by a different one in the finally block below.',
114+
91,
115+
],
116+
[
117+
'This exit point is overwritten by a different one in the finally block below.',
118+
93,
119+
],
120+
[
121+
'The overwriting return is on this line.',
122+
95,
123+
],
124+
[
125+
'This exit point is overwritten by a different one in the finally block below.',
126+
101,
127+
],
128+
[
129+
'The overwriting return is on this line.',
130+
103,
131+
],
132+
[
133+
'This throw is overwritten by a different one in the finally block below.',
134+
122,
135+
],
136+
[
137+
'This throw is overwritten by a different one in the finally block below.',
138+
124,
139+
],
140+
[
141+
'The overwriting return is on this line.',
142+
126,
143+
],
144+
[
145+
'This exit point is overwritten by a different one in the finally block below.',
146+
141,
147+
],
148+
[
149+
'This exit point is overwritten by a different one in the finally block below.',
150+
143,
151+
],
152+
[
153+
'The overwriting return is on this line.',
154+
145,
155+
],
156+
[
157+
'This exit point is overwritten by a different one in the finally block below.',
158+
151,
159+
],
160+
[
161+
'This exit point is overwritten by a different one in the finally block below.',
162+
153,
163+
],
164+
[
165+
'The overwriting return is on this line.',
166+
155,
167+
],
168+
[
169+
'This exit point is overwritten by a different one in the finally block below.',
170+
161,
171+
],
172+
[
173+
'The overwriting return is on this line.',
174+
163,
175+
],
88176
]);
89177
}
90178

tests/PHPStan/Rules/Exceptions/data/bug-5627.php

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,115 @@ public function d(): string {
5353
}
5454

5555
}
56+
57+
class Bar
58+
{
59+
60+
public function a(): string {
61+
try {
62+
throw new \Exception('try');
63+
} catch (\Exception $e) {
64+
throw new \Exception('catch');
65+
} finally {
66+
return 'finally';
67+
}
68+
}
69+
70+
/**
71+
*
72+
* @return never
73+
*/
74+
public function abort()
75+
{
76+
throw new \Exception();
77+
}
78+
79+
public function b(): string {
80+
try {
81+
$this->abort();
82+
} catch (\Exception $e) {
83+
$this->abort();
84+
} finally {
85+
return 'finally';
86+
}
87+
}
88+
89+
public function c(): string {
90+
try {
91+
$this->abort();
92+
} catch (\Throwable $e) {
93+
$this->abort();
94+
} finally {
95+
return 'finally';
96+
}
97+
}
98+
99+
public function d(): string {
100+
try {
101+
$this->abort();
102+
} finally {
103+
return 'finally';
104+
}
105+
}
106+
107+
}
108+
109+
/**
110+
* @return never
111+
*/
112+
function abort()
113+
{
114+
115+
}
116+
117+
class Baz
118+
{
119+
120+
public function a(): string {
121+
try {
122+
throw new \Exception('try');
123+
} catch (\Exception $e) {
124+
throw new \Exception('catch');
125+
} finally {
126+
return 'finally';
127+
}
128+
}
129+
130+
131+
132+
133+
134+
135+
136+
137+
138+
139+
public function b(): string {
140+
try {
141+
abort();
142+
} catch (\Exception $e) {
143+
abort();
144+
} finally {
145+
return 'finally';
146+
}
147+
}
148+
149+
public function c(): string {
150+
try {
151+
abort();
152+
} catch (\Throwable $e) {
153+
abort();
154+
} finally {
155+
return 'finally';
156+
}
157+
}
158+
159+
public function d(): string {
160+
try {
161+
abort();
162+
} finally {
163+
return 'finally';
164+
}
165+
}
166+
167+
}

0 commit comments

Comments
 (0)