Skip to content

Commit

Permalink
[TypeInfo] rework isA to handle class names and improve isNullable
Browse files Browse the repository at this point in the history
  • Loading branch information
mtarld committed May 2, 2024
1 parent 13ab9eb commit 9e9ab7b
Show file tree
Hide file tree
Showing 14 changed files with 69 additions and 26 deletions.
Expand Up @@ -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));
}
}
3 changes: 3 additions & 0 deletions src/Symfony/Component/TypeInfo/Tests/Type/BuiltinTypeTest.php
Expand Up @@ -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));
}
}
Expand Up @@ -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));
}
}
3 changes: 3 additions & 0 deletions src/Symfony/Component/TypeInfo/Tests/Type/EnumTypeTest.php
Expand Up @@ -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));
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php
Expand Up @@ -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));
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Component/TypeInfo/Tests/Type/ObjectTypeTest.php
Expand Up @@ -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));
}
}
11 changes: 0 additions & 11 deletions src/Symfony/Component/TypeInfo/Tests/TypeTest.php
Expand Up @@ -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());
Expand Down
16 changes: 8 additions & 8 deletions src/Symfony/Component/TypeInfo/Type.php
Expand Up @@ -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
*/
Expand All @@ -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;
}
13 changes: 13 additions & 0 deletions src/Symfony/Component/TypeInfo/Type/BuiltinType.php
Expand Up @@ -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<TypeIdentifier::OBJECT>|BuiltinType<TypeIdentifier::RESOURCE>|BuiltinType<TypeIdentifier::ARRAY>|BuiltinType<TypeIdentifier::STRING>|BuiltinType<TypeIdentifier::FLOAT>|BuiltinType<TypeIdentifier::INT>|BuiltinType<TypeIdentifier::BOOL>>
*/
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/TypeInfo/Type/CollectionType.php
Expand Up @@ -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;
Expand Down
9 changes: 2 additions & 7 deletions src/Symfony/Component/TypeInfo/Type/CompositeTypeTrait.php
Expand Up @@ -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));
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/TypeInfo/Type/GenericType.php
Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions src/Symfony/Component/TypeInfo/Type/ObjectType.php
Expand Up @@ -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
*/
Expand Down
6 changes: 6 additions & 0 deletions src/Symfony/Component/TypeInfo/Type/TemplateType.php
Expand Up @@ -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<T>".
Expand All @@ -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;
Expand Down

0 comments on commit 9e9ab7b

Please sign in to comment.