Skip to content

Commit

Permalink
TypeCombinator::union() - optimization of constant scalar types
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Aug 18, 2021
1 parent c56d866 commit da9e061
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 71 deletions.
198 changes: 127 additions & 71 deletions src/Type/TypeCombinator.php
Expand Up @@ -247,6 +247,10 @@ public static function union(Type ...$types): Type
unset($types[$i]);
}

foreach ($scalarTypes as $classType => $scalarTypeItems) {
$scalarTypes[$classType] = array_values($scalarTypeItems);
}

/** @var ArrayType[] $arrayTypes */
$arrayTypes = $arrayTypes;

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

foreach ($scalarTypes as $classType => $scalarTypeItems) {
if (isset($hasGenericScalarTypes[$classType])) {
unset($scalarTypes[$classType]);
continue;
}
if ($classType === ConstantBooleanType::class && count($scalarTypeItems) === 2) {
$types[] = new BooleanType();
unset($scalarTypes[$classType]);
continue;
}
foreach ($scalarTypeItems as $type) {
$types[] = $type;
}
}

// transform A | A to A
// transform A | never to A
for ($i = 0; $i < count($types); $i++) {
for ($j = $i + 1; $j < count($types); $j++) {
if ($types[$i] instanceof IntegerRangeType) {
$type = $types[$i]->tryUnion($types[$j]);
if ($type !== null) {
$types[$i] = $type;
$i--;
array_splice($types, $j, 1);
continue 2;
for ($i = 0; $i < count($scalarTypeItems); $i++) {
for ($j = 0; $j < count($types); $j++) {
$compareResult = self::compareTypesInUnion($scalarTypeItems[$i], $types[$j]);
if ($compareResult === null) {
continue;
}
}

if ($types[$i] instanceof SubtractableType) {
$typeWithoutSubtractedTypeA = $types[$i]->getTypeWithoutSubtractedType();
if ($typeWithoutSubtractedTypeA instanceof MixedType && $types[$j] instanceof MixedType) {
$isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOfMixed($types[$j]);
} else {
$isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOf($types[$j]);
}
if ($isSuperType->yes()) {
$subtractedType = null;
if ($types[$j] instanceof SubtractableType) {
$subtractedType = $types[$j]->getSubtractedType();
}
$types[$i] = self::intersectWithSubtractedType($types[$i], $subtractedType);
[$a, $b] = $compareResult;
if ($a !== null) {
$scalarTypeItems[$i] = $a;
array_splice($types, $j--, 1);
continue 1;
}
}

if ($types[$j] instanceof SubtractableType) {
$typeWithoutSubtractedTypeB = $types[$j]->getTypeWithoutSubtractedType();
if ($typeWithoutSubtractedTypeB instanceof MixedType && $types[$i] instanceof MixedType) {
$isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOfMixed($types[$i]);
} else {
$isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOf($types[$i]);
}
if ($isSuperType->yes()) {
$subtractedType = null;
if ($types[$i] instanceof SubtractableType) {
$subtractedType = $types[$i]->getSubtractedType();
}
$types[$j] = self::intersectWithSubtractedType($types[$j], $subtractedType);
array_splice($types, $i--, 1);
if ($b !== null) {
$types[$j] = $b;
array_splice($scalarTypeItems, $i--, 1);
continue 2;
}
}
}

if (
!$types[$j] instanceof ConstantArrayType
&& $types[$j]->isSuperTypeOf($types[$i])->yes()
) {
array_splice($types, $i--, 1);
continue 2;
}
$scalarTypes[$classType] = $scalarTypeItems;
}

if (
!$types[$i] instanceof ConstantArrayType
&& $types[$i]->isSuperTypeOf($types[$j])->yes()
) {
array_splice($types, $j--, 1);
continue 1;
// transform A | A to A
// transform A | never to A
for ($i = 0; $i < count($types); $i++) {
for ($j = $i + 1; $j < count($types); $j++) {
$compareResult = self::compareTypesInUnion($types[$i], $types[$j]);
if ($compareResult === null) {
continue;
}

if (
$types[$i] instanceof ConstantStringType
&& $types[$i]->getValue() === ''
&& $types[$j]->describe(VerbosityLevel::value()) === 'non-empty-string'
) {
$types[$i] = new StringType();
[$a, $b] = $compareResult;
if ($a !== null) {
$types[$i] = $a;
array_splice($types, $j--, 1);
continue 1;
}

if (
$types[$j] instanceof ConstantStringType
&& $types[$j]->getValue() === ''
&& $types[$i]->describe(VerbosityLevel::value()) === 'non-empty-string'
) {
$types[$j] = new StringType();
if ($b !== null) {
$types[$j] = $b;
array_splice($types, $i--, 1);
continue 2;
}
}
}

foreach ($scalarTypes as $scalarTypeItems) {
foreach ($scalarTypeItems as $scalarType) {
$types[] = $scalarType;
}
}

if (count($types) === 0) {
return new NeverType();

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

/**
* @param Type $a
* @param Type $b
* @return array{Type, null}|array{null, Type}|null
*/
private static function compareTypesInUnion(Type $a, Type $b): ?array
{
if ($a instanceof IntegerRangeType) {
$type = $a->tryUnion($b);
if ($type !== null) {
$a = $type;
return [$a, null];
}
}
if ($b instanceof IntegerRangeType) {
$type = $b->tryUnion($a);
if ($type !== null) {
$b = $type;
return [null, $b];
}
}

if ($a instanceof SubtractableType) {
$typeWithoutSubtractedTypeA = $a->getTypeWithoutSubtractedType();
if ($typeWithoutSubtractedTypeA instanceof MixedType && $b instanceof MixedType) {
$isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOfMixed($b);
} else {
$isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOf($b);
}
if ($isSuperType->yes()) {
$subtractedType = null;
if ($b instanceof SubtractableType) {
$subtractedType = $b->getSubtractedType();
}
$a = self::intersectWithSubtractedType($a, $subtractedType);
return [$a, null];
}
}

if ($b instanceof SubtractableType) {
$typeWithoutSubtractedTypeB = $b->getTypeWithoutSubtractedType();
if ($typeWithoutSubtractedTypeB instanceof MixedType && $a instanceof MixedType) {
$isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOfMixed($a);
} else {
$isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOf($a);
}
if ($isSuperType->yes()) {
$subtractedType = null;
if ($a instanceof SubtractableType) {
$subtractedType = $a->getSubtractedType();
}
$b = self::intersectWithSubtractedType($b, $subtractedType);
return [null, $b];
}
}

if (
!$b instanceof ConstantArrayType
&& $b->isSuperTypeOf($a)->yes()
) {
return [null, $b];
}

if (
!$a instanceof ConstantArrayType
&& $a->isSuperTypeOf($b)->yes()
) {
return [$a, null];
}

if (
$a instanceof ConstantStringType
&& $a->getValue() === ''
&& $b->describe(VerbosityLevel::value()) === 'non-empty-string'
) {
return [null, new StringType()];
}

if (
$b instanceof ConstantStringType
&& $b->getValue() === ''
&& $a->describe(VerbosityLevel::value()) === 'non-empty-string'
) {
return [new StringType(), null];
}

return null;
}

private static function unionWithSubtractedType(
Type $type,
?Type $subtractedType
Expand Down
24 changes: 24 additions & 0 deletions tests/PHPStan/Type/TypeCombinatorTest.php
Expand Up @@ -1780,6 +1780,30 @@ public function dataUnion(): array
StringType::class,
'string',
],
[
[
new StringType(),
new UnionType([
new ConstantStringType(''),
new ConstantStringType('0'),
new ConstantBooleanType(false),
]),
],
UnionType::class,
'string|false',
],
[
[
new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]),
new UnionType([
new ConstantStringType(''),
new ConstantStringType('0'),
new ConstantBooleanType(false),
]),
],
UnionType::class,
'string|false',
],
];
}

Expand Down

0 comments on commit da9e061

Please sign in to comment.