From 0d10427a1bce65f9cf0b58b210fefbac1dc9aaad Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 4 Apr 2025 12:16:37 +0200 Subject: [PATCH 1/5] ObjectType: fix isEnum --- src/Type/ObjectType.php | 22 +++++++++++++++++++- tests/PHPStan/Type/ObjectTypeTest.php | 29 +++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 0859825701..0b1f16ecd2 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -4,6 +4,7 @@ use ArrayAccess; use ArrayObject; +use BackedEnum; use Closure; use Countable; use Iterator; @@ -44,7 +45,10 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; +use Stringable; +use Throwable; use Traversable; +use UnitEnum; use function array_key_exists; use function array_map; use function array_values; @@ -730,7 +734,23 @@ public function isEnum(): TrinaryLogic return TrinaryLogic::createMaybe(); } - return TrinaryLogic::createFromBoolean($classReflection->isEnum()); + if ( + $classReflection->isEnum() + || $classReflection->is(UnitEnum::class) + || $classReflection->is(BackedEnum::class) + ) { + return TrinaryLogic::createYes(); + } + + if ( + $classReflection->isInterface() + && !$classReflection->is(Stringable::class) // enums cannot have __toString + && !$classReflection->is(Throwable::class) // enums cannot extend Exception/Error + ) { + 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..eb6786fe9b 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 array + */ + public function dataIsEnum(): array + { + return [ + [new ObjectType('UnitEnum'), TrinaryLogic::createYes()], + [new ObjectType('BackedEnum'), TrinaryLogic::createYes()], + [new ObjectType('Unknown'), TrinaryLogic::createMaybe()], + [new ObjectType('Countable'), TrinaryLogic::createMaybe()], + [new ObjectType('Stringable'), TrinaryLogic::createNo()], + [new ObjectType('Throwable'), TrinaryLogic::createNo()], + [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 [ From c3255a183b244cc2f8358666cb558a31b36e993d Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 4 Apr 2025 12:23:53 +0200 Subject: [PATCH 2/5] Fix test for PHP 7.4 --- tests/PHPStan/Type/ObjectTypeTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Type/ObjectTypeTest.php b/tests/PHPStan/Type/ObjectTypeTest.php index eb6786fe9b..8f685b1126 100644 --- a/tests/PHPStan/Type/ObjectTypeTest.php +++ b/tests/PHPStan/Type/ObjectTypeTest.php @@ -76,8 +76,8 @@ public function testIsIterable(ObjectType $type, TrinaryLogic $expectedResult): public function dataIsEnum(): array { return [ - [new ObjectType('UnitEnum'), TrinaryLogic::createYes()], - [new ObjectType('BackedEnum'), TrinaryLogic::createYes()], + [new ObjectType('UnitEnum'), PHP_VERSION_ID < 80000 ? TrinaryLogic::createMaybe() : TrinaryLogic::createYes()], + [new ObjectType('BackedEnum'), PHP_VERSION_ID < 80000 ? TrinaryLogic::createMaybe() : TrinaryLogic::createYes()], [new ObjectType('Unknown'), TrinaryLogic::createMaybe()], [new ObjectType('Countable'), TrinaryLogic::createMaybe()], [new ObjectType('Stringable'), TrinaryLogic::createNo()], From d75ad3bfc608d6ba17dab77c1b88bd306ff8b4c4 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 4 Apr 2025 12:30:15 +0200 Subject: [PATCH 3/5] Better test fix --- tests/PHPStan/Type/ObjectTypeTest.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/PHPStan/Type/ObjectTypeTest.php b/tests/PHPStan/Type/ObjectTypeTest.php index 8f685b1126..9a1bdf2fea 100644 --- a/tests/PHPStan/Type/ObjectTypeTest.php +++ b/tests/PHPStan/Type/ObjectTypeTest.php @@ -71,19 +71,19 @@ public function testIsIterable(ObjectType $type, TrinaryLogic $expectedResult): } /** - * @return array + * @return iterable */ - public function dataIsEnum(): array + public function dataIsEnum(): iterable { - return [ - [new ObjectType('UnitEnum'), PHP_VERSION_ID < 80000 ? TrinaryLogic::createMaybe() : TrinaryLogic::createYes()], - [new ObjectType('BackedEnum'), PHP_VERSION_ID < 80000 ? TrinaryLogic::createMaybe() : TrinaryLogic::createYes()], - [new ObjectType('Unknown'), TrinaryLogic::createMaybe()], - [new ObjectType('Countable'), TrinaryLogic::createMaybe()], - [new ObjectType('Stringable'), TrinaryLogic::createNo()], - [new ObjectType('Throwable'), TrinaryLogic::createNo()], - [new ObjectType('DateTime'), TrinaryLogic::createNo()], - ]; + 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()]; } /** From c85decacf9fb8cdd59e74c73a54c636bc90ec112 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 4 Apr 2025 12:34:45 +0200 Subject: [PATCH 4/5] Reference UnitEnum by string --- src/Type/ObjectType.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 0b1f16ecd2..5923cfbf65 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -4,7 +4,6 @@ use ArrayAccess; use ArrayObject; -use BackedEnum; use Closure; use Countable; use Iterator; @@ -48,7 +47,6 @@ use Stringable; use Throwable; use Traversable; -use UnitEnum; use function array_key_exists; use function array_map; use function array_values; @@ -736,8 +734,8 @@ public function isEnum(): TrinaryLogic if ( $classReflection->isEnum() - || $classReflection->is(UnitEnum::class) - || $classReflection->is(BackedEnum::class) + || $classReflection->is('UnitEnum') + || $classReflection->is('BackedEnum') ) { return TrinaryLogic::createYes(); } From 3438477abf843c566fb10567345931ad33fe8fd7 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Mon, 7 Apr 2025 10:38:09 +0200 Subject: [PATCH 5/5] DateTimeInterface, BackedEnum --- src/Type/ObjectType.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 5923cfbf65..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; @@ -735,7 +736,6 @@ public function isEnum(): TrinaryLogic if ( $classReflection->isEnum() || $classReflection->is('UnitEnum') - || $classReflection->is('BackedEnum') ) { return TrinaryLogic::createYes(); } @@ -744,6 +744,7 @@ public function isEnum(): TrinaryLogic $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(); }