Fix phpstan/phpstan#14455: missing has-offset via conditional type#5446
Closed
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Closed
Fix phpstan/phpstan#14455: missing has-offset via conditional type#5446phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Conversation
- Added processBooleanNotSureSureConditionalTypes method to TypeSpecifier that cross-pairs sureNotType conditions with sureType results - Registered the new method in both BooleanAnd (false context) and BooleanOr (true context) conditional holder generation - New regression test in tests/PHPStan/Analyser/nsrt/bug-14455.php - Root cause: existing methods only paired sureTypes with sureTypes and sureNotTypes with sureNotTypes, missing the cross-pairing needed when one side of a compound condition produces sureNotTypes and the other produces sureTypes (e.g. empty($arr['key']) && $type === 'filter')
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 (empty($aggregation['field']) && $type === 'filter') { return; }was used as an early return, PHPStan failed to narrow$aggregationtohasOffset('field')inside a subsequentif ($type === 'filter')branch.The type system correctly tracks that after the early return,
!empty($aggregation['field']) || $type !== 'filter'holds. However, when later checking$type === 'filter', it should deduce via modus tollens that!empty($aggregation['field'])must be true, meaning$aggregationhas the'field'offset. This deduction was not happening.Changes
processBooleanNotSureSureConditionalTypesmethod tosrc/Analyser/TypeSpecifier.phpthat creates conditional expression holders by cross-pairingsureNotTypeconditions withsureTyperesultsBooleanAnd(false context) andBooleanOr(true context) conditional holder generation alongside the existing four methodsRoot cause
The
TypeSpecifiercreatesConditionalExpressionHolderobjects to track deferred type narrowing relationships in compound boolean conditions. Two existing methods handle this:processBooleanSureConditionalTypes: pairs sureTypes (conditions) with sureTypes (results)processBooleanNotSureConditionalTypes: pairs sureNotTypes (conditions) with sureNotTypes (results)In the bug scenario,
empty($aggregation['field'])in the falsey context produces a sureType for$aggregation(HasOffsetType), while$type === 'filter'in the falsey context produces a sureNotType for$type(ConstantStringType). Since no method cross-paired sureNotTypes with sureTypes, the conditional relationship was never recorded.Additionally, the existing
processBooleanSureConditionalTypescouldn't use the HasOffsetType as a condition becauseTypeCombinator::remove(array<string, mixed>, HasOffsetType('field'))returns the same type (the condition is "trivially true"), causing it to be skipped.Test
Added
tests/PHPStan/Analyser/nsrt/bug-14455.phpwhich reproduces the exact scenario from the issue: an early return withempty($arr['key']) && $type === 'filter', followed by a check on$type === 'filter'where$arrshould be narrowed tonon-empty-array<string, mixed>&hasOffset('field').Fixes phpstan/phpstan#14455