Skip to content

Commit 1b0aeb3

Browse files
authored
Merge pull request #12 from PackageFactory/feature/typedEnumAccessAndStrictEnumMatches
FEATURE: typed enum access and strict enum matches
2 parents fe3403f + f374216 commit 1b0aeb3

File tree

29 files changed

+742
-188
lines changed

29 files changed

+742
-188
lines changed

src/Module/Loader/ModuleFile/ModuleFileLoader.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
namespace PackageFactory\ComponentEngine\Module\Loader\ModuleFile;
2424

2525
use PackageFactory\ComponentEngine\Module\LoaderInterface;
26+
use PackageFactory\ComponentEngine\Module\ModuleId;
2627
use PackageFactory\ComponentEngine\Parser\Ast\ComponentDeclarationNode;
2728
use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode;
2829
use PackageFactory\ComponentEngine\Parser\Ast\ImportNode;
@@ -32,7 +33,7 @@
3233
use PackageFactory\ComponentEngine\Parser\Source\Source;
3334
use PackageFactory\ComponentEngine\Parser\Tokenizer\Tokenizer;
3435
use PackageFactory\ComponentEngine\TypeSystem\Type\ComponentType\ComponentType;
35-
use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType;
36+
use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType;
3637
use PackageFactory\ComponentEngine\TypeSystem\Type\StructType\StructType;
3738
use PackageFactory\ComponentEngine\TypeSystem\TypeInterface;
3839

@@ -54,9 +55,14 @@ public function resolveTypeOfImport(ImportNode $importNode): TypeInterface
5455
);
5556
}
5657

58+
$moduleId = ModuleId::fromSource($source);
59+
5760
return match ($export->declaration::class) {
5861
ComponentDeclarationNode::class => ComponentType::fromComponentDeclarationNode($export->declaration),
59-
EnumDeclarationNode::class => EnumType::fromEnumDeclarationNode($export->declaration),
62+
EnumDeclarationNode::class => EnumStaticType::fromModuleIdAndDeclaration(
63+
$moduleId,
64+
$export->declaration,
65+
),
6066
StructDeclarationNode::class => StructType::fromStructDeclarationNode($export->declaration)
6167
};
6268
}

src/Target/Php/Transpiler/ComponentDeclaration/ComponentDeclarationTranspiler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public function transpile(ComponentDeclarationNode $componentDeclarationNode): s
6060
}
6161

6262
foreach ($this->module->imports->items as $importNode) {
63-
// @TODO: Generate Namespaces Dynamically
63+
// @TODO: Generate Namespaces + Name via TypeReferenceStrategyInterface Dynamically
6464
$lines[] = 'use Vendor\\Project\\Component\\' . $importNode->name->value . ';';
6565
}
6666

src/Target/Php/Transpiler/Identifier/IdentifierTranspiler.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public function transpile(IdentifierNode $identifierNode): string
3737
$typeOfIdentifiedValue = $this->scope->lookupTypeFor($identifierNode->value);
3838

3939
return match (true) {
40+
// @TODO: Generate Name via TypeReferenceStrategyInterface Dynamically
4041
$typeOfIdentifiedValue instanceof EnumStaticType => $identifierNode->value,
4142
default => '$this->' . $identifierNode->value
4243
};

src/Target/Php/Transpiler/TypeReference/TypeReferenceStrategyInterface.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
use PackageFactory\ComponentEngine\Parser\Ast\TypeReferenceNode;
2626
use PackageFactory\ComponentEngine\TypeSystem\Type\ComponentType\ComponentType;
27-
use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType;
27+
use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType;
2828
use PackageFactory\ComponentEngine\TypeSystem\Type\SlotType\SlotType;
2929
use PackageFactory\ComponentEngine\TypeSystem\Type\StructType\StructType;
3030
use PackageFactory\ComponentEngine\TypeSystem\TypeInterface;
@@ -33,7 +33,7 @@ interface TypeReferenceStrategyInterface
3333
{
3434
public function getPhpTypeReferenceForSlotType(SlotType $slotType, TypeReferenceNode $typeReferenceNode): string;
3535
public function getPhpTypeReferenceForComponentType(ComponentType $componentType, TypeReferenceNode $typeReferenceNode): string;
36-
public function getPhpTypeReferenceForEnumType(EnumType $enumType, TypeReferenceNode $typeReferenceNode): string;
36+
public function getPhpTypeReferenceForEnumType(EnumStaticType $enumType, TypeReferenceNode $typeReferenceNode): string;
3737
public function getPhpTypeReferenceForStructType(StructType $structType, TypeReferenceNode $typeReferenceNode): string;
3838
public function getPhpTypeReferenceForCustomType(TypeInterface $customType, TypeReferenceNode $typeReferenceNode): string;
3939
}

