Skip to content

Commit f0762db

Browse files
committed
fix union/intersect involving enum case
1 parent dc8d8d0 commit f0762db

File tree

4 files changed

+103
-2
lines changed

4 files changed

+103
-2
lines changed

phpstan-baseline.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,6 +1692,12 @@ parameters:
16921692
count: 5
16931693
path: src/Type/TypeCombinator.php
16941694

1695+
-
1696+
message: '#^Doing instanceof PHPStan\\Type\\Enum\\EnumCaseObjectType is error\-prone and deprecated\. Use Type\:\:getEnumCases\(\) instead\.$#'
1697+
identifier: phpstanApi.instanceofType
1698+
count: 2
1699+
path: src/Type/TypeCombinator.php
1700+
16951701
-
16961702
message: '#^Doing instanceof PHPStan\\Type\\FloatType is error\-prone and deprecated\. Use Type\:\:isFloat\(\) instead\.$#'
16971703
identifier: phpstanApi.instanceofType

src/Type/TypeCombinator.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use PHPStan\Type\Constant\ConstantFloatType;
1919
use PHPStan\Type\Constant\ConstantIntegerType;
2020
use PHPStan\Type\Constant\ConstantStringType;
21+
use PHPStan\Type\Enum\EnumCaseObjectType;
2122
use PHPStan\Type\Generic\GenericClassStringType;
2223
use PHPStan\Type\Generic\TemplateArrayType;
2324
use PHPStan\Type\Generic\TemplateBenevolentUnionType;
@@ -539,7 +540,7 @@ private static function unionWithSubtractedType(
539540
return $type;
540541
}
541542

542-
if ($type instanceof SubtractableType) {
543+
if ($type instanceof SubtractableType && ! $type instanceof EnumCaseObjectType) {
543544
$subtractedType = $type->getSubtractedType() === null
544545
? $subtractedType
545546
: self::union($type->getSubtractedType(), $subtractedType);
@@ -595,7 +596,7 @@ private static function intersectWithSubtractedType(
595596
}
596597

597598
$subtractedType = self::union(...$subtractedTypes);
598-
} elseif ($b instanceof SubtractableType) {
599+
} elseif ($b instanceof SubtractableType && ! $b instanceof EnumCaseObjectType) {
599600
$subtractedType = $b->getSubtractedType();
600601
if ($subtractedType === null) {
601602
return $a->getTypeWithoutSubtractedType();
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php // lint >= 8.1
2+
3+
declare(strict_types = 1);
4+
5+
namespace EnumVsInArray;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
enum FooEnum
10+
{
11+
case A;
12+
case B;
13+
case C;
14+
case D;
15+
case E;
16+
case F;
17+
case G;
18+
case H;
19+
case I;
20+
case J;
21+
}
22+
23+
function foo(FooEnum $e): int
24+
{
25+
if (in_array($e, [FooEnum::A, FooEnum::B, FooEnum::C], true)) {
26+
throw new \Exception('a');
27+
}
28+
29+
assertType('EnumVsInArray\FooEnum~(EnumVsInArray\FooEnum::A|EnumVsInArray\FooEnum::B|EnumVsInArray\FooEnum::C)', $e);
30+
31+
if (rand(0, 10) === 1) {
32+
if (!in_array($e, [FooEnum::D, FooEnum::E], true)) {
33+
throw new \Exception('d');
34+
}
35+
}
36+
37+
assertType('EnumVsInArray\FooEnum~(EnumVsInArray\FooEnum::A|EnumVsInArray\FooEnum::B|EnumVsInArray\FooEnum::C)', $e);
38+
39+
return match ($e) {
40+
FooEnum::D, FooEnum::E, FooEnum::F, FooEnum::G, FooEnum::H, FooEnum::I => 2,
41+
FooEnum::J => 3,
42+
};
43+
}

tests/PHPStan/Type/TypeCombinatorTest.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2262,6 +2262,36 @@ public function dataUnion(): iterable
22622262
'PHPStan\Fixture\ManyCasesTestEnum~PHPStan\Fixture\ManyCasesTestEnum::A',
22632263
];
22642264

2265+
yield [
2266+
[
2267+
new ObjectType('PHPStan\Fixture\ManyCasesTestEnum', new UnionType([
2268+
new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A'),
2269+
new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'B'),
2270+
])),
2271+
new UnionType([
2272+
new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'C'),
2273+
new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'D'),
2274+
]),
2275+
],
2276+
ObjectType::class,
2277+
'PHPStan\Fixture\ManyCasesTestEnum~(PHPStan\Fixture\ManyCasesTestEnum::A|PHPStan\Fixture\ManyCasesTestEnum::B)',
2278+
];
2279+
2280+
yield [
2281+
[
2282+
new ObjectType('PHPStan\Fixture\ManyCasesTestEnum', new UnionType([
2283+
new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A'),
2284+
new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'B'),
2285+
])),
2286+
new UnionType([
2287+
new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A'),
2288+
new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'D'),
2289+
]),
2290+
],
2291+
ObjectType::class,
2292+
'PHPStan\Fixture\ManyCasesTestEnum~PHPStan\Fixture\ManyCasesTestEnum::B',
2293+
];
2294+
22652295
yield [
22662296
[
22672297
new ThisType(
@@ -4224,6 +4254,27 @@ public function dataIntersect(): iterable
42244254
'$this(stdClass)&stdClass::foo',
42254255
];
42264256

4257+
yield [
4258+
[
4259+
new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A'),
4260+
new MixedType(false, new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A')),
4261+
],
4262+
NeverType::class,
4263+
'*NEVER*=implicit',
4264+
];
4265+
4266+
yield [
4267+
[
4268+
new UnionType([
4269+
new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A'),
4270+
new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'B'),
4271+
]),
4272+
new MixedType(false, new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A')),
4273+
],
4274+
EnumCaseObjectType::class,
4275+
'PHPStan\Fixture\ManyCasesTestEnum::B',
4276+
];
4277+
42274278
yield [
42284279
[
42294280
TypeCombinator::intersect(new StringType(), new AccessoryNonEmptyStringType()),

0 commit comments

Comments
 (0)