Fix #5051: Execution branch confusion#5058
Merged
staabm merged 5 commits intophpstan:2.1.xfrom Feb 27, 2026
Merged
Conversation
…if/else - Changed createConditionalExpressions to create pairwise conditional holders instead of one holder with all guards - In a binary branch merge, any single type guard uniquely identifies the branch, so individual guards are sufficient - Previously, 3+ type guards created holders requiring all-minus-one conditions, causing circular dependency in resolution - New regression test in tests/PHPStan/Analyser/nsrt/bug-5051.php Closes phpstan/phpstan#5051
Contributor
|
@phpstan-bot add regression test for phpstan/phpstan#9647 |
staabm
approved these changes
Feb 27, 2026
Contributor
staabm
left a comment
There was a problem hiding this comment.
I think this looks good. I checked performance and I could not measure a meaningfull regression
Member
|
The comment reactions don't work here. But you should be able to push to the fork. |
VincentLanglet
approved these changes
Feb 27, 2026
This was referenced Feb 27, 2026
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
When an if/else block assigned multiple variables alongside a boolean flag correlated with a null check, PHPStan failed to narrow the nullable variable through the boolean flag. For example:
Without the
$fooassignments, the narrowing worked correctly. The issue was that extra variable assignments in both branches created multi-condition guards that couldn't be resolved.Changes
MutatingScope::createConditionalExpressions()insrc/Analyser/MutatingScope.phpto create pairwise conditional expression holders instead of one holder per variable with all other type guards as conditionsRoot cause
When merging two branches of an if/else,
createConditionalExpressions()identified variables that differed between branches as "type guards" and createdConditionalExpressionHolderinstances. Each holder required ALL other type guards to match before the guarded variable's type could be resolved.With 2 type guards (e.g.,
$dataand$update), each holder had exactly 1 condition — which could be matched directly when the user checkedif ($update).With 3+ type guards (e.g.,
$data,$foo,$update), each holder had 2+ conditions, creating a circular dependency: resolving$datarequired$fooAND$updateto be known, but resolving$foorequired$dataAND$update. The iterative resolution loop infilterBySpecifiedTypes()couldn't break this cycle.The fix creates individual pairwise holders instead. In a binary branch merge, knowing any single type guard's value uniquely identifies which branch was taken, so each guard is independently sufficient. This allows the resolution loop to resolve variables step by step.
Test
Added
tests/PHPStan/Analyser/nsrt/bug-5051.phpwith four test methods covering:Fixes phpstan/phpstan#5051
Fixes phpstan/phpstan#9647