Fix phpstan/phpstan#10055: Conditional argument type not properly narrowing type (for constant string unions?)#5386
Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Conversation
- Split union condition types into individual ConditionalExpressionHolders - When TypeCombinator::intersect or ::remove produces a UnionType, each member gets its own holder so the equals() check can match individual constant types - New regression test in tests/PHPStan/Analyser/nsrt/bug-10055.php
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
Conditional parameter types (
@param ($param1 is 'value3' ? bool : int) $param2) fail to narrow correctly when the parameter union type has more than 2 members. The "false" branch condition becomes a UnionType that never matches individual constant types during theequals()check.Changes
src/Analyser/MutatingScope.phpto split union condition types into individualConditionalExpressionHolderinstances when creating holders for@paramconditional typestests/PHPStan/Analyser/nsrt/bug-10055.phpRoot cause
When processing
@param ($param1 is 'value3' ? bool : int) $param2with$param1typed as'value1'|'value2'|'value3':TypeCombinator::intersect('value1'|'value2'|'value3', 'value3')='value3'(works fine)TypeCombinator::remove('value1'|'value2'|'value3', 'value3')='value1'|'value2'(a UnionType)Later, when matching conditions in a
matcharm where$param1is narrowed to'value1', theequals()check comparesConstantStringType('value1')againstUnionType('value1'|'value2'), which always fails.The fix splits union condition types into individual holders: instead of one holder with condition
'value1'|'value2', two holders are created — one for'value1'and one for'value2'— both mapping to the same result type (int). This allows theequals()check to match correctly.Test
Added
tests/PHPStan/Analyser/nsrt/bug-10055.phpwhich verifies that in a match expression, conditional parameter types correctly narrow$param2tointfor'value1'and'value2'arms, and toboolfor the'value3'arm.Fixes phpstan/phpstan#10055