Skip to content

Commit c92bb85

Browse files
committed
Add support for object and list shape types
1 parent 153ae66 commit c92bb85

12 files changed

+265
-52
lines changed

src/PseudoTypes/ArrayShapeItem.php

+1-48
Original file line numberDiff line numberDiff line change
@@ -13,53 +13,6 @@
1313

1414
namespace phpDocumentor\Reflection\PseudoTypes;
1515

16-
use phpDocumentor\Reflection\Type;
17-
use phpDocumentor\Reflection\Types\Mixed_;
18-
19-
use function sprintf;
20-
21-
final class ArrayShapeItem
16+
class ArrayShapeItem extends ShapeItem
2217
{
23-
/** @var string|null */
24-
private $key;
25-
/** @var Type */
26-
private $value;
27-
/** @var bool */
28-
private $optional;
29-
30-
public function __construct(?string $key, ?Type $value, bool $optional)
31-
{
32-
$this->key = $key;
33-
$this->value = $value ?? new Mixed_();
34-
$this->optional = $optional;
35-
}
36-
37-
public function getKey(): ?string
38-
{
39-
return $this->key;
40-
}
41-
42-
public function getValue(): Type
43-
{
44-
return $this->value;
45-
}
46-
47-
public function isOptional(): bool
48-
{
49-
return $this->optional;
50-
}
51-
52-
public function __toString(): string
53-
{
54-
if ($this->key !== null) {
55-
return sprintf(
56-
'%s%s: %s',
57-
$this->key,
58-
$this->optional ? '?' : '',
59-
(string) $this->value
60-
);
61-
}
62-
63-
return (string) $this->value;
64-
}
6518
}

src/PseudoTypes/ListShape.php

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Reflection\PseudoTypes;
6+
7+
use function implode;
8+
9+
/** @psalm-immutable */
10+
final class ListShape extends ArrayShape
11+
{
12+
public function __toString(): string
13+
{
14+
return 'list{' . implode(', ', $this->getItems()) . '}';
15+
}
16+
}

src/PseudoTypes/ListShapeItem.php

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Reflection\PseudoTypes;
6+
7+
final class ListShapeItem extends ArrayShapeItem
8+
{
9+
}

src/PseudoTypes/ObjectShape.php

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Reflection\PseudoTypes;
6+
7+
use phpDocumentor\Reflection\PseudoType;
8+
use phpDocumentor\Reflection\Type;
9+
use phpDocumentor\Reflection\Types\Object_;
10+
11+
use function implode;
12+
13+
/** @psalm-immutable */
14+
final class ObjectShape implements PseudoType
15+
{
16+
/** @var ObjectShapeItem[] */
17+
private $items;
18+
19+
public function __construct(ObjectShapeItem ...$items)
20+
{
21+
$this->items = $items;
22+
}
23+
24+
/**
25+
* @return ObjectShapeItem[]
26+
*/
27+
public function getItems(): array
28+
{
29+
return $this->items;
30+
}
31+
32+
public function underlyingType(): Type
33+
{
34+
return new Object_();
35+
}
36+
37+
public function __toString(): string
38+
{
39+
return 'object{' . implode(', ', $this->items) . '}';
40+
}
41+
}

src/PseudoTypes/ObjectShapeItem.php

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Reflection\PseudoTypes;
6+
7+
final class ObjectShapeItem extends ShapeItem
8+
{
9+
}

