From 0053750eb366089f2cf34c091078582ddd4ba41e Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Sat, 20 Apr 2024 11:41:56 +0200 Subject: [PATCH] [TypeInfo] Proxies methods to non-nullable and fail gracefully --- .../Tests/Type/CollectionTypeTest.php | 6 ++++++ .../TypeInfo/Tests/Type/GenericTypeTest.php | 6 ++++++ .../TypeInfo/Tests/Type/UnionTypeTest.php | 19 ++++++++++++++++++ .../Component/TypeInfo/Tests/TypeTest.php | 6 ++++++ src/Symfony/Component/TypeInfo/Type.php | 11 ++++++++++ .../Component/TypeInfo/Type/UnionType.php | 20 +++++++++++++++++++ 6 files changed, 68 insertions(+) diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php index 821bd366ef85f..8104121f5e592 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php @@ -108,4 +108,10 @@ public function testIsA() $this->assertTrue($type->isA(TypeIdentifier::OBJECT)); $this->assertTrue($type->isA(self::class)); } + + public function testProxiesMethodsToBaseType() + { + $type = new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ARRAY), Type::string(), Type::bool())); + $this->assertEquals([Type::string(), Type::bool()], $type->getVariableTypes()); + } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php index af510b4ce9370..bf0775cccfd1e 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php @@ -62,4 +62,10 @@ public function testIsA() $this->assertFalse($type->isA(TypeIdentifier::STRING)); $this->assertTrue($type->isA(self::class)); } + + public function testProxiesMethodsToBaseType() + { + $type = new GenericType(Type::object(self::class), Type::float()); + $this->assertSame(self::class, $type->getClassName()); + } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/UnionTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/UnionTypeTest.php index d270b56b02d66..bc308d4651466 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/UnionTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/UnionTypeTest.php @@ -123,4 +123,23 @@ public function testIsA() $type = new UnionType(Type::string(), Type::intersection(Type::int(), Type::int())); $this->assertTrue($type->isA(TypeIdentifier::INT)); } + + public function testProxiesMethodsToNonNullableType() + { + $this->assertEquals(Type::string(), (new UnionType(Type::list(Type::string()), Type::null()))->getCollectionValueType()); + + try { + (new UnionType(Type::int(), Type::null()))->getCollectionValueType(); + $this->fail(); + } catch (LogicException) { + $this->addToAssertionCount(1); + } + + try { + (new UnionType(Type::list(Type::string()), Type::string()))->getCollectionValueType(); + $this->fail(); + } catch (LogicException) { + $this->addToAssertionCount(1); + } + } } diff --git a/src/Symfony/Component/TypeInfo/Tests/TypeTest.php b/src/Symfony/Component/TypeInfo/Tests/TypeTest.php index 4b399c4fcca41..c271ba581ed1f 100644 --- a/src/Symfony/Component/TypeInfo/Tests/TypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/TypeTest.php @@ -51,4 +51,10 @@ public function testCannotGetBaseTypeOnCompoundType() $this->expectException(LogicException::class); Type::union(Type::int(), Type::string())->getBaseType(); } + + public function testThrowsOnUnexistingMethod() + { + $this->expectException(LogicException::class); + Type::int()->unexistingMethod(); + } } diff --git a/src/Symfony/Component/TypeInfo/Type.php b/src/Symfony/Component/TypeInfo/Type.php index 45b7c57938256..72b1838966655 100644 --- a/src/Symfony/Component/TypeInfo/Type.php +++ b/src/Symfony/Component/TypeInfo/Type.php @@ -11,6 +11,7 @@ namespace Symfony\Component\TypeInfo; +use Symfony\Component\TypeInfo\Exception\LogicException; use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\Type\ObjectType; @@ -45,4 +46,14 @@ public function isNullable(): bool { return $this->is(fn (Type $t): bool => $t->isA(TypeIdentifier::NULL) || $t->isA(TypeIdentifier::MIXED)); } + + /** + * Graceful fallback for unexisting methods. + * + * @param list $arguments + */ + public function __call(string $method, array $arguments): mixed + { + throw new LogicException(sprintf('Cannot call "%s" on "%s" type.', $method, $this)); + } } diff --git a/src/Symfony/Component/TypeInfo/Type/UnionType.php b/src/Symfony/Component/TypeInfo/Type/UnionType.php index 70802f025f300..601ebcfb3019e 100644 --- a/src/Symfony/Component/TypeInfo/Type/UnionType.php +++ b/src/Symfony/Component/TypeInfo/Type/UnionType.php @@ -78,4 +78,24 @@ public function __toString(): string return $string; } + + /** + * Proxies all method calls to the original non-nullable type. + * + * @param list $arguments + */ + public function __call(string $method, array $arguments): mixed + { + $nonNullableType = $this->asNonNullable(); + + if (!$nonNullableType instanceof self) { + if (!method_exists($nonNullableType, $method)) { + throw new LogicException(sprintf('Method "%s" doesn\'t exist on "%s" type.', $method, $nonNullableType)); + } + + return $nonNullableType->{$method}(...$arguments); + } + + throw new LogicException(sprintf('Cannot call "%s" on "%s" compound type.', $method, $this)); + } }