Skip to content

Commit

Permalink
Support narrowing down via in_array with enum cases
Browse files Browse the repository at this point in the history
  • Loading branch information
herndlm committed May 5, 2022
1 parent 7bd9fb7 commit 3b20724
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php
Expand Up @@ -46,6 +46,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
if (
$context->truthy()
|| count(TypeUtils::getConstantScalars($arrayValueType)) > 0
|| count(TypeUtils::getEnumCaseObjects($arrayValueType)) > 0
) {
return $this->typeSpecifier->create(
$node->getArgs()[0]->value,
Expand Down
9 changes: 9 additions & 0 deletions src/Type/TypeUtils.php
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Enum\EnumCaseObjectType;
use PHPStan\Type\Generic\TemplateType;
use function array_merge;

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

/**
* @return EnumCaseObjectType[]
*/
public static function getEnumCaseObjects(Type $type): array
{
return self::map(EnumCaseObjectType::class, $type, false);
}

/**
* @internal
* @return ConstantArrayType[]
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -640,6 +640,7 @@ public function dataFileAsserts(): iterable
if (PHP_VERSION_ID >= 80100) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/enums.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/enums-import-alias.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7176.php');
}

if (PHP_VERSION_ID >= 80000) {
Expand Down
30 changes: 30 additions & 0 deletions tests/PHPStan/Analyser/data/bug-7176.php
@@ -0,0 +1,30 @@
<?php declare(strict_types = 1); // lint >= 8.1

namespace Bug7176;

use function PHPStan\Testing\assertType;

enum Suit
{
case Hearts;
case Diamonds;
case Clubs;
case Spades;
}

function test(Suit $x): string {
if ($x === Suit::Clubs) {
assertType('Bug7176\Suit::Clubs', $x);
}

if (in_array($x, [Suit::Spades], true)) {
assertType('Bug7176\Suit::Spades', $x);
return 'DOES NOT WORK';
}
assertType('Bug7176\Suit::Clubs|Bug7176\Suit::Diamonds|Bug7176\Suit::Hearts', $x);

return match ($x) {
Suit::Hearts => 'a',
Suit::Diamonds => 'b',
};
}
8 changes: 8 additions & 0 deletions tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php
Expand Up @@ -194,4 +194,12 @@ public function testBug7095(): void
$this->analyse([__DIR__ . '/data/bug-7095.php'], []);
}

public function testBug7176(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('Test requires PHP 8.1.');
}
$this->analyse([__DIR__ . '/data/bug-7176.php'], []);
}

}
28 changes: 28 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/bug-7176.php
@@ -0,0 +1,28 @@
<?php declare(strict_types = 1); // lint >= 8.1

namespace Bug7176;

enum Suit
{
case Hearts;
case Diamonds;
case Clubs;
case Spades;
}

function test(Suit $x): string {
if ($x === Suit::Clubs) {
return 'WORKS';
}
// Suit::Clubs is correctly eliminated from possible values

if (in_array($x, [Suit::Spades], true)) {
return 'DOES NOT WORK';
}
// Suit::Spades is not eliminated from possible values

return match ($x) { // no error is expected here
Suit::Hearts => 'a',
Suit::Diamonds => 'b',
};
}

0 comments on commit 3b20724

Please sign in to comment.