Skip to content

Commit 178d579

Browse files
committed
TASK: Add constraints for union type references
1 parent bb05fd6 commit 178d579

7 files changed

Lines changed: 255 additions & 2 deletions

File tree

src/Language/AST/ASTException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ abstract class ASTException extends \Exception
2929
protected function __construct(
3030
int $code,
3131
string $message,
32-
public readonly NodeAttributes $attributesOfAffectedNode
32+
public readonly ?NodeAttributes $attributesOfAffectedNode = null
3333
) {
3434
parent::__construct($message, $code);
3535
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/**
4+
* PackageFactory.ComponentEngine - Universal View Components for PHP
5+
* Copyright (C) 2023 Contributors of PackageFactory.ComponentEngine
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
namespace PackageFactory\ComponentEngine\Language\AST\Node\TypeReference;
24+
25+
use PackageFactory\ComponentEngine\Language\AST\ASTException;
26+
27+
final class InvalidTypeNameNodes extends ASTException
28+
{
29+
public static function becauseTheyWereEmpty(): self
30+
{
31+
return new self(
32+
code: 1690549442,
33+
message: 'A type reference must refer to at least one type name.'
34+
);
35+
}
36+
37+
public static function becauseTheyContainDuplicates(
38+
TypeNameNode $duplicateTypeNameNode
39+
): self {
40+
return new self(
41+
code: 1690551330,
42+
message: 'A type reference must not contain duplicates.',
43+
attributesOfAffectedNode: $duplicateTypeNameNode->attributes
44+
);
45+
}
46+
}

src/Language/AST/Node/TypeReference/InvalidTypeReferenceNode.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,32 @@ public static function becauseItWasOptionalAndArrayAtTheSameTime(
4141
attributesOfAffectedNode: $attributesOfAffectedNode
4242
);
4343
}
44+
45+
public static function becauseItWasUnionAndArrayAtTheSameTime(
46+
TypeNames $affectedTypeNames,
47+
NodeAttributes $attributesOfAffectedNode
48+
): self {
49+
return new self(
50+
code: 1690552344,
51+
message: sprintf(
52+
'The type reference to "%s" must not be union and array at the same time.',
53+
$affectedTypeNames->toDebugString()
54+
),
55+
attributesOfAffectedNode: $attributesOfAffectedNode
56+
);
57+
}
58+
59+
public static function becauseItWasUnionAndOptionalAtTheSameTime(
60+
TypeNames $affectedTypeNames,
61+
NodeAttributes $attributesOfAffectedNode
62+
): self {
63+
return new self(
64+
code: 1690552586,
65+
message: sprintf(
66+
'The type reference to "%s" must not be union and optional at the same time.',
67+
$affectedTypeNames->toDebugString()
68+
),
69+
attributesOfAffectedNode: $attributesOfAffectedNode
70+
);
71+
}
4472
}

src/Language/AST/Node/TypeReference/TypeNameNodes.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ final class TypeNameNodes
3535

3636
public function __construct(TypeNameNode ...$items)
3737
{
38+
if (count($items) === 0) {
39+
throw InvalidTypeNameNodes::becauseTheyWereEmpty();
40+
}
41+
42+
$typeNames = [];
43+
foreach ($items as $item) {
44+
if (isset($typeNames[$item->value->value])) {
45+
throw InvalidTypeNameNodes::becauseTheyContainDuplicates(
46+
duplicateTypeNameNode: $item
47+
);
48+
}
49+
50+
$typeNames[$item->value->value] = true;
51+
}
52+
3853
$this->items = $items;
3954
}
4055

src/Language/AST/Node/TypeReference/TypeReferenceNode.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,19 @@ public function __construct(
3939
attributesOfAffectedNode: $attributes
4040
);
4141
}
42+
43+
if ($names->getSize() > 1 && $isArray === true) {
44+
throw InvalidTypeReferenceNode::becauseItWasUnionAndArrayAtTheSameTime(
45+
affectedTypeNames: $names->toTypeNames(),
46+
attributesOfAffectedNode: $attributes
47+
);
48+
}
49+
50+
if ($names->getSize() > 1 && $isOptional === true) {
51+
throw InvalidTypeReferenceNode::becauseItWasUnionAndOptionalAtTheSameTime(
52+
affectedTypeNames: $names->toTypeNames(),
53+
attributesOfAffectedNode: $attributes
54+
);
55+
}
4256
}
4357
}

test/Unit/Language/AST/Node/TypeReference/TypeNameNodesTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,13 @@
2424

2525
use PackageFactory\ComponentEngine\Domain\TypeName\TypeName;
2626
use PackageFactory\ComponentEngine\Domain\TypeName\TypeNames;
27+
use PackageFactory\ComponentEngine\Language\AST\Node\TypeReference\InvalidTypeNameNodes;
2728
use PackageFactory\ComponentEngine\Language\AST\Node\TypeReference\TypeNameNode;
2829
use PackageFactory\ComponentEngine\Language\AST\Node\TypeReference\TypeNameNodes;
30+
use PackageFactory\ComponentEngine\Language\AST\NodeAttributes\NodeAttributes;
31+
use PackageFactory\ComponentEngine\Parser\Source\Path;
32+
use PackageFactory\ComponentEngine\Parser\Source\Position;
33+
use PackageFactory\ComponentEngine\Parser\Source\Range;
2934
use PackageFactory\ComponentEngine\Test\Unit\Language\AST\Helpers\DummyAttributes;
3035
use PHPUnit\Framework\TestCase;
3136

@@ -41,6 +46,48 @@ protected function createTypeNameNode(string $typeName): TypeNameNode
4146
);
4247
}
4348

49+
/**
50+
* @test
51+
*/
52+
public function mustNotBeEmpty(): void
53+
{
54+
$this->expectExceptionObject(
55+
InvalidTypeNameNodes::becauseTheyWereEmpty()
56+
);
57+
58+
new TypeNameNodes();
59+
}
60+
61+
/**
62+
* @test
63+
*/
64+
public function mustNotContainDuplicates(): void
65+
{
66+
$duplicate = new TypeNameNode(
67+
attributes: new NodeAttributes(
68+
pathToSource: Path::fromString(':memory:'),
69+
rangeInSource: Range::from(
70+
new Position(1, 1),
71+
new Position(1, 1)
72+
)
73+
),
74+
value: TypeName::from('Foo')
75+
);
76+
77+
$this->expectExceptionObject(
78+
InvalidTypeNameNodes::becauseTheyContainDuplicates(
79+
duplicateTypeNameNode: $duplicate
80+
)
81+
);
82+
83+
new TypeNameNodes(
84+
$this->createTypeNameNode('Foo'),
85+
$this->createTypeNameNode('Bar'),
86+
$duplicate,
87+
$this->createTypeNameNode('Baz'),
88+
);
89+
}
90+
4491
/**
4592
* @test
4693
*/

test/Unit/Language/AST/Node/TypeReference/TypeReferenceNodeTest.php

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,40 @@ public function validOptionalTypeReferenceIsValid(): void
107107
/**
108108
* @test
109109
*/
110-
public function typeReferenceCannotBeArrayAndOptionalSimultaneously(): void
110+
public function validUnionTypeReferenceIsValid(): void
111+
{
112+
$typeReferenceNode = new TypeReferenceNode(
113+
attributes: $this->dummyAttributes,
114+
names: new TypeNameNodes(
115+
new TypeNameNode(
116+
attributes: $this->dummyAttributes,
117+
value: TypeName::from('Foo')
118+
),
119+
new TypeNameNode(
120+
attributes: $this->dummyAttributes,
121+
value: TypeName::from('Bar')
122+
),
123+
new TypeNameNode(
124+
attributes: $this->dummyAttributes,
125+
value: TypeName::from('Baz')
126+
)
127+
),
128+
isArray: false,
129+
isOptional: false
130+
);
131+
132+
$this->assertEquals(3, $typeReferenceNode->names->getSize());
133+
$this->assertEquals('Foo', $typeReferenceNode->names->items[0]->value->value);
134+
$this->assertEquals('Bar', $typeReferenceNode->names->items[1]->value->value);
135+
$this->assertEquals('Baz', $typeReferenceNode->names->items[2]->value->value);
136+
$this->assertFalse($typeReferenceNode->isArray);
137+
$this->assertFalse($typeReferenceNode->isOptional);
138+
}
139+
140+
/**
141+
* @test
142+
*/
143+
public function mustNotBeArrayAndOptionalSimultaneously(): void
111144
{
112145
$name = TypeName::from('Foo');
113146

@@ -130,4 +163,74 @@ public function typeReferenceCannotBeArrayAndOptionalSimultaneously(): void
130163
isOptional: true
131164
);
132165
}
166+
167+
/**
168+
* @test
169+
*/
170+
public function mustNotBeUnionAndArraySimultaneously(): void
171+
{
172+
$typeNameNodes = new TypeNameNodes(
173+
new TypeNameNode(
174+
attributes: $this->dummyAttributes,
175+
value: TypeName::from('Foo')
176+
),
177+
new TypeNameNode(
178+
attributes: $this->dummyAttributes,
179+
value: TypeName::from('Bar')
180+
),
181+
new TypeNameNode(
182+
attributes: $this->dummyAttributes,
183+
value: TypeName::from('Baz')
184+
)
185+
);
186+
187+
$this->expectExceptionObject(
188+
InvalidTypeReferenceNode::becauseItWasUnionAndArrayAtTheSameTime(
189+
affectedTypeNames: $typeNameNodes->toTypeNames(),
190+
attributesOfAffectedNode: $this->dummyAttributes
191+
)
192+
);
193+
194+
new TypeReferenceNode(
195+
attributes: $this->dummyAttributes,
196+
names: $typeNameNodes,
197+
isArray: true,
198+
isOptional: false
199+
);
200+
}
201+
202+
/**
203+
* @test
204+
*/
205+
public function mustNotBeUnionAndOptionalSimultaneously(): void
206+
{
207+
$typeNameNodes = new TypeNameNodes(
208+
new TypeNameNode(
209+
attributes: $this->dummyAttributes,
210+
value: TypeName::from('Foo')
211+
),
212+
new TypeNameNode(
213+
attributes: $this->dummyAttributes,
214+
value: TypeName::from('Bar')
215+
),
216+
new TypeNameNode(
217+
attributes: $this->dummyAttributes,
218+
value: TypeName::from('Baz')
219+
)
220+
);
221+
222+
$this->expectExceptionObject(
223+
InvalidTypeReferenceNode::becauseItWasUnionAndOptionalAtTheSameTime(
224+
affectedTypeNames: $typeNameNodes->toTypeNames(),
225+
attributesOfAffectedNode: $this->dummyAttributes
226+
)
227+
);
228+
229+
new TypeReferenceNode(
230+
attributes: $this->dummyAttributes,
231+
names: $typeNameNodes,
232+
isArray: false,
233+
isOptional: true
234+
);
235+
}
133236
}

0 commit comments

Comments
 (0)