Skip to content

Commit

Permalink
Fix statement analysis after early-terminating statements
Browse files Browse the repository at this point in the history
  • Loading branch information
takaram authored Jul 13, 2024
1 parent 3a32992 commit 2301b8b
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 8 deletions.
24 changes: 16 additions & 8 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,24 +292,28 @@ public function processNodes(
callable $nodeCallback,
): void
{
$alreadyTerminated = false;
foreach ($nodes as $i => $node) {
if (!$node instanceof Node\Stmt) {
if (
!$node instanceof Node\Stmt
|| ($alreadyTerminated && !($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike))
) {
continue;
}

$statementResult = $this->processStmtNode($node, $scope, $nodeCallback, StatementContext::createTopLevel());
$scope = $statementResult->getScope();
if (!$statementResult->isAlwaysTerminating()) {
if ($alreadyTerminated || !$statementResult->isAlwaysTerminating()) {
continue;
}

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

$nodeCallback(new UnreachableStatementNode($nextStmt), $scope);
break;
}
}

Expand Down Expand Up @@ -339,6 +343,10 @@ public function processStmtNodes(
|| $parentNode instanceof Node\Stmt\ClassMethod
|| $parentNode instanceof Expr\Closure;
foreach ($stmts as $i => $stmt) {
if ($alreadyTerminated && !($stmt instanceof Node\Stmt\Function_ || $stmt instanceof Node\Stmt\ClassLike)) {
continue;
}

$isLast = $i === $stmtCount - 1;
$statementResult = $this->processStmtNode(
$stmt,
Expand Down Expand Up @@ -370,16 +378,16 @@ public function processStmtNodes(
$throwPoints = array_merge($throwPoints, $statementResult->getThrowPoints());
$impurePoints = array_merge($impurePoints, $statementResult->getImpurePoints());

if (!$statementResult->isAlwaysTerminating()) {
if ($alreadyTerminated || !$statementResult->isAlwaysTerminating()) {
continue;
}

$alreadyTerminated = true;
$nextStmt = $this->getFirstUnreachableNode(array_slice($stmts, $i + 1), $parentNode instanceof Node\Stmt\Namespace_);
if ($nextStmt !== null) {
$nodeCallback(new UnreachableStatementNode($nextStmt), $scope);
if ($nextStmt === null) {
continue;
}
break;
$nodeCallback(new UnreachableStatementNode($nextStmt), $scope);
}

$statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints, $impurePoints);
Expand Down Expand Up @@ -6064,7 +6072,7 @@ private function getFirstUnreachableNode(array $nodes, bool $earlyBinding): ?Nod
if ($node instanceof Node\Stmt\Nop) {
continue;
}
if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike)) {
if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\HaltCompiler)) {
continue;
}
return $node;
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,10 @@ public function testBug8966(): void
]);
}

public function testBug11179(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-11179.php'], []);
}

}
14 changes: 14 additions & 0 deletions tests/PHPStan/Rules/DeadCode/data/bug-11179.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare(strict_types = 1);

namespace Bug11179;

exit(0);

function foo(string $p): string
{
\PHPStan\dumpType($p);
return "";
}

__halt_compiler();
foo
20 changes: 20 additions & 0 deletions tests/PHPStan/Rules/Debug/DumpTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,24 @@ public function testBug10377(): void
]);
}

public function testBug11179(): void
{
$this->analyse([__DIR__ . '/../DeadCode/data/bug-11179.php'], [
[
'Dumped type: string',
9,
],
]);
}

public function testBug11179NoNamespace(): void
{
$this->analyse([__DIR__ . '/data/bug-11179-no-namespace.php'], [
[
'Dumped type: string',
11,
],
]);
}

}
13 changes: 13 additions & 0 deletions tests/PHPStan/Rules/Debug/data/bug-11179-no-namespace.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php declare(strict_types = 1);

// no namespace

exit(0);

echo 1;

function bug11179Foo(string $p): string
{
\PHPStan\dumpType($p);
return "";
}

0 comments on commit 2301b8b

Please sign in to comment.