Skip to content

Commit a1eb148

Browse files
committed
Introduce intersection as different type
1 parent 6be94d5 commit a1eb148

File tree

6 files changed

+182
-90
lines changed

6 files changed

+182
-90
lines changed

src/TypeResolver.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
use phpDocumentor\Reflection\Types\Collection;
2121
use phpDocumentor\Reflection\Types\Compound;
2222
use phpDocumentor\Reflection\Types\Context;
23-
use phpDocumentor\Reflection\Types\Expression_;
23+
use phpDocumentor\Reflection\Types\Expression;
2424
use phpDocumentor\Reflection\Types\Integer;
25+
use phpDocumentor\Reflection\Types\Intersection;
2526
use phpDocumentor\Reflection\Types\Iterable_;
2627
use phpDocumentor\Reflection\Types\Nullable;
2728
use phpDocumentor\Reflection\Types\Object_;
@@ -220,7 +221,7 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
220221

221222
$tokens->next();
222223

223-
$resolvedType = new Expression_($type);
224+
$resolvedType = new Expression($type);
224225

225226
$types[] = $resolvedType;
226227
} elseif ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION && $token[0] === ')') {
@@ -250,7 +251,7 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
250251
end($types);
251252
$last = key($types);
252253
$lastItem = $types[$last];
253-
if ($lastItem instanceof Expression_) {
254+
if ($lastItem instanceof Expression) {
254255
$lastItem = $lastItem->getValueType();
255256
}
256257

@@ -296,7 +297,11 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
296297
return $types[0];
297298
}
298299

299-
return new Compound(array_values($types), $compoundToken);
300+
if ($compoundToken === '|') {
301+
return new Compound(array_values($types));
302+
}
303+
304+
return new Intersection(array_values($types));
300305
}
301306

302307
/**

src/Types/AggregatedType.php

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
/**
3+
* This file is part of phpDocumentor.
4+
*
5+
* For the full copyright and license information, please view the LICENSE
6+
* file that was distributed with this source code.
7+
*
8+
* @link http://phpdoc.org
9+
*/
10+
11+
declare(strict_types=1);
12+
13+
namespace phpDocumentor\Reflection\Types;
14+
15+
use ArrayIterator;
16+
use IteratorAggregate;
17+
use phpDocumentor\Reflection\Type;
18+
use function array_key_exists;
19+
use function implode;
20+
21+
/**
22+
* Base class for aggregated types like Compound and Intersection
23+
*
24+
* A Aggregated Type is not so much a special keyword or object reference but is a series of Types that are separated
25+
* using separator.
26+
*
27+
* @psalm-immutable
28+
* @template-implements IteratorAggregate<int, Type>
29+
*/
30+
abstract class AggregatedType implements Type, IteratorAggregate
31+
{
32+
/**
33+
* @psalm-allow-private-mutation
34+
* @var array<int, Type>
35+
*/
36+
private $types = [];
37+
38+
/** @var string */
39+
private $token;
40+
41+
/**
42+
* @param Type[] $types
43+
*
44+
* @phpstan-param list<Type> $types
45+
*/
46+
public function __construct(array $types, string $token)
47+
{
48+
foreach ($types as $type) {
49+
$this->add($type);
50+
}
51+
52+
$this->token = $token;
53+
}
54+
55+
/**
56+
* Returns the type at the given index.
57+
*/
58+
public function get(int $index) : ?Type
59+
{
60+
if (!$this->has($index)) {
61+
return null;
62+
}
63+
64+
return $this->types[$index];
65+
}
66+
67+
/**
68+
* Tests if this compound type has a type with the given index.
69+
*/
70+
public function has(int $index) : bool
71+
{
72+
return array_key_exists($index, $this->types);
73+
}
74+
75+
/**
76+
* Tests if this compound type contains the given type.
77+
*/
78+
public function contains(Type $type) : bool
79+
{
80+
foreach ($this->types as $typePart) {
81+
// if the type is duplicate; do not add it
82+
if ((string) $typePart === (string) $type) {
83+
return true;
84+
}
85+
}
86+
87+
return false;
88+
}
89+
90+
/**
91+
* Returns a rendered output of the Type as it would be used in a DocBlock.
92+
*/
93+
public function __toString() : string
94+
{
95+
return implode($this->token, $this->types);
96+
}
97+
98+
/**
99+
* @return ArrayIterator<int, Type>
100+
*/
101+
public function getIterator() : ArrayIterator
102+
{
103+
return new ArrayIterator($this->types);
104+
}
105+
106+
/**
107+
* @psalm-suppress ImpureMethodCall
108+
*/
109+
private function add(Type $type) : void
110+
{
111+
if ($type instanceof self) {
112+
foreach ($type->getIterator() as $subType) {
113+
$this->add($subType);
114+
}
115+
116+
return;
117+
}
118+
119+
// if the type is duplicate; do not add it
120+
if ($this->contains($type)) {
121+
return;
122+
}
123+
124+
$this->types[] = $type;
125+
}
126+
}

src/Types/Compound.php

Lines changed: 3 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@
1313

1414
namespace phpDocumentor\Reflection\Types;
1515

16-
use ArrayIterator;
17-
use IteratorAggregate;
1816
use phpDocumentor\Reflection\Type;
19-
use function array_key_exists;
20-
use function implode;
2117

