Skip "might not exist" report for string types with integer offsets in union decomposition check#5705
Closed
phpstan-bot wants to merge 1 commit into
Closed
Conversation
…n union decomposition check - In NonexistentOffsetInArrayDimFetchCheck, the reportMaybes decomposition loop flattens union types and checks each member individually. When a constant string member (e.g. '') returns "no" for an integer offset (e.g. 0), it incorrectly triggers "might not exist" even though the union-level check correctly returns "maybe". - Skip the report when innerType is a string and innerDimType is an integer, since string offset validity is length-dependent and users typically guard with length checks through variables that PHPStan cannot track. - The fix does not affect: definite "does not exist" errors (caught earlier), non-integer offsets on strings (objects, floats, strings still report), or array offset checks (completely unaffected). - Probed analogous cases: non-integer dim types on strings, array offset decomposition, ArrayDestructuringRule (shares the same check) — all already correct.
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 accessing a string offset with an integer index on a union of constant strings (e.g.
''|':'), thereportMaybesdecomposition inNonexistentOffsetInArrayDimFetchCheckincorrectly reports "Offset 0 might not exist" because the empty string member returnsnofor offset 0. The union-levelhasOffsetValueTypecorrectly returnsmaybe, but the per-member decomposition is too aggressive for string types where offset validity depends on length.Changes
src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php: In the inner decomposition loop, skip the "might not exist" report when$innerType->isString()->yes()and$innerDimType->isInteger()->yes(). This prevents false positives from short string members in unions while preserving all other offset checks.tests/PHPStan/Rules/Arrays/data/bug-13688.phpwith the exact reproducer from the issue (empty string in union with strlen guard) plus an additional case for mixed-length constant string unions.testBug13688inNonexistentOffsetInArrayDimFetchRuleTest.php.Root cause
The
reportMaybesblock inNonexistentOffsetInArrayDimFetchCheckdecomposes both the container type and the dim type into individual members and checks every combination. When the container is a union of constant strings like''|':'and the dim is an integer like0:hasOffsetValueType(0)correctly returnsmaybeConstantStringType('')->hasOffsetValueType(ConstantIntegerType(0))returnsno(empty string has no valid offsets)For arrays, this decomposition is valuable because array keys are structural properties. For strings, offset validity is a function of string length, which users typically guard with length checks through variables that PHPStan cannot track (e.g.
$len = strlen($s); if ($len > 0) { ... $s[$len-1] ... }).The fix skips the per-member report specifically for string+integer combinations. This does not affect:
->no()check on the full type)isString()->yes()is false for arraysProbed analogous cases that are already correct:
ArrayDestructuringRulesharesNonexistentOffsetInArrayDimFetchCheckand benefits from the same fixisInteger()->yes()guard preserves these reportsisString()->yes()guard preserves these reportsTest
testBug13688: Regression test with the exact reproducer from the issue —strlen()guard in&&with string offset access on''|':'union. Also tests that accessing offset 0 on a mixed-length constant string union ('a'|'abc') does not produce a false positive.Fixes phpstan/phpstan#13688