Skip to content

Commit c333639

Browse files
committed
Fix broken edge-case, few more test cases
1 parent c340d13 commit c333639

File tree

2 files changed

+86
-5
lines changed

2 files changed

+86
-5
lines changed

src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@
1515
use PHPStan\Reflection\FunctionReflection;
1616
use PHPStan\Type\Accessory\NonEmptyArrayType;
1717
use PHPStan\Type\ArrayType;
18+
use PHPStan\Type\Enum\EnumCaseObjectType;
1819
use PHPStan\Type\FunctionTypeSpecifyingExtension;
20+
use PHPStan\Type\IntersectionType;
1921
use PHPStan\Type\MixedType;
22+
use PHPStan\Type\Type;
2023
use PHPStan\Type\TypeCombinator;
24+
use PHPStan\Type\TypeTraverser;
25+
use PHPStan\Type\UnionType;
2126
use function count;
2227
use function strtolower;
2328

@@ -111,6 +116,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
111116
|| (
112117
$context->false()
113118
&& count($arrayValueType->getFiniteTypes()) > 0
119+
&& $this->isEnumCasesArray($arrayValueType) // avoid eliminating all enum cases in edge case like in_array(Enum, list<Enum>, true)
114120
)
115121
) {
116122
$specifiedTypes = $this->typeSpecifier->create(
@@ -162,4 +168,23 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
162168
return $specifiedTypes;
163169
}
164170

171+
private function isEnumCasesArray(Type $type): bool
172+
{
173+
$containsOnlyEnumCases = true;
174+
175+
TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$containsOnlyEnumCases): Type {
176+
if ($type instanceof UnionType || $type instanceof IntersectionType) {
177+
return $traverse($type);
178+
}
179+
180+
if (!$type instanceof EnumCaseObjectType) {
181+
$containsOnlyEnumCases = false;
182+
}
183+
184+
return $type;
185+
});
186+
187+
return $containsOnlyEnumCases;
188+
}
189+
165190
}
Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,78 @@
11
<?php
22

3+
use function PHPStan\Testing\assertType;
4+
35
enum MyEnum: string
46
{
57

68
case A = 'a';
79
case B = 'b';
810
case C = 'c';
911

10-
const SET1 = [self::A, self::B, self::C];
12+
const SET_AB = [self::A, self::B];
13+
const SET_C = [self::C];
14+
const SET_ABC = [self::A, self::B, self::C];
15+
16+
public function test1(): void
17+
{
18+
foreach (MyEnum::cases() as $enum) {
19+
if (in_array($enum, MyEnum::SET_AB, true)) {
20+
assertType('MyEnum::A|MyEnum::B', $enum);
21+
} elseif (in_array($enum, MyEnum::SET_C, true)) {
22+
assertType('MyEnum::C', $enum);
23+
} else {
24+
assertType('*NEVER*', $enum);
25+
}
26+
}
27+
}
28+
29+
public function test2(): void
30+
{
31+
foreach (MyEnum::cases() as $enum) {
32+
if (in_array($enum, MyEnum::SET_ABC, true)) {
33+
assertType('MyEnum::A|MyEnum::B|MyEnum::C', $enum);
34+
} else {
35+
assertType('*NEVER*', $enum);
36+
}
37+
}
38+
}
39+
40+
public function test3(): void
41+
{
42+
foreach (MyEnum::cases() as $enum) {
43+
if (in_array($enum, MyEnum::SET_C, true)) {
44+
assertType('MyEnum::C', $enum);
45+
} else {
46+
assertType('MyEnum::A|MyEnum::B', $enum);
47+
}
48+
}
49+
}
50+
public function test4(): void
51+
{
52+
foreach ([MyEnum::C] as $enum) {
53+
if (in_array($enum, MyEnum::SET_C, true)) {
54+
assertType('MyEnum::C', $enum);
55+
} else {
56+
assertType('*NEVER*', $enum);
57+
}
58+
}
59+
}
1160

1261
}
1362

63+
class InArrayEnum
64+
{
1465

15-
foreach ([MyEnum::A, MyEnum::B, MyEnum::C] as $enum) {
66+
/** @var list<MyEnum> */
67+
private array $list = [];
1668

17-
if (in_array($enum, MyEnum::SET1, true)) {
69+
public function doFoo(MyEnum $enum): void
70+
{
71+
if (in_array($enum, $this->list, true)) {
72+
return;
73+
}
1874

19-
} else {
20-
\PHPStan\Testing\assertType('*NEVER*', $enum);
75+
assertType(MyEnum::class, $enum);
2176
}
77+
2278
}

0 commit comments

Comments
 (0)