Fix phpstan/phpstan#13591: False positive: Type narrowing not working with conditional exception validation#5385
Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Conversation
…l exception validation - When a compound condition like `$x === null && ($y === 'a' || $y === 'b')` throws, the negation creates conditional holders with union condition types (e.g. $y: 'a'|'b') - These union conditions could never be matched exactly by later narrowing like `$y === 'a'` - Fix: expand union condition types in conditional holders into separate holders for each union member, while keeping the original union holder for exact matches - New regression test in tests/PHPStan/Analyser/nsrt/bug-13591.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
When a compound condition like
if ($hotelId === null && ($action === 'get_rooms' || $action === 'update')) { throw ...; }guards subsequent code, PHPStan should narrow$hotelIdto non-null when$action === 'get_rooms'. This wasn't happening because the conditional expression holders created union condition types that couldn't be matched by more specific type narrowings.Changes
processBooleanSureConditionalTypesandprocessBooleanNotSureConditionalTypesinsrc/Analyser/TypeSpecifier.phpto expand union condition types into separate holdersexpandUnionConditionshelper method that takes a condition set and produces additional holder variants for each member of any union types in the conditionsRoot cause
When
$hotelId === null && ($action === 'get_rooms' || $action === 'update')is evaluated in the falsey context (after throwing), theprocessBooleanNotSureConditionalTypesmethod creates a conditional expression holder with condition{$action: 'get_rooms'|'update'}and result{$hotelId: int}. This means "if $action is exactly'get_rooms'|'update', then $hotelId is int".However, when entering
if ($action === 'get_rooms'), the scope narrows$actionto'get_rooms'— a single constant type, not the union'get_rooms'|'update'. Theequalscheck infilterBySpecifiedTypesrequires exact type equality, so the holder never matches.The fix expands the union condition into separate holders: one for
{$action: 'get_rooms'}and one for{$action: 'update'}, each pointing to the same result. This way, enteringif ($action === 'get_rooms')matches the first expanded holder exactly.Test
Added
tests/PHPStan/Analyser/nsrt/bug-13591.php— an NSRT test that verifies$hotelIdis narrowed toint(notint|null) insideif ($action === 'get_rooms')after the throwing guard.Fixes phpstan/phpstan#13591