From 9e9ab7b3b770eaac82e4186ae4c8dd84472caf91 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Thu, 18 Apr 2024 15:52:38 +0200 Subject: [PATCH] [TypeInfo] rework isA to handle class names and improve isNullable --- .../TypeInfo/Tests/Type/BackedEnumTypeTest.php | 4 ++++ .../TypeInfo/Tests/Type/BuiltinTypeTest.php | 3 +++ .../TypeInfo/Tests/Type/CollectionTypeTest.php | 7 +++++++ .../TypeInfo/Tests/Type/EnumTypeTest.php | 3 +++ .../TypeInfo/Tests/Type/GenericTypeTest.php | 2 ++ .../TypeInfo/Tests/Type/ObjectTypeTest.php | 2 ++ .../Component/TypeInfo/Tests/TypeTest.php | 11 ----------- src/Symfony/Component/TypeInfo/Type.php | 16 ++++++++-------- .../Component/TypeInfo/Type/BuiltinType.php | 13 +++++++++++++ .../Component/TypeInfo/Type/CollectionType.php | 5 +++++ .../TypeInfo/Type/CompositeTypeTrait.php | 9 ++------- .../Component/TypeInfo/Type/GenericType.php | 5 +++++ .../Component/TypeInfo/Type/ObjectType.php | 9 +++++++++ .../Component/TypeInfo/Type/TemplateType.php | 6 ++++++ 14 files changed, 69 insertions(+), 26 deletions(-) diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/BackedEnumTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/BackedEnumTypeTest.php index be3fb65678e3e..ec5ed67865075 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/BackedEnumTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/BackedEnumTypeTest.php @@ -40,5 +40,9 @@ public function testIsA() { $this->assertFalse((new BackedEnumType(DummyBackedEnum::class, Type::int()))->isA(TypeIdentifier::ARRAY)); $this->assertTrue((new BackedEnumType(DummyBackedEnum::class, Type::int()))->isA(TypeIdentifier::OBJECT)); + $this->assertFalse((new BackedEnumType(DummyBackedEnum::class, Type::int()))->isA(self::class)); + $this->assertTrue((new BackedEnumType(DummyBackedEnum::class, Type::int()))->isA(DummyBackedEnum::class)); + $this->assertTrue((new BackedEnumType(DummyBackedEnum::class, Type::int()))->isA(\BackedEnum::class)); + $this->assertTrue((new BackedEnumType(DummyBackedEnum::class, Type::int()))->isA(\UnitEnum::class)); } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/BuiltinTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/BuiltinTypeTest.php index c417e71ec58fa..02e204fc90511 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/BuiltinTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/BuiltinTypeTest.php @@ -61,5 +61,8 @@ public function testIsA() { $this->assertFalse((new BuiltinType(TypeIdentifier::INT))->isA(TypeIdentifier::ARRAY)); $this->assertTrue((new BuiltinType(TypeIdentifier::INT))->isA(TypeIdentifier::INT)); + $this->assertFalse((new BuiltinType(TypeIdentifier::INT))->isA('array')); + $this->assertTrue((new BuiltinType(TypeIdentifier::INT))->isA('int')); + $this->assertFalse((new BuiltinType(TypeIdentifier::INT))->isA(self::class)); } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php index 5faee73d95ea4..4d7494999a62d 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php @@ -95,5 +95,12 @@ public function testIsA() $this->assertTrue($type->isA(TypeIdentifier::ARRAY)); $this->assertFalse($type->isA(TypeIdentifier::STRING)); $this->assertFalse($type->isA(TypeIdentifier::INT)); + $this->assertFalse($type->isA(self::class)); + + $type = new CollectionType(new GenericType(Type::object(self::class), Type::string(), Type::bool())); + + $this->assertFalse($type->isA(TypeIdentifier::ARRAY)); + $this->assertTrue($type->isA(TypeIdentifier::OBJECT)); + $this->assertTrue($type->isA(self::class)); } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/EnumTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/EnumTypeTest.php index fdf7bafb3805a..225ac1e6c3dbf 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/EnumTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/EnumTypeTest.php @@ -39,5 +39,8 @@ public function testIsA() { $this->assertFalse((new EnumType(DummyEnum::class))->isA(TypeIdentifier::ARRAY)); $this->assertTrue((new EnumType(DummyEnum::class))->isA(TypeIdentifier::OBJECT)); + $this->assertTrue((new EnumType(DummyEnum::class))->isA(DummyEnum::class)); + $this->assertTrue((new EnumType(DummyEnum::class))->isA(\UnitEnum::class)); + $this->assertFalse((new EnumType(DummyEnum::class))->isA(\BackedEnum::class)); } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php index bdaf7e8654834..3ad00781a4f75 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php @@ -49,10 +49,12 @@ public function testIsA() $type = new GenericType(Type::builtin(TypeIdentifier::ARRAY), Type::string(), Type::bool()); $this->assertTrue($type->isA(TypeIdentifier::ARRAY)); $this->assertFalse($type->isA(TypeIdentifier::STRING)); + $this->assertFalse($type->isA(self::class)); $type = new GenericType(Type::object(self::class), Type::union(Type::bool(), Type::string()), Type::int(), Type::float()); $this->assertTrue($type->isA(TypeIdentifier::OBJECT)); $this->assertFalse($type->isA(TypeIdentifier::INT)); $this->assertFalse($type->isA(TypeIdentifier::STRING)); + $this->assertTrue($type->isA(self::class)); } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/ObjectTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/ObjectTypeTest.php index c9e3399361a20..19902909ab070 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/ObjectTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/ObjectTypeTest.php @@ -31,5 +31,7 @@ public function testIsA() { $this->assertFalse((new ObjectType(self::class))->isA(TypeIdentifier::ARRAY)); $this->assertTrue((new ObjectType(self::class))->isA(TypeIdentifier::OBJECT)); + $this->assertTrue((new ObjectType(self::class))->isA(self::class)); + $this->assertFalse((new ObjectType(self::class))->isA(\stdClass::class)); } } diff --git a/src/Symfony/Component/TypeInfo/Tests/TypeTest.php b/src/Symfony/Component/TypeInfo/Tests/TypeTest.php index 2f0ad858ed3e9..dacf7477a7e3d 100644 --- a/src/Symfony/Component/TypeInfo/Tests/TypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/TypeTest.php @@ -31,17 +31,6 @@ public function testIs() $this->assertFalse(Type::generic(Type::string(), Type::int())->is($isInt)); } - public function testIsA() - { - $this->assertTrue(Type::int()->isA(TypeIdentifier::INT)); - $this->assertTrue(Type::union(Type::string(), Type::int())->isA(TypeIdentifier::INT)); - $this->assertTrue(Type::generic(Type::int(), Type::string())->isA(TypeIdentifier::INT)); - - $this->assertFalse(Type::string()->isA(TypeIdentifier::INT)); - $this->assertFalse(Type::union(Type::string(), Type::float())->isA(TypeIdentifier::INT)); - $this->assertFalse(Type::generic(Type::string(), Type::int())->isA(TypeIdentifier::INT)); - } - public function testIsNullable() { $this->assertTrue(Type::null()->isNullable()); diff --git a/src/Symfony/Component/TypeInfo/Type.php b/src/Symfony/Component/TypeInfo/Type.php index 6cdb61d870891..d3d1944c0bf51 100644 --- a/src/Symfony/Component/TypeInfo/Type.php +++ b/src/Symfony/Component/TypeInfo/Type.php @@ -24,6 +24,13 @@ abstract class Type implements \Stringable abstract public function getBaseType(): BuiltinType|ObjectType; + /** + * @param TypeIdentifier|class-string $subject + */ + abstract public function isA(TypeIdentifier|string $subject): bool; + + abstract public function asNonNullable(): self; + /** * @param callable(Type): bool $callable */ @@ -32,15 +39,8 @@ public function is(callable $callable): bool return $callable($this); } - public function isA(TypeIdentifier $typeIdentifier): bool - { - return $this->getBaseType()->getTypeIdentifier() === $typeIdentifier; - } - public function isNullable(): bool { - return \in_array($this->getBaseType()->getTypeIdentifier(), [TypeIdentifier::NULL, TypeIdentifier::MIXED], true); + return $this->is(fn (Type $t): bool => $t->isA(TypeIdentifier::NULL) || $t->isA(TypeIdentifier::MIXED)); } - - abstract public function asNonNullable(): self; } diff --git a/src/Symfony/Component/TypeInfo/Type/BuiltinType.php b/src/Symfony/Component/TypeInfo/Type/BuiltinType.php index 3ce7de790f3f2..7dcc3685a5f81 100644 --- a/src/Symfony/Component/TypeInfo/Type/BuiltinType.php +++ b/src/Symfony/Component/TypeInfo/Type/BuiltinType.php @@ -44,6 +44,19 @@ public function getTypeIdentifier(): TypeIdentifier return $this->typeIdentifier; } + public function isA(TypeIdentifier|string $subject): bool + { + if ($subject instanceof TypeIdentifier) { + return $this->getTypeIdentifier() === $subject; + } + + try { + return TypeIdentifier::from($subject) === $this->getTypeIdentifier(); + } catch (\ValueError) { + return false; + } + } + /** * @return self|UnionType|BuiltinType|BuiltinType|BuiltinType|BuiltinType|BuiltinType|BuiltinType> */ diff --git a/src/Symfony/Component/TypeInfo/Type/CollectionType.php b/src/Symfony/Component/TypeInfo/Type/CollectionType.php index 950befcbb8ec4..addaa3536a440 100644 --- a/src/Symfony/Component/TypeInfo/Type/CollectionType.php +++ b/src/Symfony/Component/TypeInfo/Type/CollectionType.php @@ -56,6 +56,11 @@ public function getType(): BuiltinType|ObjectType|GenericType return $this->type; } + public function isA(TypeIdentifier|string $subject): bool + { + return $this->getType()->isA($subject); + } + public function isList(): bool { return $this->isList; diff --git a/src/Symfony/Component/TypeInfo/Type/CompositeTypeTrait.php b/src/Symfony/Component/TypeInfo/Type/CompositeTypeTrait.php index 15373674ee79a..e0f170b9d7d5b 100644 --- a/src/Symfony/Component/TypeInfo/Type/CompositeTypeTrait.php +++ b/src/Symfony/Component/TypeInfo/Type/CompositeTypeTrait.php @@ -55,14 +55,9 @@ public function getBaseType(): BuiltinType|ObjectType throw new LogicException(sprintf('Cannot get base type on "%s" compound type.', $this)); } - public function isA(TypeIdentifier $typeIdentifier): bool + public function isA(TypeIdentifier|string $subject): bool { - return $this->is(fn (Type $type) => $type->isA($typeIdentifier)); - } - - public function isNullable(): bool - { - return $this->is(fn (Type $type) => $type->isNullable()); + return $this->is(fn (Type $type) => $type->isA($subject)); } /** diff --git a/src/Symfony/Component/TypeInfo/Type/GenericType.php b/src/Symfony/Component/TypeInfo/Type/GenericType.php index e71a54b20c8c1..bbaf47dca35f6 100644 --- a/src/Symfony/Component/TypeInfo/Type/GenericType.php +++ b/src/Symfony/Component/TypeInfo/Type/GenericType.php @@ -54,6 +54,11 @@ public function getType(): BuiltinType|ObjectType return $this->type; } + public function isA(TypeIdentifier|string $subject): bool + { + return $this->getType()->isA($subject); + } + public function asNonNullable(): self { return $this; diff --git a/src/Symfony/Component/TypeInfo/Type/ObjectType.php b/src/Symfony/Component/TypeInfo/Type/ObjectType.php index ebb36537b9db7..edcc06446025b 100644 --- a/src/Symfony/Component/TypeInfo/Type/ObjectType.php +++ b/src/Symfony/Component/TypeInfo/Type/ObjectType.php @@ -40,6 +40,15 @@ public function getTypeIdentifier(): TypeIdentifier return TypeIdentifier::OBJECT; } + public function isA(TypeIdentifier|string $subject): bool + { + if ($subject instanceof TypeIdentifier) { + return $this->getTypeIdentifier() === $subject; + } + + return is_a($this->getClassName(), $subject, allow_string: true); + } + /** * @return T */ diff --git a/src/Symfony/Component/TypeInfo/Type/TemplateType.php b/src/Symfony/Component/TypeInfo/Type/TemplateType.php index e9b5c0875df5b..d6a28427f63d5 100644 --- a/src/Symfony/Component/TypeInfo/Type/TemplateType.php +++ b/src/Symfony/Component/TypeInfo/Type/TemplateType.php @@ -13,6 +13,7 @@ use Symfony\Component\TypeInfo\Exception\LogicException; use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * Represents a template placeholder, such as "T" in "Collection". @@ -33,6 +34,11 @@ public function getBaseType(): BuiltinType|ObjectType throw new LogicException(sprintf('Cannot get base type on "%s" template type.', $this)); } + public function isA(TypeIdentifier|string $subject): bool + { + return false; + } + public function getName(): string { return $this->name;