From 1c7bc20e0ff34ade75c07a718ae7d03975c847ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1chym=20Tou=C5=A1ek?= Date: Fri, 29 Oct 2021 17:10:18 +0200 Subject: [PATCH] Add list pseudo-type --- src/PseudoTypes/List_.php | 50 +++++++++++++++++++++++++++ src/TypeResolver.php | 11 ++++-- src/Types/Array_.php | 2 +- tests/unit/CollectionResolverTest.php | 25 ++++++++++++++ tests/unit/PseudoTypes/ListTest.php | 48 +++++++++++++++++++++++++ tests/unit/TypeResolverTest.php | 1 + 6 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 src/PseudoTypes/List_.php create mode 100644 tests/unit/PseudoTypes/ListTest.php diff --git a/src/PseudoTypes/List_.php b/src/PseudoTypes/List_.php new file mode 100644 index 0000000..f9f0c6b --- /dev/null +++ b/src/PseudoTypes/List_.php @@ -0,0 +1,50 @@ +valueType instanceof Mixed_) { + return 'list'; + } + + return 'list<' . $this->valueType . '>'; + } +} diff --git a/src/TypeResolver.php b/src/TypeResolver.php index 282bb61..f6f07b9 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -26,6 +26,7 @@ use phpDocumentor\Reflection\Types\InterfaceString; use phpDocumentor\Reflection\Types\Intersection; use phpDocumentor\Reflection\Types\Iterable_; +use phpDocumentor\Reflection\PseudoTypes\List_; use phpDocumentor\Reflection\Types\Nullable; use phpDocumentor\Reflection\Types\Object_; use phpDocumentor\Reflection\Types\String_; @@ -110,6 +111,7 @@ final class TypeResolver 'parent' => Types\Parent_::class, 'iterable' => Types\Iterable_::class, 'never' => Types\Never_::class, + 'list' => PseudoTypes\List_::class, ]; /** @@ -521,10 +523,11 @@ private function resolveCollection(ArrayIterator $tokens, Type $classType, Conte { $isArray = ((string) $classType === 'array'); $isIterable = ((string) $classType === 'iterable'); + $isList = ((string) $classType === 'list'); // allow only "array", "iterable" or class name before "<" if ( - !$isArray && !$isIterable + !$isArray && !$isIterable && !$isList && (!$classType instanceof Object_ || $classType->getFqsen() === null) ) { throw new RuntimeException( @@ -538,7 +541,7 @@ private function resolveCollection(ArrayIterator $tokens, Type $classType, Conte $keyType = null; $token = $tokens->current(); - if ($token !== null && trim($token) === ',') { + if ($token !== null && trim($token) === ',' && !$isList) { // if we have a comma, then we just parsed the key type, not the value type $keyType = $valueType; if ($isArray) { @@ -596,6 +599,10 @@ private function resolveCollection(ArrayIterator $tokens, Type $classType, Conte return new Iterable_($valueType, $keyType); } + if ($isList) { + return new List_($valueType); + } + if ($classType instanceof Object_) { return $this->makeCollectionFromObject($classType, $valueType, $keyType); } diff --git a/src/Types/Array_.php b/src/Types/Array_.php index 7f880e2..bc17225 100644 --- a/src/Types/Array_.php +++ b/src/Types/Array_.php @@ -24,6 +24,6 @@ * * @psalm-immutable */ -final class Array_ extends AbstractList +class Array_ extends AbstractList { } diff --git a/tests/unit/CollectionResolverTest.php b/tests/unit/CollectionResolverTest.php index 4e82328..fcfd782 100644 --- a/tests/unit/CollectionResolverTest.php +++ b/tests/unit/CollectionResolverTest.php @@ -13,6 +13,7 @@ namespace phpDocumentor\Reflection; +use phpDocumentor\Reflection\PseudoTypes\List_; use phpDocumentor\Reflection\Types\Array_; use phpDocumentor\Reflection\Types\Collection; use phpDocumentor\Reflection\Types\Context; @@ -297,4 +298,28 @@ public function testResolvingCollectionAsArray(): void $this->assertInstanceOf(Types\Float_::class, $valueType); $this->assertInstanceOf(Types\String_::class, $keyType); } + + /** + * @uses \phpDocumentor\Reflection\Types\Context + * @uses \phpDocumentor\Reflection\Types\String_ + * + * @covers ::__construct + * @covers ::resolve + */ + public function testResolvingList(): void + { + $fixture = new TypeResolver(); + + $resolvedType = $fixture->resolve('list', new Context('')); + + $this->assertInstanceOf(List_::class, $resolvedType); + $this->assertSame('list', (string) $resolvedType); + + $valueType = $resolvedType->getValueType(); + + $keyType = $resolvedType->getKeyType(); + + $this->assertInstanceOf(Types\String_::class, $valueType); + $this->assertInstanceOf(Types\Integer::class, $keyType); + } } diff --git a/tests/unit/PseudoTypes/ListTest.php b/tests/unit/PseudoTypes/ListTest.php new file mode 100644 index 0000000..aca7db8 --- /dev/null +++ b/tests/unit/PseudoTypes/ListTest.php @@ -0,0 +1,48 @@ +assertSame($expectedString, (string) $array); + } + + /** + * @return mixed[] + */ + public function provideArrays(): array + { + return [ + 'simple list' => [new List_(), 'list'], + 'list of mixed' => [new List_(new Mixed_()), 'list'], + 'list of single type' => [new List_(new String_()), 'list'], + 'list of compound type' => [new List_(new Compound([new Integer(), new String_()])), 'list'], + ]; + } +} diff --git a/tests/unit/TypeResolverTest.php b/tests/unit/TypeResolverTest.php index 8aee1ea..7a8f062 100644 --- a/tests/unit/TypeResolverTest.php +++ b/tests/unit/TypeResolverTest.php @@ -759,6 +759,7 @@ public function provideKeywords(): array ['iterable', Types\Iterable_::class], ['never', Types\Never_::class], ['literal-string', PseudoTypes\LiteralString::class], + ['list', PseudoTypes\List_::class], ]; }