Skip to content

Commit ea12a00

Browse files
committed
Support narrowing down via in_array with enum cases
1 parent 7bd9fb7 commit ea12a00

File tree

6 files changed

+79
-0
lines changed

6 files changed

+79
-0
lines changed

src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
4646
if (
4747
$context->truthy()
4848
|| count(TypeUtils::getConstantScalars($arrayValueType)) > 0
49+
|| count(TypeUtils::getEnumCaseObjects($arrayValueType)) > 0
4950
) {
5051
return $this->typeSpecifier->create(
5152
$node->getArgs()[0]->value,

src/Type/TypeUtils.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPStan\Type\Constant\ConstantArrayType;
88
use PHPStan\Type\Constant\ConstantIntegerType;
99
use PHPStan\Type\Constant\ConstantStringType;
10+
use PHPStan\Type\Enum\EnumCaseObjectType;
1011
use PHPStan\Type\Generic\TemplateType;
1112
use function array_merge;
1213

@@ -173,6 +174,14 @@ public static function getConstantScalars(Type $type): array
173174
return self::map(ConstantScalarType::class, $type, false);
174175
}
175176

177+
/**
178+
* @return EnumCaseObjectType[]
179+
*/
180+
public static function getEnumCaseObjects(Type $type): array
181+
{
182+
return self::map(EnumCaseObjectType::class, $type, false);
183+
}
184+
176185
/**
177186
* @internal
178187
* @return ConstantArrayType[]

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@ public function dataFileAsserts(): iterable
640640
if (PHP_VERSION_ID >= 80100) {
641641
yield from $this->gatherAssertTypes(__DIR__ . '/data/enums.php');
642642
yield from $this->gatherAssertTypes(__DIR__ . '/data/enums-import-alias.php');
643+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7176.php');
643644
}
644645

645646
if (PHP_VERSION_ID >= 80000) {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1); // lint >= 8.1
2+
3+
namespace Bug7176;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
enum Suit
8+
{
9+
case Hearts;
10+
case Diamonds;
11+
case Clubs;
12+
case Spades;
13+
}
14+
15+
function test(Suit $x): string {
16+
if ($x === Suit::Clubs) {
17+
assertType('Bug7176\Suit::Clubs', $x);
18+
return 'WORKS';
19+
}
20+
assertType('Bug7176\Suit::Diamonds|Bug7176\Suit::Hearts|Bug7176\Suit::Spades', $x);
21+
22+
if (in_array($x, [Suit::Spades], true)) {
23+
assertType('Bug7176\Suit::Spades', $x);
24+
return 'DOES NOT WORK';
25+
}
26+
assertType('Bug7176\Suit::Diamonds|Bug7176\Suit::Hearts', $x);
27+
28+
return match ($x) {
29+
Suit::Hearts => 'a',
30+
Suit::Diamonds => 'b',
31+
};
32+
}

tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,4 +194,12 @@ public function testBug7095(): void
194194
$this->analyse([__DIR__ . '/data/bug-7095.php'], []);
195195
}
196196

197+
public function testBug7176(): void
198+
{
199+
if (PHP_VERSION_ID < 80100) {
200+
$this->markTestSkipped('Test requires PHP 8.1.');
201+
}
202+
$this->analyse([__DIR__ . '/data/bug-7176.php'], []);
203+
}
204+
197205
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1); // lint >= 8.1
2+
3+
namespace Bug7176;
4+
5+
enum Suit
6+
{
7+
case Hearts;
8+
case Diamonds;
9+
case Clubs;
10+
case Spades;
11+
}
12+
13+
function test(Suit $x): string {
14+
if ($x === Suit::Clubs) {
15+
return 'WORKS';
16+
}
17+
// Suit::Clubs is correctly eliminated from possible values
18+
19+
if (in_array($x, [Suit::Spades], true)) {
20+
return 'DOES NOT WORK';
21+
}
22+
// Suit::Spades is not eliminated from possible values
23+
24+
return match ($x) { // no error is expected here
25+
Suit::Hearts => 'a',
26+
Suit::Diamonds => 'b',
27+
};
28+
}

0 commit comments

Comments
 (0)