3 declare(strict_types=1);
5 namespace CuyZ\Valinor\Definition\Repository\Cache\Compiler;
7 use CuyZ\Valinor\Definition\Repository\Cache\Compiler\Exception\TypeCannotBeCompiled;
8 use CuyZ\Valinor\Type\Type;
9 use CuyZ\Valinor\Type\Types\ArrayKeyType;
10 use CuyZ\Valinor\Type\Types\ArrayType;
11 use CuyZ\Valinor\Type\Types\BooleanValueType;
12 use CuyZ\Valinor\Type\Types\ClassStringType;
13 use CuyZ\Valinor\Type\Types\NativeClassType;
14 use CuyZ\Valinor\Type\Types\FloatValueType;
15 use CuyZ\Valinor\Type\Types\IntegerRangeType;
16 use CuyZ\Valinor\Type\Types\IntegerValueType;
17 use CuyZ\Valinor\Type\Types\InterfaceType;
18 use CuyZ\Valinor\Type\Types\IntersectionType;
19 use CuyZ\Valinor\Type\Types\IterableType;
20 use CuyZ\Valinor\Type\Types\ListType;
21 use CuyZ\Valinor\Type\Types\MixedType;
22 use CuyZ\Valinor\Type\Types\NativeBooleanType;
23 use CuyZ\Valinor\Type\Types\EnumType;
24 use CuyZ\Valinor\Type\Types\NativeFloatType;
25 use CuyZ\Valinor\Type\Types\NativeIntegerType;
26 use CuyZ\Valinor\Type\Types\NativeStringType;
27 use CuyZ\Valinor\Type\Types\NegativeIntegerType;
28 use CuyZ\Valinor\Type\Types\NonEmptyArrayType;
29 use CuyZ\Valinor\Type\Types\NonEmptyListType;
30 use CuyZ\Valinor\Type\Types\NonEmptyStringType;
31 use CuyZ\Valinor\Type\Types\NullType;
32 use CuyZ\Valinor\Type\Types\NumericStringType;
33 use CuyZ\Valinor\Type\Types\PositiveIntegerType;
34 use CuyZ\Valinor\Type\Types\ShapedArrayElement;
35 use CuyZ\Valinor\Type\Types\ShapedArrayType;
36 use CuyZ\Valinor\Type\Types\StringValueType;
37 use CuyZ\Valinor\Type\Types\UndefinedObjectType;
38 use CuyZ\Valinor\Type\Types\UnionType;
39 use CuyZ\Valinor\Type\Types\UnresolvableType;
42 use function array_keys;
43 use function array_map;
45 use function var_export;
48 final class TypeCompiler
50 public function compile(Type $type): string
52 $class = $type::class;
55 case $type instanceof NullType:
56 case $type instanceof NativeBooleanType:
57 case $type instanceof NativeFloatType:
58 case $type instanceof NativeIntegerType:
59 case $type instanceof PositiveIntegerType:
60 case $type instanceof NegativeIntegerType:
61 case $type instanceof NativeStringType:
62 case $type instanceof NonEmptyStringType:
63 case $type instanceof NumericStringType:
64 case $type instanceof UndefinedObjectType:
65 case $type instanceof MixedType:
66 return "$class::get()";
67 case $type instanceof BooleanValueType:
68 return $type->value() === true
71 case $type instanceof IntegerRangeType:
72 return "new $class({$type->min()}, {$type->max()})";
73 case $type instanceof StringValueType:
74 case $type instanceof IntegerValueType:
75 case $type instanceof FloatValueType:
76 $value = var_export($type->value(), true);
78 return "new $class($value)";
79 case $type instanceof IntersectionType:
80 case $type instanceof UnionType:
81 $subTypes = array_map(
82 fn (Type $subType) => $this->compile($subType),
86 return "new $class(" . implode(', ', $subTypes) . ')';
87 case $type instanceof ArrayKeyType:
88 return match ($type->toString()) {
89 'string' => "$class::string()",
90 'int' => "$class::integer()",
91 default => "$class::default()",
93 case $type instanceof ShapedArrayType:
95 fn (ShapedArrayElement $element) => $this->compileArrayShapeElement($element),
98 $shapes = implode(', ', $shapes);
100 return "new $class(...[$shapes])";
101 case $type instanceof ArrayType:
102 case $type instanceof NonEmptyArrayType:
103 if ($type->toString() === 'array' || $type->toString() === 'non-empty-array') {
104 return "$class::native()";
107 $keyType = $this->compile($type->keyType());
108 $subType = $this->compile($type->subType());
110 return "new $class($keyType, $subType)";
111 case $type instanceof ListType:
112 case $type instanceof NonEmptyListType:
113 if ($type->toString() === 'list' || $type->toString() === 'non-empty-list') {
114 return "$class::native()";
117 $subType = $this->compile($type->subType());
119 return "new $class($subType)";
120 case $type instanceof IterableType:
121 $keyType = $this->compile($type->keyType());
122 $subType = $this->compile($type->subType());
124 return "new $class($keyType, $subType)";
125 case $type instanceof NativeClassType:
126 case $type instanceof InterfaceType:
129 foreach ($type->generics() as $key => $generic) {
130 $generics[] = var_export($key, true) . ' => ' . $this->compile($generic);
133 $generics = implode(', ', $generics);
135 if ($type instanceof InterfaceType) {
136 return "new $class('{$type->className()}', [$generics])";
139 $parent = $type->hasParent()
140 ? $this->compile($type->parent())
143 return "new $class('{$type->className()}', [$generics], $parent)";
144 case $type instanceof ClassStringType:
145 if (null === $type->subType()) {
146 return "new $class()";
149 $subType = $this->compile($type->subType());
151 return "new $class($subType)";
152 case $type instanceof EnumType:
153 $enumName = var_export($type->className(), true);
154 $pattern = var_export($type->pattern(), true);
157 fn (string|int $key, UnitEnum $case) => var_export($key, true) . ' => ' . var_export($case, true),
158 array_keys($type->cases()),
161 $cases = implode(', ', $cases);
163 return "new $class($enumName, $pattern, [$cases])";
164 case $type instanceof UnresolvableType:
165 $raw = var_export($type->toString(), true);
166 $message = var_export($type->getMessage(), true);
168 return "new $class($raw, $message)";
170 throw new TypeCannotBeCompiled($type);
174 private function compileArrayShapeElement(ShapedArrayElement $element): string
176 $class = ShapedArrayElement::class;
177 $key = $this->compile($element->key());
178 $type = $this->compile($element->type());
179 $optional = var_export($element->isOptional(), true);
181 return "new $class($key, $type, $optional)";