| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
getIterableKeyType(), getFirstIterableKeyType() and getLastIterableKeyType() each spelled out the same "implicit mixed tail means array-key" normalization. The first/last pair was also missing the reportUnsafeArrayStringKeyCasting cast that getIterableKeyType() right next to them already applied. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
fillKeysArray() on a general array only called toString(), so array_fill_keys() disagreed with array_flip() right next to it: `list<decimal-int-string>` gave `array<decimal-int-string, …>` instead of `array<int, …>`, and `list<float>` kept a numeric-string key instead of the int PHP actually stores. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
PHP casts a decimal-integer string array key ("123") to int, so
`array_key_first([$string => null])` is not necessarily a string. PHPStan
inferred `string` for it and reported `is_int()` on the result as always false.
UnsafeArrayStringKeyCastingTraverser already modelled that cast, but only for
the key type an array *has* — a type that also decides how the array describes
itself and what it accepts, which is why only
`reportUnsafeArrayStringKeyCasting: detect` widens it. Widening it with the
toggle off turns `array<string, X>` into `array<X>` everywhere.
castReadKeyType() is the second entry point, for a key that leaves the array as
a value of its own. With the toggle off it widens `string` to the benevolent
`(int|string)`, so neither branch reports anything; `detect` and `prevent` keep
the types they have today. unionWithReadKeyType() adds the `null` an empty array
gives back without losing the benevolence on the way.
The remaining accessors follow in the next commit.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
PHP casts a decimal-integer string array key ("123") to int, so
`array_key_first([$string => null])` is not necessarily a string. PHPStan
inferred `string` for it and reported `is_int()` on the result as always false.
array_key_first() and array_key_last() went first; call castReadKeyType() from
the rest of the accessors that hand a key back as a value of its own:
array_keys(), key(), array_find_key(), array_search() and the values of
array_flip().
`foreach` keys stay unwidened with the toggle off: the key usually goes straight
back into another array, and a benevolent `(int|string)` key collapses that
array to `array<mixed, …>`. `detect` remains the level with accurate `foreach`
keys.
The rename in OptimizedDirectorySourceLocatorFactory is fallout: with the wider
`array_keys()` type PHPStan now proves `$file` defined inside
`if ($findInFiles !== [])`, which is correct and makes strict-rules flag the
reuse.
The `@var int|string` workarounds over `key([$string => null])` in
ArgumentsNormalizer and ConstantStringType are what this bug looks like from the
inside; both are redundant now and go away with their baseline entries. The two
calls also read better as array_key_first(), which says what they are after.
Closes phpstan/phpstan#15073
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
| Back | FazBrowse Home | New Git URL |
Closes phpstan/phpstan#15073
Continues the numeric-string / decimal-int-string consistency work of #3326, phpstan/phpstan#6847 and #5328.
Problem
PHP casts a decimal-integer string array key ("123") to int, so a key read out of an array
with a string key type can be an int:
array_key_last(), array_keys()[0], key(), array_find_key(), array_search() and the values of array_flip() have the same problem.
Change
UnsafeArrayStringKeyCastingTraverser already models this cast for the key type an array has. That type also drives describe(), accepts() and type subtraction, which is why only reportUnsafeArrayStringKeyCasting: detect widens it. I tried widening it with the toggle off: array<string, X> turns into array<X> everywhere and 113 tests fail, including mixed~array<string, mixed> losing every array.
So this adds a second entry point, for keys that leave the array as a value:
The benevolent union keeps new errors out: is_int($key) reports maybe instead of "always false", and strlen($key) still passes. unionWithReadKeyType() exists because TypeCombinator::union() drops the benevolence, which would break strlen(array_key_first($a) ?? '') on a possibly empty array<string, …>.
Left alone
Also in here
The first two commits stand on their own:
Two more things ride along in the third commit. The @var int|string over key([$string => null]) in ArgumentsNormalizer and ConstantStringType covered for this bug; both are gone, with their baseline entries. The $file rename in OptimizedDirectorySourceLocatorFactory is fallout: PHPStan now proves $file defined inside if ($findInFiles !== []), so strict-rules flags the reuse.
Tests
nsrt/bug-15073.php is new and fails on 8 assertions without the src/ change. The detect fixture gets read-key cases. Other expectation updates come in two shapes: int|string|null to (int|string|null) where the benevolence survives now, and string to (int|string) where the fix applies.