Narrow string type through variable assigned from strlen()/mb_strlen() via conditional expression holders#5706
Closed
phpstan-bot wants to merge 1 commit into
Closed
Conversation
…en()` via conditional expression holders - When `$len = strlen($str)` is assigned and later `$len > 0` is checked, PHPStan now narrows `$str` to `non-empty-string` - When `$len >= 2` is checked, `$str` is narrowed to `non-falsy-string` - Implemented via ConditionalExpressionHolder in TypeSpecifier for the Assign null context, matching the existing pattern for array_key_first and array_search - Also handles `mb_strlen()` with the same narrowing rules - Conditional holders are correctly invalidated when the variable is re-assigned to a non-strlen value Closes phpstan/phpstan#13688
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
strlen()ormb_strlen()result is assigned to a variable and that variable is later compared (e.g.$len > 0), PHPStan now propagates the narrowing back to the original string argument. Previously, only direct comparisons likestrlen($str) > 0narrowed the string type; an intermediate variable broke the narrowing chain, causing false positives like "Offset 0 might not exist on''|':'".Changes
src/Analyser/TypeSpecifier.php: Added conditional expression holders for$var = strlen($str)and$var = mb_strlen($str)assignments in the null (assignment) context. Two holders are created per assignment:$var >= 1(int<1, max>), narrow$strtonon-empty-string$var >= 2(int<2, max>), narrow$strtonon-falsy-stringAdded new private method
createStrlenConditionalExpressionHolders()following the same pattern ascreateArrayDimFetchConditionalExpressionHolder().Root cause
When code like this was analysed:
The TypeSpecifier already had handlers for narrowing strings when
strlen()appears directly in a comparison (e.g.strlen($str) > 0). However, when the result was assigned to an intermediate variable first, the connection between the variable and thestrlen()call was lost. The comparison$inputLen > 0only narrowed$inputLen(toint<1, max>), not$input.The fix uses PHPStan's existing
ConditionalExpressionHoldermechanism to create a deferred relationship at assignment time: "if$inputLenis later narrowed toint<1, max>, then also narrow$inputtonon-empty-string". This mechanism is already used successfully forarray_key_first(),array_search(), and similar functions.Analogous cases probed
mb_strlen(): Included in the fix with the same narrowing rulescount()/sizeof(): Already works through variables (verified — has existing narrowing mechanism)$len = 5) correctly invalidates the conditional holders> 0,>= 1,!== 0,>= 2,> 1all work correctly through the holder mechanism$obj->name) as the strlen argument also narrows correctlyTest
tests/PHPStan/Analyser/nsrt/bug-13688.php): Tests type narrowing through variable for constant string unions, non-empty-string, non-falsy-string, mb_strlen,!== 0comparison,=== 0non-narrowing, and re-assignment invalidation.tests/PHPStan/Rules/Arrays/data/bug-13688.php+NonexistentOffsetInArrayDimFetchRuleTest::testBug13688): Verifies the original false positive "Offset 0 might not exist" is no longer reported.Fixes phpstan/phpstan#13688