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\CallableType;
13 use CuyZ\Valinor\Type\Types\ClassStringType;
14 use CuyZ\Valinor\Type\Types\NativeClassType;
15 use CuyZ\Valinor\Type\Types\FloatValueType;
16 use CuyZ\Valinor\Type\Types\IntegerRangeType;
17 use CuyZ\Valinor\Type\Types\IntegerValueType;
18 use CuyZ\Valinor\Type\Types\InterfaceType;
19 use CuyZ\Valinor\Type\Types\IntersectionType;
20 use CuyZ\Valinor\Type\Types\IterableType;
21 use CuyZ\Valinor\Type\Types\ListType;
22 use CuyZ\Valinor\Type\Types\MixedType;
23 use CuyZ\Valinor\Type\Types\NativeBooleanType;
24 use CuyZ\Valinor\Type\Types\EnumType;
25 use CuyZ\Valinor\Type\Types\NativeFloatType;
26 use CuyZ\Valinor\Type\Types\NativeIntegerType;
27 use CuyZ\Valinor\Type\Types\NativeStringType;
28 use CuyZ\Valinor\Type\Types\NegativeIntegerType;
29 use CuyZ\Valinor\Type\Types\NonEmptyArrayType;
30 use CuyZ\Valinor\Type\Types\NonEmptyListType;
31 use CuyZ\Valinor\Type\Types\NonEmptyStringType;
32 use CuyZ\Valinor\Type\Types\NonNegativeIntegerType;
33 use CuyZ\Valinor\Type\Types\NonPositiveIntegerType;
34 use CuyZ\Valinor\Type\Types\NullType;
35 use CuyZ\Valinor\Type\Types\NumericStringType;
36 use CuyZ\Valinor\Type\Types\PositiveIntegerType;
37 use CuyZ\Valinor\Type\Types\ShapedArrayElement;
38 use CuyZ\Valinor\Type\Types\ShapedArrayType;
39 use CuyZ\Valinor\Type\Types\StringValueType;
40 use CuyZ\Valinor\Type\Types\UndefinedObjectType;
41 use CuyZ\Valinor\Type\Types\UnionType;
42 use CuyZ\Valinor\Type\Types\UnresolvableType;
45 use function array_keys;
46 use function array_map;
48 use function var_export;
51 final class TypeCompiler
53 public function compile(Type $type): string
55 $class = $type::class;
58 case $type instanceof NullType:
59 case $type instanceof NativeBooleanType:
60 case $type instanceof NativeFloatType:
61 case $type instanceof NativeIntegerType:
62 case $type instanceof PositiveIntegerType:
63 case $type instanceof NegativeIntegerType:
64 case $type instanceof NonPositiveIntegerType:
65 case $type instanceof NonNegativeIntegerType:
66 case $type instanceof NativeStringType:
67 case $type instanceof NonEmptyStringType:
68 case $type instanceof NumericStringType:
69 case $type instanceof UndefinedObjectType:
70 case $type instanceof CallableType:
71 case $type instanceof MixedType:
72 return "$class::get()";
73 case $type instanceof BooleanValueType:
74 return $type->value() === true
77 case $type instanceof IntegerRangeType:
78 return "new $class({$type->min()}, {$type->max()})";
79 case $type instanceof StringValueType:
80 case $type instanceof IntegerValueType:
81 case $type instanceof FloatValueType:
82 $value = var_export($type->value(), true);
84 return "new $class($value)";
85 case $type instanceof IntersectionType:
86 case $type instanceof UnionType:
87 $subTypes = array_map(
88 fn (Type $subType) => $this->compile($subType),
92 return "new $class(" . implode(', ', $subTypes) . ')';
93 case $type instanceof ArrayKeyType:
94 return match ($type->toString()) {
95 'string' => "$class::string()",
96 'int' => "$class::integer()",
97 default => "$class::default()",
99 case $type instanceof ShapedArrayType:
101 fn (ShapedArrayElement $element) => $this->compileArrayShapeElement($element),
104 $shapes = implode(', ', $shapes);
106 return "new $class(...[$shapes])";
107 case $type instanceof ArrayType:
108 case $type instanceof NonEmptyArrayType:
109 if ($type->toString() === 'array' || $type->toString() === 'non-empty-array') {
110 return "$class::native()";
113 $keyType = $this->compile($type->keyType());
114 $subType = $this->compile($type->subType());
116 return "new $class($keyType, $subType)";
117 case $type instanceof ListType:
118 case $type instanceof NonEmptyListType:
119 if ($type->toString() === 'list' || $type->toString() === 'non-empty-list') {
120 return "$class::native()";
123 $subType = $this->compile($type->subType());
125 return "new $class($subType)";
126 case $type instanceof IterableType:
127 $keyType = $this->compile($type->keyType());
128 $subType = $this->compile($type->subType());
130 return "new $class($keyType, $subType)";
131 case $type instanceof NativeClassType:
132 case $type instanceof InterfaceType:
135 foreach ($type->generics() as $key => $generic) {
136 $generics[] = var_export($key, true) . ' => ' . $this->compile($generic);
139 $generics = implode(', ', $generics);
141 if ($type instanceof InterfaceType) {
142 return "new $class('{$type->className()}', [$generics])";
145 $parent = $type->hasParent()
146 ? $this->compile($type->parent())
149 return "new $class('{$type->className()}', [$generics], $parent)";
150 case $type instanceof ClassStringType:
151 if (null === $type->subType()) {
152 return "new $class()";
155 $subType = $this->compile($type->subType());
157 return "new $class($subType)";
158 case $type instanceof EnumType:
159 $enumName = var_export($type->className(), true);
160 $pattern = var_export($type->pattern(), true);
163 fn (string|int $key, UnitEnum $case) => var_export($key, true) . ' => ' . var_export($case, true),
164 array_keys($type->cases()),
167 $cases = implode(', ', $cases);
169 return "new $class($enumName, $pattern, [$cases])";
170 case $type instanceof UnresolvableType:
171 $raw = var_export($type->toString(), true);
172 $message = var_export($type->message(), true);
174 return "new $class($raw, $message)";
176 throw new TypeCannotBeCompiled($type);
180 private function compileArrayShapeElement(ShapedArrayElement $element): string
182 $class = ShapedArrayElement::class;
183 $key = $this->compile($element->key());
184 $type = $this->compile($element->type());
185 $optional = var_export($element->isOptional(), true);
187 return "new $class($key, $type, $optional)";