Skip to content

Commit a1dd1d8

Browse files
committed
intersecting improvement
1 parent 297ae85 commit a1dd1d8

3 files changed

Lines changed: 418 additions & 61 deletions

File tree

src/Type/Constant/ConstantArrayType.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,36 @@ public function isOptionalKey(int $i): bool
392392
return in_array($i, $this->optionalKeys, true);
393393
}
394394

395+
public function sortKeys(): self
396+
{
397+
$indices = array_keys($this->keyTypes);
398+
usort($indices, fn (int $a, int $b): int => $this->keyTypes[$a]->getValue() <=> $this->keyTypes[$b]->getValue());
399+
400+
$newKeyTypes = [];
401+
$newValueTypes = [];
402+
$indexMap = [];
403+
foreach ($indices as $newIdx => $oldIdx) {
404+
$newKeyTypes[] = $this->keyTypes[$oldIdx];
405+
$newValueTypes[] = $this->valueTypes[$oldIdx];
406+
$indexMap[$oldIdx] = $newIdx;
407+
}
408+
409+
$newOptionalKeys = [];
410+
foreach ($this->optionalKeys as $oldIdx) {
411+
$newOptionalKeys[] = $indexMap[$oldIdx];
412+
}
413+
sort($newOptionalKeys);
414+
415+
return $this->recreate(
416+
$newKeyTypes,
417+
$newValueTypes,
418+
$this->nextAutoIndexes,
419+
$newOptionalKeys,
420+
$this->isList,
421+
$this->unsealed,
422+
);
423+
}
424+
395425
public function accepts(Type $type, bool $strictTypes): AcceptsResult
396426
{
397427
if ($type instanceof CompoundType && !$type instanceof IntersectionType) {

src/Type/TypeCombinator.php

Lines changed: 141 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,25 +1549,38 @@ public static function intersect(Type ...$types): Type
15491549
$constArray = $constArrayIsI ? $types[$i] : $types[$j];
15501550
$otherArray = $constArrayIsI ? $types[$j] : $types[$i];
15511551

1552-
$newArray = ConstantArrayTypeBuilder::createEmpty();
1553-
$valueTypes = $constArray->getValueTypes();
1554-
foreach ($constArray->getKeyTypes() as $k => $keyType) {
1555-
$hasOffset = $otherArray->hasOffsetValueType($keyType);
1556-
if ($hasOffset->no()) {
1557-
continue;
1552+
if (
1553+
$otherArray instanceof ConstantArrayType
1554+
&& !$constArray->isUnsealed()->maybe()
1555+
&& !$otherArray->isUnsealed()->maybe()
1556+
) {
1557+
$merged = self::intersectDefiniteConstantArrays($constArray, $otherArray);
1558+
if ($merged instanceof NeverType) {
1559+
return $merged;
15581560
}
1559-
$newArray->setOffsetValueType(
1560-
self::intersect($keyType, $otherArray->getIterableKeyType()),
1561-
self::intersect($valueTypes[$k], $otherArray->getOffsetValueType($keyType)),
1562-
$constArray->isOptionalKey($k) && !$hasOffset->yes(),
1563-
);
1561+
$newArrayType = $merged;
1562+
} else {
1563+
$newArray = ConstantArrayTypeBuilder::createEmpty();
1564+
$valueTypes = $constArray->getValueTypes();
1565+
foreach ($constArray->getKeyTypes() as $k => $keyType) {
1566+
$hasOffset = $otherArray->hasOffsetValueType($keyType);
1567+
if ($hasOffset->no()) {
1568+
continue;
1569+
}
1570+
$newArray->setOffsetValueType(
1571+
self::intersect($keyType, $otherArray->getIterableKeyType()),
1572+
self::intersect($valueTypes[$k], $otherArray->getOffsetValueType($keyType)),
1573+
$constArray->isOptionalKey($k) && !$hasOffset->yes(),
1574+
);
1575+
}
1576+
$newArrayType = $newArray->getArray();
15641577
}
15651578

15661579
if ($constArrayIsI) {
1567-
$types[$i] = $newArray->getArray();
1580+
$types[$i] = $newArrayType;
15681581
array_splice($types, $j--, 1);
15691582
} else {
1570-
$types[$j] = $newArray->getArray();
1583+
$types[$j] = $newArrayType;
15711584
array_splice($types, $i--, 1);
15721585
}
15731586
$typesCount--;
@@ -1634,6 +1647,121 @@ public static function intersect(Type ...$types): Type
16341647
return new IntersectionType($types);
16351648
}
16361649

1650+
private static function intersectDefiniteConstantArrays(ConstantArrayType $a, ConstantArrayType $b): Type
1651+
{
1652+
$aSealed = $a->isUnsealed()->no();
1653+
$bSealed = $b->isUnsealed()->no();
1654+
$bothUnsealed = !$aSealed && !$bSealed;
1655+
1656+
$aKeyByValue = [];
1657+
foreach ($a->getKeyTypes() as $k => $keyType) {
1658+
$aKeyByValue[$keyType->getValue()] = $k;
1659+
}
1660+
$bKeyByValue = [];
1661+
foreach ($b->getKeyTypes() as $k => $keyType) {
1662+
$bKeyByValue[$keyType->getValue()] = $k;
1663+
}
1664+
1665+
if ($aSealed && $bSealed) {
1666+
foreach ($aKeyByValue as $keyValue => $k) {
1667+
if (!$a->isOptionalKey($k) && !array_key_exists($keyValue, $bKeyByValue)) {
1668+
return new NeverType();
1669+
}
1670+
}
1671+
foreach ($bKeyByValue as $keyValue => $k) {
1672+
if (!$b->isOptionalKey($k) && !array_key_exists($keyValue, $aKeyByValue)) {
1673+
return new NeverType();
1674+
}
1675+
}
1676+
}
1677+
1678+
$newArray = ConstantArrayTypeBuilder::createEmpty();
1679+
1680+
if ($bothUnsealed) {
1681+
$aUnsealed = $a->getUnsealedTypes();
1682+
$bUnsealed = $b->getUnsealedTypes();
1683+
$unsealedKey = self::intersect($aUnsealed[0], $bUnsealed[0]);
1684+
$unsealedValue = self::intersect($aUnsealed[1], $bUnsealed[1]);
1685+
if ($unsealedKey instanceof NeverType || $unsealedValue instanceof NeverType) {
1686+
return new NeverType();
1687+
}
1688+
$newArray->makeUnsealed($unsealedKey, $unsealedValue);
1689+
} else {
1690+
$never = new NeverType(true);
1691+
$newArray->makeUnsealed($never, $never);
1692+
}
1693+
1694+
$resolveOtherValue = static function (ConstantArrayType $other, Type $keyType): ?Type {
1695+
if ($other->hasOffsetValueType($keyType)->yes()) {
1696+
return $other->getOffsetValueType($keyType);
1697+
}
1698+
$otherUnsealed = $other->getUnsealedTypes();
1699+
if ($otherUnsealed === null) {
1700+
return null;
1701+
}
1702+
[$unsealedKey, $unsealedValue] = $otherUnsealed;
1703+
if ($unsealedKey instanceof NeverType && $unsealedKey->isExplicit()) {
1704+
return null;
1705+
}
1706+
if ($unsealedKey->isSuperTypeOf($keyType)->no()) {
1707+
return null;
1708+
}
1709+
return $unsealedValue;
1710+
};
1711+
1712+
$keysToProcess = [];
1713+
foreach ($aKeyByValue as $keyValue => $k) {
1714+
$keysToProcess[$keyValue] = [$k, $bKeyByValue[$keyValue] ?? null];
1715+
}
1716+
foreach ($bKeyByValue as $keyValue => $k) {
1717+
if (!array_key_exists($keyValue, $keysToProcess)) {
1718+
$keysToProcess[$keyValue] = [null, $k];
1719+
}
1720+
}
1721+
1722+
foreach ($keysToProcess as [$aIdx, $bIdx]) {
1723+
if ($aIdx !== null && $bIdx !== null) {
1724+
$keyType = $a->getKeyTypes()[$aIdx];
1725+
$value = self::intersect($a->getValueTypes()[$aIdx], $b->getValueTypes()[$bIdx]);
1726+
$optional = $a->isOptionalKey($aIdx) && $b->isOptionalKey($bIdx);
1727+
} elseif ($aIdx !== null) {
1728+
$keyType = $a->getKeyTypes()[$aIdx];
1729+
$aValue = $a->getValueTypes()[$aIdx];
1730+
$bValue = $resolveOtherValue($b, $keyType);
1731+
if ($bValue === null) {
1732+
if ($a->isOptionalKey($aIdx)) {
1733+
continue;
1734+
}
1735+
return new NeverType();
1736+
}
1737+
$value = self::intersect($aValue, $bValue);
1738+
$optional = $a->isOptionalKey($aIdx);
1739+
} else {
1740+
$keyType = $b->getKeyTypes()[$bIdx];
1741+
$bValue = $b->getValueTypes()[$bIdx];
1742+
$aValue = $resolveOtherValue($a, $keyType);
1743+
if ($aValue === null) {
1744+
if ($b->isOptionalKey($bIdx)) {
1745+
continue;
1746+
}
1747+
return new NeverType();
1748+
}
1749+
$value = self::intersect($aValue, $bValue);
1750+
$optional = $b->isOptionalKey($bIdx);
1751+
}
1752+
1753+
if ($value instanceof NeverType) {
1754+
if ($optional) {
1755+
continue;
1756+
}
1757+
return new NeverType();
1758+
}
1759+
$newArray->setOffsetValueType($keyType, $value, $optional);
1760+
}
1761+
1762+
return $newArray->getArray();
1763+
}
1764+
16371765
/**
16381766
* Merge two IntersectionTypes that have the same structure but differ
16391767
* in HasOffsetValueType value types (matched by offset key).

0 commit comments

Comments
 (0)