From 6e08ea5c1aa3e16aeccbb55adb232d7f1b1c201a Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 1 Apr 2020 02:37:57 +0300 Subject: [PATCH 1/6] Add support "&" --- src/TypeResolver.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/TypeResolver.php b/src/TypeResolver.php index b46d2d6..ad75ee5 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -133,7 +133,7 @@ public function resolve(string $type, ?Context $context = null) : Type // split the type string into tokens `|`, `?`, `<`, `>`, `,`, `(`, `)[]`, '<', '>' and type names $tokens = preg_split( - '/(\\||\\?|<|>|, ?|\\(|\\)(?:\\[\\])+)/', + '/(\\||\\?|<|>|&|, ?|\\(|\\)(?:\\[\\])+)/', $type, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE @@ -162,7 +162,7 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser while ($tokens->valid()) { $token = $tokens->current(); - if ($token === '|') { + if ($token === '|' || $token === '&') { if (count($types) === 0) { throw new RuntimeException( 'A type is missing before a type separator' @@ -250,7 +250,7 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser } } - if ($token === '|') { + if ($token === '|' || $token === '&') { throw new RuntimeException( 'A type is missing after a type separator' ); From 8bcaf35f608b64972d8f1bb16a08dea1b957fd19 Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 1 Apr 2020 16:56:16 +0300 Subject: [PATCH 2/6] Add unittest --- tests/unit/TypeResolverTest.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/unit/TypeResolverTest.php b/tests/unit/TypeResolverTest.php index 6f70bd3..4713c0d 100644 --- a/tests/unit/TypeResolverTest.php +++ b/tests/unit/TypeResolverTest.php @@ -251,6 +251,37 @@ public function testResolvingCompoundTypes() : void $this->assertInstanceOf(Fqsen::class, $secondType->getFqsen()); } + /** + * @uses \phpDocumentor\Reflection\Types\Context + * @uses \phpDocumentor\Reflection\Types\Compound + * @uses \phpDocumentor\Reflection\Types\String_ + * @uses \phpDocumentor\Reflection\Types\Object_ + * @uses \phpDocumentor\Reflection\Fqsen + * @uses \phpDocumentor\Reflection\FqsenResolver + * + * @covers ::__construct + * @covers ::resolve + * @covers :: + */ + public function testResolvingAmpersandCompoundTypes() : void + { + $fixture = new TypeResolver(); + + $resolvedType = $fixture->resolve('Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject ', new Context('phpDocumentor')); + + $this->assertInstanceOf(Compound::class, $resolvedType); + $this->assertSame('\phpDocumentor\Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject', (string) $resolvedType); + + $firstType = $resolvedType->get(0); + + $secondType = $resolvedType->get(1); + + $this->assertInstanceOf(Object_::class, $firstType); + $this->assertInstanceOf(Fqsen::class, $firstType->getFqsen()); + $this->assertInstanceOf(Object_::class, $secondType); + $this->assertInstanceOf(Fqsen::class, $secondType->getFqsen()); + } + /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound From afd87c27a8b208451c239a05a6f91545312e7517 Mon Sep 17 00:00:00 2001 From: Big Shark Date: Wed, 1 Apr 2020 23:28:09 +0300 Subject: [PATCH 3/6] Add Expression type --- src/TypeResolver.php | 53 ++++++++++--------------- src/Types/AbstractList.php | 20 ---------- src/Types/Array_.php | 19 +++++++++ src/Types/Compound.php | 10 ++++- src/Types/Expression_.php | 29 ++++++++++++++ tests/unit/TypeResolverTest.php | 44 +++++++++++++++++++- tests/unit/Types/ContextFactoryTest.php | 3 ++ 7 files changed, 122 insertions(+), 56 deletions(-) create mode 100644 src/Types/Expression_.php diff --git a/src/TypeResolver.php b/src/TypeResolver.php index ad75ee5..9e61553 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -16,6 +16,7 @@ use ArrayIterator; use InvalidArgumentException; use phpDocumentor\Reflection\Types\Array_; +use phpDocumentor\Reflection\Types\Expression_; use phpDocumentor\Reflection\Types\ClassString; use phpDocumentor\Reflection\Types\Collection; use phpDocumentor\Reflection\Types\Compound; @@ -131,9 +132,9 @@ public function resolve(string $type, ?Context $context = null) : Type $context = new Context(''); } - // split the type string into tokens `|`, `?`, `<`, `>`, `,`, `(`, `)[]`, '<', '>' and type names + // split the type string into tokens `|`, `?`, `<`, `>`, `,`, `(`, `)`, `[]`, '<', '>' and type names $tokens = preg_split( - '/(\\||\\?|<|>|&|, ?|\\(|\\)(?:\\[\\])+)/', + '/(\\||\\?|<|>|&|, ?|\\(|\\)|\\[\\]+)/', $type, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE @@ -159,9 +160,9 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser { $types = []; $token = ''; + $compoundToken = '|'; while ($tokens->valid()) { $token = $tokens->current(); - if ($token === '|' || $token === '&') { if (count($types) === 0) { throw new RuntimeException( @@ -180,6 +181,7 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser ); } + $compoundToken = $token; $tokens->next(); } elseif ($token === '?') { if (!in_array($parserContext, [ @@ -200,22 +202,22 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser $tokens->next(); $type = $this->parseTypes($tokens, $context, self::PARSER_IN_ARRAY_EXPRESSION); - $resolvedType = new Array_($type); - $token = $tokens->current(); - // Someone did not properly close their array expression .. - if ($token === null) { + if ($token === null) { // Someone did not properly close their array expression .. break; } - // we generate arrays corresponding to the number of '[]' after the ')' - $numberOfArrays = (strlen($token) - 1) / 2; - for ($i = 0; $i < $numberOfArrays - 1; ++$i) { - $resolvedType = new Array_($resolvedType); + $tokens->next(); + $token = $tokens->current(); + + if ($token === self::OPERATOR_ARRAY) { + $tokens->next(); + $resolvedType = new Array_($type); + } else { + $resolvedType = new Expression_($type); } $types[] = $resolvedType; - $tokens->next(); } elseif ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION && $token[0] === ')') { break; } elseif ($token === '<') { @@ -239,6 +241,11 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser && ($token === '>' || trim($token) === ',') ) { break; + } elseif ($token === self::OPERATOR_ARRAY) { + $last = array_key_last($types); + $types[$last] = new Array_($types[$last]); + + $tokens->next(); } else { $type = $this->resolveSingleType($token, $context); $tokens->next(); @@ -278,7 +285,7 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser return $types[0]; } - return new Compound($types); + return new Compound($types, $compoundToken); } /** @@ -293,8 +300,6 @@ private function resolveSingleType(string $type, Context $context) : object switch (true) { case $this->isKeyword($type): return $this->resolveKeyword($type); - case $this->isTypedArray($type): - return $this->resolveTypedArray($type, $context); case $this->isFqsen($type): return $this->resolveTypedObject($type); case $this->isPartialStructuralElementName($type): @@ -334,16 +339,6 @@ public function addKeyword(string $keyword, string $typeClassName) : void $this->keywords[$keyword] = $typeClassName; } - /** - * Detects whether the given type represents an array. - * - * @param string $type A relative or absolute type as defined in the phpDocumentor documentation. - */ - private function isTypedArray(string $type) : bool - { - return substr($type, -2) === self::OPERATOR_ARRAY; - } - /** * Detects whether the given type represents a PHPDoc keyword. * @@ -372,14 +367,6 @@ private function isFqsen(string $type) : bool return strpos($type, self::OPERATOR_NAMESPACE) === 0; } - /** - * Resolves the given typed array string (i.e. `string[]`) into an Array object with the right types set. - */ - private function resolveTypedArray(string $type, Context $context) : Array_ - { - return new Array_($this->resolveSingleType(substr($type, 0, -2), $context)); - } - /** * Resolves the given keyword (such as `string`) into a Type object representing that keyword. */ diff --git a/src/Types/AbstractList.php b/src/Types/AbstractList.php index 39ea637..38105e7 100644 --- a/src/Types/AbstractList.php +++ b/src/Types/AbstractList.php @@ -58,24 +58,4 @@ public function getValueType() : Type { return $this->valueType; } - - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string - { - if ($this->keyType) { - return 'array<' . $this->keyType . ',' . $this->valueType . '>'; - } - - if ($this->valueType instanceof Mixed_) { - return 'array'; - } - - if ($this->valueType instanceof Compound) { - return '(' . $this->valueType . ')[]'; - } - - return $this->valueType . '[]'; - } } diff --git a/src/Types/Array_.php b/src/Types/Array_.php index 7e3b13f..30519a7 100644 --- a/src/Types/Array_.php +++ b/src/Types/Array_.php @@ -24,4 +24,23 @@ */ final class Array_ extends AbstractList { + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + if ($this->keyType) { + return 'array<' . $this->keyType . ',' . $this->valueType . '>'; + } + + if ($this->valueType instanceof Mixed_) { + return 'array'; + } + + if ($this->valueType instanceof Compound) { + return '(' . $this->valueType . ')[]'; + } + + return $this->valueType . '[]'; + } } diff --git a/src/Types/Compound.php b/src/Types/Compound.php index bcdbc05..ea3ed74 100644 --- a/src/Types/Compound.php +++ b/src/Types/Compound.php @@ -33,18 +33,24 @@ final class Compound implements Type, IteratorAggregate /** @var array */ private $types = []; + /** @var string */ + private $token; + /** * Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface. * * @param Type[] $types + * @param string $token * * @phpstan-param list $types */ - public function __construct(array $types) + public function __construct(array $types, string $token = '|') { foreach ($types as $type) { $this->add($type); } + + $this->token = $token; } /** @@ -87,7 +93,7 @@ public function contains(Type $type) : bool */ public function __toString() : string { - return implode('|', $this->types); + return implode($this->token, $this->types); } /** diff --git a/src/Types/Expression_.php b/src/Types/Expression_.php new file mode 100644 index 0000000..4102915 --- /dev/null +++ b/src/Types/Expression_.php @@ -0,0 +1,29 @@ +valueType . ')'; + } +} diff --git a/tests/unit/TypeResolverTest.php b/tests/unit/TypeResolverTest.php index 4713c0d..6cd2b4c 100644 --- a/tests/unit/TypeResolverTest.php +++ b/tests/unit/TypeResolverTest.php @@ -15,11 +15,13 @@ use Mockery as m; use phpDocumentor\Reflection\Types\Array_; +use phpDocumentor\Reflection\Types\Expression_; use phpDocumentor\Reflection\Types\Boolean; use phpDocumentor\Reflection\Types\ClassString; use phpDocumentor\Reflection\Types\Compound; use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Iterable_; +use phpDocumentor\Reflection\Types\Null_; use phpDocumentor\Reflection\Types\Nullable; use phpDocumentor\Reflection\Types\Object_; use phpDocumentor\Reflection\Types\String_; @@ -281,7 +283,47 @@ public function testResolvingAmpersandCompoundTypes() : void $this->assertInstanceOf(Object_::class, $secondType); $this->assertInstanceOf(Fqsen::class, $secondType->getFqsen()); } - + + /** + * @uses \phpDocumentor\Reflection\Types\Context + * @uses \phpDocumentor\Reflection\Types\Compound + * @uses \phpDocumentor\Reflection\Types\String_ + * @uses \phpDocumentor\Reflection\Types\Object_ + * @uses \phpDocumentor\Reflection\Fqsen + * @uses \phpDocumentor\Reflection\FqsenResolver + * + * @covers ::__construct + * @covers ::resolve + * @covers :: + */ + public function testResolvingMixedCompoundTypes() : void + { + $fixture = new TypeResolver(); + + $resolvedType = $fixture->resolve('(Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject)|null', new Context('phpDocumentor')); + + $this->assertInstanceOf(Compound::class, $resolvedType); + $this->assertSame('(\phpDocumentor\Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject)|null', (string) $resolvedType); + + $firstType = $resolvedType->get(0); + + $secondType = $resolvedType->get(1); + + $this->assertInstanceOf(Expression_::class, $firstType); + $this->assertSame('(\phpDocumentor\Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject)', (string) $firstType); + $this->assertInstanceOf(Null_::class, $secondType); + + $resolvedType = $firstType->getValueType(); + + $firstSubType = $resolvedType->get(0); + $secondSubType = $resolvedType->get(1); + + $this->assertInstanceOf(Object_::class, $firstSubType); + $this->assertInstanceOf(Fqsen::class, $secondSubType->getFqsen()); + $this->assertInstanceOf(Object_::class, $secondSubType); + $this->assertInstanceOf(Fqsen::class, $secondSubType->getFqsen()); + } + /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound diff --git a/tests/unit/Types/ContextFactoryTest.php b/tests/unit/Types/ContextFactoryTest.php index 89e2e97..fe431c7 100644 --- a/tests/unit/Types/ContextFactoryTest.php +++ b/tests/unit/Types/ContextFactoryTest.php @@ -206,6 +206,9 @@ public function tearDown() : void class Foo extends AbstractList { + public function __toString(): string + { + } // dummy class } } From c758c748b471bc38ee867c4f2f8ee1fa54b4838c Mon Sep 17 00:00:00 2001 From: Big Shark Date: Wed, 1 Apr 2020 23:44:10 +0300 Subject: [PATCH 4/6] Some fixes --- src/TypeResolver.php | 17 ++++++++--------- src/Types/AbstractList.php | 20 ++++++++++++++++++++ src/Types/Array_.php | 19 ------------------- src/Types/Expression_.php | 25 +++++++++++++++++++++++-- 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/TypeResolver.php b/src/TypeResolver.php index 9e61553..02fbeb2 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -208,14 +208,8 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser } $tokens->next(); - $token = $tokens->current(); - if ($token === self::OPERATOR_ARRAY) { - $tokens->next(); - $resolvedType = new Array_($type); - } else { - $resolvedType = new Expression_($type); - } + $resolvedType = new Expression_($type); $types[] = $resolvedType; } elseif ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION && $token[0] === ')') { @@ -242,8 +236,13 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser ) { break; } elseif ($token === self::OPERATOR_ARRAY) { - $last = array_key_last($types); - $types[$last] = new Array_($types[$last]); + end($types); + $last = key($types); + $lastItem = $types[$last]; + if ($lastItem instanceof Expression_) { + $lastItem = $lastItem->getValueType(); + } + $types[$last] = new Array_($lastItem); $tokens->next(); } else { diff --git a/src/Types/AbstractList.php b/src/Types/AbstractList.php index 38105e7..39ea637 100644 --- a/src/Types/AbstractList.php +++ b/src/Types/AbstractList.php @@ -58,4 +58,24 @@ public function getValueType() : Type { return $this->valueType; } + + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + if ($this->keyType) { + return 'array<' . $this->keyType . ',' . $this->valueType . '>'; + } + + if ($this->valueType instanceof Mixed_) { + return 'array'; + } + + if ($this->valueType instanceof Compound) { + return '(' . $this->valueType . ')[]'; + } + + return $this->valueType . '[]'; + } } diff --git a/src/Types/Array_.php b/src/Types/Array_.php index 30519a7..7e3b13f 100644 --- a/src/Types/Array_.php +++ b/src/Types/Array_.php @@ -24,23 +24,4 @@ */ final class Array_ extends AbstractList { - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string - { - if ($this->keyType) { - return 'array<' . $this->keyType . ',' . $this->valueType . '>'; - } - - if ($this->valueType instanceof Mixed_) { - return 'array'; - } - - if ($this->valueType instanceof Compound) { - return '(' . $this->valueType . ')[]'; - } - - return $this->valueType . '[]'; - } } diff --git a/src/Types/Expression_.php b/src/Types/Expression_.php index 4102915..ba5562a 100644 --- a/src/Types/Expression_.php +++ b/src/Types/Expression_.php @@ -13,12 +13,33 @@ namespace phpDocumentor\Reflection\Types; +use phpDocumentor\Reflection\Type; + /** - * Represents an array type as described in the PSR-5, the PHPDoc Standard. + * Represents an expression type as described in the PSR-5, the PHPDoc Standard. * */ -final class Expression_ extends AbstractList +final class Expression_ implements Type { + /** @var Type */ + protected $valueType; + + /** + * Initializes this representation of an array with the given Type. + */ + public function __construct(Type $valueType) + { + $this->valueType = $valueType; + } + + /** + * Returns the value for the keys of this array. + */ + public function getValueType() : Type + { + return $this->valueType; + } + /** * Returns a rendered output of the Type as it would be used in a DocBlock. */ From b9948f5f4ae91db1b02c2b10ecc8f31ae39c12eb Mon Sep 17 00:00:00 2001 From: Big Shark Date: Wed, 1 Apr 2020 23:55:03 +0300 Subject: [PATCH 5/6] Revert changes --- tests/unit/Types/ContextFactoryTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unit/Types/ContextFactoryTest.php b/tests/unit/Types/ContextFactoryTest.php index fe431c7..89e2e97 100644 --- a/tests/unit/Types/ContextFactoryTest.php +++ b/tests/unit/Types/ContextFactoryTest.php @@ -206,9 +206,6 @@ public function tearDown() : void class Foo extends AbstractList { - public function __toString(): string - { - } // dummy class } } From defed2a4926525df620cae4392219effaa63d5c4 Mon Sep 17 00:00:00 2001 From: Big Shark Date: Thu, 2 Apr 2020 00:11:43 +0300 Subject: [PATCH 6/6] Fix code style --- src/Types/Compound.php | 1 - src/Types/Expression_.php | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Types/Compound.php b/src/Types/Compound.php index ea3ed74..c655aa6 100644 --- a/src/Types/Compound.php +++ b/src/Types/Compound.php @@ -40,7 +40,6 @@ final class Compound implements Type, IteratorAggregate * Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface. * * @param Type[] $types - * @param string $token * * @phpstan-param list $types */ diff --git a/src/Types/Expression_.php b/src/Types/Expression_.php index ba5562a..a3da762 100644 --- a/src/Types/Expression_.php +++ b/src/Types/Expression_.php @@ -10,14 +10,12 @@ * * @link http://phpdoc.org */ - namespace phpDocumentor\Reflection\Types; use phpDocumentor\Reflection\Type; /** * Represents an expression type as described in the PSR-5, the PHPDoc Standard. - * */ final class Expression_ implements Type { @@ -29,7 +27,7 @@ final class Expression_ implements Type */ public function __construct(Type $valueType) { - $this->valueType = $valueType; + $this->valueType = $valueType; } /**