From a4024089f62747dca18f3616c83eb1b1e1b1806f 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: Fri, 12 Dec 2025 14:39:44 +0400 Subject: [PATCH 1/3] Improve the conversion of arrays to strings --- src/Types/AbstractList.php | 12 +++++++++--- tests/unit/TypeResolverTest.php | 8 ++++---- tests/unit/Types/ArrayTest.php | 25 ++++++++++++++++++++++++- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/Types/AbstractList.php b/src/Types/AbstractList.php index 32eb0f23..0347b161 100644 --- a/src/Types/AbstractList.php +++ b/src/Types/AbstractList.php @@ -15,6 +15,9 @@ use phpDocumentor\Reflection\Type; +use function preg_match; +use function substr; + /** * Represents a list of values. This is an abstract class for Array_ and List_. * @@ -84,10 +87,13 @@ public function __toString(): string return 'array<' . $this->keyType . ',' . $this->valueType . '>'; } - if ($this->valueType instanceof Compound) { - return '(' . $this->valueType . ')[]'; + if ( + !preg_match('/[^\w\\\\]/', (string) $this->valueType) || + substr((string) $this->valueType, -2, 2) === '[]' + ) { + return $this->valueType . '[]'; } - return $this->valueType . '[]'; + return 'array<' . $this->valueType . '>'; } } diff --git a/tests/unit/TypeResolverTest.php b/tests/unit/TypeResolverTest.php index 287688fe..417ee2ae 100644 --- a/tests/unit/TypeResolverTest.php +++ b/tests/unit/TypeResolverTest.php @@ -376,7 +376,7 @@ public function testResolvingArrayExpressionObjectsTypes(): void $resolvedType = $fixture->resolve('(\stdClass|Reflection\DocBlock)[]', new Context('phpDocumentor')); $this->assertInstanceOf(Array_::class, $resolvedType); - $this->assertSame('(\stdClass|\phpDocumentor\Reflection\DocBlock)[]', (string) $resolvedType); + $this->assertSame('array<\stdClass|\phpDocumentor\Reflection\DocBlock>', (string) $resolvedType); $valueType = $resolvedType->getValueType(); @@ -405,7 +405,7 @@ public function testResolvingArrayExpressionSimpleTypes(): void $resolvedType = $fixture->resolve('(string|\stdClass|boolean)[]', new Context('')); $this->assertInstanceOf(Array_::class, $resolvedType); - $this->assertSame('(string|\stdClass|bool)[]', (string) $resolvedType); + $this->assertSame('array', (string) $resolvedType); $valueType = $resolvedType->getValueType(); @@ -437,7 +437,7 @@ public function testResolvingArrayOfArrayExpressionTypes(): void $resolvedType = $fixture->resolve('(string|\stdClass)[][]', new Context('')); $this->assertInstanceOf(Array_::class, $resolvedType); - $this->assertSame('(string|\stdClass)[][]', (string) $resolvedType); + $this->assertSame('array>', (string) $resolvedType); $parentArrayType = $resolvedType->getValueType(); $this->assertInstanceOf(Array_::class, $parentArrayType); @@ -483,7 +483,7 @@ public function testResolvingArrayExpressionOrCompoundTypes(): void $resolvedType = $fixture->resolve('\stdClass|(string|\stdClass)[]|bool', new Context('')); $this->assertInstanceOf(Compound::class, $resolvedType); - $this->assertSame('\stdClass|(string|\stdClass)[]|bool', (string) $resolvedType); + $this->assertSame('\stdClass|array|bool', (string) $resolvedType); $firstType = $resolvedType->get(0); $this->assertInstanceOf(Object_::class, $firstType); diff --git a/tests/unit/Types/ArrayTest.php b/tests/unit/Types/ArrayTest.php index 3c567dd6..df5d42d9 100644 --- a/tests/unit/Types/ArrayTest.php +++ b/tests/unit/Types/ArrayTest.php @@ -14,6 +14,10 @@ namespace phpDocumentor\Reflection\Types; use phpDocumentor\Reflection\Fqsen; +use phpDocumentor\Reflection\PseudoTypes\ArrayShape; +use phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem; +use phpDocumentor\Reflection\PseudoTypes\ObjectShape; +use phpDocumentor\Reflection\PseudoTypes\ObjectShapeItem; use PHPUnit\Framework\TestCase; final class ArrayTest extends TestCase @@ -63,8 +67,27 @@ public function provideToStringData(): array 'simple array' => [new Array_(), 'array'], 'array of mixed' => [new Array_(new Mixed_()), 'mixed[]'], 'array of single type' => [new Array_(new String_()), 'string[]'], - 'array of compound type' => [new Array_(new Compound([new Integer(), new String_()])), '(int|string)[]'], + 'multidimensional array' => [new Array_(new Array_(new String_())), 'string[][]'], + 'array of compound type' => [new Array_(new Compound([new Integer(), new String_()])), 'array'], 'array with key type' => [new Array_(new String_(), new Integer()), 'array'], + 'array of array shapes' => [ + new Array_( + new ArrayShape( + new ArrayShapeItem('foo', new String_(), false), + new ArrayShapeItem('bar', new Integer(), false) + ) + ), + 'array', + ], + 'array of object shapes' => [ + new Array_( + new ObjectShape( + new ObjectShapeItem('foo', new String_(), false), + new ObjectShapeItem('bar', new Integer(), false) + ) + ), + 'array', + ], ]; } } From cae9aaa76a7b20eafb144647d67b9e832d09d812 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, 13 Dec 2025 23:42:59 +0400 Subject: [PATCH 2/3] Optimize `AbstractList::__toString()` --- src/Types/AbstractList.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Types/AbstractList.php b/src/Types/AbstractList.php index f33d2cc0..d7c94f06 100644 --- a/src/Types/AbstractList.php +++ b/src/Types/AbstractList.php @@ -83,17 +83,16 @@ public function __toString(): string return 'array'; } + $valueTypeString = (string) $this->valueType; + if ($this->keyType) { - return 'array<' . $this->keyType . ', ' . $this->valueType . '>'; + return 'array<' . $this->keyType . ', ' . $valueTypeString . '>'; } - if ( - !preg_match('/[^\w\\\\]/', (string) $this->valueType) || - substr((string) $this->valueType, -2, 2) === '[]' - ) { - return $this->valueType . '[]'; + if (!preg_match('/[^\w\\\\]/', $valueTypeString) || substr($valueTypeString, -2, 2) === '[]') { + return $valueTypeString . '[]'; } - return 'array<' . $this->valueType . '>'; + return 'array<' . $valueTypeString . '>'; } } From cbc0615cc9d4f582124496db668e8f6a846ee6bf 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: Sun, 14 Dec 2025 00:03:29 +0400 Subject: [PATCH 3/3] Move the logic of `AbstractList::__toString()` to `Array_::__toString()` --- src/Types/AbstractList.php | 30 +++++-------------------- src/Types/Array_.php | 21 +++++++++++++++++ tests/unit/Types/ContextFactoryTest.php | 5 +++++ 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/Types/AbstractList.php b/src/Types/AbstractList.php index d7c94f06..831be40d 100644 --- a/src/Types/AbstractList.php +++ b/src/Types/AbstractList.php @@ -15,9 +15,6 @@ use phpDocumentor\Reflection\Type; -use function preg_match; -use function substr; - /** * Represents a list of values. This is an abstract class for Array_ and List_. * @@ -48,6 +45,11 @@ public function __construct(?Type $valueType = null, ?Type $keyType = null) $this->keyType = $keyType; } + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + abstract public function __toString(): string; + public function getOriginalKeyType(): ?Type { return $this->keyType; @@ -73,26 +75,4 @@ public function getValueType(): Type { return $this->valueType ?? $this->defaultValueType; } - - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString(): string - { - if ($this->valueType === null) { - return 'array'; - } - - $valueTypeString = (string) $this->valueType; - - if ($this->keyType) { - return 'array<' . $this->keyType . ', ' . $valueTypeString . '>'; - } - - if (!preg_match('/[^\w\\\\]/', $valueTypeString) || substr($valueTypeString, -2, 2) === '[]') { - return $valueTypeString . '[]'; - } - - return 'array<' . $valueTypeString . '>'; - } } diff --git a/src/Types/Array_.php b/src/Types/Array_.php index bc17225f..b5e7eaba 100644 --- a/src/Types/Array_.php +++ b/src/Types/Array_.php @@ -13,6 +13,9 @@ namespace phpDocumentor\Reflection\Types; +use function preg_match; +use function substr; + /** * Represents an array type as described in the PSR-5, the PHPDoc Standard. * @@ -26,4 +29,22 @@ */ class Array_ extends AbstractList { + public function __toString(): string + { + if ($this->valueType === null) { + return 'array'; + } + + $valueTypeString = (string) $this->valueType; + + if ($this->keyType) { + return 'array<' . $this->keyType . ', ' . $valueTypeString . '>'; + } + + if (!preg_match('/[^\w\\\\]/', $valueTypeString) || substr($valueTypeString, -2, 2) === '[]') { + return $valueTypeString . '[]'; + } + + return 'array<' . $valueTypeString . '>'; + } } diff --git a/tests/unit/Types/ContextFactoryTest.php b/tests/unit/Types/ContextFactoryTest.php index 5d9a13ea..a2eb0a72 100644 --- a/tests/unit/Types/ContextFactoryTest.php +++ b/tests/unit/Types/ContextFactoryTest.php @@ -262,5 +262,10 @@ public function assertNamespaceAliasesFrom(Context $context): void class Foo extends AbstractList { // dummy class + + public function __toString(): string + { + return ''; + } } }