Skip to content

Commit afd87c2

Browse files
committed
Add Expression type
1 parent 8bcaf35 commit afd87c2

File tree

7 files changed

+122
-56
lines changed

7 files changed

+122
-56
lines changed

src/TypeResolver.php

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ArrayIterator;
1717
use InvalidArgumentException;
1818
use phpDocumentor\Reflection\Types\Array_;
19+
use phpDocumentor\Reflection\Types\Expression_;
1920
use phpDocumentor\Reflection\Types\ClassString;
2021
use phpDocumentor\Reflection\Types\Collection;
2122
use phpDocumentor\Reflection\Types\Compound;
@@ -131,9 +132,9 @@ public function resolve(string $type, ?Context $context = null) : Type
131132
$context = new Context('');
132133
}
133134

134-
// split the type string into tokens `|`, `?`, `<`, `>`, `,`, `(`, `)[]`, '<', '>' and type names
135+
// split the type string into tokens `|`, `?`, `<`, `>`, `,`, `(`, `)`, `[]`, '<', '>' and type names
135136
$tokens = preg_split(
136-
'/(\\||\\?|<|>|&|, ?|\\(|\\)(?:\\[\\])+)/',
137+
'/(\\||\\?|<|>|&|, ?|\\(|\\)|\\[\\]+)/',
137138
$type,
138139
-1,
139140
PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
@@ -159,9 +160,9 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
159160
{
160161
$types = [];
161162
$token = '';
163+
$compoundToken = '|';
162164
while ($tokens->valid()) {
163165
$token = $tokens->current();
164-
165166
if ($token === '|' || $token === '&') {
166167
if (count($types) === 0) {
167168
throw new RuntimeException(
@@ -180,6 +181,7 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
180181
);
181182
}
182183

184+
$compoundToken = $token;
183185
$tokens->next();
184186
} elseif ($token === '?') {
185187
if (!in_array($parserContext, [
@@ -200,22 +202,22 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
200202
$tokens->next();
201203
$type = $this->parseTypes($tokens, $context, self::PARSER_IN_ARRAY_EXPRESSION);
202204

203-
$resolvedType = new Array_($type);
204-
205205
$token = $tokens->current();
206-
// Someone did not properly close their array expression ..
207-
if ($token === null) {
206+
if ($token === null) { // Someone did not properly close their array expression ..
208207
break;
209208
}
210209

211-
// we generate arrays corresponding to the number of '[]' after the ')'
212-
$numberOfArrays = (strlen($token) - 1) / 2;
213-
for ($i = 0; $i < $numberOfArrays - 1; ++$i) {
214-
$resolvedType = new Array_($resolvedType);
210+
$tokens->next();
211+
$token = $tokens->current();
212+
213+
if ($token === self::OPERATOR_ARRAY) {
214+
$tokens->next();
215+
$resolvedType = new Array_($type);
216+
} else {
217+
$resolvedType = new Expression_($type);
215218
}
216219

217220
$types[] = $resolvedType;
218-
$tokens->next();
219221
} elseif ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION && $token[0] === ')') {
220222
break;
221223
} elseif ($token === '<') {
@@ -239,6 +241,11 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
239241
&& ($token === '>' || trim($token) === ',')
240242
) {
241243
break;
244+
} elseif ($token === self::OPERATOR_ARRAY) {
245+
$last = array_key_last($types);
246+
$types[$last] = new Array_($types[$last]);
247+
248+
$tokens->next();
242249
} else {
243250
$type = $this->resolveSingleType($token, $context);
244251
$tokens->next();
@@ -278,7 +285,7 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
278285
return $types[0];
279286
}
280287

281-
return new Compound($types);
288+
return new Compound($types, $compoundToken);
282289
}
283290

284291
/**
@@ -293,8 +300,6 @@ private function resolveSingleType(string $type, Context $context) : object
293300
switch (true) {
294301
case $this->isKeyword($type):
295302
return $this->resolveKeyword($type);
296-
case $this->isTypedArray($type):
297-
return $this->resolveTypedArray($type, $context);
298303
case $this->isFqsen($type):
299304
return $this->resolveTypedObject($type);
300305
case $this->isPartialStructuralElementName($type):
@@ -334,16 +339,6 @@ public function addKeyword(string $keyword, string $typeClassName) : void
334339
$this->keywords[$keyword] = $typeClassName;
335340
}
336341

337-
/**
338-
* Detects whether the given type represents an array.
339-
*
340-
* @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
341-
*/
342-
private function isTypedArray(string $type) : bool
343-
{
344-
return substr($type, -2) === self::OPERATOR_ARRAY;
345-
}
346-
347342
/**
348343
* Detects whether the given type represents a PHPDoc keyword.
349344
*
@@ -372,14 +367,6 @@ private function isFqsen(string $type) : bool
372367
return strpos($type, self::OPERATOR_NAMESPACE) === 0;
373368
}
374369

375-
/**
376-
* Resolves the given typed array string (i.e. `string[]`) into an Array object with the right types set.
377-
*/
378-
private function resolveTypedArray(string $type, Context $context) : Array_
379-
{
380-
return new Array_($this->resolveSingleType(substr($type, 0, -2), $context));
381-
}
382-
383370
/**
384371
* Resolves the given keyword (such as `string`) into a Type object representing that keyword.
385372
*/

src/Types/AbstractList.php

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -58,24 +58,4 @@ public function getValueType() : Type
5858
{
5959
return $this->valueType;
6060
}
61-
62-
/**
63-
* Returns a rendered output of the Type as it would be used in a DocBlock.
64-
*/
65-
public function __toString() : string
66-
{
67-
if ($this->keyType) {
68-
return 'array<' . $this->keyType . ',' . $this->valueType . '>';
69-
}
70-
71-
if ($this->valueType instanceof Mixed_) {
72-
return 'array';
73-
}
74-
75-
if ($this->valueType instanceof Compound) {
76-
return '(' . $this->valueType . ')[]';
77-
}
78-
79-
return $this->valueType . '[]';
80-
}
8161
}

src/Types/Array_.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,23 @@
2424
*/
2525
final class Array_ extends AbstractList
2626
{
27+
/**
28+
* Returns a rendered output of the Type as it would be used in a DocBlock.
29+
*/
30+
public function __toString() : string
31+
{
32+
if ($this->keyType) {
33+
return 'array<' . $this->keyType . ',' . $this->valueType . '>';
34+
}
35+
36+
if ($this->valueType instanceof Mixed_) {
37+
return 'array';
38+
}
39+
40+
if ($this->valueType instanceof Compound) {
41+
return '(' . $this->valueType . ')[]';
42+
}
43+
44+
return $this->valueType . '[]';
45+
}
2746
}

src/Types/Compound.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,24 @@ final class Compound implements Type, IteratorAggregate
3333
/** @var array<int, Type> */
3434
private $types = [];
3535

36+
/** @var string */
37+
private $token;
38+
3639
/**
3740
* Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface.
3841
*
3942
* @param Type[] $types
43+
* @param string $token
4044
*
4145
* @phpstan-param list<Type> $types
4246
*/
43-
public function __construct(array $types)
47+
public function __construct(array $types, string $token = '|')
4448
{
4549
foreach ($types as $type) {
4650
$this->add($type);
4751
}
52+
53+
$this->token = $token;
4854
}
4955

5056
/**
@@ -87,7 +93,7 @@ public function contains(Type $type) : bool
8793
*/
8894
public function __toString() : string
8995
{
90-
return implode('|', $this->types);
96+
return implode($this->token, $this->types);
9197
}
9298

9399
/**

src/Types/Expression_.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link http://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Reflection\Types;
15+
16+
/**
17+
* Represents an array type as described in the PSR-5, the PHPDoc Standard.
18+
*
19+
*/
20+
final class Expression_ extends AbstractList
21+
{
22+
/**
23+
* Returns a rendered output of the Type as it would be used in a DocBlock.
24+
*/
25+
public function __toString() : string
26+
{
27+
return '(' . $this->valueType . ')';
28+
}
29+
}

tests/unit/TypeResolverTest.php

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515

1616
use Mockery as m;
1717
use phpDocumentor\Reflection\Types\Array_;
18+
use phpDocumentor\Reflection\Types\Expression_;
1819
use phpDocumentor\Reflection\Types\Boolean;
1920
use phpDocumentor\Reflection\Types\ClassString;
2021
use phpDocumentor\Reflection\Types\Compound;
2122
use phpDocumentor\Reflection\Types\Context;
2223
use phpDocumentor\Reflection\Types\Iterable_;
24+
use phpDocumentor\Reflection\Types\Null_;
2325
use phpDocumentor\Reflection\Types\Nullable;
2426
use phpDocumentor\Reflection\Types\Object_;
2527
use phpDocumentor\Reflection\Types\String_;
@@ -281,7 +283,47 @@ public function testResolvingAmpersandCompoundTypes() : void
281283
$this->assertInstanceOf(Object_::class, $secondType);
282284
$this->assertInstanceOf(Fqsen::class, $secondType->getFqsen());
283285
}
284-
286+
287+
/**
288+
* @uses \phpDocumentor\Reflection\Types\Context
289+
* @uses \phpDocumentor\Reflection\Types\Compound
290+
* @uses \phpDocumentor\Reflection\Types\String_
291+
* @uses \phpDocumentor\Reflection\Types\Object_
292+
* @uses \phpDocumentor\Reflection\Fqsen
293+
* @uses \phpDocumentor\Reflection\FqsenResolver
294+
*
295+
* @covers ::__construct
296+
* @covers ::resolve
297+
* @covers ::<private>
298+
*/
299+
public function testResolvingMixedCompoundTypes() : void
300+
{
301+
$fixture = new TypeResolver();
302+
303+
$resolvedType = $fixture->resolve('(Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject)|null', new Context('phpDocumentor'));
304+
305+
$this->assertInstanceOf(Compound::class, $resolvedType);
306+
$this->assertSame('(\phpDocumentor\Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject)|null', (string) $resolvedType);
307+
308+
$firstType = $resolvedType->get(0);
309+
310+
$secondType = $resolvedType->get(1);
311+
312+
$this->assertInstanceOf(Expression_::class, $firstType);
313+
$this->assertSame('(\phpDocumentor\Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject)', (string) $firstType);
314+
$this->assertInstanceOf(Null_::class, $secondType);
315+
316+
$resolvedType = $firstType->getValueType();
317+
318+
$firstSubType = $resolvedType->get(0);
319+
$secondSubType = $resolvedType->get(1);
320+
321+
$this->assertInstanceOf(Object_::class, $firstSubType);
322+
$this->assertInstanceOf(Fqsen::class, $secondSubType->getFqsen());
323+
$this->assertInstanceOf(Object_::class, $secondSubType);
324+
$this->assertInstanceOf(Fqsen::class, $secondSubType->getFqsen());
325+
}
326+
285327
/**
286328
* @uses \phpDocumentor\Reflection\Types\Context
287329
* @uses \phpDocumentor\Reflection\Types\Compound

tests/unit/Types/ContextFactoryTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ public function tearDown() : void
206206

207207
class Foo extends AbstractList
208208
{
209+
public function __toString(): string
210+
{
211+
}
209212
// dummy class
210213
}
211214
}

0 commit comments

Comments
 (0)