Skip to content

Commit da9e061

Browse files
committed
TypeCombinator::union() - optimization of constant scalar types
1 parent c56d866 commit da9e061

File tree

2 files changed

+151
-71
lines changed

2 files changed

+151
-71
lines changed

src/Type/TypeCombinator.php

Lines changed: 127 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ public static function union(Type ...$types): Type
247247
unset($types[$i]);
248248
}
249249

250+
foreach ($scalarTypes as $classType => $scalarTypeItems) {
251+
$scalarTypes[$classType] = array_values($scalarTypeItems);
252+
}
253+
250254
/** @var ArrayType[] $arrayTypes */
251255
$arrayTypes = $arrayTypes;
252256

@@ -280,105 +284,68 @@ public static function union(Type ...$types): Type
280284

281285
foreach ($scalarTypes as $classType => $scalarTypeItems) {
282286
if (isset($hasGenericScalarTypes[$classType])) {
287+
unset($scalarTypes[$classType]);
283288
continue;
284289
}
285290
if ($classType === ConstantBooleanType::class && count($scalarTypeItems) === 2) {
286291
$types[] = new BooleanType();
292+
unset($scalarTypes[$classType]);
287293
continue;
288294
}
289-
foreach ($scalarTypeItems as $type) {
290-
$types[] = $type;
291-
}
292-
}
293295

