From 520ab7b2b0f88e24fe7fe53a41d2bb261bd799cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=A1=D0=BF=D0=B8?= =?UTF-8?q?=D1=80=D0=BA=D0=BE=D0=B2?= Date: Sat, 29 Nov 2025 00:10:37 +0400 Subject: [PATCH 1/5] Replace `Collection` with `GenericType` --- src/TypeResolver.php | 15 +++-- src/Types/AbstractList.php | 2 +- src/Types/Collection.php | 69 ----------------------- src/Types/GenericType.php | 60 ++++++++++++++++++++ tests/unit/CollectionResolverTest.php | 74 ++++++++++--------------- tests/unit/IntegerRangeResolverTest.php | 6 -- tests/unit/NumericResolverTest.php | 1 - tests/unit/TypeResolverTest.php | 10 ++-- tests/unit/Types/CollectionTest.php | 57 ------------------- tests/unit/Types/GenericTypeTest.php | 71 ++++++++++++++++++++++++ 10 files changed, 174 insertions(+), 191 deletions(-) delete mode 100644 src/Types/Collection.php create mode 100644 src/Types/GenericType.php delete mode 100644 tests/unit/Types/CollectionTest.php create mode 100644 tests/unit/Types/GenericTypeTest.php diff --git a/src/TypeResolver.php b/src/TypeResolver.php index de20a1dd..4f31aea9 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -56,11 +56,11 @@ use phpDocumentor\Reflection\Types\Callable_; use phpDocumentor\Reflection\Types\CallableParameter; use phpDocumentor\Reflection\Types\ClassString; -use phpDocumentor\Reflection\Types\Collection; use phpDocumentor\Reflection\Types\Compound; use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Expression; use phpDocumentor\Reflection\Types\Float_; +use phpDocumentor\Reflection\Types\GenericType; use phpDocumentor\Reflection\Types\Integer; use phpDocumentor\Reflection\Types\InterfaceString; use phpDocumentor\Reflection\Types\Intersection; @@ -460,15 +460,14 @@ private function createFromGeneric(GenericTypeNode $type, Context $context): Typ return new Self_(...$this->createTypesByTypeNodes($type->genericTypes, $context)); default: - $collectionType = $this->createType($type->type, $context); - if ($collectionType instanceof Object_ === false) { - throw new RuntimeException(sprintf('%s is not a collection', (string) $collectionType)); + $mainType = $this->createType($type->type, $context); + if ($mainType instanceof Object_ === false) { + throw new RuntimeException(sprintf('%s is an unsupported generic', (string) $mainType)); } - return new Collection( - $collectionType->getFqsen(), - ...array_reverse($this->createTypesByTypeNodes($type->genericTypes, $context)) - ); + $types = $this->createTypesByTypeNodes($type->genericTypes, $context); + + return new GenericType($mainType->getFqsen(), $types); } } diff --git a/src/Types/AbstractList.php b/src/Types/AbstractList.php index 68c1f88c..32eb0f23 100644 --- a/src/Types/AbstractList.php +++ b/src/Types/AbstractList.php @@ -16,7 +16,7 @@ use phpDocumentor\Reflection\Type; /** - * Represents a list of values. This is an abstract class for Array_ and Collection. + * Represents a list of values. This is an abstract class for Array_ and List_. * * @psalm-immutable */ diff --git a/src/Types/Collection.php b/src/Types/Collection.php deleted file mode 100644 index 0963787a..00000000 --- a/src/Types/Collection.php +++ /dev/null @@ -1,69 +0,0 @@ -` - * 2. `ACollectionObject` - * - * - ACollectionObject can be 'array' or an object that can act as an array - * - aValueType and aKeyType can be any type expression - * - * @psalm-immutable - */ -final class Collection extends AbstractList -{ - /** @var Fqsen|null */ - private $fqsen; - - /** - * Initializes this representation of an array with the given Type or Fqsen. - */ - public function __construct(?Fqsen $fqsen, Type $valueType, ?Type $keyType = null) - { - parent::__construct($valueType, $keyType); - - $this->fqsen = $fqsen; - } - - /** - * Returns the FQSEN associated with this object. - */ - public function getFqsen(): ?Fqsen - { - return $this->fqsen; - } - - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString(): string - { - $objectType = (string) ($this->fqsen ?? 'object'); - $valueType = $this->getValueType(); - - if ($this->keyType === null) { - return $objectType . '<' . $valueType . '>'; - } - - return $objectType . '<' . $this->keyType . ',' . $valueType . '>'; - } -} diff --git a/src/Types/GenericType.php b/src/Types/GenericType.php new file mode 100644 index 00000000..2e03e4e3 --- /dev/null +++ b/src/Types/GenericType.php @@ -0,0 +1,60 @@ +fqsen = $fqsen; + $this->types = $types; + } + + public function getFqsen(): ?Fqsen + { + return $this->fqsen; + } + + /** + * @return Type[] + */ + public function getTypes(): array + { + return $this->types; + } + + public function __toString(): string + { + $objectType = (string) ($this->fqsen ?? 'object'); + + return $objectType . '<' . implode(', ', $this->types) . '>'; + } +} diff --git a/tests/unit/CollectionResolverTest.php b/tests/unit/CollectionResolverTest.php index 967c1156..a2d5859e 100644 --- a/tests/unit/CollectionResolverTest.php +++ b/tests/unit/CollectionResolverTest.php @@ -16,10 +16,10 @@ use phpDocumentor\Reflection\PseudoTypes\List_; use phpDocumentor\Reflection\PseudoTypes\NonEmptyList; use phpDocumentor\Reflection\Types\Array_; -use phpDocumentor\Reflection\Types\Collection; use phpDocumentor\Reflection\Types\Compound; use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Float_; +use phpDocumentor\Reflection\Types\GenericType; use phpDocumentor\Reflection\Types\Integer; use phpDocumentor\Reflection\Types\Nullable; use phpDocumentor\Reflection\Types\Object_; @@ -36,7 +36,7 @@ class CollectionResolverTest extends TestCase /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection + * @uses \phpDocumentor\Reflection\Types\GenericType * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::resolve @@ -49,23 +49,16 @@ public function testResolvingCollection(): void $resolvedType = $fixture->resolve('ArrayObject', new Context('')); - $this->assertInstanceOf(Collection::class, $resolvedType); + $this->assertInstanceOf(GenericType::class, $resolvedType); $this->assertSame('\\ArrayObject', (string) $resolvedType); - - $this->assertEquals('\\ArrayObject', (string) $resolvedType->getFqsen()); - - $valueType = $resolvedType->getValueType(); - - $keyType = $resolvedType->getKeyType(); - - $this->assertInstanceOf(String_::class, $valueType); - $this->assertInstanceOf(Compound::class, $keyType); + $this->assertSame('\\ArrayObject', (string) $resolvedType->getFqsen()); + $this->assertEquals([new String_()], $resolvedType->getTypes()); } /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection + * @uses \phpDocumentor\Reflection\Types\GenericType * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct @@ -78,25 +71,22 @@ public function testResolvingCollectionWithKeyType(): void $resolvedType = $fixture->resolve('ArrayObject', new Context('')); - $this->assertInstanceOf(Collection::class, $resolvedType); - $this->assertSame('\\ArrayObject', (string) $resolvedType); + $this->assertInstanceOf(GenericType::class, $resolvedType); + $this->assertSame('\\ArrayObject', (string) $resolvedType); + $this->assertSame('\\ArrayObject', (string) $resolvedType->getFqsen()); - $this->assertEquals('\\ArrayObject', (string) $resolvedType->getFqsen()); + $types = $resolvedType->getTypes(); - $valueType = $resolvedType->getValueType(); - - $keyType = $resolvedType->getKeyType(); - - $this->assertInstanceOf(Object_::class, $valueType); - $this->assertEquals('\\Iterator', (string) $valueType->getFqsen()); - $this->assertInstanceOf(Array_::class, $keyType); - $this->assertInstanceOf(String_::class, $keyType->getValueType()); + $this->assertArrayHasKey(0, $types); + $this->assertEquals(new Array_(new String_()), $types[0]); + $this->assertArrayHasKey(1, $types); + $this->assertInstanceOf(Object_::class, $types[1]); + $this->assertSame('\\Iterator', (string) $types[1]->getFqsen()); } /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct @@ -123,7 +113,6 @@ public function testResolvingArrayCollection(): void /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct @@ -150,7 +139,6 @@ public function testResolvingArrayCollectionWithKey(): void /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection * @uses \phpDocumentor\Reflection\Types\String_ * @covers ::__construct @@ -177,7 +165,7 @@ public function testResolvingArrayCollectionWithKeyAndWhitespace(): void /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection + * @uses \phpDocumentor\Reflection\Types\GenericType * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct @@ -190,25 +178,22 @@ public function testResolvingCollectionOfCollection(): void $resolvedType = $fixture->resolve('ArrayObject>', new Context('')); - $this->assertInstanceOf(Collection::class, $resolvedType); - $this->assertSame('\\ArrayObject>', (string) $resolvedType); + $this->assertInstanceOf(GenericType::class, $resolvedType); + $this->assertSame('\\ArrayObject>', (string) $resolvedType); + $this->assertSame('\\ArrayObject', (string) $resolvedType->getFqsen()); - $this->assertEquals('\\ArrayObject', (string) $resolvedType->getFqsen()); + $types = $resolvedType->getTypes(); - $valueType = $resolvedType->getValueType(); - $this->assertInstanceOf(Collection::class, $valueType); - $collectionValueType = $valueType->getValueType(); + $this->assertArrayHasKey(0, $types); + $this->assertEquals(new Compound([new String_(), new Integer(), new Float_()]), $types[0]); - $this->assertInstanceOf(Object_::class, $valueType->getValueType()); - $this->assertEquals('\\ArrayObject', (string) $valueType->getFqsen()); - $this->assertInstanceOf(Object_::class, $collectionValueType); - $this->assertEquals('\\DateTime', (string) $collectionValueType->getFqsen()); + $this->assertArrayHasKey(0, $types); + $this->assertInstanceOf(GenericType::class, $types[1]); + $this->assertSame('\\ArrayObject', (string) $types[1]->getFqsen()); - $keyType = $resolvedType->getKeyType(); - $this->assertInstanceOf(Compound::class, $keyType); - $this->assertInstanceOf(String_::class, $keyType->get(0)); - $this->assertInstanceOf(Integer::class, $keyType->get(1)); - $this->assertInstanceOf(Float_::class, $keyType->get(2)); + $nestedGenericTypes = $types[1]->getTypes(); + $this->assertArrayHasKey(0, $nestedGenericTypes); + $this->assertEquals(new Object_(new Fqsen('\\DateTime')), $nestedGenericTypes[0]); } /** @@ -278,7 +263,7 @@ public function testMissingEndCollection(): void public function testBadCollectionClass(): void { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('string is not a collection'); + $this->expectExceptionMessage('string is an unsupported generic'); $fixture = new TypeResolver(); $fixture->resolve('string', new Context('')); } @@ -286,7 +271,6 @@ public function testBadCollectionClass(): void /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct diff --git a/tests/unit/IntegerRangeResolverTest.php b/tests/unit/IntegerRangeResolverTest.php index cfe35f6a..d53c7ec3 100644 --- a/tests/unit/IntegerRangeResolverTest.php +++ b/tests/unit/IntegerRangeResolverTest.php @@ -27,7 +27,6 @@ class IntegerRangeResolverTest extends TestCase /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct @@ -53,7 +52,6 @@ public function testResolvingIntRange(): void /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct @@ -79,7 +77,6 @@ public function testResolvingIntRangeWithKeywords(): void /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct @@ -98,7 +95,6 @@ public function testResolvingIntRangeErrorMissingMaxValue(): void /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct @@ -117,7 +113,6 @@ public function testResolvingIntRangeErrorMisingMinValue(): void /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct @@ -136,7 +131,6 @@ public function testResolvingIntRangeErrorMisingComma(): void /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct diff --git a/tests/unit/NumericResolverTest.php b/tests/unit/NumericResolverTest.php index 20284b66..6a8c935e 100644 --- a/tests/unit/NumericResolverTest.php +++ b/tests/unit/NumericResolverTest.php @@ -28,7 +28,6 @@ class NumericResolverTest extends TestCase /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct diff --git a/tests/unit/TypeResolverTest.php b/tests/unit/TypeResolverTest.php index 56cc23c0..fdb869f7 100644 --- a/tests/unit/TypeResolverTest.php +++ b/tests/unit/TypeResolverTest.php @@ -55,11 +55,11 @@ use phpDocumentor\Reflection\Types\Callable_; use phpDocumentor\Reflection\Types\CallableParameter; use phpDocumentor\Reflection\Types\ClassString; -use phpDocumentor\Reflection\Types\Collection; use phpDocumentor\Reflection\Types\Compound; use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Expression; use phpDocumentor\Reflection\Types\Float_; +use phpDocumentor\Reflection\Types\GenericType; use phpDocumentor\Reflection\Types\Integer; use phpDocumentor\Reflection\Types\InterfaceString; use phpDocumentor\Reflection\Types\Intersection; @@ -1117,10 +1117,12 @@ public function genericsProvider(): array [ 'Collection[]', new Array_( - new Collection( + new GenericType( new Fqsen('\\phpDocumentor\\Collection'), - new Integer(), - new ArrayKey() + [ + new ArrayKey(), + new Integer(), + ] ) ), ], diff --git a/tests/unit/Types/CollectionTest.php b/tests/unit/Types/CollectionTest.php deleted file mode 100644 index c48f3f2c..00000000 --- a/tests/unit/Types/CollectionTest.php +++ /dev/null @@ -1,57 +0,0 @@ -assertSame($expectedString, (string) $collection); - } - - /** - * @return mixed[] - */ - 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/GenericTypeTest.php b/tests/unit/Types/GenericTypeTest.php new file mode 100644 index 00000000..29f9cd04 --- /dev/null +++ b/tests/unit/Types/GenericTypeTest.php @@ -0,0 +1,71 @@ +assertSame($fqsen, $type->getFqsen()); + $this->assertSame($types, $type->getTypes()); + } + + + /** + * @dataProvider provideToStringData + * @covers ::__toString + */ + public function testToString(string $expectedResult, GenericType $type): void + { + $this->assertSame($expectedResult, (string) $type); + } + + /** + * @return array + */ + public static function provideToStringData(): array + { + return [ + 'collection without key' => [ + '\\ArrayObject', + new GenericType(new Fqsen('\\ArrayObject'), [new String_()]), + ], + 'collection with key' => [ + '\\ArrayObject', + new GenericType( + new Fqsen('\\ArrayObject'), + [new Array_(new String_()), new Object_(new Fqsen('\\Iterator'))] + ), + ], + 'more than two generics' => [ + '\\MyClass<\\SomeClassFirst, \\SomeClassSecond, \\SomeClassThird>', + new GenericType( + new Fqsen('\\MyClass'), + [ + new Object_(new Fqsen('\\SomeClassFirst')), + new Object_(new Fqsen('\\SomeClassSecond')), + new Object_(new Fqsen('\\SomeClassThird')), + ] + ), + ], + ]; + } +} From 249b5d7b839a8c84feb2ffac52a0fdfce16ad3af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=A1=D0=BF=D0=B8?= =?UTF-8?q?=D1=80=D0=BA=D0=BE=D0=B2?= Date: Sat, 29 Nov 2025 00:12:55 +0400 Subject: [PATCH 2/5] Add tests --- tests/unit/Types/GenericTypeTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/unit/Types/GenericTypeTest.php b/tests/unit/Types/GenericTypeTest.php index 29f9cd04..43432bf8 100644 --- a/tests/unit/Types/GenericTypeTest.php +++ b/tests/unit/Types/GenericTypeTest.php @@ -44,6 +44,15 @@ public function testToString(string $expectedResult, GenericType $type): void public static function provideToStringData(): array { return [ + 'without fqsen' => [ + 'object', + new GenericType( + null, + [ + new String_(), + ] + ), + ], 'collection without key' => [ '\\ArrayObject', new GenericType(new Fqsen('\\ArrayObject'), [new String_()]), From d117644ee8969841feab7c93e14a61a4dd2a9804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=A1=D0=BF=D0=B8?= =?UTF-8?q?=D1=80=D0=BA=D0=BE=D0=B2?= Date: Sat, 29 Nov 2025 00:16:46 +0400 Subject: [PATCH 3/5] Fix CS --- src/Types/GenericType.php | 2 ++ tests/unit/Types/GenericTypeTest.php | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Types/GenericType.php b/src/Types/GenericType.php index 2e03e4e3..68077cf7 100644 --- a/src/Types/GenericType.php +++ b/src/Types/GenericType.php @@ -16,6 +16,8 @@ use phpDocumentor\Reflection\Fqsen; use phpDocumentor\Reflection\Type; +use function implode; + /** * Value Object representing a type with generics. * diff --git a/tests/unit/Types/GenericTypeTest.php b/tests/unit/Types/GenericTypeTest.php index 43432bf8..28852f8f 100644 --- a/tests/unit/Types/GenericTypeTest.php +++ b/tests/unit/Types/GenericTypeTest.php @@ -6,7 +6,6 @@ use phpDocumentor\Reflection\Fqsen; use phpDocumentor\Reflection\PseudoTypes\List_; -use phpDocumentor\Reflection\Types\Object_; use PHPUnit\Framework\TestCase; /** @@ -28,7 +27,6 @@ public function testCreate(): void $this->assertSame($types, $type->getTypes()); } - /** * @dataProvider provideToStringData * @covers ::__toString From 9f4525fcd7dcd2d94e8dfc630d0f77f63374d781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=A1=D0=BF=D0=B8?= =?UTF-8?q?=D1=80=D0=BA=D0=BE=D0=B2?= Date: Sat, 29 Nov 2025 11:56:55 +0400 Subject: [PATCH 4/5] CR Fix --- .../Generic.php} | 16 ++++-------- src/TypeResolver.php | 4 +-- src/Types/Object_.php | 4 +-- tests/unit/CollectionResolverTest.php | 16 ++++++------ .../GenericTest.php} | 25 +++++++++++-------- tests/unit/TypeResolverTest.php | 4 +-- 6 files changed, 33 insertions(+), 36 deletions(-) rename src/{Types/GenericType.php => PseudoTypes/Generic.php} (79%) rename tests/unit/{Types/GenericTypeTest.php => PseudoTypes/GenericTest.php} (75%) diff --git a/src/Types/GenericType.php b/src/PseudoTypes/Generic.php similarity index 79% rename from src/Types/GenericType.php rename to src/PseudoTypes/Generic.php index 68077cf7..21e27ac3 100644 --- a/src/Types/GenericType.php +++ b/src/PseudoTypes/Generic.php @@ -11,10 +11,11 @@ * @link http://phpdoc.org */ -namespace phpDocumentor\Reflection\Types; +namespace phpDocumentor\Reflection\PseudoTypes; use phpDocumentor\Reflection\Fqsen; use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\Types\Object_; use function implode; @@ -23,11 +24,8 @@ * * @psalm-immutable */ -final class GenericType implements Type +final class Generic extends Object_ { - /** @var Fqsen|null */ - private $fqsen; - /** @var Type[] */ private $types; @@ -36,13 +34,9 @@ final class GenericType implements Type */ public function __construct(?Fqsen $fqsen, array $types) { - $this->fqsen = $fqsen; - $this->types = $types; - } + parent::__construct($fqsen); - public function getFqsen(): ?Fqsen - { - return $this->fqsen; + $this->types = $types; } /** diff --git a/src/TypeResolver.php b/src/TypeResolver.php index 4f31aea9..c1c24372 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -23,6 +23,7 @@ use phpDocumentor\Reflection\PseudoTypes\ConstExpression; use phpDocumentor\Reflection\PseudoTypes\False_; use phpDocumentor\Reflection\PseudoTypes\FloatValue; +use phpDocumentor\Reflection\PseudoTypes\Generic; use phpDocumentor\Reflection\PseudoTypes\HtmlEscapedString; use phpDocumentor\Reflection\PseudoTypes\IntegerRange; use phpDocumentor\Reflection\PseudoTypes\IntegerValue; @@ -60,7 +61,6 @@ use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Expression; use phpDocumentor\Reflection\Types\Float_; -use phpDocumentor\Reflection\Types\GenericType; use phpDocumentor\Reflection\Types\Integer; use phpDocumentor\Reflection\Types\InterfaceString; use phpDocumentor\Reflection\Types\Intersection; @@ -467,7 +467,7 @@ private function createFromGeneric(GenericTypeNode $type, Context $context): Typ $types = $this->createTypesByTypeNodes($type->genericTypes, $context); - return new GenericType($mainType->getFqsen(), $types); + return new Generic($mainType->getFqsen(), $types); } } diff --git a/src/Types/Object_.php b/src/Types/Object_.php index 90dee57a..649f945e 100644 --- a/src/Types/Object_.php +++ b/src/Types/Object_.php @@ -28,10 +28,10 @@ * * @psalm-immutable */ -final class Object_ implements Type +class Object_ implements Type { /** @var Fqsen|null */ - private $fqsen; + protected $fqsen; /** * Initializes this object with an optional FQSEN, if not provided this object is considered 'untyped'. diff --git a/tests/unit/CollectionResolverTest.php b/tests/unit/CollectionResolverTest.php index a2d5859e..23d17f60 100644 --- a/tests/unit/CollectionResolverTest.php +++ b/tests/unit/CollectionResolverTest.php @@ -13,13 +13,13 @@ namespace phpDocumentor\Reflection; +use phpDocumentor\Reflection\PseudoTypes\Generic; use phpDocumentor\Reflection\PseudoTypes\List_; use phpDocumentor\Reflection\PseudoTypes\NonEmptyList; use phpDocumentor\Reflection\Types\Array_; use phpDocumentor\Reflection\Types\Compound; use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Float_; -use phpDocumentor\Reflection\Types\GenericType; use phpDocumentor\Reflection\Types\Integer; use phpDocumentor\Reflection\Types\Nullable; use phpDocumentor\Reflection\Types\Object_; @@ -36,7 +36,7 @@ class CollectionResolverTest extends TestCase /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\GenericType + * @uses \phpDocumentor\Reflection\Types\Generic * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::resolve @@ -49,7 +49,7 @@ public function testResolvingCollection(): void $resolvedType = $fixture->resolve('ArrayObject', new Context('')); - $this->assertInstanceOf(GenericType::class, $resolvedType); + $this->assertInstanceOf(Generic::class, $resolvedType); $this->assertSame('\\ArrayObject', (string) $resolvedType); $this->assertSame('\\ArrayObject', (string) $resolvedType->getFqsen()); $this->assertEquals([new String_()], $resolvedType->getTypes()); @@ -58,7 +58,7 @@ public function testResolvingCollection(): void /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\GenericType + * @uses \phpDocumentor\Reflection\Types\Generic * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct @@ -71,7 +71,7 @@ public function testResolvingCollectionWithKeyType(): void $resolvedType = $fixture->resolve('ArrayObject', new Context('')); - $this->assertInstanceOf(GenericType::class, $resolvedType); + $this->assertInstanceOf(Generic::class, $resolvedType); $this->assertSame('\\ArrayObject', (string) $resolvedType); $this->assertSame('\\ArrayObject', (string) $resolvedType->getFqsen()); @@ -165,7 +165,7 @@ public function testResolvingArrayCollectionWithKeyAndWhitespace(): void /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\GenericType + * @uses \phpDocumentor\Reflection\Types\Generic * @uses \phpDocumentor\Reflection\Types\String_ * * @covers ::__construct @@ -178,7 +178,7 @@ public function testResolvingCollectionOfCollection(): void $resolvedType = $fixture->resolve('ArrayObject>', new Context('')); - $this->assertInstanceOf(GenericType::class, $resolvedType); + $this->assertInstanceOf(Generic::class, $resolvedType); $this->assertSame('\\ArrayObject>', (string) $resolvedType); $this->assertSame('\\ArrayObject', (string) $resolvedType->getFqsen()); @@ -188,7 +188,7 @@ public function testResolvingCollectionOfCollection(): void $this->assertEquals(new Compound([new String_(), new Integer(), new Float_()]), $types[0]); $this->assertArrayHasKey(0, $types); - $this->assertInstanceOf(GenericType::class, $types[1]); + $this->assertInstanceOf(Generic::class, $types[1]); $this->assertSame('\\ArrayObject', (string) $types[1]->getFqsen()); $nestedGenericTypes = $types[1]->getTypes(); diff --git a/tests/unit/Types/GenericTypeTest.php b/tests/unit/PseudoTypes/GenericTest.php similarity index 75% rename from tests/unit/Types/GenericTypeTest.php rename to tests/unit/PseudoTypes/GenericTest.php index 28852f8f..8d343072 100644 --- a/tests/unit/Types/GenericTypeTest.php +++ b/tests/unit/PseudoTypes/GenericTest.php @@ -2,16 +2,19 @@ declare(strict_types=1); -namespace phpDocumentor\Reflection\Types; +namespace phpDocumentor\Reflection\PseudoTypes; use phpDocumentor\Reflection\Fqsen; -use phpDocumentor\Reflection\PseudoTypes\List_; +use phpDocumentor\Reflection\Types\Array_; +use phpDocumentor\Reflection\Types\Integer; +use phpDocumentor\Reflection\Types\Object_; +use phpDocumentor\Reflection\Types\String_; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \phpDocumentor\Reflection\Types\GenericType + * @coversDefaultClass \phpDocumentor\Reflection\Types\Generic */ -class GenericTypeTest extends TestCase +class GenericTest extends TestCase { /** * @covers ::getFqsen @@ -21,7 +24,7 @@ public function testCreate(): void { $fqsen = new Fqsen('\\Foo\\Bar'); $types = [new Object_(new Fqsen('\\Foo\\SomeClass')), new List_(new Integer())]; - $type = new GenericType($fqsen, $types); + $type = new Generic($fqsen, $types); $this->assertSame($fqsen, $type->getFqsen()); $this->assertSame($types, $type->getTypes()); @@ -31,20 +34,20 @@ public function testCreate(): void * @dataProvider provideToStringData * @covers ::__toString */ - public function testToString(string $expectedResult, GenericType $type): void + public function testToString(string $expectedResult, Generic $type): void { $this->assertSame($expectedResult, (string) $type); } /** - * @return array + * @return array */ public static function provideToStringData(): array { return [ 'without fqsen' => [ 'object', - new GenericType( + new Generic( null, [ new String_(), @@ -53,18 +56,18 @@ public static function provideToStringData(): array ], 'collection without key' => [ '\\ArrayObject', - new GenericType(new Fqsen('\\ArrayObject'), [new String_()]), + new Generic(new Fqsen('\\ArrayObject'), [new String_()]), ], 'collection with key' => [ '\\ArrayObject', - new GenericType( + new Generic( new Fqsen('\\ArrayObject'), [new Array_(new String_()), new Object_(new Fqsen('\\Iterator'))] ), ], 'more than two generics' => [ '\\MyClass<\\SomeClassFirst, \\SomeClassSecond, \\SomeClassThird>', - new GenericType( + new Generic( new Fqsen('\\MyClass'), [ new Object_(new Fqsen('\\SomeClassFirst')), diff --git a/tests/unit/TypeResolverTest.php b/tests/unit/TypeResolverTest.php index fdb869f7..4cff6369 100644 --- a/tests/unit/TypeResolverTest.php +++ b/tests/unit/TypeResolverTest.php @@ -23,6 +23,7 @@ use phpDocumentor\Reflection\PseudoTypes\ConstExpression; use phpDocumentor\Reflection\PseudoTypes\False_; use phpDocumentor\Reflection\PseudoTypes\FloatValue; +use phpDocumentor\Reflection\PseudoTypes\Generic; use phpDocumentor\Reflection\PseudoTypes\HtmlEscapedString; use phpDocumentor\Reflection\PseudoTypes\IntegerRange; use phpDocumentor\Reflection\PseudoTypes\IntegerValue; @@ -59,7 +60,6 @@ use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Expression; use phpDocumentor\Reflection\Types\Float_; -use phpDocumentor\Reflection\Types\GenericType; use phpDocumentor\Reflection\Types\Integer; use phpDocumentor\Reflection\Types\InterfaceString; use phpDocumentor\Reflection\Types\Intersection; @@ -1117,7 +1117,7 @@ public function genericsProvider(): array [ 'Collection[]', new Array_( - new GenericType( + new Generic( new Fqsen('\\phpDocumentor\\Collection'), [ new ArrayKey(), From 0d5382eb0bef5934837fc4a27eec744e8b08b4dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=A1=D0=BF=D0=B8?= =?UTF-8?q?=D1=80=D0=BA=D0=BE=D0=B2?= Date: Sat, 29 Nov 2025 12:27:26 +0400 Subject: [PATCH 5/5] Add `GenericTemplate` --- src/PseudoTypes/GenericTemplate.php | 43 +++++++++++++++++++ src/TypeResolver.php | 13 +++++- tests/unit/CollectionResolverTest.php | 6 +-- .../unit/PseudoTypes/GenericTemplateTest.php | 35 +++++++++++++++ tests/unit/PseudoTypes/GenericTest.php | 6 +-- 5 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 src/PseudoTypes/GenericTemplate.php create mode 100644 tests/unit/PseudoTypes/GenericTemplateTest.php diff --git a/src/PseudoTypes/GenericTemplate.php b/src/PseudoTypes/GenericTemplate.php new file mode 100644 index 00000000..52ca6086 --- /dev/null +++ b/src/PseudoTypes/GenericTemplate.php @@ -0,0 +1,43 @@ +resolvedType = $resolvedType; + } + + public function getResolvedType(): Object_ + { + return $this->resolvedType; + } + + public function __toString(): string + { + return (string) $this->resolvedType; + } +} diff --git a/src/TypeResolver.php b/src/TypeResolver.php index c1c24372..e2b8089b 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -24,6 +24,7 @@ use phpDocumentor\Reflection\PseudoTypes\False_; use phpDocumentor\Reflection\PseudoTypes\FloatValue; use phpDocumentor\Reflection\PseudoTypes\Generic; +use phpDocumentor\Reflection\PseudoTypes\GenericTemplate; use phpDocumentor\Reflection\PseudoTypes\HtmlEscapedString; use phpDocumentor\Reflection\PseudoTypes\IntegerRange; use phpDocumentor\Reflection\PseudoTypes\IntegerValue; @@ -465,7 +466,17 @@ private function createFromGeneric(GenericTypeNode $type, Context $context): Typ throw new RuntimeException(sprintf('%s is an unsupported generic', (string) $mainType)); } - $types = $this->createTypesByTypeNodes($type->genericTypes, $context); + $types = array_map( + function (TypeNode $node) use ($context): Type { + $innerType = $this->createType($node, $context); + if ($innerType instanceof Object_ && $innerType instanceof Generic === false) { + return new GenericTemplate($innerType); + } + + return $innerType; + }, + $type->genericTypes + ); return new Generic($mainType->getFqsen(), $types); } diff --git a/tests/unit/CollectionResolverTest.php b/tests/unit/CollectionResolverTest.php index 23d17f60..7f7db8ab 100644 --- a/tests/unit/CollectionResolverTest.php +++ b/tests/unit/CollectionResolverTest.php @@ -14,6 +14,7 @@ namespace phpDocumentor\Reflection; use phpDocumentor\Reflection\PseudoTypes\Generic; +use phpDocumentor\Reflection\PseudoTypes\GenericTemplate; use phpDocumentor\Reflection\PseudoTypes\List_; use phpDocumentor\Reflection\PseudoTypes\NonEmptyList; use phpDocumentor\Reflection\Types\Array_; @@ -80,8 +81,7 @@ public function testResolvingCollectionWithKeyType(): void $this->assertArrayHasKey(0, $types); $this->assertEquals(new Array_(new String_()), $types[0]); $this->assertArrayHasKey(1, $types); - $this->assertInstanceOf(Object_::class, $types[1]); - $this->assertSame('\\Iterator', (string) $types[1]->getFqsen()); + $this->assertEquals(new GenericTemplate(new Object_(new Fqsen('\\Iterator'))), $types[1]); } /** @@ -193,7 +193,7 @@ public function testResolvingCollectionOfCollection(): void $nestedGenericTypes = $types[1]->getTypes(); $this->assertArrayHasKey(0, $nestedGenericTypes); - $this->assertEquals(new Object_(new Fqsen('\\DateTime')), $nestedGenericTypes[0]); + $this->assertEquals(new GenericTemplate(new Object_(new Fqsen('\\DateTime'))), $nestedGenericTypes[0]); } /** diff --git a/tests/unit/PseudoTypes/GenericTemplateTest.php b/tests/unit/PseudoTypes/GenericTemplateTest.php new file mode 100644 index 00000000..6cfb02fb --- /dev/null +++ b/tests/unit/PseudoTypes/GenericTemplateTest.php @@ -0,0 +1,35 @@ +assertSame($resolvedType, $type->getResolvedType()); + } + + /** + * @covers ::__toString + */ + public function testToString(): void + { + $type = new GenericTemplate(new Object_(new Fqsen('\\Foo\\SomeClass'))); + $this->assertSame('\\Foo\\SomeClass', (string) $type); + } +} diff --git a/tests/unit/PseudoTypes/GenericTest.php b/tests/unit/PseudoTypes/GenericTest.php index 8d343072..2da410cf 100644 --- a/tests/unit/PseudoTypes/GenericTest.php +++ b/tests/unit/PseudoTypes/GenericTest.php @@ -12,7 +12,7 @@ use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \phpDocumentor\Reflection\Types\Generic + * @coversDefaultClass \phpDocumentor\Reflection\PseudoTypes\Generic */ class GenericTest extends TestCase { @@ -66,11 +66,11 @@ public static function provideToStringData(): array ), ], 'more than two generics' => [ - '\\MyClass<\\SomeClassFirst, \\SomeClassSecond, \\SomeClassThird>', + '\\MyClass<\\T, \\SomeClassSecond, \\SomeClassThird>', new Generic( new Fqsen('\\MyClass'), [ - new Object_(new Fqsen('\\SomeClassFirst')), + new GenericTemplate(new Object_(new Fqsen('\\T'))), new Object_(new Fqsen('\\SomeClassSecond')), new Object_(new Fqsen('\\SomeClassThird')), ]