Fix phpstan/phpstan#13921: spurious error nullCoalesce.offset#5133
Closed
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Closed
Fix phpstan/phpstan#13921: spurious error nullCoalesce.offset#5133phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Conversation
…types - Fixed false positive where `$x[0] ?? null` was flagged as unnecessary after `$x[0]['bar'] ?? null` when the inner type was nullable (e.g. list<array<?string>>) - Root cause: revertNonNullability used specifyExpressionType which recursively creates expression type holders for parent arrays as side effects, leaving spurious Yes-certainty expression types for intermediate array dim fetches - Added sideEffectSave flag to EnsuredNonNullabilityResultExpression to distinguish target saves (null actually removed) from side-effect saves (type changed by recursive parent narrowing) - After reverting, unset expression types for side-effect saves that didn't exist in the original scope - Added unsetExpressionType method to MutatingScope - New regression test in tests/PHPStan/Rules/Variables/data/bug-13921.php Closes phpstan/phpstan#13921
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
Fixes a spurious
nullCoalesce.offseterror where$x[0] ?? nullwas incorrectly flagged as "always exists and is not nullable" after$x[0]['bar'] ?? nullwhen the inner array type contained nullable values (e.g.,list<array<?string>>).Changes
sideEffectSaveflag toEnsuredNonNullabilityResultExpressionto distinguish target saves (where null was actually removed) from side-effect saves (where the type was changed by recursive parent narrowing inspecifyExpressionType)ensureShallowNonNullabilityinsrc/Analyser/NodeScopeResolver.phpto mark parent saves and early-return saves as side-effect saves, and track whether expressions existed in the original scoperevertNonNullabilityinsrc/Analyser/NodeScopeResolver.phpto clean up spurious expression types after reverting: side-effect saves that didn't exist in the original scope are unsetunsetExpressionTypemethod tosrc/Analyser/MutatingScope.phpfor removing expression type holders without recursive side effectstests/PHPStan/Rules/Variables/data/bug-13921.phpand test method inNullCoalesceRuleTestRoot cause
When processing
$x[0]['bar'] ?? null,ensureNonNullabilitycallsspecifyExpressionTypeto remove null from$x[0]['bar']. This method recursively narrows parent array types as a side effect, creating expression type holders for$x[0]and$xwithYescertainty. After processing,revertNonNullabilityrestores the saved types usingspecifyExpressionType, which again creates recursive side effects. While ancestor types ($x) are properly restored by later entries in the save list, intermediate expressions ($x[0]) retain spuriousYes-certainty expression type holders.The
IssetCheckrule checks$scope->hasExpressionType($expr)->yes()as an alternative to$type->hasOffsetValueType($dimType)->yes(). The spurious expression type for$x[0]made this check returntrue, causing the false positive on$x[0] ?? null.The fix distinguishes between target saves (the actual expression whose null was removed) and side-effect saves (expressions changed as a side effect of recursive parent narrowing). After reverting, side-effect saves that didn't have expression types in the original scope are cleaned up.
Test
Added regression test
tests/PHPStan/Rules/Variables/data/bug-13921.phpthat reproduces the exact scenario from the issue: a function withlist<array<?string>>parameter where$x[0]['bar'] ?? nullis followed by$x[0] ?? null. The test expects no errors (empty expected errors array).Fixes phpstan/phpstan#13921