294-
// transform A | A to A
295-
// transform A | never to A
296-
for ($i = 0; $i < count($types); $i++) {
297-
for ($j = $i + 1; $j < count($types); $j++) {
298-
if ($types[$i] instanceof IntegerRangeType) {
299-
$type = $types[$i]->tryUnion($types[$j]);
300-
if ($type !== null) {
301-
$types[$i] = $type;
302-
$i--;
303-
array_splice($types, $j, 1);
304-
continue 2;
296+
for ($i = 0; $i < count($scalarTypeItems); $i++) {
297+
for ($j = 0; $j < count($types); $j++) {
298+
$compareResult = self::compareTypesInUnion($scalarTypeItems[$i], $types[$j]);
299+
if ($compareResult === null) {
300+
continue;
305301
}
306-
}
307302

308-
if ($types[$i] instanceof SubtractableType) {
309-
$typeWithoutSubtractedTypeA = $types[$i]->getTypeWithoutSubtractedType();
310-
if ($typeWithoutSubtractedTypeA instanceof MixedType && $types[$j] instanceof MixedType) {
311-
$isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOfMixed($types[$j]);
312-
} else {
313-
$isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOf($types[$j]);
314-
}
315-
if ($isSuperType->yes()) {
316-
$subtractedType = null;
317-
if ($types[$j] instanceof SubtractableType) {
318-
$subtractedType = $types[$j]->getSubtractedType();
319-
}
320-
$types[$i] = self::intersectWithSubtractedType($types[$i], $subtractedType);
303+
[$a, $b] = $compareResult;
304+
if ($a !== null) {
305+
$scalarTypeItems[$i] = $a;
321306
array_splice($types, $j--, 1);
322307
continue 1;
323308
}
324-
}
325-
326-
if ($types[$j] instanceof SubtractableType) {
327-
$typeWithoutSubtractedTypeB = $types[$j]->getTypeWithoutSubtractedType();
328-
if ($typeWithoutSubtractedTypeB instanceof MixedType && $types[$i] instanceof MixedType) {
329-
$isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOfMixed($types[$i]);
330-
} else {
331-
$isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOf($types[$i]);
332-
}
333-
if ($isSuperType->yes()) {
334-
$subtractedType = null;
335-
if ($types[$i] instanceof SubtractableType) {
336-
$subtractedType = $types[$i]->getSubtractedType();
337-
}
338-
$types[$j] = self::intersectWithSubtractedType($types[$j], $subtractedType);
339-
array_splice($types, $i--, 1);
309+
if ($b !== null) {
310+
$types[$j] = $b;
311+
array_splice($scalarTypeItems, $i--, 1);
340312
continue 2;
341313
}
342314
}
315+
}
343316

344-
if (
345-
!$types[$j] instanceof ConstantArrayType
346-
&& $types[$j]->isSuperTypeOf($types[$i])->yes()
347-
) {
348-
array_splice($types, $i--, 1);
349-
continue 2;
350-
}
317+
$scalarTypes[$classType] = $scalarTypeItems;
318+
}
351319

352-
if (
353-
!$types[$i] instanceof ConstantArrayType
354-
&& $types[$i]->isSuperTypeOf($types[$j])->yes()
355-
) {
356-
array_splice($types, $j--, 1);
357-
continue 1;
320+
// transform A | A to A
321+
// transform A | never to A
322+
for ($i = 0; $i < count($types); $i++) {
323+
for ($j = $i + 1; $j < count($types); $j++) {
324+
$compareResult = self::compareTypesInUnion($types[$i], $types[$j]);
325+
if ($compareResult === null) {
326+
continue;
358327
}
359328

360-
if (
361-
$types[$i] instanceof ConstantStringType
362-
&& $types[$i]->getValue() === ''
363-
&& $types[$j]->describe(VerbosityLevel::value()) === 'non-empty-string'
364-
) {
365-
$types[$i] = new StringType();
329+
[$a, $b] = $compareResult;
330+
if ($a !== null) {
331+
$types[$i] = $a;
366332
array_splice($types, $j--, 1);
367333
continue 1;
368334
}
369-
370-
if (
371-
$types[$j] instanceof ConstantStringType
372-
&& $types[$j]->getValue() === ''
373-
&& $types[$i]->describe(VerbosityLevel::value()) === 'non-empty-string'
374-
) {
375-
$types[$j] = new StringType();
335+
if ($b !== null) {
336+
$types[$j] = $b;
376337
array_splice($types, $i--, 1);
377338
continue 2;
378339
}
379340
}
380341
}
381342

343+
foreach ($scalarTypes as $scalarTypeItems) {
344+
foreach ($scalarTypeItems as $scalarType) {
345+
$types[] = $scalarType;
346+
}
347+
}
348+
382349
if (count($types) === 0) {
383350
return new NeverType();
384351

@@ -408,6 +375,95 @@ public static function union(Type ...$types): Type
408375
return new UnionType($types);
409376
}
410377

378+
/**
379+
* @param Type $a
380+
* @param Type $b
381+
* @return array{Type, null}|array{null, Type}|null
382+
*/
383+
private static function compareTypesInUnion(Type $a, Type $b): ?array
384+
{
385+
if ($a instanceof IntegerRangeType) {
386+
$type = $a->tryUnion($b);
387+
if ($type !== null) {
388+
$a = $type;
389+
return [$a, null];
390+
}
391+
}
392+
if ($b instanceof IntegerRangeType) {
393+
$type = $b->tryUnion($a);
394+
if ($type !== null) {
395+
$b = $type;
396+
return [null, $b];
397+
}
398+
}
399+
400+
if ($a instanceof SubtractableType) {
401+
$typeWithoutSubtractedTypeA = $a->getTypeWithoutSubtractedType();
402+
if ($typeWithoutSubtractedTypeA instanceof MixedType && $b instanceof MixedType) {
403+
$isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOfMixed($b);
404+
} else {
405+
$isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOf($b);
406+
}
407+
if ($isSuperType->yes()) {
408+
$subtractedType = null;
409+
if ($b instanceof SubtractableType) {
410+
$subtractedType = $b->getSubtractedType();
411+
}
412+
$a = self::intersectWithSubtractedType($a, $subtractedType);
413+
return [$a, null];
414+
}
415+
}
416+
417+
if ($b instanceof SubtractableType) {
418+
$typeWithoutSubtractedTypeB = $b->getTypeWithoutSubtractedType();
419+
if ($typeWithoutSubtractedTypeB instanceof MixedType && $a instanceof MixedType) {
420+
$isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOfMixed($a);
421+
} else {
422+
$isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOf($a);
423+
}
424+
if ($isSuperType->yes()) {
425+
$subtractedType = null;
426+
if ($a instanceof SubtractableType) {
427+
$subtractedType = $a->getSubtractedType();
428+
}
429+
$b = self::intersectWithSubtractedType($b, $subtractedType);
430+
return [null, $b];
431+
}
432+
}
433+
434+
if (
435+
!$b instanceof ConstantArrayType
436+
&& $b->isSuperTypeOf($a)->yes()
437+
) {
438+
return [null, $b];
439+
}
440+
441+
if (
442+
!$a instanceof ConstantArrayType
443+
&& $a->isSuperTypeOf($b)->yes()
444+
) {
445+
return [$a, null];
446+
}
447+
448+
if (
449+
$a instanceof ConstantStringType
450+
&& $a->getValue() === ''
451+
&& $b->describe(VerbosityLevel::value()) === 'non-empty-string'
452+
) {
453+
return [null, new StringType()];
454+
}
455+
456+
if (
457+
$b instanceof ConstantStringType
458+
&& $b->getValue() === ''
459+
&& $a->describe(VerbosityLevel::value()) === 'non-empty-string'
460+
) {
461+
return [new StringType(), null];
462+
}
463+
464+
return null;
465+
}
466+
411467
private static function unionWithSubtractedType(
412468
Type $type,
413469
?Type $subtractedType

tests/PHPStan/Type/TypeCombinatorTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,6 +1780,30 @@ public function dataUnion(): array
17801780
StringType::class,
17811781
'string',
17821782
],
1783+
[
1784+
[
1785+
new StringType(),
1786+
new UnionType([
1787+
new ConstantStringType(''),
1788+
new ConstantStringType('0'),
1789+
new ConstantBooleanType(false),
1790+
]),
1791+
],
1792+
UnionType::class,
1793+
'string|false',
1794+
],
1795+
[
1796+
[
1797+
new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]),
1798+
new UnionType([
1799+
new ConstantStringType(''),
1800+
new ConstantStringType('0'),
1801+
new ConstantBooleanType(false),
1802+
]),
1803+
],
1804+
UnionType::class,
1805+
'string|false',
1806+
],
17831807
];
17841808
}
17851809

0 commit comments

Comments
 (0)