Skip to content

Commit 8df032a

Browse files
phpstan-botclaudeVincentLanglet
authored
Fix phpstan/phpstan#14328: Unreachable statement information gets lost (#5253)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Vincent Langlet <vincentlanglet@hotmail.fr>
1 parent 889361f commit 8df032a

File tree

4 files changed

+65
-2
lines changed

4 files changed

+65
-2
lines changed

src/Analyser/ExprHandler/MethodCallHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
136136
if ($parametersAcceptor !== null) {
137137
$normalizedExpr = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $expr) ?? $expr;
138138
$returnType = $parametersAcceptor->getReturnType();
139-
$isAlwaysTerminating = $returnType instanceof NeverType && $returnType->isExplicit();
139+
$isAlwaysTerminating = $isAlwaysTerminating || ($returnType instanceof NeverType && $returnType->isExplicit());
140140
}
141141

142142
$argsResult = $nodeScopeResolver->processArgs(

src/Analyser/ExprHandler/StaticCallHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
192192
if ($parametersAcceptor !== null) {
193193
$normalizedExpr = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $expr) ?? $expr;
194194
$returnType = $parametersAcceptor->getReturnType();
195-
$isAlwaysTerminating = $returnType instanceof NeverType && $returnType->isExplicit();
195+
$isAlwaysTerminating = $isAlwaysTerminating || ($returnType instanceof NeverType && $returnType->isExplicit());
196196
}
197197
$argsResult = $nodeScopeResolver->processArgs($stmt, $methodReflection, null, $parametersAcceptor, $normalizedExpr, $scope, $storage, $nodeCallback, $context, $closureBindScope);
198198
$scope = $argsResult->getScope();

tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,4 +373,28 @@ public function testBug13331(): void
373373
$this->analyse([__DIR__ . '/data/bug-13331.php'], []);
374374
}
375375

376+
#[RequiresPhp('>= 8.2')]
377+
public function testBug14328(): void
378+
{
379+
$this->treatPhpDocTypesAsCertain = true;
380+
$this->analyse([__DIR__ . '/data/bug-14328.php'], [
381+
[
382+
'Unreachable statement - code above always terminates.',
383+
20,
384+
],
385+
[
386+
'Unreachable statement - code above always terminates.',
387+
26,
388+
],
389+
[
390+
'Unreachable statement - code above always terminates.',
391+
32,
392+
],
393+
[
394+
'Unreachable statement - code above always terminates.',
395+
38,
396+
],
397+
]);
398+
}
399+
376400
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php // lint >= 8.2
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug14328;
6+
7+
class Foo {
8+
public function returnThis(mixed $value): self {
9+
return $this;
10+
}
11+
12+
public static function returnSelf(mixed $value): self {
13+
return new self();
14+
}
15+
}
16+
17+
function testMethodCallChainedWithMethodCall(): void {
18+
$callback = fn (): never => throw new \Exception();
19+
$x = (new Foo())->returnThis($callback())->returnThis('x');
20+
$y = 'this will never run';
21+
}
22+
23+
function testMethodCallChainedWithStaticCall(): void {
24+
$callback = fn (): never => throw new \Exception();
25+
$x = (new Foo())->returnThis($callback())::returnSelf('x');
26+
$y = 'this will never run';
27+
}
28+
29+
function testStaticCallChainedWithMethodCall(): void {
30+
$callback = fn (): never => throw new \Exception();
31+
$a = Foo::returnSelf($callback())->returnThis('x');
32+
$b = 'this will never run either';
33+
}
34+
35+
function testStaticCallChainedWithStaticCall(): void {
36+
$callback = fn (): never => throw new \Exception();
37+
$a = Foo::returnSelf($callback())::returnSelf('x');
38+
$b = 'this will never run either';
39+
}

0 commit comments

Comments
 (0)