src/PseudoTypes/ShapeItem.php

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Reflection\PseudoTypes;
6+
7+
use phpDocumentor\Reflection\Type;
8+
use phpDocumentor\Reflection\Types\Mixed_;
9+
10+
use function sprintf;
11+
12+
abstract class ShapeItem
13+
{
14+
/** @var string|null */
15+
private $key;
16+
/** @var Type */
17+
private $value;
18+
/** @var bool */
19+
private $optional;
20+
21+
public function __construct(?string $key, ?Type $value, bool $optional)
22+
{
23+
$this->key = $key;
24+
$this->value = $value ?? new Mixed_();
25+
$this->optional = $optional;
26+
}
27+
28+
public function getKey(): ?string
29+
{
30+
return $this->key;
31+
}
32+
33+
public function getValue(): Type
34+
{
35+
return $this->value;
36+
}
37+
38+
public function isOptional(): bool
39+
{
40+
return $this->optional;
41+
}
42+
43+
public function __toString(): string
44+
{
45+
if ($this->key !== null) {
46+
return sprintf(
47+
'%s%s: %s',
48+
$this->key,
49+
$this->optional ? '?' : '',
50+
(string) $this->value
51+
);
52+
}
53+
54+
return (string) $this->value;
55+
}
56+
}

src/TypeResolver.php

+42-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
use phpDocumentor\Reflection\PseudoTypes\IntegerRange;
2626
use phpDocumentor\Reflection\PseudoTypes\IntegerValue;
2727
use phpDocumentor\Reflection\PseudoTypes\List_;
28+
use phpDocumentor\Reflection\PseudoTypes\ListShape;
29+
use phpDocumentor\Reflection\PseudoTypes\ListShapeItem;
2830
use phpDocumentor\Reflection\PseudoTypes\LiteralString;
2931
use phpDocumentor\Reflection\PseudoTypes\LowercaseString;
3032
use phpDocumentor\Reflection\PseudoTypes\NegativeInteger;
@@ -33,6 +35,8 @@
3335
use phpDocumentor\Reflection\PseudoTypes\NonEmptyString;
3436
use phpDocumentor\Reflection\PseudoTypes\Numeric_;
3537
use phpDocumentor\Reflection\PseudoTypes\NumericString;
38+
use phpDocumentor\Reflection\PseudoTypes\ObjectShape;
39+
use phpDocumentor\Reflection\PseudoTypes\ObjectShapeItem;
3640
use phpDocumentor\Reflection\PseudoTypes\PositiveInteger;
3741
use phpDocumentor\Reflection\PseudoTypes\StringValue;
3842
use phpDocumentor\Reflection\PseudoTypes\TraitString;
@@ -82,6 +86,8 @@
8286
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
8387
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
8488
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
89+
use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode;
90+
use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode;
8591
use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
8692
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
8793
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
@@ -234,10 +240,43 @@ public function createType(?TypeNode $type, Context $context): Type
234240
);
235241

