From 72e25090a34947f196e9432085ec90463f1cbd13 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: Wed, 19 Nov 2025 19:33:28 +0400 Subject: [PATCH 1/4] Add support for generics in `Static_` and `Self_` --- src/TypeResolver.php | 49 +++++++++++++------------- src/Types/Self_.php | 20 +++++++++++ src/Types/Static_.php | 20 +++++++++++ tests/unit/TypeResolverTest.php | 20 +++++++++++ tests/unit/Types/SelfTest.php | 61 +++++++++++++++++++++++++++++++++ tests/unit/Types/StaticTest.php | 61 +++++++++++++++++++++++++++++++++ 6 files changed, 205 insertions(+), 26 deletions(-) create mode 100644 tests/unit/Types/SelfTest.php create mode 100644 tests/unit/Types/StaticTest.php diff --git a/src/TypeResolver.php b/src/TypeResolver.php index 6c46079..dc99c4f 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -434,16 +434,7 @@ private function createFromGeneric(GenericTypeNode $type, Context $context): Typ return new IntegerRange((string) $type->genericTypes[0], (string) $type->genericTypes[1]); case 'iterable': - return new Iterable_( - ...array_reverse( - array_map( - function (TypeNode $genericType) use ($context): Type { - return $this->createType($genericType, $context); - }, - $type->genericTypes - ) - ) - ); + return new Iterable_(...array_reverse($this->createTypesByTypeNodes($type->genericTypes, $context))); case 'key-of': return new KeyOf($this->createType($type->genericTypes[0], $context)); @@ -452,18 +443,17 @@ function (TypeNode $genericType) use ($context): Type { return new ValueOf($this->createType($type->genericTypes[0], $context)); case 'int-mask': - return new IntMask( - ...array_map( - function (TypeNode $genericType) use ($context): Type { - return $this->createType($genericType, $context); - }, - $type->genericTypes - ) - ); + return new IntMask(...$this->createTypesByTypeNodes($type->genericTypes, $context)); case 'int-mask-of': return new IntMaskOf($this->createType($type->genericTypes[0], $context)); + case 'static': + return new Static_(...$this->createTypesByTypeNodes($type->genericTypes, $context)); + + case 'self': + return new Self_(...$this->createTypesByTypeNodes($type->genericTypes, $context)); + default: $collectionType = $this->createType($type->type, $context); if ($collectionType instanceof Object_ === false) { @@ -472,14 +462,7 @@ function (TypeNode $genericType) use ($context): Type { return new Collection( $collectionType->getFqsen(), - ...array_reverse( - array_map( - function (TypeNode $genericType) use ($context): Type { - return $this->createType($genericType, $context); - }, - $type->genericTypes - ) - ) + ...array_reverse($this->createTypesByTypeNodes($type->genericTypes, $context)) ); } } @@ -727,4 +710,18 @@ private function tryParseRemainingCompoundTypes(TokenIterator $tokenIterator, Co return $type; } + + /** + * @param TypeNode[] $nodes + * @return Type[] + */ + private function createTypesByTypeNodes(array $nodes, Context $context): array + { + return array_map( + function (TypeNode $node) use ($context): Type { + return $this->createType($node, $context); + }, + $nodes + ); + } } diff --git a/src/Types/Self_.php b/src/Types/Self_.php index 5096126..96b6c2b 100644 --- a/src/Types/Self_.php +++ b/src/Types/Self_.php @@ -24,11 +24,31 @@ */ final class Self_ implements Type { + /** @var Type[] */ + private $genericTypes; + + public function __construct(Type ...$genericTypes) + { + $this->genericTypes = $genericTypes; + } + + /** + * @return Type[] + */ + public function getGenericTypes(): array + { + return $this->genericTypes; + } + /** * Returns a rendered output of the Type as it would be used in a DocBlock. */ public function __toString(): string { + if ($this->genericTypes) { + return 'self<' . implode(', ', $this->genericTypes) . '>'; + } + return 'self'; } } diff --git a/src/Types/Static_.php b/src/Types/Static_.php index 6fe365f..c03c279 100644 --- a/src/Types/Static_.php +++ b/src/Types/Static_.php @@ -29,11 +29,31 @@ */ final class Static_ implements Type { + /** @var Type[] */ + private $genericTypes; + + public function __construct(Type ...$genericTypes) + { + $this->genericTypes = $genericTypes; + } + + /** + * @return Type[] + */ + public function getGenericTypes(): array + { + return $this->genericTypes; + } + /** * Returns a rendered output of the Type as it would be used in a DocBlock. */ public function __toString(): string { + if ($this->genericTypes) { + return 'static<' . implode(', ', $this->genericTypes) . '>'; + } + return 'static'; } } diff --git a/tests/unit/TypeResolverTest.php b/tests/unit/TypeResolverTest.php index 6aca0a2..43c6cba 100644 --- a/tests/unit/TypeResolverTest.php +++ b/tests/unit/TypeResolverTest.php @@ -980,10 +980,30 @@ public function typeProvider(): array new Integer() ), ], + [ + 'static', + new Static_(), + ], + [ + 'static', + new Static_( + new Object_(new Fqsen('\\phpDocumentor\\FirstClass')), + new Object_(new Fqsen('\\phpDocumentor\\SecondClass')), + new Object_(new Fqsen('\\phpDocumentor\\ThirdClass')), + ), + ], [ 'self', new Self_(), ], + [ + 'self', + new Self_( + new Object_(new Fqsen('\\phpDocumentor\\FirstClass')), + new Object_(new Fqsen('\\phpDocumentor\\SecondClass')), + new Object_(new Fqsen('\\phpDocumentor\\ThirdClass')), + ), + ], [ '($size is positive-int ? non-empty-array : array)', new ConditionalForParameter( diff --git a/tests/unit/Types/SelfTest.php b/tests/unit/Types/SelfTest.php new file mode 100644 index 0000000..90ef14a --- /dev/null +++ b/tests/unit/Types/SelfTest.php @@ -0,0 +1,61 @@ +assertSame($genericTypes, $type->getGenericTypes()); + } + + /** + * @dataProvider provideToStringData + * @covers ::__toString + */ + public function testToString(string $expectedResult, Self_ $type): void + { + $this->assertSame($expectedResult, (string) $type); + } + + /** + * @return array + */ + public static function provideToStringData(): array + { + return [ + 'basic' => [ + 'self', + new Self_(), + ], + 'with generic' => [ + 'self<\\phpDocumentor\\FirstClass, \\phpDocumentor\\SecondClass, \\phpDocumentor\\ThirdClass>', + new Self_( + new Object_(new Fqsen('\\phpDocumentor\\FirstClass')), + new Object_(new Fqsen('\\phpDocumentor\\SecondClass')), + new Object_(new Fqsen('\\phpDocumentor\\ThirdClass')), + ), + ], + ]; + } +} diff --git a/tests/unit/Types/StaticTest.php b/tests/unit/Types/StaticTest.php new file mode 100644 index 0000000..980747d --- /dev/null +++ b/tests/unit/Types/StaticTest.php @@ -0,0 +1,61 @@ +assertSame($genericTypes, $type->getGenericTypes()); + } + + /** + * @dataProvider provideToStringData + * @covers ::__toString + */ + public function testToString(string $expectedResult, Static_ $type): void + { + $this->assertSame($expectedResult, (string) $type); + } + + /** + * @return array + */ + public static function provideToStringData(): array + { + return [ + 'basic' => [ + 'static', + new Static_(), + ], + 'with generic' => [ + 'static<\\phpDocumentor\\FirstClass, \\phpDocumentor\\SecondClass, \\phpDocumentor\\ThirdClass>', + new Static_( + new Object_(new Fqsen('\\phpDocumentor\\FirstClass')), + new Object_(new Fqsen('\\phpDocumentor\\SecondClass')), + new Object_(new Fqsen('\\phpDocumentor\\ThirdClass')), + ), + ], + ]; + } +} From b345329a041c0ea8730ef4ec90c2a00bd1f735a8 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: Wed, 19 Nov 2025 19:43:26 +0400 Subject: [PATCH 2/4] Add tests for iterable with generics --- tests/unit/TypeResolverTest.php | 36 ++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/tests/unit/TypeResolverTest.php b/tests/unit/TypeResolverTest.php index 43c6cba..272535e 100644 --- a/tests/unit/TypeResolverTest.php +++ b/tests/unit/TypeResolverTest.php @@ -984,26 +984,10 @@ public function typeProvider(): array 'static', new Static_(), ], - [ - 'static', - new Static_( - new Object_(new Fqsen('\\phpDocumentor\\FirstClass')), - new Object_(new Fqsen('\\phpDocumentor\\SecondClass')), - new Object_(new Fqsen('\\phpDocumentor\\ThirdClass')), - ), - ], [ 'self', new Self_(), ], - [ - 'self', - new Self_( - new Object_(new Fqsen('\\phpDocumentor\\FirstClass')), - new Object_(new Fqsen('\\phpDocumentor\\SecondClass')), - new Object_(new Fqsen('\\phpDocumentor\\ThirdClass')), - ), - ], [ '($size is positive-int ? non-empty-array : array)', new ConditionalForParameter( @@ -1129,6 +1113,26 @@ public function genericsProvider(): array 'int-mask-of', new IntMaskOf(new ConstExpression(new Object_(new Fqsen('\\phpDocumentor\\Foo')), 'INT_*')), ], + [ + 'iterable', + new Iterable_(new String_(), new Integer()), + ], + [ + 'static', + new Static_( + new Object_(new Fqsen('\\phpDocumentor\\FirstClass')), + new Object_(new Fqsen('\\phpDocumentor\\SecondClass')), + new Object_(new Fqsen('\\phpDocumentor\\ThirdClass')), + ), + ], + [ + 'self', + new Self_( + new Object_(new Fqsen('\\phpDocumentor\\FirstClass')), + new Object_(new Fqsen('\\phpDocumentor\\SecondClass')), + new Object_(new Fqsen('\\phpDocumentor\\ThirdClass')), + ), + ], ]; } From 8d08e67fc3ea4b7dd903989ff43955b37d416b19 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: Wed, 19 Nov 2025 19:46:54 +0400 Subject: [PATCH 3/4] Fix code style --- src/TypeResolver.php | 1 + src/Types/Self_.php | 2 ++ src/Types/Static_.php | 2 ++ tests/unit/Types/SelfTest.php | 1 - tests/unit/Types/StaticTest.php | 1 - 5 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/TypeResolver.php b/src/TypeResolver.php index dc99c4f..fad3d20 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -713,6 +713,7 @@ private function tryParseRemainingCompoundTypes(TokenIterator $tokenIterator, Co /** * @param TypeNode[] $nodes + * * @return Type[] */ private function createTypesByTypeNodes(array $nodes, Context $context): array diff --git a/src/Types/Self_.php b/src/Types/Self_.php index 96b6c2b..b3fc783 100644 --- a/src/Types/Self_.php +++ b/src/Types/Self_.php @@ -15,6 +15,8 @@ use phpDocumentor\Reflection\Type; +use function implode; + /** * Value Object representing the 'self' type. * diff --git a/src/Types/Static_.php b/src/Types/Static_.php index c03c279..eb14ecb 100644 --- a/src/Types/Static_.php +++ b/src/Types/Static_.php @@ -15,6 +15,8 @@ use phpDocumentor\Reflection\Type; +use function implode; + /** * Value Object representing the 'static' type. * diff --git a/tests/unit/Types/SelfTest.php b/tests/unit/Types/SelfTest.php index 90ef14a..b51b10f 100644 --- a/tests/unit/Types/SelfTest.php +++ b/tests/unit/Types/SelfTest.php @@ -5,7 +5,6 @@ namespace phpDocumentor\Reflection\Types; use phpDocumentor\Reflection\Fqsen; -use phpDocumentor\Reflection\Types\Object_; use PHPUnit\Framework\TestCase; /** diff --git a/tests/unit/Types/StaticTest.php b/tests/unit/Types/StaticTest.php index 980747d..f41c1c0 100644 --- a/tests/unit/Types/StaticTest.php +++ b/tests/unit/Types/StaticTest.php @@ -5,7 +5,6 @@ namespace phpDocumentor\Reflection\Types; use phpDocumentor\Reflection\Fqsen; -use phpDocumentor\Reflection\Types\Object_; use PHPUnit\Framework\TestCase; /** From 7472520abd4939765561ef5249f80b0ce8dbacf6 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: Wed, 19 Nov 2025 19:51:20 +0400 Subject: [PATCH 4/4] Reusing the `createTypesByTypeNodes` method --- src/TypeResolver.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/TypeResolver.php b/src/TypeResolver.php index fad3d20..9e2ba20 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -628,14 +628,7 @@ private function resolveTypedObject(string $type, ?Context $context = null): Obj /** @param TypeNode[] $typeNodes */ private function createArray(array $typeNodes, Context $context): Array_ { - $types = array_reverse( - array_map( - function (TypeNode $node) use ($context): Type { - return $this->createType($node, $context); - }, - $typeNodes - ) - ); + $types = array_reverse($this->createTypesByTypeNodes($typeNodes, $context)); if (isset($types[1]) === false) { return new Array_(...$types);