Skip to content

Normalize array keys through toArrayKey() in array_fill_keys and array_combine return types#6078

Open
phpstan-bot wants to merge 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-ykjolgr
Open

Normalize array keys through toArrayKey() in array_fill_keys and array_combine return types#6078
phpstan-bot wants to merge 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-ykjolgr

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

Documenting an array key as int|decimal-int-string normalizes to int, but array_fill_keys() did not perform this normalization on its result, producing array<int|decimal-int-string, ...>. Assigning that to an array<int, ...> property triggered a false positive. The fix runs the computed key type through Type::toArrayKey(), which coerces integer-like string keys to int exactly like PHP does at runtime.

Changes

  • src/Type/ArrayType.phpfillKeysArray() now returns new ArrayType($keyType->toArrayKey(), $valueType) in both branches, so the resulting key type is coerced the way PHP coerces array keys (this matches flipArray(), which already used toArrayKey()).
  • src/Type/MixedType.phpfillKeysArray() normalizes the key type through toArrayKey() as well, so array_fill_keys() on a mixed array produces an int|string key instead of mixed.
  • src/Type/Php/ArrayCombineHelper.php — the non-constant array_combine() path had the same missing normalization; the key type is now passed through toArrayKey().
  • Updated test expectations in array-fill-keys.php, array-fill-keys-php8.php and array-combine-php8.php that asserted the previous, un-normalized keys.
  • Added tests/PHPStan/Analyser/nsrt/bug-14980.php.

Root cause

array_fill_keys() (and array_combine(), and array_fill_keys() on mixed) use the input array's values as keys. PHP coerces those values with the usual array-key rules (decimal-int-strings become int, floats/bools/objects are stringified first, then re-coerced). PHPStan's Type::toArrayKey() models exactly this coercion, and array_flip()/array_count_values()/array_column() already used it — but the array_fill_keys and non-constant array_combine paths built the key type with toString() (or returned it verbatim) and never applied toArrayKey(). As a result the refinement (decimal-int-string, plain mixed, un-truncated floats, bool) leaked into the key type. The pattern is "value-as-key array functions must normalize the key through toArrayKey()"; every affected location now does.

Test

  • bug-14980.php asserts array_fill_keys() on list<int|decimal-int-string> yields array<int, true> (the reported case), plus the numeric-string, mixed-array and array_combine analogues.
  • Existing array-fill-keys / array-combine fixtures were updated to the now-correct normalized keys (float keys include int, bool keys collapse to int|string, decimal-int-string keys collapse to int).

Fixes phpstan/phpstan#14980

…`array_combine` return types

- `ArrayType::fillKeysArray()` now passes the resulting key type through `toArrayKey()`, so `decimal-int-string` (and other integer-like string keys) collapse to `int`, matching how PHP coerces array keys. This mirrors what `ArrayType::flipArray()` already does.
- `MixedType::fillKeysArray()` normalizes the key type the same way, so `array_fill_keys` on a `mixed` array no longer yields a `mixed` key type (array keys can only be `int|string`).
- `ArrayCombineHelper` (the non-constant path of `array_combine`) had the identical missing normalization and is fixed the same way.
- Probed the other value-as-key functions: `array_flip`, `array_count_values` and `array_column` already normalize via `toArrayKey()` and were correct; no change needed there.
- Updated existing `array-fill-keys` / `array-combine` expectations that encoded the un-normalized keys (float keys now include `int`, `bool` keys collapse to `int|string`, decimal-int-string keys collapse to `int`).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

array_fill_keys does not convert decimal-int-string to int

1 participant