diff --git a/src/Module/Loader/ModuleFile/ModuleFileLoader.php b/src/Module/Loader/ModuleFile/ModuleFileLoader.php index eaf342f8..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; @@ -32,7 +33,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; @@ -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 => EnumType::fromEnumDeclarationNode($export->declaration), + EnumDeclarationNode::class => EnumStaticType::fromModuleIdAndDeclaration( + $moduleId, + $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 989fb82d..7e046a6a 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/Access/AccessTypeResolver.php b/src/TypeSystem/Resolver/Access/AccessTypeResolver.php new file mode 100644 index 00000000..2bceeae2 --- /dev/null +++ b/src/TypeSystem/Resolver/Access/AccessTypeResolver.php @@ -0,0 +1,67 @@ +. + */ + +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\EnumInstanceType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; +use PackageFactory\ComponentEngine\TypeSystem\Type\StructType\StructType; +use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; +use PackageFactory\ComponentEngine\Definition\AccessType; + +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); + + return match ($rootType::class) { + 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 createEnumInstanceMemberType(AccessNode $accessNode, EnumStaticType $enumType): EnumInstanceType + { + 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; + + return $enumType->getMemberType($enumMemberName); + } +} 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/src/TypeSystem/Resolver/Match/MatchTypeResolver.php b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php index ee24cd7c..9301e179 100644 --- a/src/TypeSystem/Resolver/Match/MatchTypeResolver.php +++ b/src/TypeSystem/Resolver/Match/MatchTypeResolver.php @@ -27,7 +27,7 @@ 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\EnumType; +use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType; use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; @@ -67,7 +67,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 +86,57 @@ private function resolveTypeOfBooleanMatch(MatchNode $matchNode): TypeInterface } } - private function resolveTypeOfEnumMatch(MatchNode $matchNode): TypeInterface + private function resolveTypeOfEnumMatch(MatchNode $matchNode, EnumInstanceType $subjectEnumType): TypeInterface { $expressionTypeResolver = new ExpressionTypeResolver( scope: $this->scope ); $types = []; + $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) { + $enumMemberType = $expressionTypeResolver->resolveTypeOf($expressionNode); + if (!$enumMemberType instanceof 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); + } + + 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->getMemberName()] = true; + } + } + $types[] = $expressionTypeResolver->resolveTypeOf( $matchArmNode->right ); } - // @TODO: Ensure that match is complete + if (!$defaultArmPresent) { + foreach ($subjectEnumType->enumStaticType->getMemberNames() as $member) { + if (!isset($referencedEnumMembers[$member])) { + throw new \Error('@TODO Error: member ' . $member . ' not checked'); + } + } + } return UnionType::of(...$types); } @@ -108,7 +152,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 EnumInstanceType => $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 6d26b19d..853912d0 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); diff --git a/src/TypeSystem/Scope/ModuleScope/ModuleScope.php b/src/TypeSystem/Scope/ModuleScope/ModuleScope.php index ed83a0cc..55e386c9 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); } diff --git a/src/TypeSystem/Type/EnumType/EnumInstanceType.php b/src/TypeSystem/Type/EnumType/EnumInstanceType.php new file mode 100644 index 00000000..69729d4a --- /dev/null +++ b/src/TypeSystem/Type/EnumType/EnumInstanceType.php @@ -0,0 +1,75 @@ +. + */ + +declare(strict_types=1); + +namespace PackageFactory\ComponentEngine\TypeSystem\Type\EnumType; + +use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; + +final class EnumInstanceType implements TypeInterface +{ + private function __construct( + public readonly EnumStaticType $enumStaticType, + private readonly ?string $memberName + ) { + if ($memberName !== null && !$enumStaticType->hasMember($memberName)) { + throw new \Exception('@TODO cannot access member ' . $memberName . ' of enum ' . $enumStaticType->enumName); + } + } + + public static function createUnspecifiedEnumInstanceType(EnumStaticType $enumStaticType): self + { + return new self( + enumStaticType: $enumStaticType, + memberName: null + ); + } + + public static function fromStaticEnumTypeAndMemberName(EnumStaticType $enumStaticType, string $enumMemberName): self + { + return new self( + enumStaticType: $enumStaticType, + memberName: $enumMemberName + ); + } + + 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) { + return true; + } + if ($other instanceof EnumInstanceType) { + return $other->enumStaticType->is($other->enumStaticType) + && $other->memberName === $this->memberName; + } + return false; + } +} diff --git a/src/TypeSystem/Type/EnumType/EnumStaticType.php b/src/TypeSystem/Type/EnumType/EnumStaticType.php index fe6ca2bb..df730104 100644 --- a/src/TypeSystem/Type/EnumType/EnumStaticType.php +++ b/src/TypeSystem/Type/EnumType/EnumStaticType.php @@ -22,24 +22,73 @@ 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 { - private function __construct(public readonly string $enumName) - { + /** + * @param string $enumName + * @param ModuleId $moduleId + * @param array $memberNameHashMap + */ + public function __construct( + public readonly string $enumName, + private readonly ModuleId $moduleId, + private readonly array $memberNameHashMap, + ) { } - public static function fromEnumDeclarationNode(EnumDeclarationNode $enumDeclarationNode): self + 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 + enumName: $enumDeclarationNode->enumName, + moduleId: $moduleId, + memberNameHashMap: $memberNameHashMap + ); + } + + /** + * @return string[] + */ + 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::fromStaticEnumTypeAndMemberName( + $this, + $memberName ); } public function is(TypeInterface $other): bool { - return $other === $this; + 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 EnumInstanceType::createUnspecifiedEnumInstanceType($this); } } diff --git a/src/TypeSystem/Type/EnumType/EnumType.php b/src/TypeSystem/Type/EnumType/EnumType.php deleted file mode 100644 index c13cc1e6..00000000 --- a/src/TypeSystem/Type/EnumType/EnumType.php +++ /dev/null @@ -1,45 +0,0 @@ -. - */ - -declare(strict_types=1); - -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 $other === $this; - } -} 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 07b98195..fe0760bb 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 @@ -79,24 +72,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/Access/AccessTranspilerTest.php b/test/Unit/Target/Php/Transpiler/Access/AccessTranspilerTest.php index 1c3ffc8a..582ce398 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; @@ -65,7 +66,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 }' ) 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 c8225f7a..418c9865 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 }' ) 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 0a4371e6..a31a8bbe 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; @@ -30,7 +31,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 +50,8 @@ protected function getTypeReferenceTranspiler(): TypeReferenceTranspiler 'Button' => ComponentType::fromComponentDeclarationNode( ComponentDeclarationNode::fromString('component Button { return "" }') ), - 'DayOfWeek' => EnumType::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 new file mode 100644 index 00000000..78b548fe --- /dev/null +++ b/test/Unit/TypeSystem/Resolver/Access/AccessTypeResolverTest.php @@ -0,0 +1,127 @@ +. + */ + +declare(strict_types=1); + +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; +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\EnumInstanceType; +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 +{ + /** + * @return iterable + */ + public static function invalidAccessExamples(): iterable + { + yield 'access property on primitive string' => [ + 'someString.bar', + '@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' + ]; + + yield "access enum member on non static enum instance" => [ + 'someEnumValue.A', + "@TODO Error: Cannot access on type PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumInstanceType" + ]; + } + + 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 enumMemberAccessOnStaticEnum(): void + { + $someEnum = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + EnumDeclarationNode::fromString( + 'enum SomeEnum { A("Hi") }' + ) + ); + + $scope = new DummyScope([ + 'SomeEnum' => $someEnum + ]); + + $accessType = $this->resolveAccessType( + 'SomeEnum.A', + $scope + ); + + $this->assertInstanceOf(EnumInstanceType::class, $accessType); + assert($accessType instanceof EnumInstanceType); + + $this->assertTrue($accessType->enumStaticType->is($someEnum)); + + $this->assertEquals("A", $accessType->getMemberName()); + } + + /** + * @dataProvider invalidAccessExamples + * @test + */ + public function invalidAccessResultsInError(string $accessAsString, string $expectedErrorMessage): void + { + $this->expectExceptionMessage($expectedErrorMessage); + + $someEnum = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + EnumDeclarationNode::fromString( + 'enum SomeEnum { A }' + ) + ); + $scope = new DummyScope([ + 'someString' => StringType::get(), + 'SomeEnum' => $someEnum, + 'someEnumValue' => $someEnum->toEnumInstanceType() + ]); + $accessTypeResolver = new AccessTypeResolver( + scope: $scope + ); + $accessNode = ExpressionNode::fromString($accessAsString)->root; + assert($accessNode instanceof AccessNode); + + $accessTypeResolver->resolveTypeOf($accessNode); + } +} diff --git a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php index da4588c4..d2f202a6 100644 --- a/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/Match/MatchTypeResolverTest.php @@ -22,13 +22,14 @@ 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; 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\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\UnionType\UnionType; @@ -59,8 +60,24 @@ public static 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,8 @@ public static function matchExamples(): array */ public function resolvesMatchToResultingType(string $matchAsString, TypeInterface $expectedType): void { - $someEnumType = EnumType::fromEnumDeclarationNode( + $someStaticEnumType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), EnumDeclarationNode::fromString( 'enum SomeEnum { A B C }' ) @@ -84,7 +102,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 +118,121 @@ public function resolvesMatchToResultingType(string $matchAsString, TypeInterfac sprintf('Expected %s, got %s', $expectedType::class, $actualType::class) ); } + + /** + * @return iterable + */ + public static 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: 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' + ]; + } + + /** + * @dataProvider malformedEnumExamples + * @test + */ + public function malformedMatchCannotBeResolved(string $matchAsString, string $expectedErrorMessage): void + { + $this->expectExceptionMessage($expectedErrorMessage); + $someStaticEnumType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + EnumDeclarationNode::fromString( + 'enum SomeEnum { A B C }' + ) + ); + $scope = new DummyScope([ + 'SomeEnum' => $someStaticEnumType, + 'someEnumValue' => $someStaticEnumType->toEnumInstanceType(), + 'OtherEnum' => EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + EnumDeclarationNode::fromString('enum OtherEnum { A }') + ) + + ]); + + $matchTypeResolver = new MatchTypeResolver( + scope: $scope + ); + $matchNode = ExpressionNode::fromString($matchAsString)->root; + assert($matchNode instanceof MatchNode); + + $matchTypeResolver->resolveTypeOf($matchNode); + } } 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 +} diff --git a/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php b/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php index 84d68075..6b630e01 100644 --- a/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php +++ b/test/Unit/TypeSystem/Type/EnumType/EnumStaticTypeTest.php @@ -20,9 +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; @@ -37,7 +39,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,13 +56,88 @@ public function providesNameOfTheEnum(): void $enumDeclarationNode = EnumDeclarationNode::fromString( 'enum SomeEnum {}' ); - $enumStaticType = EnumStaticType::fromEnumDeclarationNode( + $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), $enumDeclarationNode ); $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 + */ + public function providesMemberType(): void + { + $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + EnumDeclarationNode::fromString( + 'enum SomeEnum { A B C }' + ) + ); + + $enumMemberType = $enumStaticType->getMemberType('A'); + $this->assertInstanceOf(EnumInstanceType::class, $enumMemberType); + + $this->assertSame($enumStaticType, $enumMemberType->enumStaticType); + $this->assertSame('A', $enumMemberType->getMemberName()); + } + + /** + * @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 + */ + 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); + + $this->assertInstanceOf(EnumStaticType::class, $enumInstanceType->enumStaticType); + + $this->assertTrue($enumInstanceType->isUnspecified()); + } + /** * @test * @return void @@ -65,12 +145,38 @@ public function providesNameOfTheEnum(): void public function isEquivalentToItself(): void { $enumDeclarationNode = EnumDeclarationNode::fromString( - 'enum SomeEnum {}' + 'enum SomeEnum { A }' ); - $enumStaticType = EnumStaticType::fromEnumDeclarationNode( + $enumStaticType = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), $enumDeclarationNode ); $this->assertTrue($enumStaticType->is($enumStaticType)); } + + /** + * @test + * @return void + */ + public function canBeComparedToOther(): void + { + $enumDeclarationNode1 = EnumDeclarationNode::fromString( + 'enum SomeEnum { A }' + ); + $enumStaticType1 = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + $enumDeclarationNode1 + ); + $enumDeclarationNode2 = EnumDeclarationNode::fromString( + 'enum SomeEnum { A }' + ); + $enumStaticType2 = EnumStaticType::fromModuleIdAndDeclaration( + ModuleId::fromString("module-a"), + $enumDeclarationNode2 + ); + + $this->assertTrue($enumStaticType1->is($enumStaticType2)); + $this->assertTrue($enumStaticType2->is($enumStaticType1)); + } } diff --git a/test/Unit/TypeSystem/Type/EnumType/EnumTypeTest.php b/test/Unit/TypeSystem/Type/EnumType/EnumTypeTest.php deleted file mode 100644 index 574101ee..00000000 --- a/test/Unit/TypeSystem/Type/EnumType/EnumTypeTest.php +++ /dev/null @@ -1,76 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Type\EnumType; - -use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; -use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType; -use PHPUnit\Framework\TestCase; - -final class EnumTypeTest extends TestCase -{ - /** - * @test - * @return void - */ - public function canBeCreatedFromEnumDeclarationNode(): void - { - $enumDeclarationNode = EnumDeclarationNode::fromString( - 'enum Foo { BAR BAZ }' - ); - $enumType = EnumType::fromEnumDeclarationNode($enumDeclarationNode); - - $this->assertInstanceOf(EnumType::class, $enumType); - } - - /** - * @test - * @return void - */ - public function providesNameOfTheEnum(): void - { - $enumDeclarationNode = EnumDeclarationNode::fromString( - 'enum SomeEnum {}' - ); - $enumType = EnumType::fromEnumDeclarationNode( - $enumDeclarationNode - ); - - $this->assertEquals('SomeEnum', $enumType->enumName); - } - - /** - * @test - * @return void - */ - public function isEquivalentToItself(): void - { - $enumDeclarationNode = EnumDeclarationNode::fromString( - 'enum SomeEnum {}' - ); - $enumType = EnumType::fromEnumDeclarationNode( - $enumDeclarationNode - ); - - $this->assertTrue($enumType->is($enumType)); - } -}