Create conditional expression holders for mixed sureType/sureNotType sides in BooleanAnd falsey and BooleanOr truthy#5763
Open
phpstan-bot wants to merge 1 commit into
Conversation
…sides in `BooleanAnd` falsey and `BooleanOr` truthy - When processing the falsey branch of `BooleanAnd` (or truthy branch of `BooleanOr`), conditional expression holders track relationships between variables in a disjunction (e.g. "if $a is null then $b is null"). - `processBooleanSureConditionalTypes` only handled the case where both sides had sureTypes, and `processBooleanNotSureConditionalTypes` only handled both sides having sureNotTypes. When one side had sureTypes and the other sureNotTypes (e.g. `$a === null && $b !== null`), no holders were created. - Fix: also call `processBooleanSureConditionalTypes` with normalized types, which converts sureNotTypes to sureTypes, covering the cross case. - Add `mergeHolders()` helper to properly combine holder arrays without `array_merge` overwriting string keys. - Applied the same fix to `BooleanOr` truthy context (the dual disjunction).
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 multiple
if (A && B) { return; }guards eliminate null combinations for two variables, PHPStan failed to narrow their types at the continuation point. For example:The root cause was in the conditional expression holder mechanism in
TypeSpecifier. When processing the falsey branch ofBooleanAnd(which is a disjunction by De Morgan's law), holders that track relationships between variables were only created when both sides of the&&produced the same kind of type specification (bothsureTypesor bothsureNotTypes). The "cross case" — where one side producessureTypesand the othersureNotTypes(e.g.,$a === nullproducessureNotTypeswhile$b !== nullproducessureTypesin the falsey context) — created no holders at all.Changes
src/Analyser/TypeSpecifier.php:BooleanAndfalsey handler: additionally callprocessBooleanSureConditionalTypeswith normalized types (which convertsureNotTypestosureTypes), covering the mixed sureType/sureNotType caseBooleanOrtruthy handler: apply the same fix (this is the dual disjunction case)mergeHolders()private method to properly merge holder arrays withoutarray_mergeoverwriting entries that share the same expression string keytests/PHPStan/Analyser/nsrt/bug-3385.php: regression test covering the three-guard pattern, reversed operand order,||-combined guards, two-guard variant, nested ifs (control), andBooleanOrtruthy conditional holder casesRoot cause
processBooleanSureConditionalTypesiterates overgetSureTypes()from both the left and right side of the&&/||condition.processBooleanNotSureConditionalTypesiterates overgetSureNotTypes(). Neither method handled the case where one side hadsureTypesand the other hadsureNotTypes.By normalizing the types (which calls
TypeCombinator::remove(scopeType, sureNotType)to convertsureNotTypesinto equivalentsureTypes), the existingprocessBooleanSureConditionalTypeslogic creates the correct holders for all combinations.The
BooleanOrtruthy context has the same disjunction structure and was fixed with the same approach.Test
tests/PHPStan/Analyser/nsrt/bug-3385.phpwithassertTypecalls verifying:if (A && B) { return; }guards narrow both nullable parameters to non-null||&&and the next is a simple null checkBooleanOrtruthy conditional holders for the analogous caseFixes phpstan/phpstan#3385