Skip to content

Commit 0f6d8d8

Browse files
committed
Do not generalize a union of constant arrays that is no bigger than its largest member in TypeCombinator::optimizeConstantArrays()
- `optimizeConstantArrays()` generalized any union whose constant array value count exceeded `ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT`, even when the union reduced to a single shape no bigger than one of its own members. A declared shape with more than 256 values (nested ones included) is kept as-is on its own, but after `$r['opt'] ?? null` (or `isset`, `array_key_exists`, `empty`, `!== null`) the scope merge unions it with its narrowed variant, and the merged result was degraded to `non-empty-array<..., ...>&oversized-array`. - The threshold is now raised to the largest per-member count of the unioned types (new `getConstantArrayValueTypesLimit()`), computed only once the total is already over the plain limit. Both callers in `processArrayTypes()` pass the types being unioned. - Ported the same change to the native mirror in `turbo-ext/src/TypeCombinator.cpp`. - Probed: big list shapes (fixed by the same change, test added); declared unions of a large shape with another shape (already generalized when they are declared, which is long-standing behavior and left unchanged); shapes with more than 256 *keys* (degraded by `ConstantArrayTypeBuilder::createFromConstantArray()`, an older design decision, left unchanged).
1 parent acd0511 commit 0f6d8d8

3 files changed

Lines changed: 598 additions & 8 deletions

File tree

‎src/Type/TypeCombinator.php‎

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
use function get_class;
4545
use function implode;
4646
use function in_array;
47+
use function max;
4748
use function spl_object_id;
4849
use function sprintf;
4950
use function usort;
@@ -1221,7 +1222,7 @@ private static function processArrayTypes(array $arrayTypes): array
12211222

12221223
$arrayType = new ArrayType(
12231224
self::union(...$keyTypesForGeneralArray),
1224-
self::union(...self::optimizeConstantArrays($valueTypesForGeneralArray)),
1225+
self::union(...self::optimizeConstantArrays($valueTypesForGeneralArray, $valueTypesForGeneralArray)),
12251226
);
12261227

12271228
if ($templateArrayType !== null) {
@@ -1240,7 +1241,7 @@ private static function processArrayTypes(array $arrayTypes): array
12401241
];
12411242
}
12421243

1243-
$reducedArrayTypes = self::optimizeConstantArrays(self::reduceArrays($arrayTypes, true));
1244+
$reducedArrayTypes = self::optimizeConstantArrays(self::reduceArrays($arrayTypes, true), $arrayTypes);
12441245
foreach ($reducedArrayTypes as $idx => $reducedArray) {
12451246
$applied = $accessoryTypes;
12461247
if ($reducedArray->isIterableAtLeastOnce()->no()) {
@@ -1259,17 +1260,41 @@ private static function processArrayTypes(array $arrayTypes): array
12591260
}
12601261

12611262
/**
1263+
* The count of constant array value types above which a union of the given
1264+
* types gets generalized. A single type already holding more than
1265+
* ARRAY_COUNT_LIMIT of them raises it - a union no bigger than one of its
1266+
* members does not grow the type, generalizing it would only lose precision.
1267+
*
12621268
* @param Type[] $types
1269+
*/
1270+
private static function getConstantArrayValueTypesLimit(array $types): int
1271+
{
1272+
$limit = ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT;
1273+
foreach ($types as $type) {
1274+
$limit = max($limit, self::countConstantArrayValueTypes([$type]));
1275+
}
1276+
1277+
return $limit;
1278+
}
1279+
1280+
/**
1281+
* @param Type[] $types
1282+
* @param Type[] $unionedTypes the types whose union $types represent
12631283
* @return Type[]
12641284
*/
1265-
private static function optimizeConstantArrays(array $types): array
1285+
private static function optimizeConstantArrays(array $types, array $unionedTypes): array
12661286
{
12671287
$constantArrayValuesCount = self::countConstantArrayValueTypes($types);
12681288

12691289
if ($constantArrayValuesCount <= ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) {
12701290
return $types;
12711291
}
12721292

1293+
$limit = self::getConstantArrayValueTypesLimit($unionedTypes);
1294+
if ($constantArrayValuesCount <= $limit) {
1295+
return $types;
1296+
}
1297+
12731298
// Stage 1: collapse same-key-set ConstantArrayType variants per-position
12741299
// before the (lossy) generalization below kicks in. Variants with the
12751300
// same key signature mergeWith losslessly into a single shape whose
@@ -1311,7 +1336,7 @@ private static function optimizeConstantArrays(array $types): array
13111336
if ($anyMerged) {
13121337
$types = array_values($collapsed);
13131338
$constantArrayValuesCount = self::countConstantArrayValueTypes($types);
1314-
if ($constantArrayValuesCount <= ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) {
1339+
if ($constantArrayValuesCount <= $limit) {
13151340
return $types;
13161341
}
13171342
}

0 commit comments

Comments
 (0)