3 declare(strict_types=1);
5 namespace CuyZ\Valinor\Type\Parser\Lexer\Token;
7 use CuyZ\Valinor\Type\IntegerType;
8 use CuyZ\Valinor\Type\Parser\Exception\Generic\AssignedGenericNotFound;
9 use CuyZ\Valinor\Type\Parser\Exception\Generic\CannotAssignGeneric;
10 use CuyZ\Valinor\Type\Parser\Exception\Generic\GenericClosingBracketMissing;
11 use CuyZ\Valinor\Type\Parser\Exception\Generic\GenericCommaMissing;
12 use CuyZ\Valinor\Type\Parser\Exception\Generic\InvalidAssignedGeneric;
13 use CuyZ\Valinor\Type\Parser\Exception\Generic\InvalidExtendTagClassName;
14 use CuyZ\Valinor\Type\Parser\Exception\Generic\InvalidExtendTagType;
15 use CuyZ\Valinor\Type\Parser\Exception\Generic\MissingGenerics;
16 use CuyZ\Valinor\Type\Parser\Exception\Generic\ExtendTagTypeError;
17 use CuyZ\Valinor\Type\Parser\Exception\Generic\SeveralExtendTagsFound;
18 use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
19 use CuyZ\Valinor\Type\Parser\Exception\Template\InvalidClassTemplate;
20 use CuyZ\Valinor\Type\Parser\Factory\Specifications\AliasSpecification;
21 use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
22 use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeAliasAssignerSpecification;
23 use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeParserSpecification;
24 use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
25 use CuyZ\Valinor\Type\Parser\Lexer\TokenStream;
26 use CuyZ\Valinor\Type\Parser\TypeParser;
27 use CuyZ\Valinor\Type\StringType;
28 use CuyZ\Valinor\Type\Type;
29 use CuyZ\Valinor\Type\Types\ArrayKeyType;
30 use CuyZ\Valinor\Type\ClassType;
31 use CuyZ\Valinor\Type\Types\MixedType;
32 use CuyZ\Valinor\Type\Types\NativeClassType;
33 use CuyZ\Valinor\Utility\Reflection\DocParser;
34 use CuyZ\Valinor\Utility\Reflection\Reflection;
37 use function array_keys;
38 use function array_shift;
39 use function array_slice;
43 final class AdvancedClassNameToken implements TraversingToken
45 public function __construct(
46 private ClassNameToken $delegate,
47 private TypeParserFactory $typeParserFactory,
50 public function traverse(TokenStream $stream): Type
52 $type = $this->delegate->traverse($stream);
54 if (! $type instanceof ClassType) {
58 $className = $type->className();
59 $reflection = Reflection::class($className);
60 $parentReflection = $reflection->getParentClass();
63 new ClassContextSpecification($className),
64 new AliasSpecification($reflection),
67 $templates = $this->templatesTypes($reflection, ...$specifications);
69 $generics = $this->generics($stream, $className, $templates);
70 $generics = $this->assignGenerics($className, $templates, $generics);
72 if ($parentReflection) {
73 $parserWithGenerics = $this->typeParserFactory->get(new TypeAliasAssignerSpecification($generics), ...$specifications);
75 $parentType = $this->parentType($reflection, $parentReflection, $parserWithGenerics);
78 return new NativeClassType($className, $generics, $parentType ?? null);
81 public function symbol(): string
83 return $this->delegate->symbol();
87 * @param ReflectionClass<object> $reflection
88 * @return array<string, Type>
90 private function templatesTypes(ReflectionClass $reflection, TypeParserSpecification ...$specifications): array
92 $templates = DocParser::classTemplates($reflection);
94 if ($templates === []) {
100 foreach ($templates as $templateName => $type) {
103 $types[$templateName] = MixedType::get();
105 /** @infection-ignore-all */
106 $parser ??= $this->typeParserFactory->get(...$specifications);
108 $types[$templateName] = $parser->parse($type);
110 } catch (InvalidType $invalidType) {
111 throw new InvalidClassTemplate($reflection->name, $templateName, $invalidType);
119 * @param array<string, Type> $templates
120 * @param class-string $className
123 private function generics(TokenStream $stream, string $className, array $templates): array
125 if ($stream->done() || ! $stream->next() instanceof OpeningBracketToken) {
134 if ($stream->done()) {
135 throw new MissingGenerics($className, $generics, $templates);
138 $generics[] = $stream->read();
140 if ($stream->done()) {
141 throw new GenericClosingBracketMissing($className, $generics);
144 $next = $stream->forward();
146 if ($next instanceof ClosingBracketToken) {
150 if (! $next instanceof CommaToken) {
151 throw new GenericCommaMissing($className, $generics);
159 * @param class-string $className
160 * @param array<string, Type> $templates
161 * @param Type[] $generics
162 * @return array<string, Type>
164 private function assignGenerics(string $className, array $templates, array $generics): array
166 $assignedGenerics = [];
168 foreach ($templates as $name => $template) {
169 $generic = array_shift($generics);
171 if ($generic === null) {
172 $remainingTemplates = array_keys(array_slice($templates, count($assignedGenerics)));
174 throw new AssignedGenericNotFound($className, ...$remainingTemplates);
177 if ($template instanceof ArrayKeyType && $generic instanceof StringType) {
178 $generic = ArrayKeyType::string();
181 if ($template instanceof ArrayKeyType && $generic instanceof IntegerType) {
182 $generic = ArrayKeyType::integer();
185 if (! $generic->matches($template)) {
186 throw new InvalidAssignedGeneric($generic, $template, $name, $className);
189 $assignedGenerics[$name] = $generic;
192 if (! empty($generics)) {
193 throw new CannotAssignGeneric($className, ...$generics);
196 return $assignedGenerics;
200 * @param ReflectionClass<object> $reflection
201 * @param ReflectionClass<object> $parentReflection
203 private function parentType(ReflectionClass $reflection, ReflectionClass $parentReflection, TypeParser $typeParser): NativeClassType
205 $extendedClass = DocParser::classExtendsTypes($reflection);
207 if (count($extendedClass) > 1) {
208 throw new SeveralExtendTagsFound($reflection);
209 } elseif (count($extendedClass) === 0) {
210 $extendedClass = $parentReflection->name;
212 $extendedClass = $extendedClass[0];
216 $parentType = $typeParser->parse($extendedClass);
217 } catch (InvalidType $exception) {
218 throw new ExtendTagTypeError($reflection, $exception);
221 if (! $parentType instanceof NativeClassType) {
222 throw new InvalidExtendTagType($reflection, $parentType);
225 if ($parentType->className() !== $parentReflection->name) {
226 throw new InvalidExtendTagClassName($reflection, $parentType);