Skip to content

Commit 0693e5a

Browse files
committed
Improve IntersectionType::traverseSimultaneously() for arrays
1 parent 4f635e7 commit 0693e5a

File tree

2 files changed

+42
-19
lines changed

2 files changed

+42
-19
lines changed

src/Type/IntersectionType.php

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,28 +1260,36 @@ public function traverse(callable $cb): Type
12601260

12611261
public function traverseSimultaneously(Type $right, callable $cb): Type
12621262
{
1263-
$types = [];
1264-
$changed = false;
1263+
if ($this->isArray()->yes() && $right->isArray()->yes()) {
1264+
$changed = false;
1265+
$newTypes = [];
1266+
1267+
foreach ($this->types as $innerType) {
1268+
$newKeyType = $cb($innerType->getIterableKeyType(), $right->getIterableKeyType());
1269+
$newValueType = $cb($innerType->getIterableValueType(), $right->getIterableValueType());
1270+
if ($newKeyType === $innerType->getIterableKeyType() && $newValueType === $innerType->getIterableValueType()) {
1271+
$newTypes[] = $innerType;
1272+
continue;
1273+
}
12651274

1266-
if (!$right instanceof self) {
1267-
return $this;
1268-
}
1275+
$changed = true;
1276+
$newTypes[] = TypeTraverser::map($innerType, static function (Type $type, callable $traverse) use ($innerType, $newKeyType, $newValueType): Type {
1277+
if ($type === $innerType->getIterableKeyType()) {
1278+
return $newKeyType;
1279+
}
1280+
if ($type === $innerType->getIterableValueType()) {
1281+
return $newValueType;
1282+
}
12691283

1270-
if (count($this->getTypes()) !== count($right->getTypes())) {
1271-
return $this;
1272-
}
1284+
return $traverse($type);
1285+
});
1286+
}
12731287

1274-
foreach ($this->getSortedTypes() as $i => $leftType) {
1275-
$rightType = $right->getSortedTypes()[$i];
1276-
$newType = $cb($leftType, $rightType);
1277-
if ($leftType !== $newType) {
1278-
$changed = true;
1288+
if (!$changed) {
1289+
return $this;
12791290
}
1280-
$types[] = $newType;
1281-
}
12821291

1283-
if ($changed) {
1284-
return TypeCombinator::intersect(...$types);
1292+
return TypeCombinator::intersect(...$newTypes);
12851293
}
12861294

12871295
return $this;

tests/PHPStan/Type/SimultaneousTypeTraverserTest.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,11 @@ public function testChangeIntegerIntoString(Type $left, Type $right, string $exp
9292
public static function dataDescriptionBased(): iterable
9393
{
9494
$chooseScalarSubtype = static function (Type $left, Type $right, callable $traverse): Type {
95-
if (!$left->isScalar()->yes() || $left instanceof BenevolentUnionType) {
95+
$scalar = new UnionType([new IntegerType(), new FloatType(), new StringType(), new BooleanType(), new NullType()]);
96+
if (!$scalar->isSuperTypeOf($left)->yes() || $left instanceof BenevolentUnionType) {
9697
return $traverse($left, $right);
9798
}
98-
if (!$right->isScalar()->yes() || $right instanceof BenevolentUnionType) {
99+
if (!$scalar->isSuperTypeOf($right)->yes() || $right instanceof BenevolentUnionType) {
99100
return $traverse($left, $right);
100101
}
101102
if (!$left->isSuperTypeOf($right)->yes()) {
@@ -125,6 +126,20 @@ public static function dataDescriptionBased(): iterable
125126
$chooseScalarSubtype,
126127
"'aaa'|Foo",
127128
];
129+
130+
yield [
131+
'list<string|null>',
132+
'array<string>',
133+
$chooseScalarSubtype,
134+
'list<string>',
135+
];
136+
137+
yield [
138+
'list<string|null>',
139+
'list<string>',
140+
$chooseScalarSubtype,
141+
'list<string>',
142+
];
128143
}
129144

130145
/**

0 commit comments

Comments
 (0)