$b === false on a ternary must not leak its arm-narrowing out of the if$b = !$a ? $n : 0 then if ($b === false) proves $a is false *inside*
that branch (the : 0 arm is never false). After the if, $a is still
bool. PHPStan 2.2.15 compared the arms by truthiness in exact contexts, so
$a stayed false on the fall-through.
Source: phpstan/phpstan#15307, phpstan/phpstan-src#6580 (2.2.16).
References:
- phpstan#15307
- phpstan-src#6580| Analyzer | Version | Result | Diagnostics |
|---|---|---|---|
| phan | 6.0.10Phan 6.0.10 php-ast version 1.1.3 PHP version used to run Phan: 8.5.11 | Fail | No diagnostics reported. Expectation diffLine 38: Expected 1 error(s) |
| phpstan | 2.3.1PHPStan - PHP Static Analysis Tool 2.3.1 | Pass reported Lv.5+ | regressions_ternary_exact_narrowing.php:38: Parameter #1 $value of function Conformance\Tests\RegressionsTernaryExactNarrowing\takesFalse expects false, bool given. [reported-from-level=5] With strict-rulesregressions_ternary_exact_narrowing.php:38: Parameter #1 $value of function Conformance\Tests\RegressionsTernaryExactNarrowing\takesFalse expects false, bool given. |
| psalm | 6.20.0Psalm 6.20.0@804f03c0c183d149fcd4b8c7c0c9b3fbf836d34c next: 7.0.0-rc2 |
Pass | regressions_ternary_exact_narrowing.php:35: Argument 1 of Conformance\Tests\RegressionsTernaryExactNarrowing\takesFalse expects false, but bool provided [InvalidArgument] regressions_ternary_exact_narrowing.php:38: Argument 1 of Conformance\Tests\RegressionsTernaryExactNarrowing\takesFalse expects false, but bool provided [InvalidArgument] |
| mago | 1.57.0mago 1.57.0 | Pass | regressions_ternary_exact_narrowing.php:35: Possible argument type mismatch for argument #1 of `Conformance\Tests\RegressionsTernaryExactNarrowing\takesFalse`: expected `false`, but possibly received `bool`. [possibly-invalid-argument] regressions_ternary_exact_narrowing.php:38: Possible argument type mismatch for argument #1 of `Conformance\Tests\RegressionsTernaryExactNarrowing\takesFalse`: expected `false`, but possibly received `bool`. [possibly-invalid-argument] |
| mir | 0.87.0mir 0.87.0 | Fail | No diagnostics reported. Expectation diffLine 38: Expected 1 error(s) |
| phpantom | 0.11.1phpantom_lsp 0.11.1 | Fail | No diagnostics reported. Expectation diffLine 38: Expected 1 error(s) |
| intelephense | 1.18.5intelephense 1.18.5 | Pass | regressions_ternary_exact_narrowing.php:35: Expected type 'false'. Found 'bool'. [P1006] regressions_ternary_exact_narrowing.php:38: Expected type 'false'. Found 'bool'. [P1006] |
| phpactor | 2026.10.07.0Phpactor 2026.10.07.0 | Fail | No diagnostics reported. Expectation diffLine 38: Expected 1 error(s) |
| phpy | 1.0.19409phpy 1.0.19409 | Fail | No diagnostics reported. Expectation diffLine 38: Expected 1 error(s) |
| qodana | 262.10968.76PhpStorm 262.10968.76 | Fail | No diagnostics reported. Expectation diffLine 38: Expected 1 error(s) |
| noverify | 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 | Fail | regressions_ternary_exact_narrowing.php:35: potentially not safe call in function Conformance\Tests\RegressionsTernaryExactNarrowing\takesFalse signature of param value [notSafeCall] regressions_ternary_exact_narrowing.php:38: potentially not safe call in function Conformance\Tests\RegressionsTernaryExactNarrowing\takesFalse signature of param value [notSafeCall] regressions_ternary_exact_narrowing.php:39: potentially not safe call in function Conformance\Tests\RegressionsTernaryExactNarrowing\takesBool signature of param value [notSafeCall] Expectation diffLine 39: Expected valid value to be accepted, got ["potentially not safe call in function Conformance\\Tests\\RegressionsTernaryExactNarrowing\\takesBool signature of param value [notSafeCall]"] |
| steins | 0.1.8steins 0.1.8 (2026-09-21 revision 76306a9) | Fail | No diagnostics reported. Expectation diffLine 38: Expected 1 error(s) |
| phpstan-strict | 2.3.1PHPStan - PHP Static Analysis Tool 2.3.1 | Pass | regressions_ternary_exact_narrowing.php:38: Parameter #1 $value of function Conformance\Tests\RegressionsTernaryExactNarrowing\takesFalse expects false, bool given. |
| psalm-next | 7.0.0-rc2Psalm 7.0.0-rc2@20339e3fe5b8d03bcd8061030eb2a83ad50e5783 | Pass | regressions_ternary_exact_narrowing.php:35: Argument 1 of Conformance\Tests\RegressionsTernaryExactNarrowing\takesFalse expects false, but bool provided [InvalidArgument] regressions_ternary_exact_narrowing.php:38: Argument 1 of Conformance\Tests\RegressionsTernaryExactNarrowing\takesFalse expects false, but bool provided [InvalidArgument] |
<?php
declare(strict_types=1);
namespace Conformance\Tests\RegressionsTernaryExactNarrowing;
/**
* `$b === false` on a ternary must not leak its arm-narrowing out of the `if`.
*
* `$b = !$a ? $n : 0` then `if ($b === false)` proves `$a` is false *inside*
* that branch (the `: 0` arm is never false). After the `if`, `$a` is still
* `bool`. PHPStan 2.2.15 compared the arms by truthiness in exact contexts, so
* `$a` stayed `false` on the fall-through.
*
* Source: phpstan/phpstan#15307, phpstan/phpstan-src#6580 (2.2.16).
*
* References:
* - https://github.com/phpstan/phpstan/issues/15307
* - https://github.com/phpstan/phpstan-src/pull/6580
*/
function takesBool(bool $value): void
{
}
function takesFalse(false $value): void
{
}
function afterExactCheck(bool $a, int|false $n): void
{
$b = !$a ? $n : 0;
if ($b === false) {
takesFalse($a); // Q?: inside the branch, the `: 0` arm is impossible, so $a is false
}
takesFalse($a); // E: after the if, $a is still bool
takesBool($a); // V
}