Skip to content

Commit

Permalink
Function that returns never is always an explicit throw point
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Sep 16, 2021
1 parent 24c1eb4 commit c362fc5
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 6 deletions.
26 changes: 20 additions & 6 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1975,7 +1975,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
$expr->args,
$methodReflection->getVariants()
);
$methodThrowPoint = $this->getMethodThrowPoint($methodReflection, $expr, $scope);
$methodThrowPoint = $this->getMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope);
if ($methodThrowPoint !== null) {
$throwPoints[] = $methodThrowPoint;
}
Expand Down Expand Up @@ -2642,8 +2642,15 @@ private function getFunctionThrowPoint(
return ThrowPoint::createExplicit($scope, $throwType, $funcCall, false);
}

if ($functionReflection->getThrowType() !== null) {
$throwType = $functionReflection->getThrowType();
$throwType = $functionReflection->getThrowType();
if ($throwType === null && $parametersAcceptor !== null) {
$returnType = $parametersAcceptor->getReturnType();
if ($returnType instanceof NeverType && $returnType->isExplicit()) {
$throwType = new ObjectType(\Throwable::class);
}
}

if ($throwType !== null) {
if (!$throwType instanceof VoidType) {
return ThrowPoint::createExplicit($scope, $throwType, $funcCall, true);
}
Expand Down Expand Up @@ -2675,7 +2682,7 @@ private function getFunctionThrowPoint(
return null;
}

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

if ($methodReflection->getThrowType() !== null) {
$throwType = $methodReflection->getThrowType();
$throwType = $methodReflection->getThrowType();
if ($throwType === null) {
$returnType = $parametersAcceptor->getReturnType();
if ($returnType instanceof NeverType && $returnType->isExplicit()) {
$throwType = new ObjectType(\Throwable::class);
}
}

if ($throwType !== null) {
if (!$throwType instanceof VoidType) {
return ThrowPoint::createExplicit($scope, $throwType, $methodCall, true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,94 @@ public function testBug5627(): void
'The overwriting return is on this line.',
51,
],
[
'This throw is overwritten by a different one in the finally block below.',
62,
],
[
'This throw is overwritten by a different one in the finally block below.',
64,
],
[
'The overwriting return is on this line.',
66,
],
[
'This exit point is overwritten by a different one in the finally block below.',
81,
],
[
'This exit point is overwritten by a different one in the finally block below.',
83,
],
[
'The overwriting return is on this line.',
85,
],
[
'This exit point is overwritten by a different one in the finally block below.',
91,
],
[
'This exit point is overwritten by a different one in the finally block below.',
93,
],
[
'The overwriting return is on this line.',
95,
],
[
'This exit point is overwritten by a different one in the finally block below.',
101,
],
[
'The overwriting return is on this line.',
103,
],
[
'This throw is overwritten by a different one in the finally block below.',
122,
],
[
'This throw is overwritten by a different one in the finally block below.',
124,
],
[
'The overwriting return is on this line.',
126,
],
[
'This exit point is overwritten by a different one in the finally block below.',
141,
],
[
'This exit point is overwritten by a different one in the finally block below.',
143,
],
[
'The overwriting return is on this line.',
145,
],
[
'This exit point is overwritten by a different one in the finally block below.',
151,
],
[
'This exit point is overwritten by a different one in the finally block below.',
153,
],
[
'The overwriting return is on this line.',
155,
],
[
'This exit point is overwritten by a different one in the finally block below.',
161,
],
[
'The overwriting return is on this line.',
163,
],
]);
}

Expand Down
112 changes: 112 additions & 0 deletions tests/PHPStan/Rules/Exceptions/data/bug-5627.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,115 @@ public function d(): string {
}

}

class Bar
{

public function a(): string {
try {
throw new \Exception('try');
} catch (\Exception $e) {
throw new \Exception('catch');
} finally {
return 'finally';
}
}

/**
*
* @return never
*/
public function abort()
{
throw new \Exception();
}

public function b(): string {
try {
$this->abort();
} catch (\Exception $e) {
$this->abort();
} finally {
return 'finally';
}
}

public function c(): string {
try {
$this->abort();
} catch (\Throwable $e) {
$this->abort();
} finally {
return 'finally';
}
}

public function d(): string {
try {
$this->abort();
} finally {
return 'finally';
}
}

}

/**
* @return never
*/
function abort()
{

}

class Baz
{

public function a(): string {
try {
throw new \Exception('try');
} catch (\Exception $e) {
throw new \Exception('catch');
} finally {
return 'finally';
}
}










public function b(): string {
try {
abort();
} catch (\Exception $e) {
abort();
} finally {
return 'finally';
}
}

public function c(): string {
try {
abort();
} catch (\Throwable $e) {
abort();
} finally {
return 'finally';
}
}

public function d(): string {
try {
abort();
} finally {
return 'finally';
}
}

}

0 comments on commit c362fc5

Please sign in to comment.