Skip to content

Commit eec4924

Browse files
committed
TypeCombinator::union optimization in case of many *NEVER*
Merge the separate NeverType pre-scan into the existing flattening loop, eliminating the redundant iteration over all types. Also skip the classification loop and inline the dedup for the common case of 2 non-scalar, non-array, non-enum types.
1 parent 3c61234 commit eec4924

File tree

1 file changed

+74
-76
lines changed

1 file changed

+74
-76
lines changed

src/Type/TypeCombinator.php

Lines changed: 74 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -188,58 +188,12 @@ public static function union(Type ...$types): Type
188188
}
189189
}
190190

191-
// Fast path for N>2: strip implicit NeverTypes and short-circuit on mixed
192-
if ($typesCount > 2) {
193-
$neverCount = 0;
194-
$hasUnionOrBenevolent = false;
195-
for ($i = 0; $i < $typesCount; $i++) {
196-
$t = $types[$i];
197-
if (
198-
$t instanceof MixedType
199-
&& !$t->isExplicitMixed()
200-
&& !$t instanceof TemplateMixedType
201-
&& $t->getSubtractedType() === null
202-
) {
203-
return $t;
204-
}
205-
if ($t instanceof NeverType && !$t->isExplicit()) {
206-
$neverCount++;
207-
} elseif ($t instanceof UnionType && !$t instanceof TemplateType) {
208-
$hasUnionOrBenevolent = true;
209-
}
210-
}
211-
212-
if ($neverCount > 0 && !$hasUnionOrBenevolent) {
213-
if ($neverCount === $typesCount) {
214-
return new NeverType();
215-
}
216-
217-
$filtered = [];
218-
for ($i = 0; $i < $typesCount; $i++) {
219-
if ($types[$i] instanceof NeverType && !$types[$i]->isExplicit()) {
220-
continue;
221-
}
222-
223-
$filtered[] = $types[$i];
224-
}
225-
$filteredCount = count($filtered);
226-
227-
if ($filteredCount === 1 && !$filtered[0]->isArray()->yes()) {
228-
return $filtered[0];
229-
}
230-
if ($filteredCount === 2) {
231-
return self::union($filtered[0], $filtered[1]);
232-
}
233-
$types = $filtered;
234-
$typesCount = $filteredCount;
235-
}
236-
}
237-
238191
$alreadyNormalized = [];
239192
$alreadyNormalizedCounter = 0;
240193

241194
$benevolentTypes = [];
242195
$benevolentUnionObject = null;
196+
$neverCount = 0;
243197
// transform A | (B | C) to A | B | C
244198
for ($i = 0; $i < $typesCount; $i++) {
245199
if (
@@ -251,8 +205,7 @@ public static function union(Type ...$types): Type
251205
return $types[$i];
252206
}
253207
if ($types[$i] instanceof NeverType && !$types[$i]->isExplicit()) {
254-
array_splice($types, $i--, 1);
255-
$typesCount--;
208+
$neverCount++;
256209
continue;
257210
}
258211
if ($types[$i] instanceof BenevolentUnionType) {
@@ -283,6 +236,33 @@ public static function union(Type ...$types): Type
283236
$typesCount += count($typesInner) - 1;
284237
}
285238

239+
// Bulk-remove implicit NeverTypes (skipped during the loop above)
240+
if ($neverCount > 0) {
241+
if ($neverCount === $typesCount) {
242+
return new NeverType();
243+
}
244+
245+
$filtered = [];
246+
for ($i = 0; $i < $typesCount; $i++) {
247+
if ($types[$i] instanceof NeverType && !$types[$i]->isExplicit()) {
248+
continue;
249+
}
250+
$filtered[] = $types[$i];
251+
}
252+
$types = $filtered;
253+
$typesCount = count($types);
254+
255+
if ($typesCount === 0) {
256+
return new NeverType();
257+
}
258+
if ($typesCount === 1 && !$types[0]->isArray()->yes()) {
259+
return $types[0];
260+
}
261+
if ($typesCount === 2) {
262+
return self::union($types[0], $types[1]);
263+
}
264+
}
265+
286266
if ($typesCount === 0) {
287267
return new NeverType();
288268
}
@@ -337,13 +317,17 @@ public static function union(Type ...$types): Type
337317
unset($types[$i]);
338318
}
339319

