diff --git a/src/TypeResolver.php b/src/TypeResolver.php index d849d5a..49dc30b 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -387,14 +387,15 @@ private function resolveTypedObject(string $type, ?Context $context = null) : Ob /** * Resolves the collection values and keys * - * @return Array_|Collection + * @return Array_|Iterable_|Collection */ private function resolveCollection(ArrayIterator $tokens, Type $classType, Context $context) : Type { $isArray = ((string) $classType === 'array'); + $isIterable = ((string) $classType === 'iterable'); - // allow only "array" or class name before "<" - if (!$isArray + // allow only "array", "iterable" or class name before "<" + if (!$isArray && !$isIterable && (!$classType instanceof Object_ || $classType->getFqsen() === null)) { throw new RuntimeException( $classType . ' is not a collection' @@ -455,6 +456,10 @@ private function resolveCollection(ArrayIterator $tokens, Type $classType, Conte return new Array_($valueType, $keyType); } + if ($isIterable) { + return new Iterable_($valueType, $keyType); + } + /** @psalm-suppress RedundantCondition */ if ($classType instanceof Object_) { return $this->makeCollectionFromObject($classType, $valueType, $keyType); diff --git a/src/Types/Iterable_.php b/src/Types/Iterable_.php index 4916951..141fac9 100644 --- a/src/Types/Iterable_.php +++ b/src/Types/Iterable_.php @@ -18,13 +18,21 @@ /** * Value Object representing iterable type */ -final class Iterable_ implements Type +final class Iterable_ extends AbstractList { /** * Returns a rendered output of the Type as it would be used in a DocBlock. */ public function __toString() : string { - return 'iterable'; + if ($this->keyType) { + return 'iterable<' . $this->keyType . ',' . $this->valueType . '>'; + } + + if ($this->valueType instanceof Mixed_) { + return 'iterable'; + } + + return 'iterable<' . $this->valueType . '>'; } } diff --git a/tests/unit/TypeResolverTest.php b/tests/unit/TypeResolverTest.php index 15be85a..29284f6 100644 --- a/tests/unit/TypeResolverTest.php +++ b/tests/unit/TypeResolverTest.php @@ -485,6 +485,47 @@ public function testResolvingArrayExpressionOrCompoundTypes() : void $this->assertInstanceOf(Object_::class, $secondArrayType); } + /** + * @uses \phpDocumentor\Reflection\Types\Context + * @uses \phpDocumentor\Reflection\Types\Compound + * @uses \phpDocumentor\Reflection\Types\Iterable_ + * @uses \phpDocumentor\Reflection\Types\Object_ + * @uses \phpDocumentor\Reflection\Fqsen + * @uses \phpDocumentor\Reflection\FqsenResolver + * + * @covers ::__construct + * @covers ::resolve + * @covers :: + */ + public function testResolvingIterableExpressionSimpleTypes() : void + { + $fixture = new TypeResolver(); + + /** @var Iterable_ $resolvedType */ + $resolvedType = $fixture->resolve('iterable', new Context('')); + + $this->assertInstanceOf(Iterable_::class, $resolvedType); + $this->assertSame('iterable', (string) $resolvedType); + + /** @var Compound $valueType */ + $valueType = $resolvedType->getValueType(); + + $this->assertInstanceOf(Compound::class, $valueType); + + /** @var String_ $firstType */ + $firstType = $valueType->get(0); + + /** @var Object_ $secondType */ + $secondType = $valueType->get(1); + + /** @var Boolean $thirdType */ + $thirdType = $valueType->get(2); + + $this->assertInstanceOf(String_::class, $firstType); + $this->assertInstanceOf(Object_::class, $secondType); + $this->assertInstanceOf(Boolean::class, $thirdType); + } + /** * This test asserts that the parameter order is correct. * diff --git a/tests/unit/Types/ArrayTest.php b/tests/unit/Types/ArrayTest.php new file mode 100644 index 0000000..973dd88 --- /dev/null +++ b/tests/unit/Types/ArrayTest.php @@ -0,0 +1,43 @@ +assertSame($expectedString, (string) $array); + } + + public function provideArrays() : array + { + return [ + 'simple array' => [new Array_(), 'array'], + 'array of mixed' => [new Array_(new Mixed_()), 'array'], + 'array of single type' => [new Array_(new String_()), 'string[]'], + 'array of compound type' => [new Array_(new Compound([new Integer(), new String_()])), '(int|string)[]'], + 'array with key type' => [new Array_(new String_(), new Integer()), 'array'], + ]; + } +} diff --git a/tests/unit/Types/CollectionTest.php b/tests/unit/Types/CollectionTest.php new file mode 100644 index 0000000..389e60c --- /dev/null +++ b/tests/unit/Types/CollectionTest.php @@ -0,0 +1,43 @@ +assertSame($expectedString, (string) $collection); + } + + public function provideCollections() : array + { + return [ + 'simple collection' => [new Collection(null, new Integer()), 'object'], + 'simple collection with key type' => [new Collection(null, new Integer(), new String_()), 'object'], + 'collection of single type using specific class' => [new Collection(new Fqsen('\Foo\Bar'), new Integer()), '\Foo\Bar'], + 'collection of single type with key type and using specific class' => [new Collection(new Fqsen('\Foo\Bar'), new String_(), new Integer()), '\Foo\Bar'], + ]; + } +} diff --git a/tests/unit/Types/IterableTest.php b/tests/unit/Types/IterableTest.php new file mode 100644 index 0000000..ba2b2e8 --- /dev/null +++ b/tests/unit/Types/IterableTest.php @@ -0,0 +1,43 @@ +assertSame($expectedString, (string) $iterable); + } + + public function provideIterables() : array + { + return [ + 'simple iterable' => [new Iterable_(), 'iterable'], + 'iterable of mixed' => [new Iterable_(new Mixed_()), 'iterable'], + 'iterable of single type' => [new Iterable_(new String_()), 'iterable'], + 'iterable of compound type' => [new Iterable_(new Compound([new Integer(), new String_()])), 'iterable'], + 'iterable with key type' => [new Iterable_(new String_(), new Integer()), 'iterable'], + ]; + } +}