diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index bbc886a410..2216db1a6b 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -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; } } @@ -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, @@ -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); @@ -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; diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php index 0efdb81138..191ba39134 100644 --- a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php @@ -218,4 +218,10 @@ public function testBug8966(): void ]); } + public function testBug11179(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-11179.php'], []); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-11179.php b/tests/PHPStan/Rules/DeadCode/data/bug-11179.php new file mode 100644 index 0000000000..aba6adb265 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-11179.php @@ -0,0 +1,14 @@ +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, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Debug/data/bug-11179-no-namespace.php b/tests/PHPStan/Rules/Debug/data/bug-11179-no-namespace.php new file mode 100644 index 0000000000..c7f0ad68f8 --- /dev/null +++ b/tests/PHPStan/Rules/Debug/data/bug-11179-no-namespace.php @@ -0,0 +1,13 @@ +