From 5fc4bf1468851a5d387a6e21b890496e353a6fec Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 8 Apr 2023 14:49:28 +0200 Subject: [PATCH 01/16] TASK: WIP AccessTypeResolver --- .../Resolver/Access/AccessTypeResolver.php | 52 ++++++++++++++++ .../Expression/ExpressionTypeResolver.php | 5 ++ .../Access/AccessTypeResolverTest.php | 61 +++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 src/TypeSystem/Resolver/Access/AccessTypeResolver.php create mode 100644 test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php diff --git a/src/TypeSystem/Resolver/Access/AccessTypeResolver.php b/src/TypeSystem/Resolver/Access/AccessTypeResolver.php new file mode 100644 index 00000000..8eabd4de --- /dev/null +++ b/src/TypeSystem/Resolver/Access/AccessTypeResolver.php @@ -0,0 +1,52 @@ +. + */ + +declare(strict_types=1); + +namespace PackageFactory\ComponentEngine\TypeSystem\Resolver\Access; + + +use PackageFactory\ComponentEngine\Parser\Ast\AccessNode; +use PackageFactory\ComponentEngine\TypeSystem\Resolver\Expression\ExpressionTypeResolver; +use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; +use PackageFactory\ComponentEngine\TypeSystem\Type\StructType\StructType; +use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; + +final class AccessTypeResolver +{ + public function __construct( + private readonly ScopeInterface $scope + ) { + } + + public function resolveTypeOf(AccessNode $accessNode): TypeInterface + { + $expressionResolver = new ExpressionTypeResolver(scope: $this->scope); + $rootType = $expressionResolver->resolveTypeOf($accessNode->root); + + if (!$rootType instanceof EnumType || !$rootType instanceof EnumStaticType || !$rootType instanceof StructType) { + throw new \Exception('@TODO: Cannot access on type ' . $rootType::class); + } + + throw new \Exception('@TODO: Enum and StructType Access is not implemented'); + } +} diff --git a/src/TypeSystem/Resolver/Expression/ExpressionTypeResolver.php b/src/TypeSystem/Resolver/Expression/ExpressionTypeResolver.php index c823123c..fcc3b7ec 100644 --- a/src/TypeSystem/Resolver/Expression/ExpressionTypeResolver.php +++ b/src/TypeSystem/Resolver/Expression/ExpressionTypeResolver.php @@ -22,6 +22,7 @@ namespace PackageFactory\ComponentEngine\TypeSystem\Resolver\Expression; +use PackageFactory\ComponentEngine\Parser\Ast\AccessNode; use PackageFactory\ComponentEngine\Parser\Ast\BinaryOperationNode; use PackageFactory\ComponentEngine\Parser\Ast\BooleanLiteralNode; use PackageFactory\ComponentEngine\Parser\Ast\ExpressionNode; @@ -33,6 +34,7 @@ use PackageFactory\ComponentEngine\Parser\Ast\TagNode; use PackageFactory\ComponentEngine\Parser\Ast\TemplateLiteralNode; use PackageFactory\ComponentEngine\Parser\Ast\TernaryOperationNode; +use PackageFactory\ComponentEngine\TypeSystem\Resolver\Access\AccessTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\Resolver\BinaryOperation\BinaryOperationTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\Resolver\BooleanLiteral\BooleanLiteralTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\Resolver\Identifier\IdentifierTypeResolver; @@ -81,6 +83,9 @@ public function resolveTypeOf(ExpressionNode $expressionNode): TypeInterface TernaryOperationNode::class => (new TernaryOperationTypeResolver( scope: $this->scope ))->resolveTypeOf($rootNode), + AccessNode::class => (new AccessTypeResolver( + scope: $this->scope + ))->resolveTypeOf($rootNode), default => throw new \Exception('@TODO: Resolve type of ' . $expressionNode->root::class) }; } diff --git a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php new file mode 100644 index 00000000..7764a3a4 --- /dev/null +++ b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php @@ -0,0 +1,61 @@ +. + */ + +declare(strict_types=1); + +namespace PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Resolver\Access; + +use PackageFactory\ComponentEngine\Parser\Ast\AccessNode; +use PackageFactory\ComponentEngine\Parser\Ast\ExpressionNode; +use PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Scope\Fixtures\DummyScope; +use PackageFactory\ComponentEngine\TypeSystem\Resolver\Access\AccessTypeResolver; +use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; +use PHPUnit\Framework\TestCase; + +final class AccessTypeResolverTest extends TestCase +{ + public function invalidAccessExamples(): iterable + { + yield 'access property on primitive string' => [ + 'someString.bar', + '@TODO: Cannot access on type PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType' + ]; + } + + /** + * @dataProvider invalidAccessExamples + * @test + */ + public function invalidAccessResultsInError(string $accessAsString, string $expectedErrorMessage): void + { + $this->expectExceptionMessage($expectedErrorMessage); + + $scope = new DummyScope([ + 'someString' => StringType::get() + ]); + $accessTypeResolver = new AccessTypeResolver( + scope: $scope + ); + $accessNode = ExpressionNode::fromString($accessAsString)->root; + assert($accessNode instanceof AccessNode); + + $accessTypeResolver->resolveTypeOf($accessNode); + } +} From 7c4879b78e3abc48a91b819121023e96494ac64a Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 8 Apr 2023 17:43:25 +0200 Subject: [PATCH 02/16] TASK: WIP add EnumMemberType - fail if enum doesnt have this property - resolve property value type --- .../Resolver/Access/AccessTypeResolver.php | 24 +++++- .../Type/EnumType/EnumMemberType.php | 40 +++++++++ .../Type/EnumType/EnumStaticType.php | 17 +--- src/TypeSystem/Type/EnumType/EnumTrait.php | 82 +++++++++++++++++++ src/TypeSystem/Type/EnumType/EnumType.php | 17 +--- .../Access/AccessTypeResolverTest.php | 61 +++++++++++++- 6 files changed, 202 insertions(+), 39 deletions(-) create mode 100644 src/TypeSystem/Type/EnumType/EnumMemberType.php create mode 100644 src/TypeSystem/Type/EnumType/EnumTrait.php diff --git a/src/TypeSystem/Resolver/Access/AccessTypeResolver.php b/src/TypeSystem/Resolver/Access/AccessTypeResolver.php index 8eabd4de..36e0bfa6 100644 --- a/src/TypeSystem/Resolver/Access/AccessTypeResolver.php +++ b/src/TypeSystem/Resolver/Access/AccessTypeResolver.php @@ -26,10 +26,12 @@ use PackageFactory\ComponentEngine\Parser\Ast\AccessNode; use PackageFactory\ComponentEngine\TypeSystem\Resolver\Expression\ExpressionTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; use PackageFactory\ComponentEngine\TypeSystem\Type\StructType\StructType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; +use PackageFactory\ComponentEngine\Definition\AccessType; final class AccessTypeResolver { @@ -42,11 +44,25 @@ public function resolveTypeOf(AccessNode $accessNode): TypeInterface { $expressionResolver = new ExpressionTypeResolver(scope: $this->scope); $rootType = $expressionResolver->resolveTypeOf($accessNode->root); - - if (!$rootType instanceof EnumType || !$rootType instanceof EnumStaticType || !$rootType instanceof StructType) { - throw new \Exception('@TODO: Cannot access on type ' . $rootType::class); + + return match ($rootType::class) { + EnumType::class, EnumStaticType::class => $this->createEnumMemberType($accessNode, $rootType), + StructType::class => throw new \Exception('@TODO: StructType Access is not implemented'), + default => throw new \Exception('@TODO Error: Cannot access on type ' . $rootType::class) + }; + } + + private function createEnumMemberType(AccessNode $accessNode, EnumType|EnumStaticType $enumType): EnumMemberType + { + if (!( + count($accessNode->chain->items) === 1 + && $accessNode->chain->items[0]->accessType === AccessType::MANDATORY + )) { + throw new \Error('@TODO Error: Enum access malformed, only one level member access is allowed.'); } + + $enumMemberName = $accessNode->chain->items[0]->accessor->value; - throw new \Exception('@TODO: Enum and StructType Access is not implemented'); + return $enumType->getMemberType($enumMemberName); } } diff --git a/src/TypeSystem/Type/EnumType/EnumMemberType.php b/src/TypeSystem/Type/EnumType/EnumMemberType.php new file mode 100644 index 00000000..dd237bdd --- /dev/null +++ b/src/TypeSystem/Type/EnumType/EnumMemberType.php @@ -0,0 +1,40 @@ +. + */ + +declare(strict_types=1); + +namespace PackageFactory\ComponentEngine\TypeSystem\Type\EnumType; + +use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; + +final class EnumMemberType implements TypeInterface +{ + public function __construct( + public readonly EnumType|EnumStaticType $enumType, + public readonly string $memberName, + public readonly ?TypeInterface $memberValueType + ) { + } + + public function is(TypeInterface $other): bool + { + return $this->memberValueType?->is($other) ?? false; + } +} diff --git a/src/TypeSystem/Type/EnumType/EnumStaticType.php b/src/TypeSystem/Type/EnumType/EnumStaticType.php index 866e63a3..467e7235 100644 --- a/src/TypeSystem/Type/EnumType/EnumStaticType.php +++ b/src/TypeSystem/Type/EnumType/EnumStaticType.php @@ -22,24 +22,9 @@ namespace PackageFactory\ComponentEngine\TypeSystem\Type\EnumType; -use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; final class EnumStaticType implements TypeInterface { - private function __construct(public readonly string $enumName) - { - } - - public static function fromEnumDeclarationNode(EnumDeclarationNode $enumDeclarationNode): self - { - return new self( - enumName: $enumDeclarationNode->enumName - ); - } - - public function is(TypeInterface $other): bool - { - return false; - } + use EnumTrait; } diff --git a/src/TypeSystem/Type/EnumType/EnumTrait.php b/src/TypeSystem/Type/EnumType/EnumTrait.php new file mode 100644 index 00000000..ec86df8a --- /dev/null +++ b/src/TypeSystem/Type/EnumType/EnumTrait.php @@ -0,0 +1,82 @@ +. + */ + +declare(strict_types=1); + +namespace PackageFactory\ComponentEngine\TypeSystem\Type\EnumType; + +use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; +use PackageFactory\ComponentEngine\Parser\Ast\NumberLiteralNode; +use PackageFactory\ComponentEngine\Parser\Ast\StringLiteralNode; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; +use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType; +use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; +use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; + +trait EnumTrait +{ + private function __construct( + public readonly string $enumName, + private readonly array $membersWithType, + ) { + } + + public static function fromEnumDeclarationNode(EnumDeclarationNode $enumDeclarationNode): self + { + $membersWithType = []; + + foreach ($enumDeclarationNode->memberDeclarations->items as $memberDeclarationNode) { + $membersWithType[$memberDeclarationNode->name] = match ($memberDeclarationNode->value + ? $memberDeclarationNode->value::class + : null + ) { + StringLiteralNode::class => StringType::get(), + NumberLiteralNode::class => NumberType::get(), + null => null + }; + } + + return new self( + enumName: $enumDeclarationNode->enumName, + membersWithType: $membersWithType + ); + } + + public function getMemberType(string $memberName): EnumMemberType + { + if (!isset($this->membersWithType[$memberName])) { + throw new \Exception('@TODO cannot access member ' . $memberName . ' of enum ' . $this->enumName); + } + return new EnumMemberType( + $this, + $memberName, + $this->membersWithType[$memberName] + ); + } + + public function is(TypeInterface $other): bool + { + // todo more satisfied check with namespace taken into account + return match ($other::class) { + EnumType::class, EnumStaticType::class => $this->enumName === $other->enumName, + default => false + }; + } +} diff --git a/src/TypeSystem/Type/EnumType/EnumType.php b/src/TypeSystem/Type/EnumType/EnumType.php index f83a28d6..331b6a02 100644 --- a/src/TypeSystem/Type/EnumType/EnumType.php +++ b/src/TypeSystem/Type/EnumType/EnumType.php @@ -22,24 +22,9 @@ namespace PackageFactory\ComponentEngine\TypeSystem\Type\EnumType; -use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; final class EnumType implements TypeInterface { - private function __construct(public readonly string $enumName) - { - } - - public static function fromEnumDeclarationNode(EnumDeclarationNode $enumDeclarationNode): self - { - return new self( - enumName: $enumDeclarationNode->enumName - ); - } - - public function is(TypeInterface $other): bool - { - return false; - } + use EnumTrait; } diff --git a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php index 7764a3a4..2ff5c7ea 100644 --- a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php @@ -23,10 +23,15 @@ namespace PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Resolver\Access; use PackageFactory\ComponentEngine\Parser\Ast\AccessNode; +use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\ExpressionNode; use PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Scope\Fixtures\DummyScope; use PackageFactory\ComponentEngine\TypeSystem\Resolver\Access\AccessTypeResolver; +use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; +use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; use PHPUnit\Framework\TestCase; final class AccessTypeResolverTest extends TestCase @@ -35,8 +40,52 @@ public function invalidAccessExamples(): iterable { yield 'access property on primitive string' => [ 'someString.bar', - '@TODO: Cannot access on type PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType' + '@TODO Error: Cannot access on type PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType' ]; + + yield 'access invalid property on enum' => [ + 'SomeEnum.NonExistent', + '@TODO cannot access member NonExistent of enum SomeEnum' + ]; + } + + private function resolveAccessType(string $accessAsString, ScopeInterface $scope): TypeInterface + { + $accessTypeResolver = new AccessTypeResolver( + scope: $scope + ); + $accessNode = ExpressionNode::fromString($accessAsString)->root; + assert($accessNode instanceof AccessNode); + return $accessTypeResolver->resolveTypeOf($accessNode); + } + + /** + * @test + */ + public function access(): void + { + $someEnum = EnumStaticType::fromEnumDeclarationNode( + EnumDeclarationNode::fromString( + 'enum SomeEnum { A("Hi") }' + ) + ); + + $scope = new DummyScope([ + 'SomeEnum' => $someEnum + ]); + + $accessType = $this->resolveAccessType( + 'SomeEnum.A', + $scope + ); + + $this->assertInstanceOf(EnumMemberType::class, $accessType); + + $this->assertTrue($accessType->enumType->is($someEnum)); + + $this->assertEquals("A", $accessType->memberName); + + $this->assertTrue($accessType->memberValueType->is(StringType::get())); } /** @@ -46,9 +95,15 @@ public function invalidAccessExamples(): iterable public function invalidAccessResultsInError(string $accessAsString, string $expectedErrorMessage): void { $this->expectExceptionMessage($expectedErrorMessage); - + + $someEnum = EnumStaticType::fromEnumDeclarationNode( + EnumDeclarationNode::fromString( + 'enum SomeEnum { A }' + ) + ); $scope = new DummyScope([ - 'someString' => StringType::get() + 'someString' => StringType::get(), + 'SomeEnum' => $someEnum ]); $accessTypeResolver = new AccessTypeResolver( scope: $scope From 86e1e612f6f51081a866b7590d90f72c4ad2ba97 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 8 Apr 2023 20:41:13 +0200 Subject: [PATCH 03/16] FEATURE: WIP Enums ensure that match is complete - reimplement #9 cleaner - enum static will be converted to enum instance in ComponentScope, but enum static is the imported type - Remove global ButtonType hack in the tests and import it correctly in the Match.afx - ModuleScope::lookupTypeFor didnt work for imported enums (IdentifierResolver Failed) Match related: - multiple default arms -> throws - if there is no default arm and not all enum members are listed -> throws - if enum members are listed twice -> throws - if enum is matched against non enum member -> throws - if enum is matched against other enum -> throws --- .../Loader/ModuleFile/ModuleFileLoader.php | 4 +- .../ComponentDeclarationTranspiler.php | 2 +- .../Identifier/IdentifierTranspiler.php | 1 + .../TypeReferenceStrategyInterface.php | 4 +- .../TypeReference/TypeReferenceTranspiler.php | 4 +- .../Resolver/Match/MatchTypeResolver.php | 57 +++++++- .../Scope/ComponentScope/ComponentScope.php | 10 +- .../Scope/ModuleScope/ModuleScope.php | 3 + .../Type/EnumType/EnumStaticType.php | 5 + src/TypeSystem/Type/EnumType/EnumTrait.php | 10 +- .../Integration/Examples/Match/ButtonType.afx | 6 + test/Integration/Examples/Match/Match.afx | 2 + .../Integration/Examples/Match/Match.ast.json | 10 +- test/Integration/Examples/Match/Match.php | 1 + .../Examples/Match/Match.tokens.json | 13 ++ .../PhpTranspilerIntegrationTest.php | 29 +--- .../TypeReferenceTestStrategy.php | 4 +- .../TypeReferenceTranspilerTest.php | 4 +- .../Resolver/Match/MatchTypeResolverTest.php | 134 +++++++++++++++++- 19 files changed, 251 insertions(+), 52 deletions(-) create mode 100644 test/Integration/Examples/Match/ButtonType.afx diff --git a/src/Module/Loader/ModuleFile/ModuleFileLoader.php b/src/Module/Loader/ModuleFile/ModuleFileLoader.php index eaf342f8..ae57218c 100644 --- a/src/Module/Loader/ModuleFile/ModuleFileLoader.php +++ b/src/Module/Loader/ModuleFile/ModuleFileLoader.php @@ -32,7 +32,7 @@ use PackageFactory\ComponentEngine\Parser\Source\Source; use PackageFactory\ComponentEngine\Parser\Tokenizer\Tokenizer; use PackageFactory\ComponentEngine\TypeSystem\Type\ComponentType\ComponentType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\Type\StructType\StructType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; @@ -56,7 +56,7 @@ public function resolveTypeOfImport(ImportNode $importNode): TypeInterface return match ($export->declaration::class) { ComponentDeclarationNode::class => ComponentType::fromComponentDeclarationNode($export->declaration), - EnumDeclarationNode::class => EnumType::fromEnumDeclarationNode($export->declaration), + EnumDeclarationNode::class => EnumStaticType::fromEnumDeclarationNode($export->declaration), StructDeclarationNode::class => StructType::fromStructDeclarationNode($export->declaration) }; } diff --git a/src/Target/Php/Transpiler/ComponentDeclaration/ComponentDeclarationTranspiler.php b/src/Target/Php/Transpiler/ComponentDeclaration/ComponentDeclarationTranspiler.php index bccceea9..6bdd5a59 100644 --- a/src/Target/Php/Transpiler/ComponentDeclaration/ComponentDeclarationTranspiler.php +++ b/src/Target/Php/Transpiler/ComponentDeclaration/ComponentDeclarationTranspiler.php @@ -60,7 +60,7 @@ public function transpile(ComponentDeclarationNode $componentDeclarationNode): s } foreach ($this->module->imports->items as $importNode) { - // @TODO: Generate Namespaces Dynamically + // @TODO: Generate Namespaces + Name via TypeReferenceStrategyInterface Dynamically $lines[] = 'use Vendor\\Project\\Component\\' . $importNode->name->value . ';'; } diff --git a/src/Target/Php/Transpiler/Identifier/IdentifierTranspiler.php b/src/Target/Php/Transpiler/Identifier/IdentifierTranspiler.php index 5bd9e193..e58291d1 100644 --- a/src/Target/Php/Transpiler/Identifier/IdentifierTranspiler.php +++ b/src/Target/Php/Transpiler/Identifier/IdentifierTranspiler.php @@ -37,6 +37,7 @@ public function transpile(IdentifierNode $identifierNode): string $typeOfIdentifiedValue = $this->scope->lookupTypeFor($identifierNode->value); return match (true) { + // @TODO: Generate Name via TypeReferenceStrategyInterface Dynamically $typeOfIdentifiedValue instanceof EnumStaticType => $identifierNode->value, default => '$this->' . $identifierNode->value }; diff --git a/src/Target/Php/Transpiler/TypeReference/TypeReferenceStrategyInterface.php b/src/Target/Php/Transpiler/TypeReference/TypeReferenceStrategyInterface.php index 5f861296..45ce8814 100644 --- a/src/Target/Php/Transpiler/TypeReference/TypeReferenceStrategyInterface.php +++ b/src/Target/Php/Transpiler/TypeReference/TypeReferenceStrategyInterface.php @@ -24,7 +24,7 @@ use PackageFactory\ComponentEngine\Parser\Ast\TypeReferenceNode; use PackageFactory\ComponentEngine\TypeSystem\Type\ComponentType\ComponentType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\Type\SlotType\SlotType; use PackageFactory\ComponentEngine\TypeSystem\Type\StructType\StructType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; @@ -33,7 +33,7 @@ interface TypeReferenceStrategyInterface { public function getPhpTypeReferenceForSlotType(SlotType $slotType, TypeReferenceNode $typeReferenceNode): string; public function getPhpTypeReferenceForComponentType(ComponentType $componentType, TypeReferenceNode $typeReferenceNode): string; - public function getPhpTypeReferenceForEnumType(EnumType $enumType, TypeReferenceNode $typeReferenceNode): string; + public function getPhpTypeReferenceForEnumType(EnumStaticType $enumType, TypeReferenceNode $typeReferenceNode): string; public function getPhpTypeReferenceForStructType(StructType $structType, TypeReferenceNode $typeReferenceNode): string; public function getPhpTypeReferenceForCustomType(TypeInterface $customType, TypeReferenceNode $typeReferenceNode): string; } diff --git a/src/Target/Php/Transpiler/TypeReference/TypeReferenceTranspiler.php b/src/Target/Php/Transpiler/TypeReference/TypeReferenceTranspiler.php index af5a7326..b36a7e2b 100644 --- a/src/Target/Php/Transpiler/TypeReference/TypeReferenceTranspiler.php +++ b/src/Target/Php/Transpiler/TypeReference/TypeReferenceTranspiler.php @@ -26,7 +26,7 @@ use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType; use PackageFactory\ComponentEngine\TypeSystem\Type\ComponentType\ComponentType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType; use PackageFactory\ComponentEngine\TypeSystem\Type\SlotType\SlotType; use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; @@ -49,7 +49,7 @@ public function transpile(TypeReferenceNode $typeReferenceNode): string BooleanType::class => 'bool', SlotType::class => $this->strategy->getPhpTypeReferenceForSlotType($type, $typeReferenceNode), ComponentType::class => $this->strategy->getPhpTypeReferenceForComponentType($type, $typeReferenceNode), - EnumType::class => $this->strategy->getPhpTypeReferenceForEnumType($type, $typeReferenceNode), + EnumStaticType::class => $this->strategy->getPhpTypeReferenceForEnumType($type, $typeReferenceNode), StructType::class => $this->strategy->getPhpTypeReferenceForStructType($type, $typeReferenceNode), default => $this->strategy->getPhpTypeReferenceForCustomType($type, $typeReferenceNode) }; diff --git a/src/TypeSystem/Resolver/Match/MatchTypeResolver.php b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php index ee24cd7c..d8c19f4d 100644 --- a/src/TypeSystem/Resolver/Match/MatchTypeResolver.php +++ b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php @@ -22,11 +22,15 @@ namespace PackageFactory\ComponentEngine\TypeSystem\Resolver\Match; +use PackageFactory\ComponentEngine\Parser\Ast\AccessNode; use PackageFactory\ComponentEngine\Parser\Ast\BooleanLiteralNode; use PackageFactory\ComponentEngine\Parser\Ast\MatchNode; +use PackageFactory\ComponentEngine\TypeSystem\Resolver\Access\AccessTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\Resolver\Expression\ExpressionTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; @@ -67,7 +71,14 @@ private function resolveTypeOfBooleanMatch(MatchNode $matchNode): TypeInterface } else { $types = []; + $defaultArmPresent = false; foreach ($matchNode->arms->items as $matchArmNode) { + if ($defaultArmPresent) { + throw new \Exception('@TODO: Multiple illegal default arms'); + } + if ($matchArmNode->left === null) { + $defaultArmPresent = true; + } $types[] = $expressionTypeResolver->resolveTypeOf( $matchArmNode->right ); @@ -79,20 +90,60 @@ private function resolveTypeOfBooleanMatch(MatchNode $matchNode): TypeInterface } } - private function resolveTypeOfEnumMatch(MatchNode $matchNode): TypeInterface + private function resolveTypeOfEnumMatch(MatchNode $matchNode, EnumType $subjectEnumType): TypeInterface { $expressionTypeResolver = new ExpressionTypeResolver( scope: $this->scope ); $types = []; + $accessTypeResolver = new AccessTypeResolver(scope: $this->scope); + + $defaultArmPresent = false; + $referencedEnumMembers = []; + foreach ($matchNode->arms->items as $matchArmNode) { + if ($defaultArmPresent) { + throw new \Exception('@TODO Error: Multiple illegal default arms'); + } + if ($matchArmNode->left === null) { + $defaultArmPresent = true; + } else { + foreach ($matchArmNode->left->items as $expressionNode) { + $accessNode = $expressionNode->root; + if (!$accessNode instanceof AccessNode + || !($enumMemberType = $accessTypeResolver->resolveTypeOf($accessNode)) instanceof EnumMemberType) { + throw new \Error('@TODO Error: To be matched enum value should be referenced like: `Enum.B`'); + } + + if (!$enumMemberType->enumType instanceof EnumStaticType) { + throw new \Exception('@TODO Error: To be matched enum must be referenced static'); + } + + if (!$enumMemberType->enumType->is($subjectEnumType)) { + throw new \Error('@TODO Error: incompatible enum match: got ' . $enumMemberType->enumType->enumName . ' expected ' . $subjectEnumType->enumName); + } + + if (isset($referencedEnumMembers[$enumMemberType->memberName])) { + throw new \Error('@TODO Error: Enum path ' . $enumMemberType->memberName . ' was already defined once in this match and cannot be used twice'); + } + + $referencedEnumMembers[$enumMemberType->memberName] = true; + } + } + $types[] = $expressionTypeResolver->resolveTypeOf( $matchArmNode->right ); } - // @TODO: Ensure that match is complete + if (!$defaultArmPresent) { + foreach ($subjectEnumType->getMemberNames() as $member) { + if (!isset($referencedEnumMembers[$member])) { + throw new \Error('@TODO Error: member ' . $member . ' not checked'); + } + } + } return UnionType::of(...$types); } @@ -108,7 +159,7 @@ public function resolveTypeOf(MatchNode $matchNode): TypeInterface return match (true) { BooleanType::get()->is($typeOfSubject) => $this->resolveTypeOfBooleanMatch($matchNode), - $typeOfSubject instanceof EnumType => $this->resolveTypeOfEnumMatch($matchNode), + $typeOfSubject instanceof EnumType => $this->resolveTypeOfEnumMatch($matchNode, $typeOfSubject), default => throw new \Exception('@TODO: Not handled ' . $typeOfSubject::class) }; } diff --git a/src/TypeSystem/Scope/ComponentScope/ComponentScope.php b/src/TypeSystem/Scope/ComponentScope/ComponentScope.php index 7769cd98..ada08e9f 100644 --- a/src/TypeSystem/Scope/ComponentScope/ComponentScope.php +++ b/src/TypeSystem/Scope/ComponentScope/ComponentScope.php @@ -25,9 +25,7 @@ use PackageFactory\ComponentEngine\Parser\Ast\ComponentDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\TypeReferenceNode; use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; -use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType; -use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType; -use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; final class ComponentScope implements ScopeInterface @@ -43,7 +41,11 @@ public function lookupTypeFor(string $name): ?TypeInterface $propertyDeclarationNode = $this->componentDeclarationNode->propertyDeclarations->getPropertyDeclarationNodeOfName($name); if ($propertyDeclarationNode) { $typeReferenceNode = $propertyDeclarationNode->type; - return $this->resolveTypeReference($typeReferenceNode); + $type = $this->resolveTypeReference($typeReferenceNode); + if ($type instanceof EnumStaticType) { + $type = $type->toEnumInstanceType(); + } + return $type; } return $this->parentScope?->lookupTypeFor($name) ?? null; diff --git a/src/TypeSystem/Scope/ModuleScope/ModuleScope.php b/src/TypeSystem/Scope/ModuleScope/ModuleScope.php index 034da99e..5f10d06a 100644 --- a/src/TypeSystem/Scope/ModuleScope/ModuleScope.php +++ b/src/TypeSystem/Scope/ModuleScope/ModuleScope.php @@ -39,6 +39,9 @@ public function __construct( public function lookupTypeFor(string $name): ?TypeInterface { + if ($importNode = $this->moduleNode->imports->get($name)) { + return $this->loader->resolveTypeOfImport($importNode); + } return $this->parentScope?->lookupTypeFor($name) ?? null; } diff --git a/src/TypeSystem/Type/EnumType/EnumStaticType.php b/src/TypeSystem/Type/EnumType/EnumStaticType.php index 467e7235..fcb711b4 100644 --- a/src/TypeSystem/Type/EnumType/EnumStaticType.php +++ b/src/TypeSystem/Type/EnumType/EnumStaticType.php @@ -27,4 +27,9 @@ final class EnumStaticType implements TypeInterface { use EnumTrait; + + public function toEnumInstanceType(): EnumType + { + return new EnumType($this->enumName, $this->membersWithType); + } } diff --git a/src/TypeSystem/Type/EnumType/EnumTrait.php b/src/TypeSystem/Type/EnumType/EnumTrait.php index ec86df8a..41eb4fdf 100644 --- a/src/TypeSystem/Type/EnumType/EnumTrait.php +++ b/src/TypeSystem/Type/EnumType/EnumTrait.php @@ -25,14 +25,13 @@ use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\NumberLiteralNode; use PackageFactory\ComponentEngine\Parser\Ast\StringLiteralNode; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType; use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; trait EnumTrait { - private function __construct( + public function __construct( public readonly string $enumName, private readonly array $membersWithType, ) { @@ -58,10 +57,15 @@ enumName: $enumDeclarationNode->enumName, membersWithType: $membersWithType ); } + + public function getMemberNames(): array + { + return array_keys($this->membersWithType); + } public function getMemberType(string $memberName): EnumMemberType { - if (!isset($this->membersWithType[$memberName])) { + if (!array_key_exists($memberName, $this->membersWithType)) { throw new \Exception('@TODO cannot access member ' . $memberName . ' of enum ' . $this->enumName); } return new EnumMemberType( diff --git a/test/Integration/Examples/Match/ButtonType.afx b/test/Integration/Examples/Match/ButtonType.afx new file mode 100644 index 00000000..a960dba8 --- /dev/null +++ b/test/Integration/Examples/Match/ButtonType.afx @@ -0,0 +1,6 @@ +export enum ButtonType { + LINK + BUTTON + SUBMIT + NONE +} diff --git a/test/Integration/Examples/Match/Match.afx b/test/Integration/Examples/Match/Match.afx index 6c23444c..1d4ebf03 100644 --- a/test/Integration/Examples/Match/Match.afx +++ b/test/Integration/Examples/Match/Match.afx @@ -1,3 +1,5 @@ +from "./ButtonType.afx" import { ButtonType } + export component Button { type: ButtonType content: slot diff --git a/test/Integration/Examples/Match/Match.ast.json b/test/Integration/Examples/Match/Match.ast.json index 04f31521..bd3a1cb2 100644 --- a/test/Integration/Examples/Match/Match.ast.json +++ b/test/Integration/Examples/Match/Match.ast.json @@ -1,7 +1,15 @@ { "type": "ModuleNode", "payload": { - "imports": {}, + "imports": [ + { + "path": "./ButtonType.afx", + "name": { + "type": "Identifier", + "payload": "ButtonType" + } + } + ], "exports": [ { "type": "ComponentDeclarationNode", diff --git a/test/Integration/Examples/Match/Match.php b/test/Integration/Examples/Match/Match.php index cb9af1c3..645af62f 100644 --- a/test/Integration/Examples/Match/Match.php +++ b/test/Integration/Examples/Match/Match.php @@ -5,6 +5,7 @@ namespace Vendor\Project\Component; use Vendor\Project\BaseClass; +use Vendor\Project\Component\ButtonType; final class Button extends BaseClass { diff --git a/test/Integration/Examples/Match/Match.tokens.json b/test/Integration/Examples/Match/Match.tokens.json index 83680bee..49d15d46 100644 --- a/test/Integration/Examples/Match/Match.tokens.json +++ b/test/Integration/Examples/Match/Match.tokens.json @@ -1,4 +1,17 @@ [ + { "type": "KEYWORD_FROM", "value": "from" }, + { "type": "SPACE", "value": " " }, + { "type": "STRING_QUOTED", "value": "./ButtonType.afx" }, + { "type": "SPACE", "value": " " }, + { "type": "KEYWORD_IMPORT", "value": "import" }, + { "type": "SPACE", "value": " " }, + { "type": "BRACKET_CURLY_OPEN", "value": "{" }, + { "type": "SPACE", "value": " " }, + { "type": "STRING", "value": "ButtonType" }, + { "type": "SPACE", "value": " " }, + { "type": "BRACKET_CURLY_CLOSE", "value": "}" }, + { "type": "END_OF_LINE", "value": "\n" }, + { "type": "END_OF_LINE", "value": "\n" }, { "type": "KEYWORD_EXPORT", "value": "export" diff --git a/test/Integration/PhpTranspilerIntegrationTest.php b/test/Integration/PhpTranspilerIntegrationTest.php index 945a4c1f..7cbfb8ce 100644 --- a/test/Integration/PhpTranspilerIntegrationTest.php +++ b/test/Integration/PhpTranspilerIntegrationTest.php @@ -23,19 +23,12 @@ namespace PackageFactory\ComponentEngine\Test\Integration; use PackageFactory\ComponentEngine\Module\Loader\ModuleFile\ModuleFileLoader; -use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\ModuleNode; -use PackageFactory\ComponentEngine\Parser\Tokenizer\Tokenizer; use PackageFactory\ComponentEngine\Parser\Source\Source; -use PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Scope\Fixtures\DummyScope; +use PackageFactory\ComponentEngine\Parser\Tokenizer\Tokenizer; use PackageFactory\ComponentEngine\Target\Php\Transpiler\Module\ModuleTranspiler; use PackageFactory\ComponentEngine\Test\Unit\Target\Php\Transpiler\Module\ModuleTestStrategy; -use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; -use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType; -use PackageFactory\ComponentEngine\TypeSystem\Type\SlotType\SlotType; -use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; +use PackageFactory\ComponentEngine\TypeSystem\Scope\GlobalScope\GlobalScope; use PHPUnit\Framework\TestCase; final class PhpTranspilerIntegrationTest extends TestCase @@ -80,23 +73,7 @@ public function testTranspiler(string $example): void $transpiler = new ModuleTranspiler( loader: new ModuleFileLoader(), // Add some assumed types to the global scope - globalScope: new DummyScope([ - 'ButtonType' => EnumStaticType::fromEnumDeclarationNode( - EnumDeclarationNode::fromString( - 'enum ButtonType { LINK BUTTON SUBMIT NONE }' - ) - ) - ], [ - 'string' => StringType::get(), - 'slot' => SlotType::get(), - 'number' => NumberType::get(), - 'boolean' => BooleanType::get(), - 'ButtonType' => EnumType::fromEnumDeclarationNode( - EnumDeclarationNode::fromString( - 'enum ButtonType { LINK BUTTON SUBMIT NONE }' - ) - ) - ]), + globalScope: GlobalScope::get(), strategy: new ModuleTestStrategy() ); diff --git a/test/Unit/Target/Php/Transpiler/TypeReference/TypeReferenceTestStrategy.php b/test/Unit/Target/Php/Transpiler/TypeReference/TypeReferenceTestStrategy.php index 72cf8633..46fa8b62 100644 --- a/test/Unit/Target/Php/Transpiler/TypeReference/TypeReferenceTestStrategy.php +++ b/test/Unit/Target/Php/Transpiler/TypeReference/TypeReferenceTestStrategy.php @@ -25,7 +25,7 @@ use PackageFactory\ComponentEngine\Parser\Ast\TypeReferenceNode; use PackageFactory\ComponentEngine\Target\Php\Transpiler\TypeReference\TypeReferenceStrategyInterface; use PackageFactory\ComponentEngine\TypeSystem\Type\ComponentType\ComponentType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\Type\SlotType\SlotType; use PackageFactory\ComponentEngine\TypeSystem\Type\StructType\StructType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; @@ -42,7 +42,7 @@ public function getPhpTypeReferenceForComponentType(ComponentType $componentType return $componentType->componentName . 'Component'; } - public function getPhpTypeReferenceForEnumType(EnumType $enumType, TypeReferenceNode $typeReferenceNode): string + public function getPhpTypeReferenceForEnumType(EnumStaticType $enumType, TypeReferenceNode $typeReferenceNode): string { return $enumType->enumName . 'Enum'; } diff --git a/test/Unit/Target/Php/Transpiler/TypeReference/TypeReferenceTranspilerTest.php b/test/Unit/Target/Php/Transpiler/TypeReference/TypeReferenceTranspilerTest.php index bd9b2929..dede4b99 100644 --- a/test/Unit/Target/Php/Transpiler/TypeReference/TypeReferenceTranspilerTest.php +++ b/test/Unit/Target/Php/Transpiler/TypeReference/TypeReferenceTranspilerTest.php @@ -30,7 +30,7 @@ use PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Scope\Fixtures\DummyScope; use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType; use PackageFactory\ComponentEngine\TypeSystem\Type\ComponentType\ComponentType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType; use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; use PackageFactory\ComponentEngine\TypeSystem\Type\StructType\StructType; @@ -49,7 +49,7 @@ protected function getTypeReferenceTranspiler(): TypeReferenceTranspiler 'Button' => ComponentType::fromComponentDeclarationNode( ComponentDeclarationNode::fromString('component Button { return "" }') ), - 'DayOfWeek' => EnumType::fromEnumDeclarationNode( + 'DayOfWeek' => EnumStaticType::fromEnumDeclarationNode( EnumDeclarationNode::fromString('enum DayOfWeek {}') ), 'Link' => StructType::fromStructDeclarationNode( diff --git a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php index ef872b07..4145dd39 100644 --- a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php @@ -28,6 +28,7 @@ use PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Scope\Fixtures\DummyScope; use PackageFactory\ComponentEngine\TypeSystem\Resolver\Match\MatchTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType; use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; @@ -59,8 +60,24 @@ public function matchExamples(): array 'match (variableOfTypeBoolean) { true -> variableOfTypeNumber false -> variableOfTypeString }', UnionType::of(NumberType::get(), StringType::get()) ], - 'match (someEnumValue) { SomeEnum.A -> variableOfTypeNumber SomeEnum.B -> variableOfTypeString SomeEnum.C -> variableOfTypeBoolean }' => [ - 'match (someEnumValue) { SomeEnum.A -> variableOfTypeNumber SomeEnum.B -> variableOfTypeString SomeEnum.C -> variableOfTypeBoolean }', + 'match enum with all declared members' => [ + <<<'EOF' + match (someEnumValue) { + SomeEnum.A -> variableOfTypeNumber + SomeEnum.B -> variableOfTypeString + SomeEnum.C -> variableOfTypeBoolean + } + EOF, + UnionType::of(NumberType::get(), StringType::get(), BooleanType::get()) + ], + 'match enum with some declared members and default' => [ + <<<'EOF' + match (someEnumValue) { + SomeEnum.A -> variableOfTypeNumber + SomeEnum.B -> variableOfTypeString + default -> variableOfTypeBoolean + } + EOF, UnionType::of(NumberType::get(), StringType::get(), BooleanType::get()) ], ]; @@ -75,7 +92,7 @@ public function matchExamples(): array */ public function resolvesMatchToResultingType(string $matchAsString, TypeInterface $expectedType): void { - $someEnumType = EnumType::fromEnumDeclarationNode( + $someStaticEnumType = EnumStaticType::fromEnumDeclarationNode( EnumDeclarationNode::fromString( 'enum SomeEnum { A B C }' ) @@ -84,7 +101,8 @@ public function resolvesMatchToResultingType(string $matchAsString, TypeInterfac 'variableOfTypeBoolean' => BooleanType::get(), 'variableOfTypeString' => StringType::get(), 'variableOfTypeNumber' => NumberType::get(), - 'someEnumValue' => $someEnumType + 'someEnumValue' => $someStaticEnumType->toEnumInstanceType(), + 'SomeEnum' => $someStaticEnumType ]); $matchTypeResolver = new MatchTypeResolver( scope: $scope @@ -99,4 +117,112 @@ public function resolvesMatchToResultingType(string $matchAsString, TypeInterfac sprintf('Expected %s, got %s', $expectedType::class, $actualType::class) ); } + + + public function malformedEnumExamples(): iterable + { + yield "Multiple default keys" => [ + <<<'EOF' + match (someEnumValue) { + SomeEnum.A -> "a" + default -> "b" + default -> "c" + } + EOF, + "@TODO Error: Multiple illegal default arms" + ]; + + yield "Missing match" => [ + <<<'EOF' + match (someEnumValue) { + SomeEnum.A -> "a" + SomeEnum.B -> "a" + } + EOF, + "@TODO Error: member C not checked" + ]; + + yield "Non existent enum member access" => [ + <<<'EOF' + match (someEnumValue) { + SomeEnum.A -> "a" + SomeEnum.B -> "a" + SomeEnum.C -> "a" + SomeEnum.NonExistent -> "a" + } + EOF, + "@TODO cannot access member NonExistent of enum SomeEnum" + ]; + + yield "Duplicate match 1" => [ + <<<'EOF' + match (someEnumValue) { + SomeEnum.A -> "a" + SomeEnum.A -> "a" + } + EOF, + "@TODO Error: Enum path A was already defined once in this match and cannot be used twice" + ]; + + yield "Duplicate match 2" => [ + <<<'EOF' + match (someEnumValue) { + SomeEnum.A, SomeEnum.A -> "a" + } + EOF, + "@TODO Error: Enum path A was already defined once in this match and cannot be used twice" + ]; + + yield "Incompatible enum types" => [ + <<<'EOF' + match (someEnumValue) { + OtherEnum.A -> "a" + } + EOF, + "@TODO Error: incompatible enum match: got OtherEnum expected SomeEnum" + ]; + + yield "Cant match enum and string" => [ + <<<'EOF' + match (someEnumValue) { + "foo" -> "a" + } + EOF, + "@TODO Error: To be matched enum value should be referenced like: `Enum.B`" + ]; + } + + /** + * @dataProvider malformedEnumExamples + * @test + */ + public function malformedMatchCannotBeResolved(string $matchAsString, string $expectedErrorMessage) + { + $this->expectExceptionMessage($expectedErrorMessage); + $someEnumDeclaration = EnumDeclarationNode::fromString( + 'enum SomeEnum { A B C }' + ); + $someEnumType = EnumType::fromEnumDeclarationNode( + $someEnumDeclaration + ); + $someStaticEnumType = EnumStaticType::fromEnumDeclarationNode( + $someEnumDeclaration + ); + $scope = new DummyScope([ + 'someEnumValue' => $someEnumType, + 'SomeEnum' => $someStaticEnumType, + 'OtherEnum' => EnumStaticType::fromEnumDeclarationNode( + EnumDeclarationNode::fromString('enum OtherEnum { A }') + ) + + ]); + + $matchTypeResolver = new MatchTypeResolver( + scope: $scope + ); + $matchNode = ExpressionNode::fromString($matchAsString)->root; + assert($matchNode instanceof MatchNode); + + $matchTypeResolver->resolveTypeOf($matchNode); + } } From e56b821e26fcc3dcb90b3f462d169b38e321ce01 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 8 Apr 2023 20:48:46 +0200 Subject: [PATCH 04/16] WIP: TASK Fix enum member access on instance --- src/TypeSystem/Resolver/Access/AccessTypeResolver.php | 5 ++--- src/TypeSystem/Type/EnumType/EnumMemberType.php | 4 ++-- .../Resolver/Access/AccessTypeResolverTest.php | 10 ++++++++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/TypeSystem/Resolver/Access/AccessTypeResolver.php b/src/TypeSystem/Resolver/Access/AccessTypeResolver.php index 36e0bfa6..8d7cebe0 100644 --- a/src/TypeSystem/Resolver/Access/AccessTypeResolver.php +++ b/src/TypeSystem/Resolver/Access/AccessTypeResolver.php @@ -28,7 +28,6 @@ use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; use PackageFactory\ComponentEngine\TypeSystem\Type\StructType\StructType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; use PackageFactory\ComponentEngine\Definition\AccessType; @@ -46,13 +45,13 @@ public function resolveTypeOf(AccessNode $accessNode): TypeInterface $rootType = $expressionResolver->resolveTypeOf($accessNode->root); return match ($rootType::class) { - EnumType::class, EnumStaticType::class => $this->createEnumMemberType($accessNode, $rootType), + EnumStaticType::class => $this->createEnumMemberType($accessNode, $rootType), StructType::class => throw new \Exception('@TODO: StructType Access is not implemented'), default => throw new \Exception('@TODO Error: Cannot access on type ' . $rootType::class) }; } - private function createEnumMemberType(AccessNode $accessNode, EnumType|EnumStaticType $enumType): EnumMemberType + private function createEnumMemberType(AccessNode $accessNode, EnumStaticType $enumType): EnumMemberType { if (!( count($accessNode->chain->items) === 1 diff --git a/src/TypeSystem/Type/EnumType/EnumMemberType.php b/src/TypeSystem/Type/EnumType/EnumMemberType.php index dd237bdd..baa265d0 100644 --- a/src/TypeSystem/Type/EnumType/EnumMemberType.php +++ b/src/TypeSystem/Type/EnumType/EnumMemberType.php @@ -29,12 +29,12 @@ final class EnumMemberType implements TypeInterface public function __construct( public readonly EnumType|EnumStaticType $enumType, public readonly string $memberName, - public readonly ?TypeInterface $memberValueType + public readonly ?TypeInterface $memberBackedValueType ) { } public function is(TypeInterface $other): bool { - return $this->memberValueType?->is($other) ?? false; + return $this->memberBackedValueType?->is($other) ?? false; } } diff --git a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php index 2ff5c7ea..19a9c3a0 100644 --- a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php @@ -47,6 +47,11 @@ public function invalidAccessExamples(): iterable 'SomeEnum.NonExistent', '@TODO cannot access member NonExistent of enum SomeEnum' ]; + + yield "access enum member on non static enum instance" => [ + 'someEnumValue.A', + "@TODO Error: Cannot access on type PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType" + ]; } private function resolveAccessType(string $accessAsString, ScopeInterface $scope): TypeInterface @@ -85,7 +90,7 @@ public function access(): void $this->assertEquals("A", $accessType->memberName); - $this->assertTrue($accessType->memberValueType->is(StringType::get())); + $this->assertTrue($accessType->memberBackedValueType?->is(StringType::get())); } /** @@ -103,7 +108,8 @@ public function invalidAccessResultsInError(string $accessAsString, string $expe ); $scope = new DummyScope([ 'someString' => StringType::get(), - 'SomeEnum' => $someEnum + 'SomeEnum' => $someEnum, + 'someEnumValue' => $someEnum->toEnumInstanceType() ]); $accessTypeResolver = new AccessTypeResolver( scope: $scope From 7cd48f892860110321b5e82eff5a2533d87ac2e8 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 8 Apr 2023 21:22:27 +0200 Subject: [PATCH 05/16] WIP: TASK Simplify MatchTypeResolver::resolveTypeOfEnumMatch --- .../Resolver/Match/MatchTypeResolver.php | 16 +++------------- .../Resolver/Match/MatchTypeResolverTest.php | 2 +- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/TypeSystem/Resolver/Match/MatchTypeResolver.php b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php index d8c19f4d..ba18aeec 100644 --- a/src/TypeSystem/Resolver/Match/MatchTypeResolver.php +++ b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php @@ -22,15 +22,12 @@ namespace PackageFactory\ComponentEngine\TypeSystem\Resolver\Match; -use PackageFactory\ComponentEngine\Parser\Ast\AccessNode; use PackageFactory\ComponentEngine\Parser\Ast\BooleanLiteralNode; use PackageFactory\ComponentEngine\Parser\Ast\MatchNode; -use PackageFactory\ComponentEngine\TypeSystem\Resolver\Access\AccessTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\Resolver\Expression\ExpressionTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; @@ -97,8 +94,6 @@ private function resolveTypeOfEnumMatch(MatchNode $matchNode, EnumType $subjectE ); $types = []; - $accessTypeResolver = new AccessTypeResolver(scope: $this->scope); - $defaultArmPresent = false; $referencedEnumMembers = []; @@ -110,14 +105,9 @@ private function resolveTypeOfEnumMatch(MatchNode $matchNode, EnumType $subjectE $defaultArmPresent = true; } else { foreach ($matchArmNode->left->items as $expressionNode) { - $accessNode = $expressionNode->root; - if (!$accessNode instanceof AccessNode - || !($enumMemberType = $accessTypeResolver->resolveTypeOf($accessNode)) instanceof EnumMemberType) { - throw new \Error('@TODO Error: To be matched enum value should be referenced like: `Enum.B`'); - } - - if (!$enumMemberType->enumType instanceof EnumStaticType) { - throw new \Exception('@TODO Error: To be matched enum must be referenced static'); + $enumMemberType = $expressionTypeResolver->resolveTypeOf($expressionNode); + if (!$enumMemberType instanceof EnumMemberType) { + throw new \Error('@TODO Error: Cannot match enum with type of ' . $enumMemberType::class); } if (!$enumMemberType->enumType->is($subjectEnumType)) { diff --git a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php index 4145dd39..17a58763 100644 --- a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php @@ -188,7 +188,7 @@ public function malformedEnumExamples(): iterable "foo" -> "a" } EOF, - "@TODO Error: To be matched enum value should be referenced like: `Enum.B`" + "@TODO Error: Cannot match enum with type of PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType" ]; } From 11b81e6dbfed9c3c607d00cf2d8e768acfceb6aa Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 8 Apr 2023 21:26:01 +0200 Subject: [PATCH 06/16] WIP: TASK Remove odd delegation in EnumMemberType::is, as we dont need it for now --- src/TypeSystem/Type/EnumType/EnumMemberType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TypeSystem/Type/EnumType/EnumMemberType.php b/src/TypeSystem/Type/EnumType/EnumMemberType.php index baa265d0..2fc460c7 100644 --- a/src/TypeSystem/Type/EnumType/EnumMemberType.php +++ b/src/TypeSystem/Type/EnumType/EnumMemberType.php @@ -35,6 +35,6 @@ public function __construct( public function is(TypeInterface $other): bool { - return $this->memberBackedValueType?->is($other) ?? false; + return false; } } From b310c5a9bfbcdfd3c5cbb53da1f03eb83dda56f8 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 9 Apr 2023 09:35:01 +0200 Subject: [PATCH 07/16] WIP: TASK more satisfied enum->is type check with module id taken into account previously enums where only compared by name and not import path (module id) --- .../Loader/ModuleFile/ModuleFileLoader.php | 8 +++++- .../Type/EnumType/EnumStaticType.php | 2 +- src/TypeSystem/Type/EnumType/EnumTrait.php | 20 +++++++++------ .../PhpTranspilerIntegrationTest.php | 1 - .../Access/AccessTranspilerTest.php | 6 +++-- .../Identifier/IdentifierTranspilerTest.php | 6 +++-- .../Transpiler/Match/MatchTranspilerTest.php | 6 +++-- .../TypeReferenceTranspilerTest.php | 4 ++- .../Access/AccessTypeResolverTest.php | 7 ++++-- .../Resolver/Match/MatchTypeResolverTest.php | 25 +++++++++---------- .../Type/EnumType/EnumStaticTypeTest.php | 9 +++++-- .../TypeSystem/Type/EnumType/EnumTypeTest.php | 9 +++++-- 12 files changed, 67 insertions(+), 36 deletions(-) diff --git a/src/Module/Loader/ModuleFile/ModuleFileLoader.php b/src/Module/Loader/ModuleFile/ModuleFileLoader.php index ae57218c..7581f0b3 100644 --- a/src/Module/Loader/ModuleFile/ModuleFileLoader.php +++ b/src/Module/Loader/ModuleFile/ModuleFileLoader.php @@ -23,6 +23,7 @@ namespace PackageFactory\ComponentEngine\Module\Loader\ModuleFile; use PackageFactory\ComponentEngine\Module\LoaderInterface; +use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\ComponentDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\ImportNode; @@ -54,9 +55,14 @@ public function resolveTypeOfImport(ImportNode $importNode): TypeInterface ); } + $moduleId = ModuleId::fromSource($source); + return match ($export->declaration::class) { ComponentDeclarationNode::class => ComponentType::fromComponentDeclarationNode($export->declaration), - EnumDeclarationNode::class => EnumStaticType::fromEnumDeclarationNode($export->declaration), + EnumDeclarationNode::class => EnumStaticType::fromModuleIdAndDeclaration( + $moduleId, + $export->declaration, + ), StructDeclarationNode::class => StructType::fromStructDeclarationNode($export->declaration) }; } diff --git a/src/TypeSystem/Type/EnumType/EnumStaticType.php b/src/TypeSystem/Type/EnumType/EnumStaticType.php index fcb711b4..2b5e34b5 100644 --- a/src/TypeSystem/Type/EnumType/EnumStaticType.php +++ b/src/TypeSystem/Type/EnumType/EnumStaticType.php @@ -30,6 +30,6 @@ final class EnumStaticType implements TypeInterface public function toEnumInstanceType(): EnumType { - return new EnumType($this->enumName, $this->membersWithType); + return new EnumType($this->moduleId, $this->enumName, $this->membersWithType); } } diff --git a/src/TypeSystem/Type/EnumType/EnumTrait.php b/src/TypeSystem/Type/EnumType/EnumTrait.php index 41eb4fdf..0ff68a05 100644 --- a/src/TypeSystem/Type/EnumType/EnumTrait.php +++ b/src/TypeSystem/Type/EnumType/EnumTrait.php @@ -22,22 +22,24 @@ namespace PackageFactory\ComponentEngine\TypeSystem\Type\EnumType; +use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\NumberLiteralNode; use PackageFactory\ComponentEngine\Parser\Ast\StringLiteralNode; -use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType; -use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; +use PackageFactory\ComponentEngine\TypeSystem\Resolver\NumberLiteral\NumberLiteralTypeResolver; +use PackageFactory\ComponentEngine\TypeSystem\Resolver\StringLiteral\StringLiteralTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; trait EnumTrait { public function __construct( + public readonly ?ModuleId $moduleId, public readonly string $enumName, private readonly array $membersWithType, ) { } - public static function fromEnumDeclarationNode(EnumDeclarationNode $enumDeclarationNode): self + public static function fromModuleIdAndDeclaration(ModuleId $moduleId, EnumDeclarationNode $enumDeclarationNode): self { $membersWithType = []; @@ -46,13 +48,16 @@ public static function fromEnumDeclarationNode(EnumDeclarationNode $enumDeclarat ? $memberDeclarationNode->value::class : null ) { - StringLiteralNode::class => StringType::get(), - NumberLiteralNode::class => NumberType::get(), + NumberLiteralNode::class => (new NumberLiteralTypeResolver()) + ->resolveTypeOf($memberDeclarationNode->value), + StringLiteralNode::class => (new StringLiteralTypeResolver()) + ->resolveTypeOf($memberDeclarationNode->value), null => null }; } return new self( + moduleId: $moduleId, enumName: $enumDeclarationNode->enumName, membersWithType: $membersWithType ); @@ -77,9 +82,10 @@ public function getMemberType(string $memberName): EnumMemberType public function is(TypeInterface $other): bool { - // todo more satisfied check with namespace taken into account return match ($other::class) { - EnumType::class, EnumStaticType::class => $this->enumName === $other->enumName, + EnumType::class, EnumStaticType::class => + $this->moduleId === $other->moduleId + && $this->enumName === $other->enumName, default => false }; } diff --git a/test/Integration/PhpTranspilerIntegrationTest.php b/test/Integration/PhpTranspilerIntegrationTest.php index 7cbfb8ce..9384021f 100644 --- a/test/Integration/PhpTranspilerIntegrationTest.php +++ b/test/Integration/PhpTranspilerIntegrationTest.php @@ -72,7 +72,6 @@ public function testTranspiler(string $example): void $transpiler = new ModuleTranspiler( loader: new ModuleFileLoader(), - // Add some assumed types to the global scope globalScope: GlobalScope::get(), strategy: new ModuleTestStrategy() ); diff --git a/test/Unit/Target/Php/Transpiler/Access/AccessTranspilerTest.php b/test/Unit/Target/Php/Transpiler/Access/AccessTranspilerTest.php index 267514fc..25b99cd5 100644 --- a/test/Unit/Target/Php/Transpiler/Access/AccessTranspilerTest.php +++ b/test/Unit/Target/Php/Transpiler/Access/AccessTranspilerTest.php @@ -22,6 +22,7 @@ namespace PackageFactory\ComponentEngine\Test\Unit\Target\Php\Transpiler\Access; +use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\AccessNode; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\ExpressionNode; @@ -62,7 +63,8 @@ public function transpilesAccessNodes(string $accessAsString, string $expectedTr 'struct A { b: B }' ) ), - 'SomeEnum' => EnumStaticType::fromEnumDeclarationNode( + 'SomeEnum' => EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), EnumDeclarationNode::fromString( 'enum SomeEnum { A B C }' ) @@ -81,4 +83,4 @@ public function transpilesAccessNodes(string $accessAsString, string $expectedTr $actualTranspilationResult ); } -} \ No newline at end of file +} diff --git a/test/Unit/Target/Php/Transpiler/Identifier/IdentifierTranspilerTest.php b/test/Unit/Target/Php/Transpiler/Identifier/IdentifierTranspilerTest.php index 3d23768c..25047efc 100644 --- a/test/Unit/Target/Php/Transpiler/Identifier/IdentifierTranspilerTest.php +++ b/test/Unit/Target/Php/Transpiler/Identifier/IdentifierTranspilerTest.php @@ -22,6 +22,7 @@ namespace PackageFactory\ComponentEngine\Test\Unit\Target\Php\Transpiler\Identifier; +use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\IdentifierNode; use PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Scope\Fixtures\DummyScope; @@ -61,7 +62,8 @@ public function transpilesIdentifierNodesReferringToEnums(): void { $identifierTranspiler = new IdentifierTranspiler( scope: new DummyScope([ - 'SomeEnum' => EnumStaticType::fromEnumDeclarationNode( + 'SomeEnum' => EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), EnumDeclarationNode::fromString( 'enum SomeEnum { A B C }' ) @@ -80,4 +82,4 @@ public function transpilesIdentifierNodesReferringToEnums(): void $actualTranspilationResult ); } -} \ No newline at end of file +} diff --git a/test/Unit/Target/Php/Transpiler/Match/MatchTranspilerTest.php b/test/Unit/Target/Php/Transpiler/Match/MatchTranspilerTest.php index 2eb04d76..809dbe0d 100644 --- a/test/Unit/Target/Php/Transpiler/Match/MatchTranspilerTest.php +++ b/test/Unit/Target/Php/Transpiler/Match/MatchTranspilerTest.php @@ -22,6 +22,7 @@ namespace PackageFactory\ComponentEngine\Test\Unit\Target\Php\Transpiler\Match; +use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\ExpressionNode; use PackageFactory\ComponentEngine\Parser\Ast\MatchNode; @@ -72,7 +73,8 @@ public function transpilesMatchNodes(string $matchAsString, string $expectedTran { $matchTranspiler = new MatchTranspiler( scope: new DummyScope([ - 'SomeEnum' => EnumStaticType::fromEnumDeclarationNode( + 'SomeEnum' => EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), EnumDeclarationNode::fromString( 'enum SomeEnum { A B C }' ) @@ -91,4 +93,4 @@ public function transpilesMatchNodes(string $matchAsString, string $expectedTran $actualTranspilationResult ); } -} \ No newline at end of file +} diff --git a/test/Unit/Target/Php/Transpiler/TypeReference/TypeReferenceTranspilerTest.php b/test/Unit/Target/Php/Transpiler/TypeReference/TypeReferenceTranspilerTest.php index dede4b99..07404296 100644 --- a/test/Unit/Target/Php/Transpiler/TypeReference/TypeReferenceTranspilerTest.php +++ b/test/Unit/Target/Php/Transpiler/TypeReference/TypeReferenceTranspilerTest.php @@ -22,6 +22,7 @@ namespace PackageFactory\ComponentEngine\Test\Unit\Target\Php\Transpiler\TypeReference; +use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\ComponentDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\StructDeclarationNode; @@ -49,7 +50,8 @@ protected function getTypeReferenceTranspiler(): TypeReferenceTranspiler 'Button' => ComponentType::fromComponentDeclarationNode( ComponentDeclarationNode::fromString('component Button { return "" }') ), - 'DayOfWeek' => EnumStaticType::fromEnumDeclarationNode( + 'DayOfWeek' => EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), EnumDeclarationNode::fromString('enum DayOfWeek {}') ), 'Link' => StructType::fromStructDeclarationNode( diff --git a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php index 19a9c3a0..e9b02b57 100644 --- a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php @@ -22,6 +22,7 @@ namespace PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Resolver\Access; +use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\AccessNode; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\ExpressionNode; @@ -69,7 +70,8 @@ private function resolveAccessType(string $accessAsString, ScopeInterface $scope */ public function access(): void { - $someEnum = EnumStaticType::fromEnumDeclarationNode( + $someEnum = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), EnumDeclarationNode::fromString( 'enum SomeEnum { A("Hi") }' ) @@ -101,7 +103,8 @@ public function invalidAccessResultsInError(string $accessAsString, string $expe { $this->expectExceptionMessage($expectedErrorMessage); - $someEnum = EnumStaticType::fromEnumDeclarationNode( + $someEnum = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), EnumDeclarationNode::fromString( 'enum SomeEnum { A }' ) diff --git a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php index 17a58763..c4f21a7a 100644 --- a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php @@ -22,6 +22,7 @@ namespace PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Resolver\Match; +use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\ExpressionNode; use PackageFactory\ComponentEngine\Parser\Ast\MatchNode; @@ -29,7 +30,6 @@ use PackageFactory\ComponentEngine\TypeSystem\Resolver\Match\MatchTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType; use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType; @@ -92,7 +92,8 @@ public function matchExamples(): array */ public function resolvesMatchToResultingType(string $matchAsString, TypeInterface $expectedType): void { - $someStaticEnumType = EnumStaticType::fromEnumDeclarationNode( + $someStaticEnumType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), EnumDeclarationNode::fromString( 'enum SomeEnum { A B C }' ) @@ -196,22 +197,20 @@ public function malformedEnumExamples(): iterable * @dataProvider malformedEnumExamples * @test */ - public function malformedMatchCannotBeResolved(string $matchAsString, string $expectedErrorMessage) + public function malformedMatchCannotBeResolved(string $matchAsString, string $expectedErrorMessage): void { $this->expectExceptionMessage($expectedErrorMessage); - $someEnumDeclaration = EnumDeclarationNode::fromString( - 'enum SomeEnum { A B C }' - ); - $someEnumType = EnumType::fromEnumDeclarationNode( - $someEnumDeclaration - ); - $someStaticEnumType = EnumStaticType::fromEnumDeclarationNode( - $someEnumDeclaration + $someStaticEnumType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + EnumDeclarationNode::fromString( + 'enum SomeEnum { A B C }' + ) ); $scope = new DummyScope([ - 'someEnumValue' => $someEnumType, 'SomeEnum' => $someStaticEnumType, - 'OtherEnum' => EnumStaticType::fromEnumDeclarationNode( + 'someEnumValue' => $someStaticEnumType->toEnumInstanceType(), + 'OtherEnum' => EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), EnumDeclarationNode::fromString('enum OtherEnum { A }') ) diff --git a/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php b/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php index 73efcf72..1919171e 100644 --- a/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php +++ b/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php @@ -22,6 +22,7 @@ namespace PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Type\EnumStaticType; +use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PHPUnit\Framework\TestCase; @@ -37,7 +38,10 @@ public function canBeCreatedFromEnumDeclarationNode(): void $enumDeclarationNode = EnumDeclarationNode::fromString( 'enum Foo { BAR BAZ }' ); - $enumStaticType = EnumStaticType::fromEnumDeclarationNode($enumDeclarationNode); + $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + $enumDeclarationNode + ); $this->assertInstanceOf(EnumStaticType::class, $enumStaticType); } @@ -51,7 +55,8 @@ public function providesNameOfTheEnum(): void $enumDeclarationNode = EnumDeclarationNode::fromString( 'enum SomeEnum {}' ); - $enumStaticType = EnumStaticType::fromEnumDeclarationNode( + $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), $enumDeclarationNode ); diff --git a/test/Unit/TypeSystem/Type/EnumType/EnumTypeTest.php b/test/Unit/TypeSystem/Type/EnumType/EnumTypeTest.php index 3ccd3c1f..dc5efe19 100644 --- a/test/Unit/TypeSystem/Type/EnumType/EnumTypeTest.php +++ b/test/Unit/TypeSystem/Type/EnumType/EnumTypeTest.php @@ -22,6 +22,7 @@ namespace PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Type\EnumType; +use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; use PHPUnit\Framework\TestCase; @@ -37,7 +38,10 @@ public function canBeCreatedFromEnumDeclarationNode(): void $enumDeclarationNode = EnumDeclarationNode::fromString( 'enum Foo { BAR BAZ }' ); - $enumType = EnumType::fromEnumDeclarationNode($enumDeclarationNode); + $enumType = EnumType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + $enumDeclarationNode + ); $this->assertInstanceOf(EnumType::class, $enumType); } @@ -51,7 +55,8 @@ public function providesNameOfTheEnum(): void $enumDeclarationNode = EnumDeclarationNode::fromString( 'enum SomeEnum {}' ); - $enumType = EnumType::fromEnumDeclarationNode( + $enumType = EnumType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), $enumDeclarationNode ); From eb5cf4e11c9951517103d8f05586f16e335ecf72 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 9 Apr 2023 09:38:27 +0200 Subject: [PATCH 08/16] WIP: TASK rename EnumType to EnumInstanceType --- src/TypeSystem/Resolver/Match/MatchTypeResolver.php | 6 +++--- .../EnumType/{EnumType.php => EnumInstanceType.php} | 2 +- src/TypeSystem/Type/EnumType/EnumMemberType.php | 2 +- src/TypeSystem/Type/EnumType/EnumStaticType.php | 4 ++-- src/TypeSystem/Type/EnumType/EnumTrait.php | 2 +- .../{EnumTypeTest.php => EnumInstanceTypeTest.php} | 10 +++++----- 6 files changed, 13 insertions(+), 13 deletions(-) rename src/TypeSystem/Type/EnumType/{EnumType.php => EnumInstanceType.php} (94%) rename test/Unit/TypeSystem/Type/EnumType/{EnumTypeTest.php => EnumInstanceTypeTest.php} (87%) diff --git a/src/TypeSystem/Resolver/Match/MatchTypeResolver.php b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php index ba18aeec..07ef1f01 100644 --- a/src/TypeSystem/Resolver/Match/MatchTypeResolver.php +++ b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php @@ -28,7 +28,7 @@ use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType; use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; @@ -87,7 +87,7 @@ private function resolveTypeOfBooleanMatch(MatchNode $matchNode): TypeInterface } } - private function resolveTypeOfEnumMatch(MatchNode $matchNode, EnumType $subjectEnumType): TypeInterface + private function resolveTypeOfEnumMatch(MatchNode $matchNode, EnumInstanceType $subjectEnumType): TypeInterface { $expressionTypeResolver = new ExpressionTypeResolver( scope: $this->scope @@ -149,7 +149,7 @@ public function resolveTypeOf(MatchNode $matchNode): TypeInterface return match (true) { BooleanType::get()->is($typeOfSubject) => $this->resolveTypeOfBooleanMatch($matchNode), - $typeOfSubject instanceof EnumType => $this->resolveTypeOfEnumMatch($matchNode, $typeOfSubject), + $typeOfSubject instanceof EnumInstanceType => $this->resolveTypeOfEnumMatch($matchNode, $typeOfSubject), default => throw new \Exception('@TODO: Not handled ' . $typeOfSubject::class) }; } diff --git a/src/TypeSystem/Type/EnumType/EnumType.php b/src/TypeSystem/Type/EnumType/EnumInstanceType.php similarity index 94% rename from src/TypeSystem/Type/EnumType/EnumType.php rename to src/TypeSystem/Type/EnumType/EnumInstanceType.php index 331b6a02..91a91b35 100644 --- a/src/TypeSystem/Type/EnumType/EnumType.php +++ b/src/TypeSystem/Type/EnumType/EnumInstanceType.php @@ -24,7 +24,7 @@ use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; -final class EnumType implements TypeInterface +final class EnumInstanceType implements TypeInterface { use EnumTrait; } diff --git a/src/TypeSystem/Type/EnumType/EnumMemberType.php b/src/TypeSystem/Type/EnumType/EnumMemberType.php index 2fc460c7..c278676c 100644 --- a/src/TypeSystem/Type/EnumType/EnumMemberType.php +++ b/src/TypeSystem/Type/EnumType/EnumMemberType.php @@ -27,7 +27,7 @@ final class EnumMemberType implements TypeInterface { public function __construct( - public readonly EnumType|EnumStaticType $enumType, + public readonly EnumInstanceType|EnumStaticType $enumType, public readonly string $memberName, public readonly ?TypeInterface $memberBackedValueType ) { diff --git a/src/TypeSystem/Type/EnumType/EnumStaticType.php b/src/TypeSystem/Type/EnumType/EnumStaticType.php index 2b5e34b5..f8a6a0c3 100644 --- a/src/TypeSystem/Type/EnumType/EnumStaticType.php +++ b/src/TypeSystem/Type/EnumType/EnumStaticType.php @@ -28,8 +28,8 @@ final class EnumStaticType implements TypeInterface { use EnumTrait; - public function toEnumInstanceType(): EnumType + public function toEnumInstanceType(): EnumInstanceType { - return new EnumType($this->moduleId, $this->enumName, $this->membersWithType); + return new EnumInstanceType($this->moduleId, $this->enumName, $this->membersWithType); } } diff --git a/src/TypeSystem/Type/EnumType/EnumTrait.php b/src/TypeSystem/Type/EnumType/EnumTrait.php index 0ff68a05..ee05982f 100644 --- a/src/TypeSystem/Type/EnumType/EnumTrait.php +++ b/src/TypeSystem/Type/EnumType/EnumTrait.php @@ -83,7 +83,7 @@ public function getMemberType(string $memberName): EnumMemberType public function is(TypeInterface $other): bool { return match ($other::class) { - EnumType::class, EnumStaticType::class => + EnumInstanceType::class, EnumStaticType::class => $this->moduleId === $other->moduleId && $this->enumName === $other->enumName, default => false diff --git a/test/Unit/TypeSystem/Type/EnumType/EnumTypeTest.php b/test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php similarity index 87% rename from test/Unit/TypeSystem/Type/EnumType/EnumTypeTest.php rename to test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php index dc5efe19..55cc44a9 100644 --- a/test/Unit/TypeSystem/Type/EnumType/EnumTypeTest.php +++ b/test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php @@ -24,10 +24,10 @@ use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType; use PHPUnit\Framework\TestCase; -final class EnumTypeTest extends TestCase +final class EnumInstanceTypeTest extends TestCase { /** * @test @@ -38,12 +38,12 @@ public function canBeCreatedFromEnumDeclarationNode(): void $enumDeclarationNode = EnumDeclarationNode::fromString( 'enum Foo { BAR BAZ }' ); - $enumType = EnumType::fromModuleIdAndDeclaration( + $enumType = EnumInstanceType::fromModuleIdAndDeclaration( ModuleId::fromString("module-a"), $enumDeclarationNode ); - $this->assertInstanceOf(EnumType::class, $enumType); + $this->assertInstanceOf(EnumInstanceType::class, $enumType); } /** @@ -55,7 +55,7 @@ public function providesNameOfTheEnum(): void $enumDeclarationNode = EnumDeclarationNode::fromString( 'enum SomeEnum {}' ); - $enumType = EnumType::fromModuleIdAndDeclaration( + $enumType = EnumInstanceType::fromModuleIdAndDeclaration( ModuleId::fromString("module-a"), $enumDeclarationNode ); From 5e1dd2057617c57d9b0a3e3670a063f460d238e1 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 9 Apr 2023 09:54:20 +0200 Subject: [PATCH 09/16] WIP: TASK add unit tests for Enum Types --- .../Access/AccessTypeResolverTest.php | 2 +- .../Type/EnumType/EnumInstanceTypeTest.php | 40 ++++++++++++ .../Type/EnumType/EnumStaticTypeTest.php | 61 ++++++++++++++++++- 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php index e9b02b57..a2a48d3a 100644 --- a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php @@ -51,7 +51,7 @@ public function invalidAccessExamples(): iterable yield "access enum member on non static enum instance" => [ 'someEnumValue.A', - "@TODO Error: Cannot access on type PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType" + "@TODO Error: Cannot access on type PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType" ]; } diff --git a/test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php b/test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php index 55cc44a9..477e13a5 100644 --- a/test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php +++ b/test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php @@ -25,6 +25,7 @@ use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PHPUnit\Framework\TestCase; final class EnumInstanceTypeTest extends TestCase @@ -62,4 +63,43 @@ public function providesNameOfTheEnum(): void $this->assertEquals('SomeEnum', $enumType->enumName); } + + /** + * @test + */ + public function providesMemberNames(): void + { + $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + EnumDeclarationNode::fromString( + 'enum SomeEnum { A B C }' + ) + ); + + $this->assertSame(["A", "B", "C"], $enumStaticType->getMemberNames()); + } + + /** + * @test + * @return void + */ + public function canBeComparedToOther(): void + { + $enumDeclarationNode = EnumDeclarationNode::fromString( + 'enum SomeEnum { A }' + ); + $enumInstanceType = EnumInstanceType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + $enumDeclarationNode + ); + + $this->assertTrue($enumInstanceType->is($enumInstanceType)); + + $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + $enumDeclarationNode + ); + + $this->assertTrue($enumInstanceType->is($enumStaticType)); + } } diff --git a/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php b/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php index 1919171e..d1b9e78a 100644 --- a/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php +++ b/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php @@ -20,10 +20,11 @@ declare(strict_types=1); -namespace PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Type\EnumStaticType; +namespace PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Type\EnumType; use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PHPUnit\Framework\TestCase; @@ -62,4 +63,62 @@ public function providesNameOfTheEnum(): void $this->assertEquals('SomeEnum', $enumStaticType->enumName); } + + /** + * @test + */ + public function providesMemberNames(): void + { + $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + EnumDeclarationNode::fromString( + 'enum SomeEnum { A B C }' + ) + ); + + $this->assertSame(["A", "B", "C"], $enumStaticType->getMemberNames()); + } + + /** + * @test + * @return void + */ + public function canBeTransformedIntoInstanceType(): void + { + $enumDeclarationNode = EnumDeclarationNode::fromString( + 'enum SomeEnum { A }' + ); + $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + $enumDeclarationNode + ); + + $enumInstanceType = $enumStaticType->toEnumInstanceType(); + + $this->assertInstanceOf(EnumInstanceType::class, $enumInstanceType); + } + + /** + * @test + * @return void + */ + public function canBeComparedToOther(): void + { + $enumDeclarationNode = EnumDeclarationNode::fromString( + 'enum SomeEnum { A }' + ); + $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + $enumDeclarationNode + ); + + $this->assertTrue($enumStaticType->is($enumStaticType)); + + $enumInstanceType = EnumInstanceType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + $enumDeclarationNode + ); + + $this->assertTrue($enumStaticType->is($enumInstanceType)); + } } From 42224b5a54a7c668d9a948b3f6a003ec72f51ac8 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 9 Apr 2023 09:58:54 +0200 Subject: [PATCH 10/16] WIP: TASK remove unneeded EnumMemberType::memberBackedValueType --- .../Type/EnumType/EnumMemberType.php | 3 +- .../Type/EnumType/EnumStaticType.php | 2 +- src/TypeSystem/Type/EnumType/EnumTrait.php | 29 +++++-------------- .../Access/AccessTypeResolverTest.php | 2 -- .../Type/EnumType/EnumInstanceTypeTest.php | 24 +++++++++++++-- .../Type/EnumType/EnumStaticTypeTest.php | 20 +++++++++++++ 6 files changed, 51 insertions(+), 29 deletions(-) diff --git a/src/TypeSystem/Type/EnumType/EnumMemberType.php b/src/TypeSystem/Type/EnumType/EnumMemberType.php index c278676c..e27a4a4f 100644 --- a/src/TypeSystem/Type/EnumType/EnumMemberType.php +++ b/src/TypeSystem/Type/EnumType/EnumMemberType.php @@ -28,8 +28,7 @@ final class EnumMemberType implements TypeInterface { public function __construct( public readonly EnumInstanceType|EnumStaticType $enumType, - public readonly string $memberName, - public readonly ?TypeInterface $memberBackedValueType + public readonly string $memberName ) { } diff --git a/src/TypeSystem/Type/EnumType/EnumStaticType.php b/src/TypeSystem/Type/EnumType/EnumStaticType.php index f8a6a0c3..d57d83c8 100644 --- a/src/TypeSystem/Type/EnumType/EnumStaticType.php +++ b/src/TypeSystem/Type/EnumType/EnumStaticType.php @@ -30,6 +30,6 @@ final class EnumStaticType implements TypeInterface public function toEnumInstanceType(): EnumInstanceType { - return new EnumInstanceType($this->moduleId, $this->enumName, $this->membersWithType); + return new EnumInstanceType($this->moduleId, $this->enumName, $this->memberNameHashMap); } } diff --git a/src/TypeSystem/Type/EnumType/EnumTrait.php b/src/TypeSystem/Type/EnumType/EnumTrait.php index ee05982f..cab39a49 100644 --- a/src/TypeSystem/Type/EnumType/EnumTrait.php +++ b/src/TypeSystem/Type/EnumType/EnumTrait.php @@ -24,10 +24,6 @@ use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; -use PackageFactory\ComponentEngine\Parser\Ast\NumberLiteralNode; -use PackageFactory\ComponentEngine\Parser\Ast\StringLiteralNode; -use PackageFactory\ComponentEngine\TypeSystem\Resolver\NumberLiteral\NumberLiteralTypeResolver; -use PackageFactory\ComponentEngine\TypeSystem\Resolver\StringLiteral\StringLiteralTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; trait EnumTrait @@ -35,48 +31,37 @@ trait EnumTrait public function __construct( public readonly ?ModuleId $moduleId, public readonly string $enumName, - private readonly array $membersWithType, + private readonly array $memberNameHashMap, ) { } public static function fromModuleIdAndDeclaration(ModuleId $moduleId, EnumDeclarationNode $enumDeclarationNode): self { - $membersWithType = []; - + $memberNameHashMap = []; foreach ($enumDeclarationNode->memberDeclarations->items as $memberDeclarationNode) { - $membersWithType[$memberDeclarationNode->name] = match ($memberDeclarationNode->value - ? $memberDeclarationNode->value::class - : null - ) { - NumberLiteralNode::class => (new NumberLiteralTypeResolver()) - ->resolveTypeOf($memberDeclarationNode->value), - StringLiteralNode::class => (new StringLiteralTypeResolver()) - ->resolveTypeOf($memberDeclarationNode->value), - null => null - }; + $memberNameHashMap[$memberDeclarationNode->name] = true; } return new self( moduleId: $moduleId, enumName: $enumDeclarationNode->enumName, - membersWithType: $membersWithType + memberNameHashMap: $memberNameHashMap ); } public function getMemberNames(): array { - return array_keys($this->membersWithType); + return array_keys($this->memberNameHashMap); } public function getMemberType(string $memberName): EnumMemberType { - if (!array_key_exists($memberName, $this->membersWithType)) { + if (!array_key_exists($memberName, $this->memberNameHashMap)) { throw new \Exception('@TODO cannot access member ' . $memberName . ' of enum ' . $this->enumName); } return new EnumMemberType( $this, - $memberName, - $this->membersWithType[$memberName] + $memberName ); } diff --git a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php index a2a48d3a..1b2b4170 100644 --- a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php @@ -91,8 +91,6 @@ public function access(): void $this->assertTrue($accessType->enumType->is($someEnum)); $this->assertEquals("A", $accessType->memberName); - - $this->assertTrue($accessType->memberBackedValueType?->is(StringType::get())); } /** diff --git a/test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php b/test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php index 477e13a5..14833aef 100644 --- a/test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php +++ b/test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php @@ -25,6 +25,7 @@ use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PHPUnit\Framework\TestCase; @@ -69,14 +70,33 @@ public function providesNameOfTheEnum(): void */ public function providesMemberNames(): void { - $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + $enumInstanceType = EnumInstanceType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + EnumDeclarationNode::fromString( + 'enum SomeEnum { A B C }' + ) + ); + + $this->assertSame(["A", "B", "C"], $enumInstanceType->getMemberNames()); + } + + /** + * @test + */ + public function providesMemberType(): void + { + $enumInstanceType = EnumInstanceType::fromModuleIdAndDeclaration( ModuleId::fromString("module-a"), EnumDeclarationNode::fromString( 'enum SomeEnum { A B C }' ) ); - $this->assertSame(["A", "B", "C"], $enumStaticType->getMemberNames()); + $enumMemberType = $enumInstanceType->getMemberType('A'); + $this->assertInstanceOf(EnumMemberType::class, $enumMemberType); + + $this->assertSame($enumInstanceType, $enumMemberType->enumType); + $this->assertSame('A', $enumMemberType->memberName); } /** diff --git a/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php b/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php index d1b9e78a..0950dbe8 100644 --- a/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php +++ b/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php @@ -25,6 +25,7 @@ use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PHPUnit\Framework\TestCase; @@ -79,6 +80,25 @@ public function providesMemberNames(): void $this->assertSame(["A", "B", "C"], $enumStaticType->getMemberNames()); } + /** + * @test + */ + public function providesMemberType(): void + { + $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + EnumDeclarationNode::fromString( + 'enum SomeEnum { A B C }' + ) + ); + + $enumMemberType = $enumStaticType->getMemberType('A'); + $this->assertInstanceOf(EnumMemberType::class, $enumMemberType); + + $this->assertSame($enumStaticType, $enumMemberType->enumType); + $this->assertSame('A', $enumMemberType->memberName); + } + /** * @test * @return void From 3c164cb69bbf82afde6d3055d168a4fc695804d7 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 9 Apr 2023 11:04:15 +0200 Subject: [PATCH 11/16] WIP: TASK combine EnumMemberType into EnumInstanceType this modeling is imo correct, as this will allow this in the future ``` Enum.A.value ``` `Enum.A` is an EnumInstanceType (which knows that its name is "A") while Enum is just an EnumStaticType if an enum is referenced like `enum: Enum` an unspecific instance (without known MemberName) will be used --- .../Resolver/Access/AccessTypeResolver.php | 6 +- .../Resolver/Match/MatchTypeResolver.php | 9 +- .../Type/EnumType/EnumInstanceType.php | 30 ++++- .../Type/EnumType/EnumMemberType.php | 39 ------ .../Type/EnumType/EnumStaticType.php | 57 +++++++- src/TypeSystem/Type/EnumType/EnumTrait.php | 77 ----------- .../Access/AccessTypeResolverTest.php | 8 +- .../Type/EnumType/EnumInstanceTypeTest.php | 125 ------------------ .../Type/EnumType/EnumStaticTypeTest.php | 33 ++++- 9 files changed, 120 insertions(+), 264 deletions(-) delete mode 100644 src/TypeSystem/Type/EnumType/EnumMemberType.php delete mode 100644 src/TypeSystem/Type/EnumType/EnumTrait.php delete mode 100644 test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php diff --git a/src/TypeSystem/Resolver/Access/AccessTypeResolver.php b/src/TypeSystem/Resolver/Access/AccessTypeResolver.php index 8d7cebe0..2bceeae2 100644 --- a/src/TypeSystem/Resolver/Access/AccessTypeResolver.php +++ b/src/TypeSystem/Resolver/Access/AccessTypeResolver.php @@ -26,7 +26,7 @@ use PackageFactory\ComponentEngine\Parser\Ast\AccessNode; use PackageFactory\ComponentEngine\TypeSystem\Resolver\Expression\ExpressionTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\Type\StructType\StructType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; @@ -45,13 +45,13 @@ public function resolveTypeOf(AccessNode $accessNode): TypeInterface $rootType = $expressionResolver->resolveTypeOf($accessNode->root); return match ($rootType::class) { - EnumStaticType::class => $this->createEnumMemberType($accessNode, $rootType), + EnumStaticType::class => $this->createEnumInstanceMemberType($accessNode, $rootType), StructType::class => throw new \Exception('@TODO: StructType Access is not implemented'), default => throw new \Exception('@TODO Error: Cannot access on type ' . $rootType::class) }; } - private function createEnumMemberType(AccessNode $accessNode, EnumStaticType $enumType): EnumMemberType + private function createEnumInstanceMemberType(AccessNode $accessNode, EnumStaticType $enumType): EnumInstanceType { if (!( count($accessNode->chain->items) === 1 diff --git a/src/TypeSystem/Resolver/Match/MatchTypeResolver.php b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php index 07ef1f01..4b0e708e 100644 --- a/src/TypeSystem/Resolver/Match/MatchTypeResolver.php +++ b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php @@ -27,7 +27,6 @@ use PackageFactory\ComponentEngine\TypeSystem\Resolver\Expression\ExpressionTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType; use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; @@ -106,12 +105,12 @@ private function resolveTypeOfEnumMatch(MatchNode $matchNode, EnumInstanceType $ } else { foreach ($matchArmNode->left->items as $expressionNode) { $enumMemberType = $expressionTypeResolver->resolveTypeOf($expressionNode); - if (!$enumMemberType instanceof EnumMemberType) { + if (!$enumMemberType instanceof EnumInstanceType) { throw new \Error('@TODO Error: Cannot match enum with type of ' . $enumMemberType::class); } - if (!$enumMemberType->enumType->is($subjectEnumType)) { - throw new \Error('@TODO Error: incompatible enum match: got ' . $enumMemberType->enumType->enumName . ' expected ' . $subjectEnumType->enumName); + if (!$enumMemberType->enumStaticType->is($subjectEnumType->enumStaticType)) { + throw new \Error('@TODO Error: incompatible enum match: got ' . $enumMemberType->enumStaticType->enumName . ' expected ' . $subjectEnumType->enumStaticType->enumName); } if (isset($referencedEnumMembers[$enumMemberType->memberName])) { @@ -128,7 +127,7 @@ private function resolveTypeOfEnumMatch(MatchNode $matchNode, EnumInstanceType $ } if (!$defaultArmPresent) { - foreach ($subjectEnumType->getMemberNames() as $member) { + foreach ($subjectEnumType->enumStaticType->getMemberNames() as $member) { if (!isset($referencedEnumMembers[$member])) { throw new \Error('@TODO Error: member ' . $member . ' not checked'); } diff --git a/src/TypeSystem/Type/EnumType/EnumInstanceType.php b/src/TypeSystem/Type/EnumType/EnumInstanceType.php index 91a91b35..d6422d6b 100644 --- a/src/TypeSystem/Type/EnumType/EnumInstanceType.php +++ b/src/TypeSystem/Type/EnumType/EnumInstanceType.php @@ -26,5 +26,33 @@ final class EnumInstanceType implements TypeInterface { - use EnumTrait; + private function __construct( + public readonly EnumStaticType $enumStaticType, + public readonly ?string $memberName + ) { + if ($memberName !== null && !$enumStaticType->hasMember($memberName)) { + throw new \Exception('@TODO cannot access member ' . $memberName . ' of enum ' . $this->enumStaticType->enumName); + } + } + + public static function fromStaticEnumCreateUnspecificInstance(EnumStaticType $enumStaticType): self + { + return new self( + enumStaticType: $enumStaticType, + memberName: null + ); + } + + public static function fromStaticEnumAndMemberName(EnumStaticType $enumStaticType, string $enumMemberName): self + { + return new self( + enumStaticType: $enumStaticType, + memberName: $enumMemberName + ); + } + + public function is(TypeInterface $other): bool + { + return false; + } } diff --git a/src/TypeSystem/Type/EnumType/EnumMemberType.php b/src/TypeSystem/Type/EnumType/EnumMemberType.php deleted file mode 100644 index e27a4a4f..00000000 --- a/src/TypeSystem/Type/EnumType/EnumMemberType.php +++ /dev/null @@ -1,39 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace PackageFactory\ComponentEngine\TypeSystem\Type\EnumType; - -use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; - -final class EnumMemberType implements TypeInterface -{ - public function __construct( - public readonly EnumInstanceType|EnumStaticType $enumType, - public readonly string $memberName - ) { - } - - public function is(TypeInterface $other): bool - { - return false; - } -} diff --git a/src/TypeSystem/Type/EnumType/EnumStaticType.php b/src/TypeSystem/Type/EnumType/EnumStaticType.php index d57d83c8..d1f13b8d 100644 --- a/src/TypeSystem/Type/EnumType/EnumStaticType.php +++ b/src/TypeSystem/Type/EnumType/EnumStaticType.php @@ -22,14 +22,65 @@ namespace PackageFactory\ComponentEngine\TypeSystem\Type\EnumType; +use PackageFactory\ComponentEngine\Module\ModuleId; +use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; final class EnumStaticType implements TypeInterface { - use EnumTrait; - + public function __construct( + public readonly string $enumName, + private readonly ModuleId $moduleId, + private readonly array $memberNameHashMap, + ) { + } + + public static function fromModuleIdAndDeclaration(ModuleId $moduleId, EnumDeclarationNode $enumDeclarationNode): self + { + $memberNameHashMap = []; + foreach ($enumDeclarationNode->memberDeclarations->items as $memberDeclarationNode) { + $memberNameHashMap[$memberDeclarationNode->name] = true; + } + + return new self( + enumName: $enumDeclarationNode->enumName, + moduleId: $moduleId, + memberNameHashMap: $memberNameHashMap + ); + } + + public function getMemberNames(): array + { + return array_keys($this->memberNameHashMap); + } + + public function hasMember(string $memberName): bool + { + return array_key_exists($memberName, $this->memberNameHashMap); + } + + public function getMemberType(string $memberName): EnumInstanceType + { + return EnumInstanceType::fromStaticEnumAndMemberName( + $this, + $memberName + ); + } + + public function is(TypeInterface $other): bool + { + if ($other === $this) { + return true; + } + if ($other instanceof EnumStaticType) { + return $this->moduleId === $other->moduleId + && $this->enumName === $other->enumName; + } + return false; + } + public function toEnumInstanceType(): EnumInstanceType { - return new EnumInstanceType($this->moduleId, $this->enumName, $this->memberNameHashMap); + return EnumInstanceType::fromStaticEnumCreateUnspecificInstance($this); } } diff --git a/src/TypeSystem/Type/EnumType/EnumTrait.php b/src/TypeSystem/Type/EnumType/EnumTrait.php deleted file mode 100644 index cab39a49..00000000 --- a/src/TypeSystem/Type/EnumType/EnumTrait.php +++ /dev/null @@ -1,77 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace PackageFactory\ComponentEngine\TypeSystem\Type\EnumType; - -use PackageFactory\ComponentEngine\Module\ModuleId; -use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; -use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; - -trait EnumTrait -{ - public function __construct( - public readonly ?ModuleId $moduleId, - public readonly string $enumName, - private readonly array $memberNameHashMap, - ) { - } - - public static function fromModuleIdAndDeclaration(ModuleId $moduleId, EnumDeclarationNode $enumDeclarationNode): self - { - $memberNameHashMap = []; - foreach ($enumDeclarationNode->memberDeclarations->items as $memberDeclarationNode) { - $memberNameHashMap[$memberDeclarationNode->name] = true; - } - - return new self( - moduleId: $moduleId, - enumName: $enumDeclarationNode->enumName, - memberNameHashMap: $memberNameHashMap - ); - } - - public function getMemberNames(): array - { - return array_keys($this->memberNameHashMap); - } - - public function getMemberType(string $memberName): EnumMemberType - { - if (!array_key_exists($memberName, $this->memberNameHashMap)) { - throw new \Exception('@TODO cannot access member ' . $memberName . ' of enum ' . $this->enumName); - } - return new EnumMemberType( - $this, - $memberName - ); - } - - public function is(TypeInterface $other): bool - { - return match ($other::class) { - EnumInstanceType::class, EnumStaticType::class => - $this->moduleId === $other->moduleId - && $this->enumName === $other->enumName, - default => false - }; - } -} diff --git a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php index 1b2b4170..3dfa4fa2 100644 --- a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php @@ -29,7 +29,7 @@ use PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Scope\Fixtures\DummyScope; use PackageFactory\ComponentEngine\TypeSystem\Resolver\Access\AccessTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; @@ -68,7 +68,7 @@ private function resolveAccessType(string $accessAsString, ScopeInterface $scope /** * @test */ - public function access(): void + public function enumMemberAccessOnStaticEnum(): void { $someEnum = EnumStaticType::fromModuleIdAndDeclaration( ModuleId::fromString("module-a"), @@ -86,9 +86,9 @@ public function access(): void $scope ); - $this->assertInstanceOf(EnumMemberType::class, $accessType); + $this->assertInstanceOf(EnumInstanceType::class, $accessType); - $this->assertTrue($accessType->enumType->is($someEnum)); + $this->assertTrue($accessType->enumStaticType->is($someEnum)); $this->assertEquals("A", $accessType->memberName); } diff --git a/test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php b/test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php deleted file mode 100644 index 14833aef..00000000 --- a/test/Unit/TypeSystem/Type/EnumType/EnumInstanceTypeTest.php +++ /dev/null @@ -1,125 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Type\EnumType; - -use PackageFactory\ComponentEngine\Module\ModuleId; -use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; -use PHPUnit\Framework\TestCase; - -final class EnumInstanceTypeTest extends TestCase -{ - /** - * @test - * @return void - */ - public function canBeCreatedFromEnumDeclarationNode(): void - { - $enumDeclarationNode = EnumDeclarationNode::fromString( - 'enum Foo { BAR BAZ }' - ); - $enumType = EnumInstanceType::fromModuleIdAndDeclaration( - ModuleId::fromString("module-a"), - $enumDeclarationNode - ); - - $this->assertInstanceOf(EnumInstanceType::class, $enumType); - } - - /** - * @test - * @return void - */ - public function providesNameOfTheEnum(): void - { - $enumDeclarationNode = EnumDeclarationNode::fromString( - 'enum SomeEnum {}' - ); - $enumType = EnumInstanceType::fromModuleIdAndDeclaration( - ModuleId::fromString("module-a"), - $enumDeclarationNode - ); - - $this->assertEquals('SomeEnum', $enumType->enumName); - } - - /** - * @test - */ - public function providesMemberNames(): void - { - $enumInstanceType = EnumInstanceType::fromModuleIdAndDeclaration( - ModuleId::fromString("module-a"), - EnumDeclarationNode::fromString( - 'enum SomeEnum { A B C }' - ) - ); - - $this->assertSame(["A", "B", "C"], $enumInstanceType->getMemberNames()); - } - - /** - * @test - */ - public function providesMemberType(): void - { - $enumInstanceType = EnumInstanceType::fromModuleIdAndDeclaration( - ModuleId::fromString("module-a"), - EnumDeclarationNode::fromString( - 'enum SomeEnum { A B C }' - ) - ); - - $enumMemberType = $enumInstanceType->getMemberType('A'); - $this->assertInstanceOf(EnumMemberType::class, $enumMemberType); - - $this->assertSame($enumInstanceType, $enumMemberType->enumType); - $this->assertSame('A', $enumMemberType->memberName); - } - - /** - * @test - * @return void - */ - public function canBeComparedToOther(): void - { - $enumDeclarationNode = EnumDeclarationNode::fromString( - 'enum SomeEnum { A }' - ); - $enumInstanceType = EnumInstanceType::fromModuleIdAndDeclaration( - ModuleId::fromString("module-a"), - $enumDeclarationNode - ); - - $this->assertTrue($enumInstanceType->is($enumInstanceType)); - - $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( - ModuleId::fromString("module-a"), - $enumDeclarationNode - ); - - $this->assertTrue($enumInstanceType->is($enumStaticType)); - } -} diff --git a/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php b/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php index 0950dbe8..92c40106 100644 --- a/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php +++ b/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php @@ -25,7 +25,6 @@ use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumMemberType; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PHPUnit\Framework\TestCase; @@ -93,12 +92,29 @@ public function providesMemberType(): void ); $enumMemberType = $enumStaticType->getMemberType('A'); - $this->assertInstanceOf(EnumMemberType::class, $enumMemberType); + $this->assertInstanceOf(EnumInstanceType::class, $enumMemberType); - $this->assertSame($enumStaticType, $enumMemberType->enumType); + $this->assertSame($enumStaticType, $enumMemberType->enumStaticType); $this->assertSame('A', $enumMemberType->memberName); } + /** + * @test + */ + public function canOnlyAccessValidMemberType(): void + { + $this->expectExceptionMessage('@TODO cannot access member NonExistent of enum SomeEnum'); + + $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + EnumDeclarationNode::fromString( + 'enum SomeEnum { A B C }' + ) + ); + + $enumStaticType->getMemberType('NonExistent'); + } + /** * @test * @return void @@ -116,6 +132,10 @@ public function canBeTransformedIntoInstanceType(): void $enumInstanceType = $enumStaticType->toEnumInstanceType(); $this->assertInstanceOf(EnumInstanceType::class, $enumInstanceType); + + $this->assertInstanceOf(EnumStaticType::class, $enumInstanceType->enumStaticType); + + $this->assertNull($enumInstanceType->memberName); } /** @@ -132,13 +152,12 @@ public function canBeComparedToOther(): void $enumDeclarationNode ); - $this->assertTrue($enumStaticType->is($enumStaticType)); - - $enumInstanceType = EnumInstanceType::fromModuleIdAndDeclaration( + $enumStaticType2 = EnumStaticType::fromModuleIdAndDeclaration( ModuleId::fromString("module-a"), $enumDeclarationNode ); - $this->assertTrue($enumStaticType->is($enumInstanceType)); + $this->assertTrue($enumStaticType->is($enumStaticType2)); + $this->assertTrue($enumStaticType2->is($enumStaticType)); } } From b4c48bc39ba1ebbea7c2cdd51221967472c40c13 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 9 Apr 2023 12:54:13 +0200 Subject: [PATCH 12/16] WIP: Task: test providesTheEnumInstanceTypeWhenAnStaticEnumTypeIsReferencedInTheComponentApi --- .../Type/EnumType/EnumInstanceType.php | 9 +++- .../ComponentScope/ComponentScopeTest.php | 42 ++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/TypeSystem/Type/EnumType/EnumInstanceType.php b/src/TypeSystem/Type/EnumType/EnumInstanceType.php index d6422d6b..72c6ea61 100644 --- a/src/TypeSystem/Type/EnumType/EnumInstanceType.php +++ b/src/TypeSystem/Type/EnumType/EnumInstanceType.php @@ -42,7 +42,7 @@ enumStaticType: $enumStaticType, memberName: null ); } - + public static function fromStaticEnumAndMemberName(EnumStaticType $enumStaticType, string $enumMemberName): self { return new self( @@ -53,6 +53,13 @@ enumStaticType: $enumStaticType, public function is(TypeInterface $other): bool { + if ($other === $this) { + return true; + } + if ($other instanceof EnumInstanceType) { + return $other->enumStaticType->is($other->enumStaticType) + && $other->memberName === $this->memberName; + } return false; } } diff --git a/test/Unit/TypeSystem/Scope/ComponentScope/ComponentScopeTest.php b/test/Unit/TypeSystem/Scope/ComponentScope/ComponentScopeTest.php index 726817fc..c75e0ce7 100644 --- a/test/Unit/TypeSystem/Scope/ComponentScope/ComponentScopeTest.php +++ b/test/Unit/TypeSystem/Scope/ComponentScope/ComponentScopeTest.php @@ -22,11 +22,14 @@ namespace PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Scope\ComponentScope; +use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\ComponentDeclarationNode; +use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; use PackageFactory\ComponentEngine\Parser\Ast\TypeReferenceNode; use PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Scope\Fixtures\DummyScope; use PackageFactory\ComponentEngine\TypeSystem\Scope\ComponentScope\ComponentScope; use PackageFactory\ComponentEngine\TypeSystem\Scope\GlobalScope\GlobalScope; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType; use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; use PHPUnit\Framework\TestCase; @@ -63,6 +66,43 @@ public function providesTheTypesOfComponentApiMembers(): void ); } + /** + * @test + * @return void + */ + public function providesTheEnumInstanceTypeWhenAnStaticEnumTypeIsReferencedInTheComponentApi(): void + { + $componentDeclarationAsString = <<{foo} + } + EOT; + $componentDeclarationNode = ComponentDeclarationNode::fromString($componentDeclarationAsString); + $componentScope = new ComponentScope( + componentDeclarationNode: $componentDeclarationNode, + parentScope: new DummyScope([], [ + 'SomeEnum' => $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + EnumDeclarationNode::fromString( + 'enum SomeEnum { A B C }' + ) + ) + ]) + ); + + $expectedType = $enumStaticType->toEnumInstanceType(); + $actualType = $componentScope->lookupTypeFor('foo'); + + $this->assertNotNull($actualType); + + $this->assertTrue( + $expectedType->is($actualType), + sprintf('Expected %s, got %s', $expectedType::class, $actualType::class) + ); + } + /** * @test * @return void @@ -122,4 +162,4 @@ public function resolvesTypeReferencesUsingParentScope(): void sprintf('Expected %s, got %s', $expectedType::class, $actualType::class) ); } -} \ No newline at end of file +} From 5a33cc505df9006e0d8d566f55199e301f293766 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 9 Apr 2023 14:04:12 +0200 Subject: [PATCH 13/16] WIP: Task: add EnumInstanceType::isUnspecified and make getMemberName throw if isUnspecified --- .../Resolver/Match/MatchTypeResolver.php | 6 +++--- .../Type/EnumType/EnumInstanceType.php | 18 ++++++++++++++---- .../Type/EnumType/EnumStaticType.php | 4 ++-- .../Resolver/Access/AccessTypeResolverTest.php | 2 +- .../Type/EnumType/EnumStaticTypeTest.php | 6 ++++-- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/TypeSystem/Resolver/Match/MatchTypeResolver.php b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php index 4b0e708e..5ec9d3a3 100644 --- a/src/TypeSystem/Resolver/Match/MatchTypeResolver.php +++ b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php @@ -113,11 +113,11 @@ private function resolveTypeOfEnumMatch(MatchNode $matchNode, EnumInstanceType $ throw new \Error('@TODO Error: incompatible enum match: got ' . $enumMemberType->enumStaticType->enumName . ' expected ' . $subjectEnumType->enumStaticType->enumName); } - if (isset($referencedEnumMembers[$enumMemberType->memberName])) { - throw new \Error('@TODO Error: Enum path ' . $enumMemberType->memberName . ' was already defined once in this match and cannot be used twice'); + if (isset($referencedEnumMembers[$enumMemberType->getMemberName()])) { + throw new \Error('@TODO Error: Enum path ' . $enumMemberType->getMemberName() . ' was already defined once in this match and cannot be used twice'); } - $referencedEnumMembers[$enumMemberType->memberName] = true; + $referencedEnumMembers[$enumMemberType->getMemberName()] = true; } } diff --git a/src/TypeSystem/Type/EnumType/EnumInstanceType.php b/src/TypeSystem/Type/EnumType/EnumInstanceType.php index 72c6ea61..69729d4a 100644 --- a/src/TypeSystem/Type/EnumType/EnumInstanceType.php +++ b/src/TypeSystem/Type/EnumType/EnumInstanceType.php @@ -28,14 +28,14 @@ final class EnumInstanceType implements TypeInterface { private function __construct( public readonly EnumStaticType $enumStaticType, - public readonly ?string $memberName + private readonly ?string $memberName ) { if ($memberName !== null && !$enumStaticType->hasMember($memberName)) { - throw new \Exception('@TODO cannot access member ' . $memberName . ' of enum ' . $this->enumStaticType->enumName); + throw new \Exception('@TODO cannot access member ' . $memberName . ' of enum ' . $enumStaticType->enumName); } } - public static function fromStaticEnumCreateUnspecificInstance(EnumStaticType $enumStaticType): self + public static function createUnspecifiedEnumInstanceType(EnumStaticType $enumStaticType): self { return new self( enumStaticType: $enumStaticType, @@ -43,7 +43,7 @@ enumStaticType: $enumStaticType, ); } - public static function fromStaticEnumAndMemberName(EnumStaticType $enumStaticType, string $enumMemberName): self + public static function fromStaticEnumTypeAndMemberName(EnumStaticType $enumStaticType, string $enumMemberName): self { return new self( enumStaticType: $enumStaticType, @@ -51,6 +51,16 @@ enumStaticType: $enumStaticType, ); } + public function isUnspecified(): bool + { + return $this->memberName === null; + } + + public function getMemberName(): string + { + return $this->memberName ?? throw new \Exception('@TODO Error cannot access memberName of unspecified instance'); + } + public function is(TypeInterface $other): bool { if ($other === $this) { diff --git a/src/TypeSystem/Type/EnumType/EnumStaticType.php b/src/TypeSystem/Type/EnumType/EnumStaticType.php index d1f13b8d..95ddd521 100644 --- a/src/TypeSystem/Type/EnumType/EnumStaticType.php +++ b/src/TypeSystem/Type/EnumType/EnumStaticType.php @@ -61,7 +61,7 @@ public function hasMember(string $memberName): bool public function getMemberType(string $memberName): EnumInstanceType { - return EnumInstanceType::fromStaticEnumAndMemberName( + return EnumInstanceType::fromStaticEnumTypeAndMemberName( $this, $memberName ); @@ -81,6 +81,6 @@ public function is(TypeInterface $other): bool public function toEnumInstanceType(): EnumInstanceType { - return EnumInstanceType::fromStaticEnumCreateUnspecificInstance($this); + return EnumInstanceType::createUnspecifiedEnumInstanceType($this); } } diff --git a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php index 3dfa4fa2..c03b0a36 100644 --- a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php @@ -90,7 +90,7 @@ public function enumMemberAccessOnStaticEnum(): void $this->assertTrue($accessType->enumStaticType->is($someEnum)); - $this->assertEquals("A", $accessType->memberName); + $this->assertEquals("A", $accessType->getMemberName()); } /** diff --git a/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php b/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php index 92c40106..93953c13 100644 --- a/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php +++ b/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php @@ -95,7 +95,7 @@ public function providesMemberType(): void $this->assertInstanceOf(EnumInstanceType::class, $enumMemberType); $this->assertSame($enumStaticType, $enumMemberType->enumStaticType); - $this->assertSame('A', $enumMemberType->memberName); + $this->assertSame('A', $enumMemberType->getMemberName()); } /** @@ -135,7 +135,7 @@ public function canBeTransformedIntoInstanceType(): void $this->assertInstanceOf(EnumStaticType::class, $enumInstanceType->enumStaticType); - $this->assertNull($enumInstanceType->memberName); + $this->assertTrue($enumInstanceType->isUnspecified()); } /** @@ -152,6 +152,8 @@ public function canBeComparedToOther(): void $enumDeclarationNode ); + $this->assertTrue($enumStaticType->is($enumStaticType)); + $enumStaticType2 = EnumStaticType::fromModuleIdAndDeclaration( ModuleId::fromString("module-a"), $enumDeclarationNode From fc8f09f1937ec43ac701d251eda9afe0f5d22fda Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 9 Apr 2023 15:21:02 +0200 Subject: [PATCH 14/16] WIP: Task: Matching enum value should be referenced statically --- src/TypeSystem/Resolver/Match/MatchTypeResolver.php | 4 ++++ .../TypeSystem/Resolver/Match/MatchTypeResolverTest.php | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/TypeSystem/Resolver/Match/MatchTypeResolver.php b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php index 5ec9d3a3..9301e179 100644 --- a/src/TypeSystem/Resolver/Match/MatchTypeResolver.php +++ b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php @@ -109,6 +109,10 @@ private function resolveTypeOfEnumMatch(MatchNode $matchNode, EnumInstanceType $ throw new \Error('@TODO Error: Cannot match enum with type of ' . $enumMemberType::class); } + if ($enumMemberType->isUnspecified()) { + throw new \Error('@TODO Error: Matching enum value should be referenced statically'); + } + if (!$enumMemberType->enumStaticType->is($subjectEnumType->enumStaticType)) { throw new \Error('@TODO Error: incompatible enum match: got ' . $enumMemberType->enumStaticType->enumName . ' expected ' . $subjectEnumType->enumStaticType->enumName); } diff --git a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php index c4f21a7a..5ef10060 100644 --- a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php @@ -191,6 +191,15 @@ public function malformedEnumExamples(): iterable EOF, "@TODO Error: Cannot match enum with type of PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType" ]; + + yield "Matching enum value should be referenced statically" => [ + <<<'EOF' + match (someEnumValue) { + someEnumValue -> "a" + } + EOF, + '@TODO Error: Matching enum value should be referenced statically' + ]; } /** From 3d55c0ceefa7fd12646c14e716826b9d8b203206 Mon Sep 17 00:00:00 2001 From: Wilhelm Behncke Date: Sat, 5 Aug 2023 18:16:36 +0200 Subject: [PATCH 15/16] TASK: Update deprecated data provider methods in tests/ --- .../TypeSystem/Resolver/Access/AccessTypeResolverTest.php | 8 ++++---- .../TypeSystem/Resolver/Match/MatchTypeResolverTest.php | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php index c03b0a36..e98180ea 100644 --- a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php @@ -37,7 +37,7 @@ final class AccessTypeResolverTest extends TestCase { - public function invalidAccessExamples(): iterable + public static function invalidAccessExamples(): iterable { yield 'access property on primitive string' => [ 'someString.bar', @@ -54,7 +54,7 @@ public function invalidAccessExamples(): iterable "@TODO Error: Cannot access on type PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType" ]; } - + private function resolveAccessType(string $accessAsString, ScopeInterface $scope): TypeInterface { $accessTypeResolver = new AccessTypeResolver( @@ -76,11 +76,11 @@ public function enumMemberAccessOnStaticEnum(): void 'enum SomeEnum { A("Hi") }' ) ); - + $scope = new DummyScope([ 'SomeEnum' => $someEnum ]); - + $accessType = $this->resolveAccessType( 'SomeEnum.A', $scope diff --git a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php index 04d31b98..bda233e1 100644 --- a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php @@ -118,9 +118,9 @@ public function resolvesMatchToResultingType(string $matchAsString, TypeInterfac sprintf('Expected %s, got %s', $expectedType::class, $actualType::class) ); } - - public function malformedEnumExamples(): iterable + + public static function malformedEnumExamples(): iterable { yield "Multiple default keys" => [ <<<'EOF' @@ -154,7 +154,7 @@ public function malformedEnumExamples(): iterable EOF, "@TODO cannot access member NonExistent of enum SomeEnum" ]; - + yield "Duplicate match 1" => [ <<<'EOF' match (someEnumValue) { From f374216e076021a64964092caffa05acec372fae Mon Sep 17 00:00:00 2001 From: Wilhelm Behncke Date: Sat, 5 Aug 2023 18:20:58 +0200 Subject: [PATCH 16/16] TASK: Satisfy phpstan --- src/TypeSystem/Type/EnumType/EnumStaticType.php | 8 ++++++++ .../TypeSystem/Resolver/Access/AccessTypeResolverTest.php | 4 ++++ .../TypeSystem/Resolver/Match/MatchTypeResolverTest.php | 4 +++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/TypeSystem/Type/EnumType/EnumStaticType.php b/src/TypeSystem/Type/EnumType/EnumStaticType.php index 95ddd521..df730104 100644 --- a/src/TypeSystem/Type/EnumType/EnumStaticType.php +++ b/src/TypeSystem/Type/EnumType/EnumStaticType.php @@ -28,6 +28,11 @@ final class EnumStaticType implements TypeInterface { + /** + * @param string $enumName + * @param ModuleId $moduleId + * @param array $memberNameHashMap + */ public function __construct( public readonly string $enumName, private readonly ModuleId $moduleId, @@ -49,6 +54,9 @@ enumName: $enumDeclarationNode->enumName, ); } + /** + * @return string[] + */ public function getMemberNames(): array { return array_keys($this->memberNameHashMap); diff --git a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php index e98180ea..78b548fe 100644 --- a/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php @@ -37,6 +37,9 @@ final class AccessTypeResolverTest extends TestCase { + /** + * @return iterable + */ public static function invalidAccessExamples(): iterable { yield 'access property on primitive string' => [ @@ -87,6 +90,7 @@ public function enumMemberAccessOnStaticEnum(): void ); $this->assertInstanceOf(EnumInstanceType::class, $accessType); + assert($accessType instanceof EnumInstanceType); $this->assertTrue($accessType->enumStaticType->is($someEnum)); diff --git a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php index bda233e1..d2f202a6 100644 --- a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php @@ -119,7 +119,9 @@ public function resolvesMatchToResultingType(string $matchAsString, TypeInterfac ); } - + /** + * @return iterable + */ public static function malformedEnumExamples(): iterable { yield "Multiple default keys" => [