diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 0859825701..e5b2540d7b 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -6,6 +6,7 @@ use ArrayObject; use Closure; use Countable; +use DateTimeInterface; use Iterator; use IteratorAggregate; use PHPStan\Analyser\OutOfClassScope; @@ -44,6 +45,8 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; +use Stringable; +use Throwable; use Traversable; use function array_key_exists; use function array_map; @@ -730,7 +733,23 @@ public function isEnum(): TrinaryLogic return TrinaryLogic::createMaybe(); } - return TrinaryLogic::createFromBoolean($classReflection->isEnum()); + if ( + $classReflection->isEnum() + || $classReflection->is('UnitEnum') + ) { + return TrinaryLogic::createYes(); + } + + if ( + $classReflection->isInterface() + && !$classReflection->is(Stringable::class) // enums cannot have __toString + && !$classReflection->is(Throwable::class) // enums cannot extend Exception/Error + && !$classReflection->is(DateTimeInterface::class) // userland classes cannot extend DateTimeInterface + ) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createNo(); } public function canAccessProperties(): TrinaryLogic diff --git a/tests/PHPStan/Type/ObjectTypeTest.php b/tests/PHPStan/Type/ObjectTypeTest.php index f17c18ea97..9a1bdf2fea 100644 --- a/tests/PHPStan/Type/ObjectTypeTest.php +++ b/tests/PHPStan/Type/ObjectTypeTest.php @@ -70,6 +70,35 @@ public function testIsIterable(ObjectType $type, TrinaryLogic $expectedResult): ); } + /** + * @return iterable + */ + public function dataIsEnum(): iterable + { + if (PHP_VERSION_ID >= 80000) { + yield [new ObjectType('UnitEnum'), TrinaryLogic::createYes()]; + yield [new ObjectType('BackedEnum'), TrinaryLogic::createYes()]; + } + yield [new ObjectType('Unknown'), TrinaryLogic::createMaybe()]; + yield [new ObjectType('Countable'), TrinaryLogic::createMaybe()]; + yield [new ObjectType('Stringable'), TrinaryLogic::createNo()]; + yield [new ObjectType('Throwable'), TrinaryLogic::createNo()]; + yield [new ObjectType('DateTime'), TrinaryLogic::createNo()]; + } + + /** + * @dataProvider dataIsEnum + */ + public function testIsEnum(ObjectType $type, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isEnum(); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isEnum()', $type->describe(VerbosityLevel::precise())), + ); + } + public function dataIsCallable(): array { return [