2218
/**
2319
* Value Object representing a Compound Type.
@@ -27,93 +23,18 @@
2723
* may contain a value with any of the given types.
2824
*
2925
* @psalm-immutable
30-
* @template-implements IteratorAggregate<int, Type>
3126
*/
32-
final class Compound implements Type, IteratorAggregate
27+
final class Compound extends AggregatedType
3328
{
34-
/**
35-
* @psalm-allow-private-mutation
36-
* @var array<int, Type>
37-
*/
38-
private $types = [];
39-
40-
/** @var string */
41-
private $token;
42-
4329
/**
4430
* Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface.
4531
*
4632
* @param Type[] $types
4733
*
4834
* @phpstan-param list<Type> $types
4935
*/
50-
public function __construct(array $types, string $token = '|')
51-
{
52-
foreach ($types as $type) {
53-
$this->add($type);
54-
}
55-
56-
$this->token = $token;
57-
}
58-
59-
/**
60-
* Returns the type at the given index.
61-
*/
62-
public function get(int $index) : ?Type
63-
{
64-
if (!$this->has($index)) {
65-
return null;
66-
}
67-
68-
return $this->types[$index];
69-
}
70-
71-
/**
72-
* Tests if this compound type has a type with the given index.
73-
*/
74-
public function has(int $index) : bool
75-
{
76-
return array_key_exists($index, $this->types);
77-
}
78-
79-
/**
80-
* Tests if this compound type contains the given type.
81-
*/
82-
public function contains(Type $type) : bool
83-
{
84-
foreach ($this->types as $typePart) {
85-
// if the type is duplicate; do not add it
86-
if ((string) $typePart === (string) $type) {
87-
return true;
88-
}
89-
}
90-
91-
return false;
92-
}
93-
94-
/**
95-
* Returns a rendered output of the Type as it would be used in a DocBlock.
96-
*/
97-
public function __toString() : string
36+
public function __construct(array $types)
9837
{
99-
return implode($this->token, $this->types);
100-
}
101-
102-
/**
103-
* @return ArrayIterator<int, Type>
104-
*/
105-
public function getIterator() : ArrayIterator
106-
{
107-
return new ArrayIterator($this->types);
108-
}
109-
110-
private function add(Type $type) : void
111-
{
112-
// if the type is duplicate; do not add it
113-
if ($this->contains($type)) {
114-
return;
115-
}
116-
117-
$this->types[] = $type;
38+
parent::__construct($types, '|');
11839
}
11940
}

src/Types/Expression_.php renamed to src/Types/Expression.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*
2121
* @psalm-immutable
2222
*/
23-
final class Expression_ implements Type
23+
final class Expression implements Type
2424
{
2525
/** @var Type */
2626
protected $valueType;

src/Types/Intersection.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
/**
3+
* This file is part of phpDocumentor.
4+
*
5+
* For the full copyright and license information, please view the LICENSE
6+
* file that was distributed with this source code.
7+
*
8+
* @link http://phpdoc.org
9+
*/
10+
11+
declare(strict_types=1);
12+
13+
namespace phpDocumentor\Reflection\Types;
14+
15+
use phpDocumentor\Reflection\Type;
16+
17+
/**
18+
* Value Object representing a Compound Type.
19+
*
20+
* A Intersection Type is not so much a special keyword or object reference but is a series of Types that are separated
21+
* using an AND operator (`&`). This combination of types signifies that whatever is associated with this Intersection
22+
* type may contain a value with any of the given types.
23+
*
24+
* @psalm-immutable
25+
*/
26+
final class Intersection extends AggregatedType
27+
{
28+
/**
29+
* Initializes a intersection type (i.e. `\A&\B`) and tests if the provided types all implement the Type interface.
30+
*
31+
* @param Type[] $types
32+
*
33+
* @phpstan-param list<Type> $types
34+
*/
35+
public function __construct(array $types)
36+
{
37+
parent::__construct($types, '&');
38+
}
39+
}

tests/unit/TypeResolverTest.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
use phpDocumentor\Reflection\Types\ClassString;
2020
use phpDocumentor\Reflection\Types\Compound;
2121
use phpDocumentor\Reflection\Types\Context;
22-
use phpDocumentor\Reflection\Types\Expression_;
22+
use phpDocumentor\Reflection\Types\Expression;
23+
use phpDocumentor\Reflection\Types\Intersection;
2324
use phpDocumentor\Reflection\Types\Iterable_;
2425
use phpDocumentor\Reflection\Types\Null_;
2526
use phpDocumentor\Reflection\Types\Nullable;
@@ -274,7 +275,7 @@ public function testResolvingAmpersandCompoundTypes() : void
274275
new Context('phpDocumentor')
275276
);
276277

277-
$this->assertInstanceOf(Compound::class, $resolvedType);
278+
$this->assertInstanceOf(Intersection::class, $resolvedType);
278279
$this->assertSame(
279280
'\phpDocumentor\Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject',
280281
(string) $resolvedType
@@ -321,7 +322,7 @@ public function testResolvingMixedCompoundTypes() : void
321322

322323
$secondType = $resolvedType->get(1);
323324

324-
$this->assertInstanceOf(Expression_::class, $firstType);
325+
$this->assertInstanceOf(Expression::class, $firstType);
325326
$this->assertSame(
326327
'(\phpDocumentor\Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject)',
327328
(string) $firstType

0 commit comments

Comments
 (0)