Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Merge conditional-expression holders for the same target#5529

Merged
ondrejmirtes merged 2 commits into
2.1.xfrom
ternary-dependent
Apr 24, 2026
Merged

Merge conditional-expression holders for the same target#5529
ondrejmirtes merged 2 commits into
2.1.xfrom
ternary-dependent

Conversation

@ondrejmirtes
Copy link
Copy Markdown
Member

@ondrejmirtes ondrejmirtes commented Apr 24, 2026

Summary

MutatingScope::addConditionalExpressions replaced 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:

\$xIsA = \$x instanceof A && \$y instanceof A;  // stores: \$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 and the nested if (\$xIsA) left \$x/\$y un-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 around src/Type/TypeCombinator.php:1650 from narrowing — the undefined-method errors PHPStan emitted on \$constArray / \$otherArray (isUnsealed, getValueTypes, getKeyTypes, isOptionalKey, call-into intersectDefiniteConstantArrays) are gone after this change.

Test plan

  • New NSRT or-condition-ternary-narrowing.php covers both the TypeCombinator shape (nested for-loop + \$types[\$i] + &&-extended booleans + if (|| ) + ternary) and a plain-variable reproducer exercising the truthy branch of the nested if.
  • bug-7716.php and multi-assign.php expectations updated — both were documenting the previous lost-holder behaviour; now \$hasFoo = isset(\$array['foo']) && … correctly marks foo required in the truthy branch, and \$foo = \$bar = \$baz = \$b aliases all three, so if (\$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/\$otherArray is gone.

🤖 Generated with Claude Code

Closes phpstan/phpstan#12677

ondrejmirtes and others added 2 commits April 24, 2026 19:16
`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]>
@ondrejmirtes ondrejmirtes merged commit 6885529 into 2.1.x Apr 24, 2026
127 of 128 checks passed
@ondrejmirtes ondrejmirtes deleted the ternary-dependent branch April 24, 2026 17:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant