Skip to content

Fix missing detection of dead code in expressions #4090

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: 2.1.x
Choose a base branch
from
6 changes: 6 additions & 0 deletions src/Analyser/ExpressionResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public function __construct(
private array $impurePoints,
?callable $truthyScopeCallback = null,
?callable $falseyScopeCallback = null,
private bool $isAlwaysTerminating = false,
)
{
$this->truthyScopeCallback = $truthyScopeCallback;
Expand Down Expand Up @@ -90,4 +91,9 @@ public function getFalseyScope(): MutatingScope
return $this->falseyScope;
}

public function isAlwaysTerminating(): bool
{
return $this->isAlwaysTerminating;
}

}
52 changes: 48 additions & 4 deletions src/Analyser/NodeScopeResolver.php

Large diffs are not rendered by default.

97 changes: 97 additions & 0 deletions tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\RequiresPhp;

/**
* @extends RuleTestCase<UnreachableStatementRule>
Expand Down Expand Up @@ -240,4 +241,100 @@ public function testMultipleUnreachable(): void
]);
}

#[RequiresPhp('>= 8.1')]
public function testBug11909(): void
{
$this->treatPhpDocTypesAsCertain = false;
$this->analyse([__DIR__ . '/data/bug-11909.php'], [
[
'Unreachable statement - code above always terminates.',
10,
],
]);
}

#[RequiresPhp('>= 8.1')]
public function testBug13232a(): void
{
$this->treatPhpDocTypesAsCertain = false;
$this->analyse([__DIR__ . '/data/bug-13232a.php'], [
[
'Unreachable statement - code above always terminates.',
10,
],
[
'Unreachable statement - code above always terminates.',
17,
],
[
'Unreachable statement - code above always terminates.',
23,
],
[
'Unreachable statement - code above always terminates.',
32,
],
[
'Unreachable statement - code above always terminates.',
38,
],
[
'Unreachable statement - code above always terminates.',
44,
],
[
'Unreachable statement - code above always terminates.',
52,
],
[
'Unreachable statement - code above always terminates.',
61,
],
[
'Unreachable statement - code above always terminates.',
70,
],
]);
}

#[RequiresPhp('>= 8.1')]
public function testBug13232b(): void
{
$this->treatPhpDocTypesAsCertain = false;
$this->analyse([__DIR__ . '/data/bug-13232b.php'], [
[
'Unreachable statement - code above always terminates.',
19,
],
]);
}

#[RequiresPhp('>= 8.1')]
public function testBug13232c(): void
{
$this->treatPhpDocTypesAsCertain = false;
$this->analyse([__DIR__ . '/data/bug-13232c.php'], [
[
'Unreachable statement - code above always terminates.',
12,
],
[
'Unreachable statement - code above always terminates.',
20,
],
]);
}

#[RequiresPhp('>= 8.1')]
public function testBug13232d(): void
{
$this->treatPhpDocTypesAsCertain = false;
$this->analyse([__DIR__ . '/data/bug-13232d.php'], [
[
'Unreachable statement - code above always terminates.',
11,
],
]);
}

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

namespace Bug11909;

function doFoo(): never {
throw new LogicException("throws");
}

echo doFoo();
echo "hello";
84 changes: 84 additions & 0 deletions tests/PHPStan/Rules/DeadCode/data/bug-13232a.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

namespace Bug13232a;

final class HelloWorld
{
public function sayHa(): void
{
echo sprintf("Hello, %s no way", $this->neverReturnsMethod());
echo 'this will never happen';
}

public function sayHi(): void
{
echo 'Hello, ' . neverReturns()
. ' no way';
echo 'this will never happen';
}

public function sayHo(): void
{
echo "Hello, {$this->neverReturnsMethod()} no way";
echo 'this will never happen';
}

public function sayHe(): void
{
$callable = function (): never {
exit();
};
echo sprintf("Hello, %s no way", $callable());
echo 'this will never happen';
}

public function sayHe2(): void
{
$this->doFoo($this->neverReturnsMethod());
echo 'this will never happen';
}

public function sayHe3(): void
{
self::doStaticFoo($this->neverReturnsMethod());
echo 'this will never happen';
}

public function sayHuu(): void
{
$x = [
$this->neverReturnsMethod()
];
echo 'this will never happen';
}

public function sayClosure(): void
{
$callable = function (): never {
exit();
};
$callable();
echo 'this will never happen';
}

public function sayIIFE(): void
{
(function (): never {
exit();
})();

echo 'this will never happen';
}

function neverReturnsMethod(): never {
exit();
}

public function doFoo() {}

static public function doStaticFoo() {}
}
function neverReturns(): never {
exit();
}

34 changes: 34 additions & 0 deletions tests/PHPStan/Rules/DeadCode/data/bug-13232b.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Bug13232b;

final class HelloWorld
{
public function sayHello(): void
{
if (rand(0, 1)) {
$x = new Foo();
} else {
$x = '1';
}
echo 'Hello, ' . mightReturnNever($x)
. ' no way';

echo 'Hello, ' . mightReturnNever(new Foo())
. ' no way';
echo 'this will never happen';
}
}

/** @phpstan-return ($x is Foo ? never : string) */
function mightReturnNever(mixed $x)
{
if ($x instanceof Foo) {
throw new LogicException();
}
return "hello";
}

class Foo
{
}
52 changes: 52 additions & 0 deletions tests/PHPStan/Rules/DeadCode/data/bug-13232c.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php // lint >= 8.0

namespace Bug13232c;

final class HelloWorld
{
public function sayHello(): void
{
echo 'Hello, ' . $this->returnNever()
. ' no way';

echo 'this will never happen';
}

static public function sayStaticHello(): void
{
echo 'Hello, ' . self::staticReturnNever()
. ' no way';

echo 'this will never happen';
}

public function sayNullsafeHello(?self $x): void
{
echo 'Hello, ' . $x?->returnNever()
. ' no way';

echo 'this might happen, in case $x is null';
}

public function sayMaybeHello(): void
{
if (rand(0, 1)) {
echo 'Hello, ' . $this->returnNever()
. ' no way';
}

echo 'this might happen';
}

function returnNever(): never

{
exit();
}

static function staticReturnNever(): never
{
exit();
}

}
15 changes: 15 additions & 0 deletions tests/PHPStan/Rules/DeadCode/data/bug-13232d.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Bug13232d;

final class HelloWorld
{
public function sayHi(): void
{
$x = 'Hello, ' . neverReturns()
. ' no way';
$x .= 'this will never happen';
}
}
function neverReturns(): never {}

Loading