diff --git a/src/main/php/lang/Type.class.php b/src/main/php/lang/Type.class.php index 0d4cf79b7a..cfb0ba7703 100755 --- a/src/main/php/lang/Type.class.php +++ b/src/main/php/lang/Type.class.php @@ -5,6 +5,7 @@ * * @see xp://lang.XPClass * @see xp://lang.Primitive + * @test xp://net.xp_framework.unittest.core.TypeResolveTest * @test xp://net.xp_framework.unittest.reflection.TypeTest */ class Type implements Value { @@ -233,15 +234,12 @@ public static function resolve($type, $context= []) { } // Map well-known named types - see static constructor for list - if ('?' === $type[0] || '@' === $type[0]) { + if ('?' === $type[0]) { return self::resolve(substr($type, 1), $context); } else if (isset(self::$named[$type])) { return self::$named[$type]; } - // Check contextual resolver function - if (isset($context[$type])) return $context[$type](); - // * function(T): R is a function // * [:T] is a map // * T is a generic type definition D with K and V components @@ -249,8 +247,11 @@ public static function resolve($type, $context= []) { // card type. // * Anything else is a qualified or unqualified class name $p= strcspn($type, '<|[*('); - if ($p >= $l) { - return XPClass::forName($type); + if ($p === $l) { + return isset($context[$type]) ? $context[$type]() : ((isset($context['*']) && strcspn($type, '.\\') === $l) + ? $context['*']($type) + : XPClass::forName($type) + ); } else if ('(' === $type[0]) { $t= self::resolve(self::matching($type, '()', 0), $context); } else if (0 === substr_compare($type, '[:', 0, 2)) { @@ -286,11 +287,11 @@ public static function resolve($type, $context= []) { } } if ($wildcard) { - $t= new WildcardType(XPClass::forName($base), $components); + $t= new WildcardType(self::resolve($base, $context), $components); } else if ('array' === $base) { $t= 1 === sizeof($components) ? new ArrayType($components[0]) : new MapType($components[1]); } else { - $t= XPClass::forName($base)->newGenericType($components); + $t= self::resolve($base, $context)->newGenericType($components); } } else { $t= self::resolve(trim(substr($type, 0, $p)), $context); diff --git a/src/test/config/unittest/core.ini b/src/test/config/unittest/core.ini index e8713a9d41..e3b1aa7c95 100644 --- a/src/test/config/unittest/core.ini +++ b/src/test/config/unittest/core.ini @@ -88,6 +88,9 @@ class="net.xp_framework.unittest.core.TypeHintsTest" [errors] class="net.xp_framework.unittest.core.ErrorsTest" +[type-resolve] +class="net.xp_framework.unittest.core.TypeResolveTest" + [arraytype] class="net.xp_framework.unittest.core.ArrayTypeTest" diff --git a/src/test/php/net/xp_framework/unittest/core/TypeResolveTest.class.php b/src/test/php/net/xp_framework/unittest/core/TypeResolveTest.class.php new file mode 100755 index 0000000000..b39dc1c866 --- /dev/null +++ b/src/test/php/net/xp_framework/unittest/core/TypeResolveTest.class.php @@ -0,0 +1,85 @@ +context= [ + 'self' => function() { return new XPClass(self::class); }, + 'parent' => function() { return new XPClass(parent::class); }, + '*' => function($type) { + switch ($type) { + case 'TypeResolveTest': return new XPClass(self::class); + case 'Lookup': return XPClass::forName(Lookup::class); + default: throw new ClassNotFoundException($type); + } + } + ]; + } + + #[Test] + public function resolve_primitive() { + $this->assertEquals(Primitive::$STRING, Type::resolve('string', $this->context)); + } + + #[Test] + public function resolve_self() { + $this->assertEquals(new XPClass(self::class), Type::resolve('self', $this->context)); + } + + #[Test, Values(['self[]', 'array'])] + public function resolve_array_of_self($type) { + $this->assertEquals(new ArrayType(new XPClass(self::class)), Type::resolve($type, $this->context)); + } + + #[Test, Values(['[:self]', 'array'])] + public function resolve_map_of_self($type) { + $this->assertEquals(new MapType(new XPClass(self::class)), Type::resolve($type, $this->context)); + } + + #[Test] + public function resolve_parent() { + $this->assertEquals(new XPClass(parent::class), Type::resolve('parent', $this->context)); + } + + #[Test] + public function resolve_literal() { + $this->assertEquals(new XPClass(self::class), Type::resolve(self::class, $this->context)); + } + + #[Test] + public function resolve_name() { + $this->assertEquals(new XPClass(self::class), Type::resolve(nameof($this), $this->context)); + } + + #[Test] + public function resolve_without_namespace() { + $this->assertEquals(new XPClass(self::class), Type::resolve('TypeResolveTest', $this->context)); + } + + #[Test, Expect(class: ClassNotFoundException::class, withMessage: '/NonExistant/')] + public function resolve_non_existant() { + Type::resolve('NonExistant', $this->context); + } + + #[Test] + public function resolve_generic() { + $this->assertEquals( + Type::forName('net.xp_framework.unittest.core.generics.Lookup'), + Type::resolve('Lookup', $this->context) + ); + } + + #[Test] + public function resolve_wildcard() { + $this->assertEquals( + Type::forName('net.xp_framework.unittest.core.generics.Lookup'), + Type::resolve('Lookup', $this->context) + ); + } +} \ No newline at end of file