Skip to content

Commit 6984c5e

Browse files
authored
Do not treat definition of functions and class-likes as unreachable
1 parent db63919 commit 6984c5e

File tree

10 files changed

+160
-7
lines changed

10 files changed

+160
-7
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ public function processNodes(
254254
continue;
255255
}
256256

257-
$nextStmt = $this->getFirstNonNopNode(array_slice($nodes, $i + 1));
257+
$nextStmt = $this->getFirstUnreachableNode(array_slice($nodes, $i + 1), true);
258258
if (!$nextStmt instanceof Node\Stmt) {
259259
continue;
260260
}
@@ -323,7 +323,7 @@ public function processStmtNodes(
323323
}
324324

325325
$alreadyTerminated = true;
326-
$nextStmt = $this->getFirstNonNopNode(array_slice($stmts, $i + 1));
326+
$nextStmt = $this->getFirstUnreachableNode(array_slice($stmts, $i + 1), $parentNode instanceof Node\Stmt\Namespace_);
327327
if ($nextStmt !== null) {
328328
$nodeCallback(new UnreachableStatementNode($nextStmt), $scope);
329329
}
@@ -4484,16 +4484,19 @@ private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $
44844484
/**
44854485
* @template T of Node
44864486
* @param array<T> $nodes
4487-
* @return T
4487+
* @return T|null
44884488
*/
4489-
private function getFirstNonNopNode(array $nodes): ?Node
4489+
private function getFirstUnreachableNode(array $nodes, bool $earlyBinding): ?Node
44904490
{
44914491
foreach ($nodes as $node) {
4492-
if (!$node instanceof Node\Stmt\Nop) {
4493-
return $node;
4492+
if ($node instanceof Node\Stmt\Nop) {
4493+
continue;
4494+
}
4495+
if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike)) {
4496+
continue;
44944497
}
4498+
return $node;
44954499
}
4496-
44974500
return null;
44984501
}
44994502

tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,64 @@ public function testBug8620(): void
149149
$this->analyse([__DIR__ . '/data/bug-8620.php'], []);
150150
}
151151

152+
public function testBug4002(): void
153+
{
154+
$this->treatPhpDocTypesAsCertain = true;
155+
$this->analyse([__DIR__ . '/data/bug-4002.php'], []);
156+
}
157+
158+
public function testBug4002_2(): void
159+
{
160+
$this->treatPhpDocTypesAsCertain = true;
161+
$this->analyse([__DIR__ . '/data/bug-4002-2.php'], []);
162+
}
163+
164+
public function testBug4002_3(): void
165+
{
166+
$this->treatPhpDocTypesAsCertain = true;
167+
$this->analyse([__DIR__ . '/data/bug-4002-3.php'], [
168+
[
169+
'Unreachable statement - code above always terminates.',
170+
13,
171+
],
172+
]);
173+
}
174+
175+
public function testBug4002_4(): void
176+
{
177+
$this->treatPhpDocTypesAsCertain = true;
178+
$this->analyse([__DIR__ . '/data/bug-4002-4.php'], [
179+
[
180+
'Unreachable statement - code above always terminates.',
181+
9,
182+
],
183+
]);
184+
}
185+
186+
public function testBug4002Class(): void
187+
{
188+
$this->treatPhpDocTypesAsCertain = true;
189+
$this->analyse([__DIR__ . '/data/bug-4002_class.php'], []);
190+
}
191+
192+
public function testBug4002Interface(): void
193+
{
194+
$this->treatPhpDocTypesAsCertain = true;
195+
$this->analyse([__DIR__ . '/data/bug-4002_interface.php'], []);
196+
}
197+
198+
public function testBug4002Trait(): void
199+
{
200+
$this->treatPhpDocTypesAsCertain = true;
201+
$this->analyse([__DIR__ . '/data/bug-4002_trait.php'], []);
202+
}
203+
204+
public function testBug8319(): void
205+
{
206+
$this->treatPhpDocTypesAsCertain = true;
207+
$this->analyse([__DIR__ . '/data/bug-8319.php'], []);
208+
}
209+
152210
public function testBug8966(): void
153211
{
154212
$this->treatPhpDocTypesAsCertain = true;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php declare(strict_types=1);
2+
3+
// no namespace
4+
5+
bug4002_test();
6+
exit;
7+
8+
function bug4002_test()
9+
{
10+
echo 'hello';
11+
}
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+
namespace Bug4002;
4+
5+
test3();
6+
exit;
7+
8+
function test3()
9+
{
10+
echo 'hello';
11+
}
12+
13+
echo 'unreachable';
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+
namespace Bug4002;
4+
5+
if (true) {
6+
test4();
7+
exit;
8+
9+
function test4()
10+
{
11+
echo 'inner';
12+
}
13+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bug4002;
4+
5+
test();
6+
exit;
7+
8+
function test()
9+
{
10+
echo 'hello';
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Bug4002\Class_;
4+
5+
new Foo;
6+
exit;
7+
8+
class Foo
9+
{
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Bug4002\Interface_;
4+
5+
echo Foo::BAR;
6+
exit;
7+
8+
interface Foo
9+
{
10+
const BAR = 1;
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Bug4002\Trait_;
4+
5+
new class {
6+
use Foo;
7+
};
8+
exit;
9+
10+
trait Foo
11+
{
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug8319;
4+
5+
foo();
6+
7+
function foo(): never
8+
{
9+
\var_dump('reachable statement!');
10+
exit();
11+
}

0 commit comments

Comments
 (0)