Add regression test for $this narrowing to never inside isset()-guarded boolean decomposition#5840
Open
phpstan-bot wants to merge 1 commit into
Open
Conversation
…uarded boolean decomposition - Adds tests/PHPStan/Analyser/nsrt/bug-14804.php reproducing the reported case verbatim from the issue's container code: an outer `$enum === X && isset($this->prop[$key])` guard followed by an inner `isset($this->other[$key])` block and a nested `if ($enum === X)`, where `$this` was incorrectly narrowed to `*NEVER*`. - The test asserts `$this` keeps its `$this(Bug14804\Container)` type both before and inside the nested narrowing, and that `$lifecycle` stays the full enum union. - The root fix already lives in ConditionalExpressionHolderHelper: the boolean-decomposition holder that collapsed an expression to `NeverType` (an artifact of the cross-kind holder + isset() truthy/falsey swap) is skipped. Verified the new test fails with `*NEVER*` when that guard is removed and passes with it. - Probed analogous constructs (empty(), array_key_exists(), `??`, BooleanOr early returns, static properties, narrowing non-`$this` locals and match results, and deeper nesting) — all already handled by the shared guard, no residual bug.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PHPStan 2.2.2 introduced a regression where, inside an
if (isset($this->prop[$key]))block that followed an earlier compound
if ($enum === X && isset($this->other[$key]))guard,
$thiswas narrowed to*NEVER*. This marked the rest of the block unreachableand produced cascading false positives such as
Cannot access offset *NEVER* on mixed.The root cause was already fixed on the
2.2.xbranch (thenever-narrowing guard inConditionalExpressionHolderHelper). This change adds the missing regression test thatcaptures the exact reproduction from the issue and proves the family of cases is covered.
Changes
tests/PHPStan/Analyser/nsrt/bug-14804.php, a faithful copy of the containerresolve()method from the issue, asserting that$thiskeeps its$this(Bug14804\Container)type before and inside the nestedif ($lifecycle === Lifecycle::PERSISTENT)block, and that
$lifecyclestaysLifecycle::PERSISTENT|Lifecycle::TRANSIENT.Root cause
The cross-kind conditional-expression holders created when decomposing a
BooleanAnd/BooleanOrcondition containingisset()could compute a holder typethat collapses to
NeverType(e.g. removing a non-nullable property's full type afterthe isset() truthy/falsey swap). Applying such a holder in a later scope narrowed an
unrelated expression — here
$this— tonever, marking the whole scope unreachable.ConditionalExpressionHolderHelpernow skips any holder whose computed narrowing isNeverTypewhile the original expression type is notnever, since that result is anartifact rather than a real contradiction. The guard lives in the shared helper, so it
covers instance/static properties,
isset()/empty(), and both theBooleanAnd(false context) and
BooleanOr(true context) paths.Test
tests/PHPStan/Analyser/nsrt/bug-14804.phpreproduces the reported bug. It fails withActual: *NEVER*when theNeverTypeguard inConditionalExpressionHolderHelperisremoved and passes with it in place.
empty(),array_key_exists(),??,BooleanOrearly returns, static properties, narrowing non-$thislocals andmatchresults, and deeper nesting — and confirmed each is already correctly handled by the
shared guard, so no further source change is required.
Fixes phpstan/phpstan#14804