From e813a70c5f0cd7a0be1ec4374b4df70d8f5f31d7 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 21 Dec 2024 20:23:03 +0700 Subject: [PATCH 01/20] Allow process multiple UnreachableStatementNode on NodeScopeResolver::processNodes() --- src/Analyser/NodeScopeResolver.php | 27 ++++++++++--------- .../DeadCode/UnreachableStatementRuleTest.php | 19 +++++++++++++ .../DeadCode/data/multiple_unreachable.php | 15 +++++++++++ 3 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 791a8920b7..eaf909815b 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -316,12 +316,10 @@ public function processNodes( } $alreadyTerminated = true; - $nextStmt = $this->getFirstUnreachableNode(array_slice($nodes, $i + 1), true); - if (!$nextStmt instanceof Node\Stmt) { - continue; + $nextStmts = $this->getNextUnreachableStatements(array_slice($nodes, $i + 1), true); + foreach ($nextStmts as $nextStmt) { + $nodeCallback(new UnreachableStatementNode($nextStmt), $scope); } - - $nodeCallback(new UnreachableStatementNode($nextStmt), $scope); } } @@ -409,11 +407,10 @@ public function processStmtNodes( } $alreadyTerminated = true; - $nextStmt = $this->getFirstUnreachableNode(array_slice($stmts, $i + 1), $parentNode instanceof Node\Stmt\Namespace_); - if ($nextStmt === null) { - continue; + $nextStmts = $this->getNextUnreachableStatements(array_slice($stmts, $i + 1), $parentNode instanceof Node\Stmt\Namespace_); + foreach ($nextStmts as $nextStmt) { + $nodeCallback(new UnreachableStatementNode($nextStmt), $scope); } - $nodeCallback(new UnreachableStatementNode($nextStmt), $scope); } $statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints, $impurePoints); @@ -6516,10 +6513,11 @@ private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $ /** * @template T of Node * @param array $nodes - * @return T|null + * @return Node\Stmt[] */ - private function getFirstUnreachableNode(array $nodes, bool $earlyBinding): ?Node + private function getNextUnreachableStatements(array $nodes, bool $earlyBinding): array { + $stmts = []; foreach ($nodes as $node) { if ($node instanceof Node\Stmt\Nop) { continue; @@ -6527,9 +6525,12 @@ private function getFirstUnreachableNode(array $nodes, bool $earlyBinding): ?Nod if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\HaltCompiler)) { continue; } - return $node; + if (! $node instanceof Node\Stmt) { + continue; + } + $stmts[] = $node; } - return null; + return $stmts; } } diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php index da076db1c7..49e1a5955d 100644 --- a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php @@ -230,4 +230,23 @@ public function testBug11992(): void $this->analyse([__DIR__ . '/data/bug-11992.php'], []); } + public function testMultipleUnreachable(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/multiple_unreachable.php'], [ + [ + 'Unreachable statement - code above always terminates.', + 12, + ], + [ + 'Unreachable statement - code above always terminates.', + 13, + ], + [ + 'Unreachable statement - code above always terminates.', + 14, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php b/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php new file mode 100644 index 0000000000..63982d8ae0 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php @@ -0,0 +1,15 @@ + Date: Sat, 21 Dec 2024 20:29:31 +0700 Subject: [PATCH 02/20] fix phpstan notice --- phpstan-baseline.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d3e9a54a9d..faef0ba8bc 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1914,7 +1914,7 @@ parameters: - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable - count: 1 + count: 5 path: tests/PHPStan/Analyser/AnalyserTest.php - From c57d114aa553021cb383fc577ce38797c68d62f8 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 21 Dec 2024 20:34:06 +0700 Subject: [PATCH 03/20] fix test on CatchWithUnthrownExceptionRuleTest --- .../Exceptions/CatchWithUnthrownExceptionRuleTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php index 6eacf1535d..46e3103176 100644 --- a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php @@ -105,6 +105,10 @@ public function testRule(): void 'Dead catch - Exception is never thrown in the try block.', 398, ], + [ + 'Dead catch - Exception is never thrown in the try block.', + 407, + ], [ 'Dead catch - Exception is never thrown in the try block.', 432, @@ -213,6 +217,10 @@ public function testRuleWithoutReportingUncheckedException(): void 'Dead catch - Exception is never thrown in the try block.', 398, ], + [ + 'Dead catch - Exception is never thrown in the try block.', + 407, + ], [ 'Dead catch - Exception is never thrown in the try block.', 432, From 85068edebb015ebc9e488f8a8b7f1c9f1e7e4d04 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 21 Dec 2024 20:53:51 +0700 Subject: [PATCH 04/20] add addNextStatement() and getNextStatements() method --- src/Analyser/NodeScopeResolver.php | 19 +++++++++++++++++-- src/Node/UnreachableStatementNode.php | 17 +++++++++++++++++ .../DeadCode/UnreachableStatementRuleTest.php | 8 -------- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index eaf909815b..59737c8112 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -317,8 +317,15 @@ public function processNodes( $alreadyTerminated = true; $nextStmts = $this->getNextUnreachableStatements(array_slice($nodes, $i + 1), true); + $unreachableStatementNode = null; foreach ($nextStmts as $nextStmt) { - $nodeCallback(new UnreachableStatementNode($nextStmt), $scope); + if ($unreachableStatementNode instanceof UnreachableStatementNode) { + $unreachableStatementNode->addNextStatement($nextStmt); + continue; + } + + $unreachableStatementNode = new UnreachableStatementNode($nextStmt); + $nodeCallback($unreachableStatementNode, $scope); } } } @@ -408,8 +415,16 @@ public function processStmtNodes( $alreadyTerminated = true; $nextStmts = $this->getNextUnreachableStatements(array_slice($stmts, $i + 1), $parentNode instanceof Node\Stmt\Namespace_); + + $unreachableStatementNode = null; foreach ($nextStmts as $nextStmt) { - $nodeCallback(new UnreachableStatementNode($nextStmt), $scope); + if ($unreachableStatementNode instanceof UnreachableStatementNode) { + $unreachableStatementNode->addNextStatement($nextStmt); + continue; + } + + $unreachableStatementNode = new UnreachableStatementNode($nextStmt); + $nodeCallback($unreachableStatementNode, $scope); } } diff --git a/src/Node/UnreachableStatementNode.php b/src/Node/UnreachableStatementNode.php index e0c8cb0af9..b3d0a72105 100644 --- a/src/Node/UnreachableStatementNode.php +++ b/src/Node/UnreachableStatementNode.php @@ -9,6 +9,10 @@ */ final class UnreachableStatementNode extends Stmt implements VirtualNode { + /** + * @var Stmt[] + */ + private array $nextStatements = []; public function __construct(private Stmt $originalStatement) { @@ -33,4 +37,17 @@ public function getSubNodeNames(): array return []; } + public function addNextStatement(Stmt $stmt): void + { + $this->nextStatements[] = $stmt; + } + + /** + * @return Stmt[] + */ + public function getNextStatements(): array + { + return $this->nextStatements; + } + } diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php index 49e1a5955d..f163afd75b 100644 --- a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php @@ -238,14 +238,6 @@ public function testMultipleUnreachable(): void 'Unreachable statement - code above always terminates.', 12, ], - [ - 'Unreachable statement - code above always terminates.', - 13, - ], - [ - 'Unreachable statement - code above always terminates.', - 14, - ], ]); } From 1cd58c5ac2e069062a9299f29b11cdf8ef0ff89e Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 21 Dec 2024 20:54:30 +0700 Subject: [PATCH 05/20] fix baseline --- phpstan-baseline.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index faef0ba8bc..d3e9a54a9d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1914,7 +1914,7 @@ parameters: - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable - count: 5 + count: 1 path: tests/PHPStan/Analyser/AnalyserTest.php - From b13a52f83a358e400a2831d39610ebeeb3fd5ee9 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 21 Dec 2024 21:00:45 +0700 Subject: [PATCH 06/20] fix cs --- src/Node/UnreachableStatementNode.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Node/UnreachableStatementNode.php b/src/Node/UnreachableStatementNode.php index b3d0a72105..800cc4c4f6 100644 --- a/src/Node/UnreachableStatementNode.php +++ b/src/Node/UnreachableStatementNode.php @@ -9,9 +9,8 @@ */ final class UnreachableStatementNode extends Stmt implements VirtualNode { - /** - * @var Stmt[] - */ + + /** @var Stmt[] */ private array $nextStatements = []; public function __construct(private Stmt $originalStatement) From d7a88aa2c411805675afdb96075724cbe6a3ed00 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 21 Dec 2024 21:12:20 +0700 Subject: [PATCH 07/20] call $nodeCallback() after loop --- src/Analyser/NodeScopeResolver.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 59737c8112..44ba798569 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -317,6 +317,11 @@ public function processNodes( $alreadyTerminated = true; $nextStmts = $this->getNextUnreachableStatements(array_slice($nodes, $i + 1), true); + + if ($nextStmts === []) { + continue; + } + $unreachableStatementNode = null; foreach ($nextStmts as $nextStmt) { if ($unreachableStatementNode instanceof UnreachableStatementNode) { @@ -325,8 +330,9 @@ public function processNodes( } $unreachableStatementNode = new UnreachableStatementNode($nextStmt); - $nodeCallback($unreachableStatementNode, $scope); } + + $nodeCallback($unreachableStatementNode, $scope); } } @@ -416,6 +422,10 @@ public function processStmtNodes( $alreadyTerminated = true; $nextStmts = $this->getNextUnreachableStatements(array_slice($stmts, $i + 1), $parentNode instanceof Node\Stmt\Namespace_); + if ($nextStmts === []) { + continue; + } + $unreachableStatementNode = null; foreach ($nextStmts as $nextStmt) { if ($unreachableStatementNode instanceof UnreachableStatementNode) { @@ -424,8 +434,9 @@ public function processStmtNodes( } $unreachableStatementNode = new UnreachableStatementNode($nextStmt); - $nodeCallback($unreachableStatementNode, $scope); } + + $nodeCallback($unreachableStatementNode, $scope); } $statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints, $impurePoints); @@ -6528,7 +6539,7 @@ private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $ /** * @template T of Node * @param array $nodes - * @return Node\Stmt[] + * @return list */ private function getNextUnreachableStatements(array $nodes, bool $earlyBinding): array { @@ -6540,7 +6551,7 @@ private function getNextUnreachableStatements(array $nodes, bool $earlyBinding): if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\HaltCompiler)) { continue; } - if (! $node instanceof Node\Stmt) { + if (!$node instanceof Node\Stmt) { continue; } $stmts[] = $node; From b3e4d26f6b9667854b2e2b4edbf3309a94b3a003 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 21 Dec 2024 21:38:45 +0700 Subject: [PATCH 08/20] make immutable, add namespace in test --- src/Analyser/NodeScopeResolver.php | 28 +++++++++++-------- src/Node/UnreachableStatementNode.php | 13 +++------ .../DeadCode/data/multiple_unreachable.php | 2 ++ .../CatchWithUnthrownExceptionRuleTest.php | 8 ------ 4 files changed, 22 insertions(+), 29 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 44ba798569..2c3d8eb464 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -322,17 +322,19 @@ public function processNodes( continue; } - $unreachableStatementNode = null; - foreach ($nextStmts as $nextStmt) { - if ($unreachableStatementNode instanceof UnreachableStatementNode) { - $unreachableStatementNode->addNextStatement($nextStmt); + $unreachableStatement = null; + $nextStatements = []; + + foreach ($nextStmts as $key => $nextStmt) { + if ($key === 0) { + $unreachableStatement = $nextStmt; continue; } - $unreachableStatementNode = new UnreachableStatementNode($nextStmt); + $nextStatements[] = $nextStmt; } - $nodeCallback($unreachableStatementNode, $scope); + $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); } } @@ -426,17 +428,19 @@ public function processStmtNodes( continue; } - $unreachableStatementNode = null; - foreach ($nextStmts as $nextStmt) { - if ($unreachableStatementNode instanceof UnreachableStatementNode) { - $unreachableStatementNode->addNextStatement($nextStmt); + $unreachableStatement = null; + $nextStatements = []; + + foreach ($nextStmts as $key => $nextStmt) { + if ($key === 0) { + $unreachableStatement = $nextStmt; continue; } - $unreachableStatementNode = new UnreachableStatementNode($nextStmt); + $nextStatements[] = $nextStmt; } - $nodeCallback($unreachableStatementNode, $scope); + $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); } $statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints, $impurePoints); diff --git a/src/Node/UnreachableStatementNode.php b/src/Node/UnreachableStatementNode.php index 800cc4c4f6..603b7d6f2f 100644 --- a/src/Node/UnreachableStatementNode.php +++ b/src/Node/UnreachableStatementNode.php @@ -10,12 +10,12 @@ final class UnreachableStatementNode extends Stmt implements VirtualNode { - /** @var Stmt[] */ - private array $nextStatements = []; - - public function __construct(private Stmt $originalStatement) + /** @param Stmt[] $nextStatements */ + public function __construct(private Stmt $originalStatement, private array $nextStatements = []) { parent::__construct($originalStatement->getAttributes()); + + $this->nextStatements = $nextStatements; } public function getOriginalStatement(): Stmt @@ -36,11 +36,6 @@ public function getSubNodeNames(): array return []; } - public function addNextStatement(Stmt $stmt): void - { - $this->nextStatements[] = $stmt; - } - /** * @return Stmt[] */ diff --git a/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php b/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php index 63982d8ae0..383d693dd7 100644 --- a/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php +++ b/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php @@ -1,5 +1,7 @@ Date: Sat, 21 Dec 2024 21:43:09 +0700 Subject: [PATCH 09/20] fix phpstan --- src/Analyser/NodeScopeResolver.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 2c3d8eb464..8916e84f40 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -334,7 +334,9 @@ public function processNodes( $nextStatements[] = $nextStmt; } - $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); + if ($unreachableStatement instanceof Node\Stmt) { + $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); + } } } @@ -440,7 +442,9 @@ public function processStmtNodes( $nextStatements[] = $nextStmt; } - $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); + if ($unreachableStatement instanceof Node\Stmt) { + $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); + } } $statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints, $impurePoints); From 979b614e857b0daf0363919d66f1a99cd9eff5b5 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 21 Dec 2024 21:46:20 +0700 Subject: [PATCH 10/20] fix unit test --- tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php index f163afd75b..ec97b0481a 100644 --- a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php @@ -236,7 +236,7 @@ public function testMultipleUnreachable(): void $this->analyse([__DIR__ . '/data/multiple_unreachable.php'], [ [ 'Unreachable statement - code above always terminates.', - 12, + 14, ], ]); } From 54c68acf6a7a9560ac425dd0d43981688cfe84ac Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 21 Dec 2024 21:50:23 +0700 Subject: [PATCH 11/20] fix cs --- src/Analyser/NodeScopeResolver.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8916e84f40..6f8bdf38cd 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -334,9 +334,11 @@ public function processNodes( $nextStatements[] = $nextStmt; } - if ($unreachableStatement instanceof Node\Stmt) { - $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); + if (!$unreachableStatement instanceof Node\Stmt) { + continue; } + + $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); } } @@ -442,9 +444,11 @@ public function processStmtNodes( $nextStatements[] = $nextStmt; } - if ($unreachableStatement instanceof Node\Stmt) { - $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); + if (!$unreachableStatement instanceof Node\Stmt) { + continue; } + + $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); } $statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints, $impurePoints); From 03bbc0491b12284585380802a7bb896bb66fcc81 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 22 Dec 2024 06:41:54 +0700 Subject: [PATCH 12/20] reuse duplicated process unreachable statement logic --- src/Analyser/NodeScopeResolver.php | 63 ++++++++++++------------------ 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 6f8bdf38cd..2d4a4d52ac 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -317,29 +317,37 @@ public function processNodes( $alreadyTerminated = true; $nextStmts = $this->getNextUnreachableStatements(array_slice($nodes, $i + 1), true); + $this->processUnreachableStatement($nextStmts, $scope, $nodeCallback); + } + } - if ($nextStmts === []) { - continue; - } - - $unreachableStatement = null; - $nextStatements = []; - - foreach ($nextStmts as $key => $nextStmt) { - if ($key === 0) { - $unreachableStatement = $nextStmt; - continue; - } + /** + * @param Node\Stmt[] $nextStmts + * @param callable(Node $node, Scope $scope): void $nodeCallback + */ + private function processUnreachableStatement(array $nextStmts, MutatingScope $scope, callable $nodeCallback): void + { + if ($nextStmts === []) { + return; + } - $nextStatements[] = $nextStmt; - } + $unreachableStatement = null; + $nextStatements = []; - if (!$unreachableStatement instanceof Node\Stmt) { + foreach ($nextStmts as $key => $nextStmt) { + if ($key === 0) { + $unreachableStatement = $nextStmt; continue; } - $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); + $nextStatements[] = $nextStmt; + } + + if (!$unreachableStatement instanceof Node\Stmt) { + return; } + + $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); } /** @@ -427,28 +435,7 @@ public function processStmtNodes( $alreadyTerminated = true; $nextStmts = $this->getNextUnreachableStatements(array_slice($stmts, $i + 1), $parentNode instanceof Node\Stmt\Namespace_); - - if ($nextStmts === []) { - continue; - } - - $unreachableStatement = null; - $nextStatements = []; - - foreach ($nextStmts as $key => $nextStmt) { - if ($key === 0) { - $unreachableStatement = $nextStmt; - continue; - } - - $nextStatements[] = $nextStmt; - } - - if (!$unreachableStatement instanceof Node\Stmt) { - continue; - } - - $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); + $this->processUnreachableStatement($nextStmts, $scope, $nodeCallback); } $statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints, $impurePoints); From 4fa2d3ad5c853433bff10303bb130a96d5ae9e88 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 22 Dec 2024 06:56:20 +0700 Subject: [PATCH 13/20] add UnreachableStatementNextStatementsRuleTest --- ...achableStatementNextStatementsRuleTest.php | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php new file mode 100644 index 0000000000..a8a8a4cb06 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php @@ -0,0 +1,57 @@ + + */ +class UnreachableStatementNextStatementsRuleTest extends RuleTestCase +{ + + /** + * @return Rule + */ + protected function getRule(): Rule + { + return new class implements Rule { + + public function getNodeType(): string + { + return UnreachableStatementNode::class; + } + + /** + * @param UnreachableStatementNode $node + */ + public function processNode(Node $node, Scope $scope): array + { + $totalNextStatements = count($node->getNextStatements()); + + return [ + RuleErrorBuilder::message(sprintf("It has %d over first unreachable statements", $totalNextStatements)) + ->identifier('tests.total.next.unreachable.statement') + ->build(), + ]; + } + + }; + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/multiple_unreachable.php'], [ + [ + 'It has 2 over first unreachable statements', + 14 + ], + ]); + } + +} From 8ad84c02245e5ac0bafdfe9ee5e9f3a4f98ee3bc Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 22 Dec 2024 06:57:44 +0700 Subject: [PATCH 14/20] Fix cs --- .../DeadCode/UnreachableStatementNextStatementsRuleTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php index a8a8a4cb06..7024b120a6 100644 --- a/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php @@ -8,6 +8,8 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Testing\RuleTestCase; +use function count; +use function sprintf; /** * @extends RuleTestCase @@ -35,7 +37,7 @@ public function processNode(Node $node, Scope $scope): array $totalNextStatements = count($node->getNextStatements()); return [ - RuleErrorBuilder::message(sprintf("It has %d over first unreachable statements", $totalNextStatements)) + RuleErrorBuilder::message(sprintf('It has %d over first unreachable statements', $totalNextStatements)) ->identifier('tests.total.next.unreachable.statement') ->build(), ]; @@ -49,7 +51,7 @@ public function testRule(): void $this->analyse([__DIR__ . '/data/multiple_unreachable.php'], [ [ 'It has 2 over first unreachable statements', - 14 + 14, ], ]); } From afd35aa26322a5db865fad36fd6a761954a6459f Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 22 Dec 2024 08:08:25 +0700 Subject: [PATCH 15/20] final touch: allow register inner function as next of after unreachable statement --- src/Analyser/NodeScopeResolver.php | 6 ++++++ .../UnreachableStatementNextStatementsRuleTest.php | 4 ++-- .../PHPStan/Rules/DeadCode/data/multiple_unreachable.php | 8 +++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 2d4a4d52ac..3a10ffb471 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -6543,7 +6543,12 @@ private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $ private function getNextUnreachableStatements(array $nodes, bool $earlyBinding): array { $stmts = []; + $isPassedUnreachableStatement = false; foreach ($nodes as $node) { + if ($isPassedUnreachableStatement) { + $stmts[] = $node; + continue; + } if ($node instanceof Node\Stmt\Nop) { continue; } @@ -6554,6 +6559,7 @@ private function getNextUnreachableStatements(array $nodes, bool $earlyBinding): continue; } $stmts[] = $node; + $isPassedUnreachableStatement = true; } return $stmts; } diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php index 7024b120a6..8b78a4848b 100644 --- a/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php @@ -37,7 +37,7 @@ public function processNode(Node $node, Scope $scope): array $totalNextStatements = count($node->getNextStatements()); return [ - RuleErrorBuilder::message(sprintf('It has %d over first unreachable statements', $totalNextStatements)) + RuleErrorBuilder::message(sprintf('It has %d stmts over first unreachable statements', $totalNextStatements)) ->identifier('tests.total.next.unreachable.statement') ->build(), ]; @@ -50,7 +50,7 @@ public function testRule(): void { $this->analyse([__DIR__ . '/data/multiple_unreachable.php'], [ [ - 'It has 2 over first unreachable statements', + 'It has 3 stmts over first unreachable statements', 14, ], ]); diff --git a/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php b/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php index 383d693dd7..0e9ab15119 100644 --- a/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php +++ b/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php @@ -13,5 +13,11 @@ function foo($foo) echo 'statement 1'; echo 'statement 2'; - echo 'statement 3'; + + function innerFunction() + { + echo 'statement 3'; + } + + echo innerFunction(); } \ No newline at end of file From 2d7efbc10fc4bf174cff2eae63959af002da2e52 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 22 Dec 2024 08:10:43 +0700 Subject: [PATCH 16/20] final touch; fix phpstan --- src/Analyser/NodeScopeResolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 3a10ffb471..a824632ee7 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -6545,7 +6545,7 @@ private function getNextUnreachableStatements(array $nodes, bool $earlyBinding): $stmts = []; $isPassedUnreachableStatement = false; foreach ($nodes as $node) { - if ($isPassedUnreachableStatement) { + if ($isPassedUnreachableStatement && $node instanceof Node\Stmt) { $stmts[] = $node; continue; } From 2c625cd631d772db90bb0059e551125b654f7f8f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 2 Jan 2025 11:42:31 +0100 Subject: [PATCH 17/20] Adjust test --- ...achableStatementNextStatementsRuleTest.php | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php index 8b78a4848b..c8b816f0d6 100644 --- a/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php @@ -8,8 +8,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Testing\RuleTestCase; -use function count; -use function sprintf; /** * @extends RuleTestCase @@ -34,13 +32,20 @@ public function getNodeType(): string */ public function processNode(Node $node, Scope $scope): array { - $totalNextStatements = count($node->getNextStatements()); - - return [ - RuleErrorBuilder::message(sprintf('It has %d stmts over first unreachable statements', $totalNextStatements)) - ->identifier('tests.total.next.unreachable.statement') + $errors = [ + RuleErrorBuilder::message('First unreachable') + ->identifier('tests.nextUnreachableStatements') ->build(), ]; + + foreach ($node->getNextStatements() as $nextStatement) { + $errors[] = RuleErrorBuilder::message('Another unreachable') + ->line($nextStatement->getStartLine()) + ->identifier('tests.nextUnreachableStatements') + ->build(); + } + + return $errors; } }; @@ -50,9 +55,21 @@ public function testRule(): void { $this->analyse([__DIR__ . '/data/multiple_unreachable.php'], [ [ - 'It has 3 stmts over first unreachable statements', + 'First unreachable', 14, ], + [ + 'Another unreachable', + 15, + ], + [ + 'Another unreachable', + 17, + ], + [ + 'Another unreachable', + 22, + ], ]); } From 10dfba4ec3be4fd0c45260d34022cf243688f62c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 2 Jan 2025 11:43:53 +0100 Subject: [PATCH 18/20] Generics here no longer make sense --- src/Analyser/NodeScopeResolver.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index a824632ee7..bb7971c4be 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -6536,8 +6536,7 @@ private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $ } /** - * @template T of Node - * @param array $nodes + * @param array $nodes * @return list */ private function getNextUnreachableStatements(array $nodes, bool $earlyBinding): array From d553fb39a607b8375cc814fe4492a2753dcf8b31 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 2 Jan 2025 11:47:13 +0100 Subject: [PATCH 19/20] Failing test for top level scope --- ...eachableStatementNextStatementsRuleTest.php | 18 ++++++++++++++++++ .../data/multiple_unreachable_top_level.php | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/PHPStan/Rules/DeadCode/data/multiple_unreachable_top_level.php diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php index c8b816f0d6..36aa85b6bb 100644 --- a/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php @@ -73,4 +73,22 @@ public function testRule(): void ]); } + public function testRuleTopLevel(): void + { + $this->analyse([__DIR__ . '/data/multiple_unreachable_top_level.php'], [ + [ + 'First unreachable', + 9, + ], + [ + 'Another unreachable', + 10, + ], + [ + 'Another unreachable', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable_top_level.php b/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable_top_level.php new file mode 100644 index 0000000000..05fd5c7ce6 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable_top_level.php @@ -0,0 +1,17 @@ + Date: Thu, 2 Jan 2025 18:01:48 +0700 Subject: [PATCH 20/20] fix top level function --- src/Analyser/NodeScopeResolver.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index bb7971c4be..fb72cfeb6b 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -6544,6 +6544,9 @@ private function getNextUnreachableStatements(array $nodes, bool $earlyBinding): $stmts = []; $isPassedUnreachableStatement = false; foreach ($nodes as $node) { + if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\HaltCompiler)) { + continue; + } if ($isPassedUnreachableStatement && $node instanceof Node\Stmt) { $stmts[] = $node; continue; @@ -6551,9 +6554,6 @@ private function getNextUnreachableStatements(array $nodes, bool $earlyBinding): if ($node instanceof Node\Stmt\Nop) { continue; } - if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\HaltCompiler)) { - continue; - } if (!$node instanceof Node\Stmt) { continue; }