*/ private ReflectionClass $reflection; private bool $mustCheckTemplates = false; /** * @param class-string $className */ public function __construct(string $className) { $this->reflection = Reflection::class($className); } public function mustCheckTemplates(): self { $self = clone $this; $self->mustCheckTemplates = true; return $self; } public function traverse(TokenStream $stream): Type { $constant = $this->classConstant($stream); if ($constant) { return $constant; } $generics = []; if ($this->mustCheckTemplates) { $templates = $this->templates($this->reflection->name); $generics = $this->generics($stream, $this->reflection->name); if (count($templates) > count($generics)) { throw new MissingGenerics($this->reflection->name, $generics, $templates); } if (count($generics) > count($templates)) { throw new CannotAssignGeneric($this->reflection->name, $templates, $generics); } } if ($this->reflection->isInterface()) { return new InterfaceType($this->reflection->name, $generics); } return new NativeClassType($this->reflection->name, $generics); } public function symbol(): string { return $this->reflection->name; } private function classConstant(TokenStream $stream): ?Type { if ($stream->done() || ! $stream->next() instanceof DoubleColonToken) { return null; } $stream->forward(); if ($stream->done()) { throw new MissingClassConstantCase($this->reflection->name); } $symbol = $stream->forward()->symbol(); $cases = []; if (! preg_match('/\*\s*\*/', $symbol)) { $finder = new CaseFinder($this->reflection->getConstants(ReflectionClassConstant::IS_PUBLIC)); $cases = $finder->matching(explode('*', $symbol)); } if (empty($cases)) { throw new ClassConstantCaseNotFound($this->reflection->name, $symbol); } $cases = array_map(static fn ($value) => ValueTypeFactory::from($value), $cases); if (count($cases) > 1) { return UnionType::from(...array_values($cases)); } return reset($cases); } /** * @param class-string $className * @return list */ private function generics(TokenStream $stream, string $className): array { if ($stream->done() || ! $stream->next() instanceof OpeningBracketToken) { return []; } $generics = []; $stream->forward(); while (! $stream->done()) { $generics[] = $stream->read(); if ($stream->done()) { throw new GenericClosingBracketMissing($className, $generics); } $next = $stream->forward(); if ($next instanceof ClosingBracketToken) { break; } if (! $next instanceof CommaToken) { throw new GenericCommaMissing($className, $generics); } } return $generics; } /** * @param class-string $className * @return list */ private function templates(string $className): array { $templates = []; $annotations = Annotations::forTemplates(Reflection::class($className)); foreach ($annotations as $annotation) { $tokens = $annotation->filtered(); $templates[] = current($tokens); } return $templates; } }