Repository navigation
Stop tracking the next implicit array key once it reaches PHP_INT_MAX - #6461
Merged
ondrejmirtes merged 1 commit intoSep 17, 2026
Merged
Conversation
- `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>
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
[9223372036854775807 => 1, 2]crashed the analysis of the whole file withConstantIntegerType::__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 alreadyPHP_INT_MAX,+ 1silently overflows to afloat, and thefloatis then handed to a constructor that requires anint. PHP itselfdoes 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, whichis 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 tofalse(the existing "cannot be sure ofthe next keys" state) and skip the item instead of incrementing.
src/Type/Constant/OversizedArrayBuilder.php— the same crash on array literals withmore than
ConstantArrayTypeBuilder::ARRAY_COUNT_LIMITitems.$nextAutoIndexis nowint|null; once it overflows, unkeyed items get a plainIntegerTypekey and the resultis no longer treated as a list.
src/Type/Php/ArrayFillFunctionReturnTypeExtension.php— the same crash forarray_fill(9223372036854775806, 3, 'x'). The constant-array loop stops atPHP_INT_MAXand, 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+ 1overflow, which reachedPhpParser\Node\Scalar\Int_::__construct(int $value)andcrashed on
$a = [9223372036854775807 => 1, &$b];.$implicitIndexnow becomesnull,the state the method already uses for unpredictable indices.
(
DuplicateKeysInLiteralArraysRule,AssignHandler::processArrayByRefItems()) werecounted 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.ConstantArrayTypeBuilder::setOffsetValueType()(all three auto-index sites alreadyguard with
is_float()),TypeCombinator(its$nextAutoIndexis only ever compared,never turned into a type),
ConstantArrayType(its counters are bounded by the shapesize), and the generator
yieldkey inference inClosureTypeResolver(it uses a plainIntegerType, it does not track indices).Root cause
The pattern is "advance the implicit array key with
$index + 1and build aConstantIntegerType/Int_from the result". AtPHP_INT_MAXthe addition produces afloat, and both constructors areint-typed understrict_types, so the analysis of thewhole 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 arraytype); 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
...$boccupies exactly one integer key.Test
tests/PHPStan/Rules/Arrays/data/bug-15244.php+testBug15244()inDuplicateKeysInLiteralArraysRuleTest— the reported reproducer, the numeric-string keyvariant
['9223372036854775807' => 1, 2], the variant that reachesPHP_INT_MAXbyincrementing (
[9223372036854775806 => 1, 2, 3]), a case where the implicit key genuinelyis a duplicate of a later explicit
PHP_INT_MAXkey (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 othercrash sites:
array_fill()at and acrossPHP_INT_MAX, an array literal with a by-refitem after a
PHP_INT_MAXkey, and a >256-item literal starting nearPHP_INT_MAX.Without the fix this test errors with the
TypeErrortoo.Fixes phpstan/phpstan#15244