src/Target/Php/Transpiler/TypeReference/TypeReferenceTranspiler.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface;
2727
use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType;
2828
use PackageFactory\ComponentEngine\TypeSystem\Type\ComponentType\ComponentType;
29-
use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType;
29+
use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType;
3030
use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType;
3131
use PackageFactory\ComponentEngine\TypeSystem\Type\SlotType\SlotType;
3232
use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType;
@@ -49,7 +49,7 @@ public function transpile(TypeReferenceNode $typeReferenceNode): string
4949
BooleanType::class => 'bool',
5050
SlotType::class => $this->strategy->getPhpTypeReferenceForSlotType($type, $typeReferenceNode),
5151
ComponentType::class => $this->strategy->getPhpTypeReferenceForComponentType($type, $typeReferenceNode),
52-
EnumType::class => $this->strategy->getPhpTypeReferenceForEnumType($type, $typeReferenceNode),
52+
EnumStaticType::class => $this->strategy->getPhpTypeReferenceForEnumType($type, $typeReferenceNode),
5353
StructType::class => $this->strategy->getPhpTypeReferenceForStructType($type, $typeReferenceNode),
5454
default => $this->strategy->getPhpTypeReferenceForCustomType($type, $typeReferenceNode)
5555
};
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
/**
4+
* PackageFactory.ComponentEngine - Universal View Components for PHP
5+
* Copyright (C) 2022 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\TypeSystem\Resolver\Access;
24+
25+
26+
use PackageFactory\ComponentEngine\Parser\Ast\AccessNode;
27+
use PackageFactory\ComponentEngine\TypeSystem\Resolver\Expression\ExpressionTypeResolver;
28+
use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface;
29+
use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType;
30+
use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType;
31+
use PackageFactory\ComponentEngine\TypeSystem\Type\StructType\StructType;
32+
use PackageFactory\ComponentEngine\TypeSystem\TypeInterface;
33+
use PackageFactory\ComponentEngine\Definition\AccessType;
34+
35+
final class AccessTypeResolver
36+
{
37+
public function __construct(
38+
private readonly ScopeInterface $scope
39+
) {
40+
}
41+
42+
public function resolveTypeOf(AccessNode $accessNode): TypeInterface
43+
{
44+
$expressionResolver = new ExpressionTypeResolver(scope: $this->scope);
45+
$rootType = $expressionResolver->resolveTypeOf($accessNode->root);
46+
47+
return match ($rootType::class) {
48+
EnumStaticType::class => $this->createEnumInstanceMemberType($accessNode, $rootType),
49+
StructType::class => throw new \Exception('@TODO: StructType Access is not implemented'),
50+
default => throw new \Exception('@TODO Error: Cannot access on type ' . $rootType::class)
51+
};
52+
}
53+
54+
private function createEnumInstanceMemberType(AccessNode $accessNode, EnumStaticType $enumType): EnumInstanceType
55+
{
56+
if (!(
57+
count($accessNode->chain->items) === 1
58+
&& $accessNode->chain->items[0]->accessType === AccessType::MANDATORY
59+
)) {
60+
throw new \Error('@TODO Error: Enum access malformed, only one level member access is allowed.');
61+
}
62+
63+
$enumMemberName = $accessNode->chain->items[0]->accessor->value;
64+
65+
return $enumType->getMemberType($enumMemberName);
66+
}
67+
}

src/TypeSystem/Resolver/Expression/ExpressionTypeResolver.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
namespace PackageFactory\ComponentEngine\TypeSystem\Resolver\Expression;
2424

25+
use PackageFactory\ComponentEngine\Parser\Ast\AccessNode;
2526
use PackageFactory\ComponentEngine\Parser\Ast\BinaryOperationNode;
2627
use PackageFactory\ComponentEngine\Parser\Ast\BooleanLiteralNode;
2728
use PackageFactory\ComponentEngine\Parser\Ast\ExpressionNode;
@@ -33,6 +34,7 @@
3334
use PackageFactory\ComponentEngine\Parser\Ast\TagNode;
3435
use PackageFactory\ComponentEngine\Parser\Ast\TemplateLiteralNode;
3536
use PackageFactory\ComponentEngine\Parser\Ast\TernaryOperationNode;
37+
use PackageFactory\ComponentEngine\TypeSystem\Resolver\Access\AccessTypeResolver;
3638
use PackageFactory\ComponentEngine\TypeSystem\Resolver\BinaryOperation\BinaryOperationTypeResolver;
3739
use PackageFactory\ComponentEngine\TypeSystem\Resolver\BooleanLiteral\BooleanLiteralTypeResolver;
3840
use PackageFactory\ComponentEngine\TypeSystem\Resolver\Identifier\IdentifierTypeResolver;
@@ -81,6 +83,9 @@ public function resolveTypeOf(ExpressionNode $expressionNode): TypeInterface
8183
TernaryOperationNode::class => (new TernaryOperationTypeResolver(
8284
scope: $this->scope
8385
))->resolveTypeOf($rootNode),
86+
AccessNode::class => (new AccessTypeResolver(
87+
scope: $this->scope
88+
))->resolveTypeOf($rootNode),
8489
default => throw new \Exception('@TODO: Resolve type of ' . $expressionNode->root::class)
8590
};
8691
}

src/TypeSystem/Resolver/Match/MatchTypeResolver.php

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
use PackageFactory\ComponentEngine\TypeSystem\Resolver\Expression\ExpressionTypeResolver;
2828
use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface;
2929
use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType;
30-
use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType;
30+
use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType;
3131
use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType;
3232
use PackageFactory\ComponentEngine\TypeSystem\TypeInterface;
3333

@@ -67,7 +67,14 @@ private function resolveTypeOfBooleanMatch(MatchNode $matchNode): TypeInterface
6767
} else {
6868
$types = [];
6969

70+
$defaultArmPresent = false;
7071
foreach ($matchNode->arms->items as $matchArmNode) {
72+
if ($defaultArmPresent) {
73+
throw new \Exception('@TODO: Multiple illegal default arms');
74+
}
75+
if ($matchArmNode->left === null) {
76+
$defaultArmPresent = true;
77+
}
7178
$types[] = $expressionTypeResolver->resolveTypeOf(
7279
$matchArmNode->right
7380
);
@@ -79,20 +86,57 @@ private function resolveTypeOfBooleanMatch(MatchNode $matchNode): TypeInterface
7986
}
8087
}
8188

82-
private function resolveTypeOfEnumMatch(MatchNode $matchNode): TypeInterface
89+
private function resolveTypeOfEnumMatch(MatchNode $matchNode, EnumInstanceType $subjectEnumType): TypeInterface
8390
{
8491
$expressionTypeResolver = new ExpressionTypeResolver(
8592
scope: $this->scope
8693
);
8794
$types = [];
8895

96+
$defaultArmPresent = false;
97+
$referencedEnumMembers = [];
98+
8999
foreach ($matchNode->arms->items as $matchArmNode) {
100+
if ($defaultArmPresent) {
101+
throw new \Exception('@TODO Error: Multiple illegal default arms');
102+
}
103+
if ($matchArmNode->left === null) {
104+
$defaultArmPresent = true;
105+
} else {
106+
foreach ($matchArmNode->left->items as $expressionNode) {
107+
$enumMemberType = $expressionTypeResolver->resolveTypeOf($expressionNode);
108+
if (!$enumMemberType instanceof EnumInstanceType) {
109+
throw new \Error('@TODO Error: Cannot match enum with type of ' . $enumMemberType::class);
110+
}
111+
112+
if ($enumMemberType->isUnspecified()) {
113+
throw new \Error('@TODO Error: Matching enum value should be referenced statically');
114+
}
115+
116+
if (!$enumMemberType->enumStaticType->is($subjectEnumType->enumStaticType)) {
117+
throw new \Error('@TODO Error: incompatible enum match: got ' . $enumMemberType->enumStaticType->enumName . ' expected ' . $subjectEnumType->enumStaticType->enumName);
118+
}
119+
120+
if (isset($referencedEnumMembers[$enumMemberType->getMemberName()])) {
121+
throw new \Error('@TODO Error: Enum path ' . $enumMemberType->getMemberName() . ' was already defined once in this match and cannot be used twice');
122+
}
123+
124+
$referencedEnumMembers[$enumMemberType->getMemberName()] = true;
125+
}
126+
}
127+
90128
$types[] = $expressionTypeResolver->resolveTypeOf(
91129
$matchArmNode->right
92130
);
93131
}
94132

95-
// @TODO: Ensure that match is complete
133+
if (!$defaultArmPresent) {
134+
foreach ($subjectEnumType->enumStaticType->getMemberNames() as $member) {
135+
if (!isset($referencedEnumMembers[$member])) {
136+
throw new \Error('@TODO Error: member ' . $member . ' not checked');
137+
}
138+
}
139+
}
96140

97141
return UnionType::of(...$types);
98142
}
@@ -108,7 +152,7 @@ public function resolveTypeOf(MatchNode $matchNode): TypeInterface
108152

109153
return match (true) {
110154
BooleanType::get()->is($typeOfSubject) => $this->resolveTypeOfBooleanMatch($matchNode),
111-
$typeOfSubject instanceof EnumType => $this->resolveTypeOfEnumMatch($matchNode),
155+
$typeOfSubject instanceof EnumInstanceType => $this->resolveTypeOfEnumMatch($matchNode, $typeOfSubject),
112156
default => throw new \Exception('@TODO: Not handled ' . $typeOfSubject::class)
113157
};
114158
}

src/TypeSystem/Scope/ComponentScope/ComponentScope.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@
2525
use PackageFactory\ComponentEngine\Parser\Ast\ComponentDeclarationNode;
2626
use PackageFactory\ComponentEngine\Parser\Ast\TypeReferenceNode;
2727
use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface;
28-
use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType;
29-
use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType;
30-
use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType;
28+
use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType;
3129
use PackageFactory\ComponentEngine\TypeSystem\TypeInterface;
3230

3331
final class ComponentScope implements ScopeInterface
@@ -43,7 +41,11 @@ public function lookupTypeFor(string $name): ?TypeInterface
4341
$propertyDeclarationNode = $this->componentDeclarationNode->propertyDeclarations->getPropertyDeclarationNodeOfName($name);
4442
if ($propertyDeclarationNode) {
4543
$typeReferenceNode = $propertyDeclarationNode->type;
46-
return $this->resolveTypeReference($typeReferenceNode);
44+
$type = $this->resolveTypeReference($typeReferenceNode);
45+
if ($type instanceof EnumStaticType) {
46+
$type = $type->toEnumInstanceType();
47+
}
48+
return $type;
4749
}
4850

4951
return $this->parentScope->lookupTypeFor($name);

src/TypeSystem/Scope/ModuleScope/ModuleScope.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public function __construct(
3939

4040
public function lookupTypeFor(string $name): ?TypeInterface
4141
{
42+
if ($importNode = $this->moduleNode->imports->get($name)) {
43+
return $this->loader->resolveTypeOfImport($importNode);
44+
}
4245
return $this->parentScope->lookupTypeFor($name);
4346
}
4447

0 commit comments

Comments
 (0)