diff --git a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php index 98576400ef..6988702c57 100644 --- a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php @@ -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, diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index ba2e249fc6..792a244e12 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -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; @@ -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[] diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 22dbcd8548..b5f4d49854 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -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) { diff --git a/tests/PHPStan/Analyser/data/bug-7176.php b/tests/PHPStan/Analyser/data/bug-7176.php new file mode 100644 index 0000000000..b6c844f66d --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-7176.php @@ -0,0 +1,32 @@ += 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); + return 'WORKS'; + } + assertType('Bug7176\Suit::Diamonds|Bug7176\Suit::Hearts|Bug7176\Suit::Spades', $x); + + if (in_array($x, [Suit::Spades], true)) { + assertType('Bug7176\Suit::Spades', $x); + return 'DOES NOT WORK'; + } + assertType('Bug7176\Suit::Diamonds|Bug7176\Suit::Hearts', $x); + + return match ($x) { + Suit::Hearts => 'a', + Suit::Diamonds => 'b', + }; +} diff --git a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php index c351a9ebd9..50a154e87d 100644 --- a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php @@ -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'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-7176.php b/tests/PHPStan/Rules/Comparison/data/bug-7176.php new file mode 100644 index 0000000000..da799fca57 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-7176.php @@ -0,0 +1,28 @@ += 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', + }; +}