Skip to content

Commit 58a336f

Browse files
committed
Keep benevolent union array key type with Foo[] after non-empty check
1 parent 9cc33cb commit 58a336f

4 files changed

Lines changed: 73 additions & 3 deletions

File tree

src/Type/TypeCombinator.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,20 @@ private static function reduceArrays(array $constantArrays): array
620620

621621
public static function intersect(Type ...$types): Type
622622
{
623+
usort($types, static function (Type $a, Type $b): int {
624+
if (!$a instanceof UnionType || !$b instanceof UnionType) {
625+
return 0;
626+
}
627+
628+
if ($a instanceof BenevolentUnionType) {
629+
return 1;
630+
}
631+
if ($b instanceof BenevolentUnionType) {
632+
return -1;
633+
}
634+
635+
return 0;
636+
});
623637
// transform A & (B | C) to (A & B) | (A & C)
624638
foreach ($types as $i => $type) {
625639
if ($type instanceof UnionType) {
@@ -632,7 +646,12 @@ public static function intersect(Type ...$types): Type
632646
);
633647
}
634648

635-
return self::union(...$topLevelUnionSubTypes);
649+
$union = self::union(...$topLevelUnionSubTypes);
650+
if ($type instanceof BenevolentUnionType) {
651+
return TypeUtils::toBenevolentUnion($union);
652+
}
653+
654+
return $union;
636655
}
637656
}
638657

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9405,11 +9405,11 @@ public function dataPhp73Functions(): array
94059405
'array_key_last($mixedArray)',
94069406
],
94079407
[
9408-
'int|string',
9408+
'(int|string)',
94099409
'array_key_first($nonEmptyArray)',
94109410
],
94119411
[
9412-
'int|string',
9412+
'(int|string)',
94139413
'array_key_last($nonEmptyArray)',
94149414
],
94159415
[
@@ -10028,6 +10028,11 @@ public function dataClassPhpDocs(): array
1002810028
return $this->gatherAssertTypes(__DIR__ . '/data/classPhpDocs.php');
1002910029
}
1003010030

10031+
public function dataNonEmptyArrayKeyType(): array
10032+
{
10033+
return $this->gatherAssertTypes(__DIR__ . '/data/non-empty-array-key-type.php');
10034+
}
10035+
1003110036
/**
1003210037
* @dataProvider dataBug2574
1003310038
* @dataProvider dataBug2577
@@ -10092,6 +10097,7 @@ public function dataClassPhpDocs(): array
1009210097
* @dataProvider dataMinMaxReturnTypeWithArrays
1009310098
* @dataProvider dataNativeStaticReturnType
1009410099
* @dataProvider dataClassPhpDocs
10100+
* @dataProvider dataNonEmptyArrayKeyType
1009510101
* @param string $assertType
1009610102
* @param string $file
1009710103
* @param mixed ...$args
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace NonEmptyArrayKeyType;
4+
5+
use function PHPStan\Analyser\assertType;
6+
7+
class Foo
8+
{
9+
10+
/**
11+
* @param \stdClass[] $items
12+
*/
13+
public function doFoo(array $items)
14+
{
15+
assertType('array<stdClass>', $items);
16+
17+
if (count($items) > 0) {
18+
assertType('array<stdClass>&nonEmpty', $items);
19+
foreach ($items as $i => $val) {
20+
assertType('(int|string)', $i);
21+
assertType('stdClass', $val);
22+
}
23+
}
24+
}
25+
26+
/**
27+
* @param \stdClass[] $items
28+
*/
29+
public function doBar(array $items)
30+
{
31+
foreach ($items as $i => $val) {
32+
assertType('(int|string)', $i);
33+
assertType('stdClass', $val);
34+
}
35+
}
36+
37+
}

tests/PHPStan/Type/TypeCombinatorTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2782,6 +2782,14 @@ public function dataIntersect(): array
27822782
IntersectionType::class,
27832783
'string&hasOffset(int)',
27842784
],
2785+
[
2786+
[
2787+
new BenevolentUnionType([new IntegerType(), new StringType()]),
2788+
new MixedType(),
2789+
],
2790+
BenevolentUnionType::class,
2791+
'(int|string)',
2792+
],
27852793
];
27862794
}
27872795

0 commit comments

Comments
 (0)