c4f06c3591a4c8579878394f800f8a5183d57b37
[GitHub/WoltLab/WCF.git] /
1 <?php
2
3 declare(strict_types=1);
4
5 namespace CuyZ\Valinor\Definition\Repository\Cache\Compiler;
6
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;
40 use UnitEnum;
41
42 use function array_keys;
43 use function array_map;
44 use function implode;
45 use function var_export;
46
47 /** @internal */
48 final class TypeCompiler
49 {
50 public function compile(Type $type): string
51 {
52 $class = $type::class;
53
54 switch (true) {
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
69 ? "$class::true()"
70 : "$class::false()";
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);
77
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),
83 $type->types()
84 );
85
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()",
92 };
93 case $type instanceof ShapedArrayType:
94 $shapes = array_map(
95 fn (ShapedArrayElement $element) => $this->compileArrayShapeElement($element),
96 $type->elements()
97 );
98 $shapes = implode(', ', $shapes);
99
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()";
105 }
106
107 $keyType = $this->compile($type->keyType());
108 $subType = $this->compile($type->subType());
109
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()";
115 }
116
117 $subType = $this->compile($type->subType());
118
119 return "new $class($subType)";
120 case $type instanceof IterableType:
121 $keyType = $this->compile($type->keyType());
122 $subType = $this->compile($type->subType());
123
124 return "new $class($keyType, $subType)";
125 case $type instanceof NativeClassType:
126 case $type instanceof InterfaceType:
127 $generics = [];
128
129 foreach ($type->generics() as $key => $generic) {
130 $generics[] = var_export($key, true) . ' => ' . $this->compile($generic);
131 }
132
133 $generics = implode(', ', $generics);
134
135 if ($type instanceof InterfaceType) {
136 return "new $class('{$type->className()}', [$generics])";
137 }
138
139 $parent = $type->hasParent()
140 ? $this->compile($type->parent())
141 : 'null';
142
143 return "new $class('{$type->className()}', [$generics], $parent)";
144 case $type instanceof ClassStringType:
145 if (null === $type->subType()) {
146 return "new $class()";
147 }
148
149 $subType = $this->compile($type->subType());
150
151 return "new $class($subType)";
152 case $type instanceof EnumType:
153 $enumName = var_export($type->className(), true);
154 $pattern = var_export($type->pattern(), true);
155
156 $cases = array_map(
157 fn (string|int $key, UnitEnum $case) => var_export($key, true) . ' => ' . var_export($case, true),
158 array_keys($type->cases()),
159 $type->cases()
160 );
161 $cases = implode(', ', $cases);
162
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);
167
168 return "new $class($raw, $message)";
169 default:
170 throw new TypeCannotBeCompiled($type);
171 }
172 }
173
174 private function compileArrayShapeElement(ShapedArrayElement $element): string
175 {
176 $class = ShapedArrayElement::class;
177 $key = $this->compile($element->key());
178 $type = $this->compile($element->type());
179 $optional = var_export($element->isOptional(), true);
180
181 return "new $class($key, $type, $optional)";
182 }
183 }