Merge conditional-expression holders for the same target#5529
Merged
Conversation
`addConditionalExpressions` replaced any existing holders for a target
expression with the ones from the current assignment. That dropped
holders stored by earlier assignments whenever a later assignment
targeted the same expression.
Concretely:
$xIsA = $x instanceof A && $y instanceof A; // holder: $x when [$xIsA:true] => A
$yIsA = $y instanceof A && $x instanceof A; // overwrote $x's entry with $yIsA-keyed holder
so inside `if ($xIsA || $yIsA) { if ($xIsA) { … } }` the `$xIsA`-keyed
holder for `$x` (and `$y`) was already gone, leaving `$x`/`$y` un-narrowed
in the nested if.
Merge new holders with the existing ones, keyed by `$holder->getKey()`
for deduplication. This also makes the TypeCombinator::intersect
`$constArrayIsI || $constArrayIsJ` + ternary pattern around
TypeCombinator.php:1650 narrow correctly (fixes the undefined-method
errors PHPStan used to report on `$constArray` / `$otherArray`).
Updated the `bug-7716.php` and `multi-assign.php` regression tests: both
had expectations that were a consequence of the lost-holder bug, so the
fix tightens the inferred types (e.g. `$hasFoo = isset($a['foo']) && …`
now correctly marks `foo` as required in the truthy branch, and
`$foo = $bar = $baz = $b` narrows `$b` in `if ($bar)` / `if ($baz)`).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
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
MutatingScope::addConditionalExpressionsreplaced any existing conditional holders for a given target expression with the new ones. When multiple assignments in the same scope produced holders for the same target, the earlier ones were dropped.Concretely:
So inside
if (\$xIsA || \$yIsA) { if (\$xIsA) { … } }, the\$xIsA-keyed holder for\$xand\$ywas already gone and the nestedif (\$xIsA)left\$x/\$yun-narrowed.The fix: merge incoming holders with the existing ones in
addConditionalExpressions, keyed by\$holder->getKey()for deduplication.Same bug had been preventing the
\$constArrayIsI || \$constArrayIsJ+ ternary pattern aroundsrc/Type/TypeCombinator.php:1650from narrowing — the undefined-method errors PHPStan emitted on\$constArray/\$otherArray(isUnsealed,getValueTypes,getKeyTypes,isOptionalKey, call-intointersectDefiniteConstantArrays) are gone after this change.Test plan
or-condition-ternary-narrowing.phpcovers both the TypeCombinator shape (nested for-loop +\$types[\$i]+&&-extended booleans +if (|| )+ ternary) and a plain-variable reproducer exercising the truthy branch of the nestedif.bug-7716.phpandmulti-assign.phpexpectations updated — both were documenting the previous lost-holder behaviour; now\$hasFoo = isset(\$array['foo']) && …correctly marksfoorequired in the truthy branch, and\$foo = \$bar = \$baz = \$baliases all three, soif (\$bar)/if (\$baz)narrows\$b.tests/PHPStan/Analyser/NodeScopeResolverTest.php: 1543 tests pass; the single remaining failure (bug-14489.php) is pre-existing and unrelated.tests/PHPStan/Analyser/+tests/PHPStan/Rules/: goes from 23 → 20 baseline failures. Three tests that were previously failing (from the same lost-holder pattern) now pass; no new regressions.tests/PHPStan/Type/: no change (15 pre-existing failures unchanged).bin/phpstan analyse src/Type/TypeCombinator.php: the 1658/1661/1668/1669/1677 cluster of errors on\$constArray/\$otherArrayis gone.🤖 Generated with Claude Code
Closes phpstan/phpstan#12677