Skip to content

Commit 2301b8b

Browse files
authored
Fix statement analysis after early-terminating statements
1 parent 3a32992 commit 2301b8b

File tree

5 files changed

+69
-8
lines changed

5 files changed

+69
-8
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -292,24 +292,28 @@ public function processNodes(
292292
callable $nodeCallback,
293293
): void
294294
{
295+
$alreadyTerminated = false;
295296
foreach ($nodes as $i => $node) {
296-
if (!$node instanceof Node\Stmt) {
297+
if (
298+
!$node instanceof Node\Stmt
299+
|| ($alreadyTerminated && !($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike))
300+
) {
297301
continue;
298302
}
299303

300304
$statementResult = $this->processStmtNode($node, $scope, $nodeCallback, StatementContext::createTopLevel());
301305
$scope = $statementResult->getScope();
302-
if (!$statementResult->isAlwaysTerminating()) {
306+
if ($alreadyTerminated || !$statementResult->isAlwaysTerminating()) {
303307
continue;
304308
}
305309

310+
$alreadyTerminated = true;
306311
$nextStmt = $this->getFirstUnreachableNode(array_slice($nodes, $i + 1), true);
307312
if (!$nextStmt instanceof Node\Stmt) {
308313
continue;
309314
}
310315

311316
$nodeCallback(new UnreachableStatementNode($nextStmt), $scope);
312-
break;
313317
}
314318
}
315319

@@ -339,6 +343,10 @@ public function processStmtNodes(
339343
|| $parentNode instanceof Node\Stmt\ClassMethod
340344
|| $parentNode instanceof Expr\Closure;
341345
foreach ($stmts as $i => $stmt) {
346+
if ($alreadyTerminated && !($stmt instanceof Node\Stmt\Function_ || $stmt instanceof Node\Stmt\ClassLike)) {
347+
continue;
348+
}
349+
342350
$isLast = $i === $stmtCount - 1;
343351
$statementResult = $this->processStmtNode(
344352
$stmt,
@@ -370,16 +378,16 @@ public function processStmtNodes(
370378
$throwPoints = array_merge($throwPoints, $statementResult->getThrowPoints());
371379
$impurePoints = array_merge($impurePoints, $statementResult->getImpurePoints());
372380

373-
if (!$statementResult->isAlwaysTerminating()) {
381+
if ($alreadyTerminated || !$statementResult->isAlwaysTerminating()) {
374382
continue;
375383
}
376384

377385
$alreadyTerminated = true;
378386
$nextStmt = $this->getFirstUnreachableNode(array_slice($stmts, $i + 1), $parentNode instanceof Node\Stmt\Namespace_);
379-
if ($nextStmt !== null) {
380-
$nodeCallback(new UnreachableStatementNode($nextStmt), $scope);
387+
if ($nextStmt === null) {
388+
continue;
381389
}
382-
break;
390+
$nodeCallback(new UnreachableStatementNode($nextStmt), $scope);
383391
}
384392

385393
$statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints, $impurePoints);
@@ -6064,7 +6072,7 @@ private function getFirstUnreachableNode(array $nodes, bool $earlyBinding): ?Nod
60646072
if ($node instanceof Node\Stmt\Nop) {
60656073
continue;
60666074
}
6067-
if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike)) {
6075+
if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\HaltCompiler)) {
60686076
continue;
60696077
}
60706078
return $node;

tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,10 @@ public function testBug8966(): void
218218
]);
219219
}
220220

221+
public function testBug11179(): void
222+
{
223+
$this->treatPhpDocTypesAsCertain = true;
224+
$this->analyse([__DIR__ . '/data/bug-11179.php'], []);
225+
}
226+
221227
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug11179;
4+
5+
exit(0);
6+
7+
function foo(string $p): string
8+
{
9+
\PHPStan\dumpType($p);
10+
return "";
11+
}
12+
13+
__halt_compiler();
14+
foo

tests/PHPStan/Rules/Debug/DumpTypeRuleTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,24 @@ public function testBug10377(): void
8282
]);
8383
}
8484

85+
public function testBug11179(): void
86+
{
87+
$this->analyse([__DIR__ . '/../DeadCode/data/bug-11179.php'], [
88+
[
89+
'Dumped type: string',
90+
9,
91+
],
92+
]);
93+
}
94+
95+
public function testBug11179NoNamespace(): void
96+
{
97+
$this->analyse([__DIR__ . '/data/bug-11179-no-namespace.php'], [
98+
[
99+
'Dumped type: string',
100+
11,
101+
],
102+
]);
103+
}
104+
85105
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php declare(strict_types = 1);
2+
3+
// no namespace
4+
5+
exit(0);
6+
7+
echo 1;
8+
9+
function bug11179Foo(string $p): string
10+
{
11+
\PHPStan\dumpType($p);
12+
return "";
13+
}

0 commit comments

Comments
 (0)