Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Analyser/ExprHandler/AssignOpHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ static function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $contex
$exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep());
if ($expr instanceof Expr\AssignOp\Coalesce) {
$nodeScopeResolver->storeBeforeScope($storage, $expr, $originalScope);
$isAlwaysTerminating = $exprResult->isAlwaysTerminating() && $originalScope->getType($expr->var)->isNull()->yes();
return new ExpressionResult(
$exprResult->getScope()->mergeWith($originalScope),
$exprResult->hasYield(),
$exprResult->isAlwaysTerminating(),
$isAlwaysTerminating,
$exprResult->getThrowPoints(),
$exprResult->getImpurePoints(),
);
Expand Down
35 changes: 35 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-14369.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php // lint >= 8.0

declare(strict_types = 1);

namespace Bug14369;

use Exception;
use function PHPStan\Testing\assertType;

function test(string|null $test): void
{
$test ??= throw new Exception();

assertType('string', $test);
}

function testMaybeNull(): void
{
if (rand(0, 1)) {
$test = null;
} else {
$test = 'hello';
}
$test ??= throw new Exception();

assertType("'hello'", $test);
}

function testAlwaysNull(): void
{
$test = null;
$test ??= throw new Exception();
Comment on lines +31 to +32
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add another test in which $test is maybe null

like

if (rand(0,1)) {
  $test = null;
} else {
  $test = 'hallo';
}

Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's already the original issue with $test being ?string (line 12)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The working tree is clean and all the reviewer feedback has already been addressed in previous commits:

  1. VincentLanglet's latest comment is just noting that the ?string case is already covered at line 12 — no action needed.
  2. staabm's request for a "maybe null" test was already addressed in commit 6435689ae.
  3. Both reviewers have approved the PR.

There are no outstanding changes to make. The branch is already up to date with the remote.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This task already completed too — all 11658 tests passed. The commit addressing staabm's review feedback has already been pushed. Everything is done.


assertType('*NEVER*', $test);
}
15 changes: 15 additions & 0 deletions tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -397,4 +397,19 @@ public function testBug14328(): void
]);
}

public function testBug14369(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-14369.php'], [
[
'Unreachable statement - code above always terminates.',
33,
],
[
'Unreachable statement - code above always terminates.',
40,
],
]);
}

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

declare(strict_types = 1);

namespace Bug14369DeadCode;

use Exception;

function test(string|null $test): void
{
$test ??= throw new Exception();

echo $test;
}

function testMaybeNull(): void
{
if (rand(0, 1)) {
$test = null;
} else {
$test = 'hello';
}
$test ??= throw new Exception();

echo $test;
}

function testAlwaysNull(): void
{
$test = null;
$test ??= throw new Exception();

echo $test;
}

function testAlwaysTerminatingLhs(): void
{
alwaysThrows()->prop ??= throw new Exception();

echo 'unreachable';
}

/** @return never */
function alwaysThrows(): never
{
throw new Exception();
}
Loading