Skip to content

Stop tracking the next implicit array key once it reaches PHP_INT_MAX - #6461

Merged
ondrejmirtes merged 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-gxg3i7n
Sep 17, 2026
Merged

ondrejmirtes merged 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-gxg3i7n

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

[9223372036854775807 => 1, 2] crashed the analysis of the whole file with
ConstantIntegerType::__construct(): Argument #1 ($value) must be of type int, float given.

Several places in PHPStan track "what is the next implicit array key" by incrementing an
int. When the tracked index is already PHP_INT_MAX, + 1 silently overflows to a
float, and the float is then handed to a constructor that requires an int. PHP itself
does not wrap around here — it throws Error: Cannot add element to the array as the next element is already occupied — so the right answer is to stop tracking implicit keys, which
is the "unknown next key" state each of these places already has.

Changes

  • src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php — the reported crash. When
    $autoGeneratedIndex === PHP_INT_MAX, set it to false (the existing "cannot be sure of
    the next keys" state) and skip the item instead of incrementing.
  • src/Type/Constant/OversizedArrayBuilder.php — the same crash on array literals with
    more than ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT items. $nextAutoIndex is now
    int|null; once it overflows, unkeyed items get a plain IntegerType key and the result
    is no longer treated as a list.
  • src/Type/Php/ArrayFillFunctionReturnTypeExtension.php — the same crash for
    array_fill(9223372036854775806, 3, 'x'). The constant-array loop stops at PHP_INT_MAX
    and, if entries remain, the extension falls through to the general
    array<int, T> result instead of building an impossible shape.
  • src/Analyser/ExprHandler/AssignHandler.php — processArrayByRefItems() had the same
    + 1 overflow, which reached PhpParser\Node\Scalar\Int_::__construct(int $value) and
    crashed on $a = [9223372036854775807 => 1, &$b];. $implicitIndex now becomes null,
    the state the method already uses for unpredictable indices.
  • Spread items in the same two implicit-index trackers
    (DuplicateKeysInLiteralArraysRule, AssignHandler::processArrayByRefItems()) were
    counted as exactly one element. An unpacked array contributes an unknown number of
    renumbered integer keys, so both now reset implicit-key tracking on $item->unpack.
  • Probed and found already correct, so left alone:
    ConstantArrayTypeBuilder::setOffsetValueType() (all three auto-index sites already
    guard with is_float()), TypeCombinator (its $nextAutoIndex is only ever compared,
    never turned into a type), ConstantArrayType (its counters are bounded by the shape
    size), and the generator yield key inference in ClosureTypeResolver (it uses a plain
    IntegerType, it does not track indices).

Root cause

The pattern is "advance the implicit array key with $index + 1 and build a
ConstantIntegerType / Int_ from the result". At PHP_INT_MAX the addition produces a
float, and both constructors are int-typed under strict_types, so the analysis of the
whole file aborts with an internal error. Every affected site already had a representation
for "the next implicit key is unknown" (false, null, or falling back to a general array
type); the fix routes the overflow into that state rather than inventing an out-of-range
key.

The related spread bug is the same tracker being wrong for a different reason: it assumed
...$b occupies exactly one integer key.

Test

  • tests/PHPStan/Rules/Arrays/data/bug-15244.php + testBug15244() in
    DuplicateKeysInLiteralArraysRuleTest — the reported reproducer, the numeric-string key
    variant ['9223372036854775807' => 1, 2], the variant that reaches PHP_INT_MAX by
    incrementing ([9223372036854775806 => 1, 2, 3]), a case where the implicit key genuinely
    is a duplicate of a later explicit PHP_INT_MAX key (still reported), and the spread cases
    ['a', ...$b, 1 => 'x'] / ['a', ...[], 1 => 'x'] / ['a', ...['k' => 1], 1 => 'x']
    (no longer reported) alongside ['a', ...$b, 0 => 'x'] (still reported).
    Without the fix this test errors with the TypeError.
  • tests/PHPStan/Analyser/nsrt/bug-15244.php — assertType() coverage for the three other
    crash sites: array_fill() at and across PHP_INT_MAX, an array literal with a by-ref
    item after a PHP_INT_MAX key, and a >256-item literal starting near PHP_INT_MAX.
    Without the fix this test errors with the TypeError too.

Fixes phpstan/phpstan#15244

- `DuplicateKeysInLiteralArraysRule`: give up on `$autoGeneratedIndex` when it
  is already `PHP_INT_MAX` instead of `new ConstantIntegerType(++$autoGeneratedIndex)`,
  which overflowed to float and threw a `TypeError`.
- `OversizedArrayBuilder`: same overflow in the >256-item array literal path;
  `$nextAutoIndex` is now nullable and unkeyed items after the overflow get a
  plain `IntegerType` key.
- `ArrayFillFunctionReturnTypeExtension`: `array_fill()` whose start index plus
  count crosses `PHP_INT_MAX` no longer overflows the loop counter; it falls back
  to the general `array<int, T>` result.
- `AssignHandler::processArrayByRefItems()`: the same `+ 1` overflowed and was
  passed to `PhpParser\Node\Scalar\Int_`; `$implicitIndex` now becomes `null`
  (the existing "unpredictable" state).
- While tracking the same variable: unpacked items (`[...$b]`) contribute an
  unknown number of renumbered integer keys, so they now reset implicit-key
  tracking in `DuplicateKeysInLiteralArraysRule` and in
  `AssignHandler::processArrayByRefItems()`. This removes false
  `array.duplicateKey` reports for `['a', ...$b, 1 => 'x']`.
- `ConstantArrayTypeBuilder`, `TypeCombinator` and `ConstantArrayType` were
  probed for the same pattern and already guard against the overflow.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ondrejmirtes
ondrejmirtes merged commit 3f97b5a into phpstan:2.2.x Sep 17, 2026
853 of 890 checks passed
@ondrejmirtes
ondrejmirtes deleted the create-pull-request/patch-gxg3i7n branch September 17, 2026 09:34
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.

2 participants