Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it possible to pass a default resolver to Type::resolve() #256

Merged
merged 4 commits into from
Jan 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions src/main/php/lang/Type.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -233,24 +234,24 @@ 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<K, V> is a generic type definition D with K and V components
// except if any of K, V contains a ?, in which case it's a wild
// 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)) {
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/test/config/unittest/core.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php namespace net\xp_framework\unittest\core;

use lang\{Type, Primitive, ArrayType, MapType, XPClass, ClassNotFoundException};
use net\xp_framework\unittest\core\generics\Lookup;
use unittest\{Test, Values, TestCase};

class TypeResolveTest extends TestCase {
private $context;

/** @return void */
public function setUp() {
$this->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<self>'])]
public function resolve_array_of_self($type) {
$this->assertEquals(new ArrayType(new XPClass(self::class)), Type::resolve($type, $this->context));
}

#[Test, Values(['[:self]', 'array<string, self>'])]
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<string, string>'),
Type::resolve('Lookup<string, string>', $this->context)
);
}

#[Test]
public function resolve_wildcard() {
$this->assertEquals(
Type::forName('net.xp_framework.unittest.core.generics.Lookup<string, ?>'),
Type::resolve('Lookup<string, ?>', $this->context)
);
}
}