1b05675e27a001ff371a3123f07a9174357f2d27
[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\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;
43 use UnitEnum;
44
45 use function array_keys;
46 use function array_map;
47 use function implode;
48 use function var_export;
49
50 /** @internal */
51 final class TypeCompiler
52 {
53 public function compile(Type $type): string
54 {
55 $class = $type::class;
56
57 switch (true) {
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
75 ? "$class::true()"
76 : "$class::false()";
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);
83
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),
89 $type->types()
90 );
91
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()",
98 };
99 case $type instanceof ShapedArrayType:
100 $shapes = array_map(
101 fn (ShapedArrayElement $element) => $this->compileArrayShapeElement($element),
102 $type->elements()
103 );
104 $shapes = implode(', ', $shapes);
105
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()";
111 }
112
113 $keyType = $this->compile($type->keyType());
114 $subType = $this->compile($type->subType());
115
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()";
121 }
122
123 $subType = $this->compile($type->subType());
124
125 return "new $class($subType)";
126 case $type instanceof IterableType:
127 $keyType = $this->compile($type->keyType());
128 $subType = $this->compile($type->subType());
129
130 return "new $class($keyType, $subType)";
131 case $type instanceof NativeClassType:
132 case $type instanceof InterfaceType:
133 $generics = [];
134
135 foreach ($type->generics() as $key => $generic) {
136 $generics[] = var_export($key, true) . ' => ' . $this->compile($generic);
137 }
138
139 $generics = implode(', ', $generics);
140
141 if ($type instanceof InterfaceType) {
142 return "new $class('{$type->className()}', [$generics])";
143 }
144
145 $parent = $type->hasParent()
146 ? $this->compile($type->parent())
147 : 'null';
148
149 return "new $class('{$type->className()}', [$generics], $parent)";
150 case $type instanceof ClassStringType:
151 if (null === $type->subType()) {
152 return "new $class()";
153 }
154
155 $subType = $this->compile($type->subType());
156
157 return "new $class($subType)";
158 case $type instanceof EnumType:
159 $enumName = var_export($type->className(), true);
160 $pattern = var_export($type->pattern(), true);
161
162 $cases = array_map(
163 fn (string|int $key, UnitEnum $case) => var_export($key, true) . ' => ' . var_export($case, true),
164 array_keys($type->cases()),
165 $type->cases()
166 );
167 $cases = implode(', ', $cases);
168
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);
173
174 return "new $class($raw, $message)";
175 default:
176 throw new TypeCannotBeCompiled($type);
177 }
178 }
179
180 private function compileArrayShapeElement(ShapedArrayElement $element): string
181 {
182 $class = ShapedArrayElement::class;
183 $key = $this->compile($element->key());
184 $type = $this->compile($element->type());
185 $optional = var_export($element->isOptional(), true);
186
187 return "new $class($key, $type, $optional)";
188 }
189 }