diff --git a/src/Target/Php/Transpiler/TypeReference/TypeReferenceTranspiler.php b/src/Target/Php/Transpiler/TypeReference/TypeReferenceTranspiler.php index af5a7326..1a3df137 100644 --- a/src/Target/Php/Transpiler/TypeReference/TypeReferenceTranspiler.php +++ b/src/Target/Php/Transpiler/TypeReference/TypeReferenceTranspiler.php @@ -27,10 +27,13 @@ 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\NullType\NullType; 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\Type\StructType\StructType; +use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType; +use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; final class TypeReferenceTranspiler { @@ -43,7 +46,34 @@ public function __construct( public function transpile(TypeReferenceNode $typeReferenceNode): string { $type = $this->scope->resolveTypeReference($typeReferenceNode); - $phpTypeReference = match ($type::class) { + + return match ($type::class) { + UnionType::class => $this->transpileUnionType($type, $typeReferenceNode), + default => $this->transpileNonUnionType($type, $typeReferenceNode) + }; + } + + private function transpileUnionType(UnionType $unionType, TypeReferenceNode $typeReferenceNode): string + { + if (count($unionType->members) === 2) { + $otherTypeIfTypeIsNullable = match (true) { + $unionType->members[0]->is(NullType::get()) => $unionType->members[1], + $unionType->members[1]->is(NullType::get()) => $unionType->members[0], + default => null + }; + + if ($otherTypeIfTypeIsNullable) { + return $this->transpileNullableType($otherTypeIfTypeIsNullable, $typeReferenceNode); + } + } + + throw new \Exception('@TODO Transpilation of complex union types is not implemented'); + + } + + private function transpileNonUnionType(TypeInterface $type, TypeReferenceNode $typeReferenceNode): string + { + return match ($type::class) { NumberType::class => 'int|float', StringType::class => 'string', BooleanType::class => 'bool', @@ -51,14 +81,16 @@ public function transpile(TypeReferenceNode $typeReferenceNode): string ComponentType::class => $this->strategy->getPhpTypeReferenceForComponentType($type, $typeReferenceNode), EnumType::class => $this->strategy->getPhpTypeReferenceForEnumType($type, $typeReferenceNode), StructType::class => $this->strategy->getPhpTypeReferenceForStructType($type, $typeReferenceNode), + UnionType::class => throw new \Exception("There is no such thing as nested unions, think again."), default => $this->strategy->getPhpTypeReferenceForCustomType($type, $typeReferenceNode) }; + } - return $typeReferenceNode->isOptional - ? match ($phpTypeReference) { - 'int|float' => 'null|int|float', - default => '?' . $phpTypeReference - } - : $phpTypeReference; + private function transpileNullableType(TypeInterface $type, TypeReferenceNode $typeReferenceNode): string + { + if ($type->is(NumberType::get())) { + return 'null|int|float'; + } + return '?' . $this->transpileNonUnionType($type, $typeReferenceNode); } } diff --git a/src/TypeSystem/Scope/GlobalScope/GlobalScope.php b/src/TypeSystem/Scope/GlobalScope/GlobalScope.php index a6ab1b87..ada0f5cb 100644 --- a/src/TypeSystem/Scope/GlobalScope/GlobalScope.php +++ b/src/TypeSystem/Scope/GlobalScope/GlobalScope.php @@ -22,13 +22,14 @@ namespace PackageFactory\ComponentEngine\TypeSystem\Scope\GlobalScope; -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\NullType\NullType; 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\Type\UnionType\UnionType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; final class GlobalScope implements ScopeInterface @@ -51,12 +52,16 @@ public function lookupTypeFor(string $name): ?TypeInterface public function resolveTypeReference(TypeReferenceNode $typeReferenceNode): TypeInterface { - return match ($typeReferenceNode->name) { + $type = match ($typeReferenceNode->name) { 'string' => StringType::get(), 'number' => NumberType::get(), 'boolean' => BooleanType::get(), 'slot' => SlotType::get(), default => throw new \Exception('@TODO: Unknown Type ' . $typeReferenceNode->name) }; + if ($typeReferenceNode->isOptional) { + $type = UnionType::of($type, NullType::get()); + } + return $type; } } diff --git a/src/TypeSystem/Scope/ModuleScope/ModuleScope.php b/src/TypeSystem/Scope/ModuleScope/ModuleScope.php index 034da99e..80748ea2 100644 --- a/src/TypeSystem/Scope/ModuleScope/ModuleScope.php +++ b/src/TypeSystem/Scope/ModuleScope/ModuleScope.php @@ -26,6 +26,8 @@ use PackageFactory\ComponentEngine\Parser\Ast\ModuleNode; use PackageFactory\ComponentEngine\Parser\Ast\TypeReferenceNode; use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; +use PackageFactory\ComponentEngine\TypeSystem\Type\NullType\NullType; +use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; final class ModuleScope implements ScopeInterface @@ -45,7 +47,11 @@ public function lookupTypeFor(string $name): ?TypeInterface public function resolveTypeReference(TypeReferenceNode $typeReferenceNode): TypeInterface { if ($importNode = $this->moduleNode->imports->get($typeReferenceNode->name)) { - return $this->loader->resolveTypeOfImport($importNode); + $type = $this->loader->resolveTypeOfImport($importNode); + if ($typeReferenceNode->isOptional) { + $type = UnionType::of($type, NullType::get()); + } + return $type; } if ($this->parentScope) { diff --git a/src/TypeSystem/Type/UnionType/UnionType.php b/src/TypeSystem/Type/UnionType/UnionType.php index 64e1f0cb..fe6b1c7f 100644 --- a/src/TypeSystem/Type/UnionType/UnionType.php +++ b/src/TypeSystem/Type/UnionType/UnionType.php @@ -29,7 +29,7 @@ final class UnionType implements TypeInterface /** * @var TypeInterface[] */ - private array $members; + public array $members; private function __construct(TypeInterface ...$members) { diff --git a/test/Unit/TypeSystem/Scope/Fixtures/DummyScope.php b/test/Unit/TypeSystem/Scope/Fixtures/DummyScope.php index 954a1f3c..cb3440b9 100644 --- a/test/Unit/TypeSystem/Scope/Fixtures/DummyScope.php +++ b/test/Unit/TypeSystem/Scope/Fixtures/DummyScope.php @@ -24,6 +24,8 @@ use PackageFactory\ComponentEngine\Parser\Ast\TypeReferenceNode; use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; +use PackageFactory\ComponentEngine\TypeSystem\Type\NullType\NullType; +use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; final class DummyScope implements ScopeInterface @@ -46,6 +48,9 @@ public function lookupTypeFor(string $name): ?TypeInterface public function resolveTypeReference(TypeReferenceNode $typeReferenceNode): TypeInterface { if ($type = $this->typeNameToTypeMap[$typeReferenceNode->name] ?? null) { + if ($typeReferenceNode->isOptional) { + $type = UnionType::of($type, NullType::get()); + } return $type; }