236242
case ArrayShapeNode::class:
237-
return new ArrayShape(
243+
switch ($type->kind) {
244+
case ArrayShapeNode::KIND_ARRAY:
245+
return new ArrayShape(
246+
...array_map(
247+
function (ArrayShapeItemNode $item) use ($context): ArrayShapeItem {
248+
return new ArrayShapeItem(
249+
(string) $item->keyName,
250+
$this->createType($item->valueType, $context),
251+
$item->optional
252+
);
253+
},
254+
$type->items
255+
)
256+
);
257+
258+
case ArrayShapeNode::KIND_LIST:
259+
return new ListShape(
260+
...array_map(
261+
function (ArrayShapeItemNode $item) use ($context): ListShapeItem {
262+
return new ListShapeItem(
263+
null,
264+
$this->createType($item->valueType, $context),
265+
$item->optional
266+
);
267+
},
268+
$type->items
269+
)
270+
);
271+
272+
default:
273+
throw new RuntimeException('Unsupported array shape kind');
274+
}
275+
case ObjectShapeNode::class:
276+
return new ObjectShape(
238277
...array_map(
239-
function (ArrayShapeItemNode $item) use ($context): ArrayShapeItem {
240-
return new ArrayShapeItem(
278+
function (ObjectShapeItemNode $item) use ($context): ObjectShapeItem {
279+
return new ObjectShapeItem(
241280
(string) $item->keyName,
242281
$this->createType($item->valueType, $context),
243282
$item->optional

tests/unit/CollectionResolverTest.php

+15
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class CollectionResolverTest extends TestCase
4040
* @uses \phpDocumentor\Reflection\Types\String_
4141
*
4242
* @covers ::resolve
43+
* @covers ::createType
4344
* @covers ::__construct
4445
*/
4546
public function testResolvingCollection(): void
@@ -69,6 +70,7 @@ public function testResolvingCollection(): void
6970
*
7071
* @covers ::__construct
7172
* @covers ::resolve
73+
* @covers ::createType
7274
*/
7375
public function testResolvingCollectionWithKeyType(): void
7476
{
@@ -99,6 +101,7 @@ public function testResolvingCollectionWithKeyType(): void
99101
*
100102
* @covers ::__construct
101103
* @covers ::resolve
104+
* @covers ::createType
102105
*/
103106
public function testResolvingArrayCollection(): void
104107
{
@@ -125,6 +128,7 @@ public function testResolvingArrayCollection(): void
125128
*
126129
* @covers ::__construct
127130
* @covers ::resolve
131+
* @covers ::createType
128132
*/
129133
public function testResolvingArrayCollectionWithKey(): void
130134
{
@@ -151,6 +155,7 @@ public function testResolvingArrayCollectionWithKey(): void
151155
152156
* @covers ::__construct
153157
* @covers ::resolve
158+
* @covers ::createType
154159
*/
155160
public function testResolvingArrayCollectionWithKeyAndWhitespace(): void
156161
{
@@ -177,6 +182,7 @@ public function testResolvingArrayCollectionWithKeyAndWhitespace(): void
177182
*
178183
* @covers ::__construct
179184
* @covers ::resolve
185+
* @covers ::createType
180186
*/
181187
public function testResolvingCollectionOfCollection(): void
182188
{
@@ -208,6 +214,7 @@ public function testResolvingCollectionOfCollection(): void
208214
/**
209215
* @covers ::__construct
210216
* @covers ::resolve
217+
* @covers ::createType
211218
*/
212219
public function testBadArrayCollectionKey(): void
213220
{
@@ -220,6 +227,7 @@ public function testBadArrayCollectionKey(): void
220227
/**
221228
* @covers ::__construct
222229
* @covers ::resolve
230+
* @covers ::createType
223231
*/
224232
public function testGoodArrayCollectionKey(): void
225233
{
@@ -239,6 +247,7 @@ public function testGoodArrayCollectionKey(): void
239247
/**
240248
* @covers ::__construct
241249
* @covers ::resolve
250+
* @covers ::createType
242251
*/
243252
public function testMissingStartCollection(): void
244253
{
@@ -251,6 +260,7 @@ public function testMissingStartCollection(): void
251260
/**
252261
* @covers ::__construct
253262
* @covers ::resolve
263+
* @covers ::createType
254264
*/
255265
public function testMissingEndCollection(): void
256266
{
@@ -263,6 +273,7 @@ public function testMissingEndCollection(): void
263273
/**
264274
* @covers ::__construct
265275
* @covers ::resolve
276+
* @covers ::createType
266277
*/
267278
public function testBadCollectionClass(): void
268279
{
@@ -280,6 +291,7 @@ public function testBadCollectionClass(): void
280291
*
281292
* @covers ::__construct
282293
* @covers ::resolve
294+
* @covers ::createType
283295
*/
284296
public function testResolvingCollectionAsArray(): void
285297
{
@@ -304,6 +316,7 @@ public function testResolvingCollectionAsArray(): void
304316
*
305317
* @covers ::__construct
306318
* @covers ::resolve
319+
* @covers ::createType
307320
*/
308321
public function testResolvingList(): void
309322
{
@@ -328,6 +341,7 @@ public function testResolvingList(): void
328341
*
329342
* @covers ::__construct
330343
* @covers ::resolve
344+
* @covers ::createType
331345
*/
332346
public function testResolvingNonEmptyList(): void
333347
{
@@ -352,6 +366,7 @@ public function testResolvingNonEmptyList(): void
352366
*
353367
* @covers ::__construct
354368
* @covers ::resolve
369+
* @covers ::createType
355370
*/
356371
public function testResolvingNullableArray(): void
357372
{

0 commit comments

Comments
 (0)