Skip to content

Commit

Permalink
minor #54669 [TypeInfo] rework isA to handle class names and improve …
Browse files Browse the repository at this point in the history
…isNullable (mtarld)

This PR was merged into the 7.1 branch.

Discussion
----------

[TypeInfo] rework isA to handle class names and improve isNullable

| Q             | A
| ------------- | ---
| Branch?       | 7.1
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Issues        |
| License       | MIT

#54659 introduces a better design that allows us to easily handle class names in `isA` and to simplify `isNullable`

Commits
-------

5624ee0 [TypeInfo] rework isA to handle class names and improve isNullable
  • Loading branch information
fabpot committed May 2, 2024
2 parents 96d664e + 5624ee0 commit 162a1fa
Show file tree
Hide file tree
Showing 14 changed files with 69 additions and 26 deletions.
Expand Up @@ -45,5 +45,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 @@ -66,5 +66,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 @@ -100,5 +100,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 @@ -44,5 +44,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 @@ -54,10 +54,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 @@ -36,5 +36,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 @@ -26,6 +26,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 @@ -34,15 +41,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 @@ -46,6 +46,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 @@ -58,6 +58,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 @@ -49,14 +49,9 @@ public function __construct(Type ...$types)
$this->types = array_values(array_unique($types));
}

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 @@ -56,6 +56,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 @@ -42,6 +42,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 @@ -35,6 +36,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 162a1fa

Please sign in to comment.