340-
$enumCaseTypes = array_values($enumCaseTypes);
341-
usort(
342-
$integerRangeTypes,
343-
static fn (IntegerRangeType $a, IntegerRangeType $b): int => ($a->getMin() ?? PHP_INT_MIN) <=> ($b->getMin() ?? PHP_INT_MIN)
344-
?: ($a->getMax() ?? PHP_INT_MAX) <=> ($b->getMax() ?? PHP_INT_MAX),
345-
);
346-
$types = array_merge($types, $integerRangeTypes);
320+
if ($enumCaseTypes !== []) {
321+
$enumCaseTypes = array_values($enumCaseTypes);
322+
}
323+
if ($integerRangeTypes !== []) {
324+
usort(
325+
$integerRangeTypes,
326+
static fn (IntegerRangeType $a, IntegerRangeType $b): int => ($a->getMin() ?? PHP_INT_MIN) <=> ($b->getMin() ?? PHP_INT_MIN)
327+
?: ($a->getMax() ?? PHP_INT_MAX) <=> ($b->getMax() ?? PHP_INT_MAX),
328+
);
329+
$types = array_merge($types, $integerRangeTypes);
330+
}
347331
$types = array_values($types);
348332
$typesCount = count($types);
349333

@@ -387,7 +371,7 @@ public static function union(Type ...$types): Type
387371
$scalarTypes[$classType] = $scalarTypeItems;
388372
}
389373

390-
if (count($types) > 16) {
374+
if ($typesCount > 16 && count($types) > 16) {
391375
$newTypes = [];
392376
foreach ($types as $type) {
393377
$newTypes[$type->describe(VerbosityLevel::cache())] = $type;
@@ -403,28 +387,42 @@ public static function union(Type ...$types): Type
403387

404388
// transform A | A to A
405389
// transform A | never to A
406-
for ($i = 0; $i < $typesCount; $i++) {
407-
for ($j = $i + 1; $j < $typesCount; $j++) {
408-
if (self::isAlreadyNormalized($alreadyNormalized, $types[$i], $types[$j])) {
409-
continue;
410-
}
411-
$compareResult = self::compareTypesInUnion($types[$i], $types[$j]);
412-
if ($compareResult === null) {
413-
continue;
414-
}
415-
390+
if ($typesCount === 2 && $alreadyNormalized === []) {
391+
$compareResult = self::compareTypesInUnion($types[0], $types[1]);
392+
if ($compareResult !== null) {
416393
[$a, $b] = $compareResult;
417394
if ($a !== null) {
418-
$types[$i] = $a;
419-
array_splice($types, $j--, 1);
420-
$typesCount--;
421-
continue 1;
395+
$types = [$a];
396+
$typesCount = 1;
397+
} elseif ($b !== null) {
398+
$types = [$b];
399+
$typesCount = 1;
422400
}
423-
if ($b !== null) {
424-
$types[$j] = $b;
425-
array_splice($types, $i--, 1);
426-
$typesCount--;
427-
continue 2;
401+
}
402+
} else {
403+
for ($i = 0; $i < $typesCount; $i++) {
404+
for ($j = $i + 1; $j < $typesCount; $j++) {
405+
if (self::isAlreadyNormalized($alreadyNormalized, $types[$i], $types[$j])) {
406+
continue;
407+
}
408+
$compareResult = self::compareTypesInUnion($types[$i], $types[$j]);
409+
if ($compareResult === null) {
410+
continue;
411+
}
412+
413+
[$a, $b] = $compareResult;
414+
if ($a !== null) {
415+
$types[$i] = $a;
416+
array_splice($types, $j--, 1);
417+
$typesCount--;
418+
continue 1;
419+
}
420+
if ($b !== null) {
421+
$types[$j] = $b;
422+
array_splice($types, $i--, 1);
423+
$typesCount--;
424+
continue 2;
425+
}
428426
}
429427
}
430428
}

0 commit comments

Comments
 (0)