Skip to content

Reading an optional key of a large array shape with ?? returns oversized-array with other value types (regression in 2.3.0) #15459

Open

Description

@bmdevel

Bug report

Since 2.3.0, reading an optional key of a large array shape with ?? can return the wrong type. The type gets &oversized-array and, when the shape has other keys, the value types of those keys (here string). 2.2.16 returns the declared type.

It happens when the shape has more than about 256 entries if nested keys are counted: 197 string keys plus 20 optional array{currency: string, price: float}|null keys (197 + 20 × 3 = 257) triggers it, 156 + 30 optional (246) does not. 86 optional nested keys alone (258) give (array{currency: string, price: float}&oversized-array)|null; 85 (255) give the correct type.

The array is not built in the code. It is only the declared @return type of a function, and the code only reads two keys.

Code snippet that reproduces the problem

https://phpstan.org/r/088dccaa-717e-4859-a9e9-2699bf7fe933

The playground shows (array{currency: string, price: float}&oversized-array)|string|null.

The same file can be generated with this script (php gen.php > repro.php):

<?php
$keys = [];
for ($i = 0; $i < 197; $i++) { $keys[] = "    k$i: string,"; }
for ($i = 0; $i < 20; $i++) { $keys[] = "    o$i?: array{currency: string, price: float}|null,"; }
echo "<?php declare(strict_types=1);\n";
echo "/** @return array{\n" . implode("\n", $keys) . "\n} */\n";
echo "function make(): array { throw new LogicException(); }\n";
echo "function f(): void {\n";
echo "    \$r = make();\n";
echo "    \$a = \$r['o0'] ?? null;\n";
echo "    \$b = \$r['o1'] ?? null;\n";
echo "    \\PHPStan\\dumpType(\$b);\n";
echo "}\n";

phpstan analyse -l 9 repro.php:

  • 2.2.16: Dumped type: array{currency: string, price: float}|null
  • 2.3.0 / 2.3.1: Dumped type: (array{currency: string, price: float}&oversized-array)|string|null

Expected output

array{currency: string, price: float}|null, as in 2.2.16.

Effect on real code

In our project a value object returns a shape with about 160 keys and about 30 optional nested keys. Code that reads two or three optional keys with ?? and then accesses an offset now gets Cannot access offset 'currency' on mixed. and Parameter #1 $num of function number_format expects float, mixed given. (about 40 new errors). Analysis of one such file also became much slower, and the total memory use went from below 1 GB to about 1.25 GB. I did not confirm that the memory increase has the same cause.

Did PHPStan help you today? Did it make you happy in any way?

Yes. It found the dead assignments and unused variables that 2.3 now reports.

Activity

  1. mergeable commented on Oct 11, 2026

    @mergeable

    This bug report is missing a link to reproduction at phpstan.org/try.

    It will most likely be closed after manual review.

  2. bmdevel commented on Oct 11, 2026

    @bmdevel
    Author

    Auto-generated answer by Claude Opus 5.5.

    Added the playground link to the description: https://phpstan.org/r/088dccaa-717e-4859-a9e9-2699bf7fe933 (it shows Dumped type: (array{currency: string, price: float}&oversized-array)|string|null).

  3. added this to the 2.3.0 feedback milestone on Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions