Skip to content

Commit d6a4374

Browse files
committed
Merge remote-tracking branch 'origin/2.1.x' into 2.2.x
2 parents 7cb1e41 + 56ecce5 commit d6a4374

File tree

3 files changed

+109
-4
lines changed

3 files changed

+109
-4
lines changed

src/Type/TypeCombinator.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,10 +1547,14 @@ public static function intersect(Type ...$types): Type
15471547
$newArray = ConstantArrayTypeBuilder::createEmpty();
15481548
$valueTypes = $types[$i]->getValueTypes();
15491549
foreach ($types[$i]->getKeyTypes() as $k => $keyType) {
1550+
$hasOffset = $types[$j]->hasOffsetValueType($keyType);
1551+
if ($hasOffset->no()) {
1552+
continue;
1553+
}
15501554
$newArray->setOffsetValueType(
15511555
self::intersect($keyType, $types[$j]->getIterableKeyType()),
1552-
self::intersect($valueTypes[$k], $types[$j]->getIterableValueType()),
1553-
$types[$i]->isOptionalKey($k) && !$types[$j]->hasOffsetValueType($keyType)->yes(),
1556+
self::intersect($valueTypes[$k], $types[$j]->getOffsetValueType($keyType)),
1557+
$types[$i]->isOptionalKey($k) && !$hasOffset->yes(),
15541558
);
15551559
}
15561560
$types[$i] = $newArray->getArray();
@@ -1563,10 +1567,14 @@ public static function intersect(Type ...$types): Type
15631567
$newArray = ConstantArrayTypeBuilder::createEmpty();
15641568
$valueTypes = $types[$j]->getValueTypes();
15651569
foreach ($types[$j]->getKeyTypes() as $k => $keyType) {
1570+
$hasOffset = $types[$i]->hasOffsetValueType($keyType);
1571+
if ($hasOffset->no()) {
1572+
continue;
1573+
}
15661574
$newArray->setOffsetValueType(
15671575
self::intersect($keyType, $types[$i]->getIterableKeyType()),
1568-
self::intersect($valueTypes[$k], $types[$i]->getIterableValueType()),
1569-
$types[$j]->isOptionalKey($k) && !$types[$i]->hasOffsetValueType($keyType)->yes(),
1576+
self::intersect($valueTypes[$k], $types[$i]->getOffsetValueType($keyType)),
1577+
$types[$j]->isOptionalKey($k) && !$hasOffset->yes(),
15701578
);
15711579
}
15721580
$types[$j] = $newArray->getArray();
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug11234;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Payload {}
8+
9+
/** @param array{0|1|2|3, int|Payload|string|null}&array{int, Payload} $x */
10+
function testIntersectConstantUnionWithInt(mixed $x): void
11+
{
12+
assertType('array{0|1|2|3, Bug11234\Payload}', $x);
13+
}
14+
15+
/** @param array{int, Payload}&array{0|1|2|3, int|Payload|string|null} $x */
16+
function testIntersectConstantUnionWithIntReverse(mixed $x): void
17+
{
18+
assertType('array{0|1|2|3, Bug11234\Payload}', $x);
19+
}
20+
21+
/** @param array{0|1|2|3, int|Payload|string|null}&array{0|1|2|3, Payload} $x */
22+
function testIntersectBothConstantUnion(mixed $x): void
23+
{
24+
assertType('array{0|1|2|3, Bug11234\Payload}', $x);
25+
}
26+
27+
/** @param array{int, int|Payload|string|null}&array{int, Payload} $y */
28+
function testIntersectPlainInt(mixed $y): void
29+
{
30+
assertType('array{int, Bug11234\Payload}', $y);
31+
}
32+
33+
/** @param array{0|1, string|int, Payload|null}&array{int, string, Payload} $z */
34+
function testIntersectThreePositions(mixed $z): void
35+
{
36+
assertType('array{0|1, string, Bug11234\Payload}', $z);
37+
}
38+
39+
/** @param array{'a'|'b', int|Payload|string|null}&array{string, Payload} $w */
40+
function testIntersectStringConstantUnion(mixed $w): void
41+
{
42+
assertType("array{'a'|'b', Bug11234\Payload}", $w);
43+
}
44+
45+
/** @param array{0|1, int|string}&array{int, int, extra?: bool} $v */
46+
function testIntersectOptionalKey(mixed $v): void
47+
{
48+
assertType('array{0|1, int}', $v);
49+
}
50+
51+
/** @param array{true|false, int|string}&array{bool, string} $u */
52+
function testIntersectBoolConstantUnion(mixed $u): void
53+
{
54+
assertType('array{bool, string}', $u);
55+
}
56+
57+
/** @param array{int<0, 3>, int|Payload|string|null}&array{int, Payload} $x */
58+
function testIntersectIntRangeValue(mixed $x): void
59+
{
60+
assertType('array{int<0, 3>, Bug11234\Payload}', $x);
61+
}
62+
63+
/** @param array{non-empty-string, int|Payload|string|null}&array{string, Payload} $x */
64+
function testIntersectNonEmptyStringValue(mixed $x): void
65+
{
66+
assertType('array{non-empty-string, Bug11234\Payload}', $x);
67+
}
68+
69+
/** @param array{0|1|2|3, non-empty-string|int|null}&array{int, string} $x */
70+
function testIntersectNonEmptyStringInUnion(mixed $x): void
71+
{
72+
assertType('array{0|1|2|3, non-empty-string}', $x);
73+
}
74+
75+
/** @param array{0|1|2|3, string|null}&array{int, non-empty-string} $x */
76+
function testIntersectWithNonEmptyStringOtherSide(mixed $x): void
77+
{
78+
assertType('array{0|1|2|3, non-empty-string}', $x);
79+
}

tests/PHPStan/Type/TypeCombinatorTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5065,6 +5065,24 @@ public static function dataIntersect(): iterable
50655065
IntersectionType::class,
50665066
'lowercase-string&non-decimal-int-string',
50675067
];
5068+
5069+
yield [
5070+
[
5071+
new ConstantArrayType(
5072+
[new ConstantIntegerType(0), new ConstantIntegerType(1)],
5073+
[
5074+
new UnionType([new ConstantIntegerType(0), new ConstantIntegerType(1), new ConstantIntegerType(2), new ConstantIntegerType(3)]),
5075+
new UnionType([new IntegerType(), new ObjectType('stdClass'), new StringType(), new NullType()]),
5076+
],
5077+
),
5078+
new ConstantArrayType(
5079+
[new ConstantIntegerType(0), new ConstantIntegerType(1)],
5080+
[new IntegerType(), new ObjectType('stdClass')],
5081+
),
5082+
],
5083+
ConstantArrayType::class,
5084+
'array{0|1|2|3, stdClass}',
5085+
];
50685086
}
50695087

50705088
/**

0 commit comments

Comments
 (0)