Skip to content

Fix #14178: Regression about complicated conditions#5025

Merged
ondrejmirtes merged 1 commit into2.1.xfrom
create-pull-request/patch-xjljo5o
Feb 22, 2026
Merged

Fix #14178: Regression about complicated conditions#5025
ondrejmirtes merged 1 commit into2.1.xfrom
create-pull-request/patch-xjljo5o

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

Fixes a regression introduced in 3daa312 (Fix #13303) where PHPStan failed to narrow nullable types through chained conditional expression holders. When boolean variables like $exists = $obj !== null were used in compound conditions, the type of $obj was not properly narrowed inside subsequent if blocks.

Changes

  • Modified MutatingScope::filterBySpecifiedTypes() in src/Analyser/MutatingScope.php to iterate over conditional expression holders in a loop until no new specified expressions are discovered (convergence loop)
  • Updated tests/PHPStan/Analyser/nsrt/dependent-variable-certainty.php and tests/PHPStan/Analyser/nsrt/dependent-expression-certainty.php to reflect improved certainty (the comment "could be Yes" confirmed this was a known limitation)
  • Added regression test tests/PHPStan/Analyser/nsrt/bug-14178.php

Root cause

The mergeConditionalExpressions() method introduced in 3daa312 changed the iteration order of conditional expression holders compared to the old array_merge() approach. Previously, new holders (from boolean conditions like !$a && !$b) came before existing holders (from assignments like $exists = $obj !== null). The new method reversed this order by starting with existing holders.

In filterBySpecifiedTypes(), conditional expression holders are processed in a single pass: when one holder fires, it adds to specifiedExpressions, which may enable subsequent holders. With the reversed order, a holder like "when $newVersionExists is true, $newVersion is non-null" was evaluated before the holder "when $previousVersionExists is false, $newVersionExists is true" had fired, so $newVersionExists wasn't yet in specifiedExpressions.

The fix makes filterBySpecifiedTypes order-independent by looping until convergence — it keeps iterating as long as new expressions are being resolved, allowing chains of any depth to propagate correctly.

Test

The regression test tests/PHPStan/Analyser/nsrt/bug-14178.php reproduces the exact scenario from the issue: two nullable parameters with boolean tracking variables, compound conditions that eliminate impossible combinations, and a subsequent if block where the type should be narrowed to non-null.

Fixes phpstan/phpstan#14178

- Iterating conditional expression holders in filterBySpecifiedTypes
  now loops until convergence, making it order-independent
- The previous commit (3daa312, Fix #13303) changed
  mergeConditionalExpressions to put existing holders before new ones,
  breaking chains where a new holder must fire before an existing one
- Updated dependent-variable-certainty and dependent-expression-certainty
  tests to reflect improved certainty resolution (Maybe -> Yes)
- New regression test in tests/PHPStan/Analyser/nsrt/bug-14178.php

Fixes phpstan/phpstan#14178
@ondrejmirtes ondrejmirtes merged commit 676555f into 2.1.x Feb 22, 2026
638 of 642 checks passed
@ondrejmirtes ondrejmirtes deleted the create-pull-request/patch-xjljo5o branch February 22, 2026 18:06
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.

2 participants