Skip to content

Commit

Permalink
misc: refactor generic types parsing and checking
Browse files Browse the repository at this point in the history
This refactor is needed for an upcoming change in the types parsing
system, which will allow to distinguish native types and types coming
from doc-blocks.
  • Loading branch information
romm committed Mar 17, 2024
1 parent 2738de1 commit ba67703
Show file tree
Hide file tree
Showing 31 changed files with 498 additions and 478 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

declare(strict_types=1);

namespace CuyZ\Valinor\Type\Parser\Exception\Generic;
namespace CuyZ\Valinor\Definition\Exception;

use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
use ReflectionClass;
use RuntimeException;

/** @internal */
final class ExtendTagTypeError extends RuntimeException implements InvalidType
final class ExtendTagTypeError extends RuntimeException
{
/**
* @param ReflectionClass<object> $reflection
Expand All @@ -19,7 +19,7 @@ public function __construct(ReflectionClass $reflection, InvalidType $previous)
parent::__construct(
"The `@extends` tag of the class `$reflection->name` is not valid: {$previous->getMessage()}",
1670193574,
$previous
$previous,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

declare(strict_types=1);

namespace CuyZ\Valinor\Type\Parser\Exception\Generic;
namespace CuyZ\Valinor\Definition\Exception;

use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
use CuyZ\Valinor\Type\Type;
use ReflectionClass;
use RuntimeException;

/** @internal */
final class InvalidExtendTagClassName extends RuntimeException implements InvalidType
final class InvalidExtendTagClassName extends RuntimeException
{
/**
* @param ReflectionClass<object> $reflection
Expand All @@ -22,7 +21,7 @@ public function __construct(ReflectionClass $reflection, Type $invalidExtendTag)

parent::__construct(
"The `@extends` tag of the class `$reflection->name` has invalid class `{$invalidExtendTag->toString()}`, it should be `$parentClass->name`.",
1670183564
1670183564,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

declare(strict_types=1);

namespace CuyZ\Valinor\Type\Parser\Exception\Generic;
namespace CuyZ\Valinor\Definition\Exception;

use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
use CuyZ\Valinor\Type\Type;
use ReflectionClass;
use RuntimeException;

/** @internal */
final class InvalidExtendTagType extends RuntimeException implements InvalidType
final class InvalidExtendTagType extends RuntimeException
{
/**
* @param ReflectionClass<object> $reflection
Expand All @@ -22,7 +21,7 @@ public function __construct(ReflectionClass $reflection, Type $invalidExtendTag)

parent::__construct(
"The `@extends` tag of the class `$reflection->name` has invalid type `{$invalidExtendTag->toString()}`, it should be `{$parentClass->name}`.",
1670181134
1670181134,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

declare(strict_types=1);

namespace CuyZ\Valinor\Type\Parser\Exception\Generic;
namespace CuyZ\Valinor\Definition\Exception;

use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
use ReflectionClass;
use RuntimeException;

/** @internal */
final class SeveralExtendTagsFound extends RuntimeException implements InvalidType
final class SeveralExtendTagsFound extends RuntimeException
{
/**
* @param ReflectionClass<object> $reflection
Expand All @@ -18,7 +17,7 @@ public function __construct(ReflectionClass $reflection)
{
parent::__construct(
"Only one `@extends` tag should be set for the class `$reflection->name`.",
1670195494
1670195494,
);
}
}
10 changes: 1 addition & 9 deletions src/Definition/Repository/Cache/Compiler/TypeCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,7 @@ public function compile(Type $type): string

$generics = implode(', ', $generics);

if ($type instanceof InterfaceType) {
return "new $class('{$type->className()}', [$generics])";
}

$parent = $type->hasParent()
? $this->compile($type->parent())
: 'null';

return "new $class('{$type->className()}', [$generics], $parent)";
return "new $class('{$type->className()}', [$generics])";
case $type instanceof ClassStringType:
if (null === $type->subType()) {
return "new $class()";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function __construct(private ClassDefinitionRepository $classDefinitionRe

public function for(ReflectionAttribute $reflection): AttributeDefinition
{
$class = $this->classDefinitionRepository->for(NativeClassType::for($reflection->getName()));
$class = $this->classDefinitionRepository->for(new NativeClassType($reflection->getName()));

return new AttributeDefinition(
$class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
use CuyZ\Valinor\Definition\Attributes;
use CuyZ\Valinor\Definition\ClassDefinition;
use CuyZ\Valinor\Definition\Exception\ClassTypeAliasesDuplication;
use CuyZ\Valinor\Definition\Exception\ExtendTagTypeError;
use CuyZ\Valinor\Definition\Exception\InvalidExtendTagClassName;
use CuyZ\Valinor\Definition\Exception\InvalidExtendTagType;
use CuyZ\Valinor\Definition\Exception\InvalidTypeAliasImportClass;
use CuyZ\Valinor\Definition\Exception\InvalidTypeAliasImportClassType;
use CuyZ\Valinor\Definition\Exception\SeveralExtendTagsFound;
use CuyZ\Valinor\Definition\Exception\UnknownTypeAliasImport;
use CuyZ\Valinor\Definition\MethodDefinition;
use CuyZ\Valinor\Definition\Methods;
Expand All @@ -22,17 +26,19 @@
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\AliasSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\GenericCheckerSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeAliasAssignerSpecification;
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Parser\TypeParser;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\NativeClassType;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\DocParser;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use CuyZ\Valinor\Utility\Reflection\DocParser;

use function array_filter;
use function array_keys;
Expand Down Expand Up @@ -138,7 +144,7 @@ private function typeResolver(ClassType $type, ReflectionClass $target): Reflect
}

while ($type->className() !== $target->name) {
$type = $type->parent();
$type = $this->parentType($type);
}

$generics = $type instanceof GenericType ? $type->generics() : [];
Expand All @@ -164,6 +170,7 @@ private function typeResolver(ClassType $type, ReflectionClass $target): Reflect
new ClassContextSpecification($type->className()),
new AliasSpecification(Reflection::class($type->className())),
new TypeAliasAssignerSpecification($generics + $localAliases + $importedAliases),
new GenericCheckerSpecification(),
);

$nativeParser = $this->typeParserFactory->get(
Expand Down Expand Up @@ -240,6 +247,7 @@ private function typeParser(ClassType $type): TypeParser
$specs = [
new ClassContextSpecification($type->className()),
new AliasSpecification(Reflection::class($type->className())),
new GenericCheckerSpecification(),
];

if ($type instanceof GenericType) {
Expand All @@ -248,4 +256,38 @@ private function typeParser(ClassType $type): TypeParser

return $this->typeParserFactory->get(...$specs);
}

private function parentType(ClassType $type): NativeClassType
{
$reflection = Reflection::class($type->className());

/** @var ReflectionClass<object> $parentReflection */
$parentReflection = $reflection->getParentClass();

$extendedClass = DocParser::classExtendsTypes($reflection);

if (count($extendedClass) > 1) {
throw new SeveralExtendTagsFound($reflection);
} elseif (count($extendedClass) === 0) {
$extendedClass = $parentReflection->name;
} else {
$extendedClass = $extendedClass[0];
}

try {
$parentType = $this->typeParser($type)->parse($extendedClass);
} catch (InvalidType $exception) {
throw new ExtendTagTypeError($reflection, $exception);
}

if (! $parentType instanceof NativeClassType) {
throw new InvalidExtendTagType($reflection, $parentType);
}

if ($parentType->className() !== $parentReflection->name) {
throw new InvalidExtendTagClassName($reflection, $parentType);
}

return $parentType;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\AliasSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\GenericCheckerSpecification;
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionAttribute;
Expand Down Expand Up @@ -71,7 +72,10 @@ private function typeResolver(ReflectionFunction $reflection): ReflectionTypeRes
$class = $reflection->getClosureScopeClass();

$nativeSpecifications = [];
$advancedSpecification = [new AliasSpecification($reflection)];
$advancedSpecification = [
new AliasSpecification($reflection),
new GenericCheckerSpecification(),
];

if ($class !== null) {
$nativeSpecifications[] = new ClassContextSpecification($class->name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace CuyZ\Valinor\Definition\Repository\Reflection;

use CuyZ\Valinor\Definition\Exception\TypesDoNotMatch;
use CuyZ\Valinor\Type\GenericType;
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
use CuyZ\Valinor\Type\Parser\TypeParser;
use CuyZ\Valinor\Type\Type;
Expand Down Expand Up @@ -41,6 +42,14 @@ public function resolveType(ReflectionProperty|ReflectionParameter|ReflectionFun
}

if (! $typeFromDocBlock) {
// When the type is a class, it may declare templates that must be
// filled with generics. PHP does not handle generics natively, so
// we need to make sure that no generics are left unassigned by
// parsing the type again using the advanced parser.
if ($nativeType instanceof GenericType) {
$nativeType = $this->parseType($nativeType->toString(), $reflection, $this->advancedParser);
}

return $nativeType;
}

Expand Down
4 changes: 2 additions & 2 deletions src/Normalizer/Transformer/RecursiveTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private function doTransform(mixed $value, WeakMap $references, array $attribute
}

if (is_object($value)) {
$classAttributes = $this->classDefinitionRepository->for(NativeClassType::for($value::class))->attributes;
$classAttributes = $this->classDefinitionRepository->for(new NativeClassType($value::class))->attributes;
$classAttributes = $this->filterAttributes($classAttributes);

$attributes = [...$attributes, ...$classAttributes];
Expand Down Expand Up @@ -123,7 +123,7 @@ private function defaultTransformer(mixed $value, WeakMap $references): mixed

$transformed = [];

$class = $this->classDefinitionRepository->for(NativeClassType::for($value::class));
$class = $this->classDefinitionRepository->for(new NativeClassType($value::class));

foreach ($values as $key => $subValue) {
$attributes = $this->filterAttributes($class->properties->get($key)->attributes)->toArray();
Expand Down
7 changes: 6 additions & 1 deletion src/Type/GenericType.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
interface GenericType extends CompositeType
{
/**
* @return array<string, Type>
* @return class-string
*/
public function className(): string;

/**
* @return array<non-empty-string, Type>
*/
public function generics(): array;
}
2 changes: 1 addition & 1 deletion src/Type/Parser/Exception/Generic/MissingGenerics.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final class MissingGenerics extends RuntimeException implements InvalidType
/**
* @param class-string $className
* @param Type[] $generics
* @param Type[] $templates
* @param array<non-empty-string> $templates
*/
public function __construct(string $className, array $generics, array $templates)
{
Expand Down
27 changes: 13 additions & 14 deletions src/Type/Parser/Factory/LexingTypeParserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use CuyZ\Valinor\Type\Parser\CachedParser;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeParserSpecification;
use CuyZ\Valinor\Type\Parser\Lexer\AdvancedClassLexer;
use CuyZ\Valinor\Type\Parser\Lexer\NativeLexer;
use CuyZ\Valinor\Type\Parser\Lexer\ObjectLexer;
use CuyZ\Valinor\Type\Parser\LexingParser;
Expand All @@ -19,29 +18,29 @@ final class LexingTypeParserFactory implements TypeParserFactory

public function get(TypeParserSpecification ...$specifications): TypeParser
{
if (empty($specifications)) {
return $this->nativeParser ??= $this->nativeParser();
if ($specifications === []) {
return $this->nativeParser ??= new CachedParser($this->buildTypeParser());
}

return $this->buildTypeParser(...$specifications);
}

private function buildTypeParser(TypeParserSpecification ...$specifications): TypeParser
{
$lexer = new ObjectLexer();
$lexer = new AdvancedClassLexer($lexer, $this);

foreach ($specifications as $specification) {
$lexer = $specification->transform($lexer);
$lexer = $specification->manipulateLexer($lexer);
}

$lexer = new NativeLexer($lexer);

return new LexingParser($lexer);
}

private function nativeParser(): TypeParser
{
$lexer = new ObjectLexer();
$lexer = new AdvancedClassLexer($lexer, $this);
$lexer = new NativeLexer($lexer);
$parser = new LexingParser($lexer);

return new CachedParser($parser);
foreach ($specifications as $specification) {
$parser = $specification->manipulateParser($parser, $this);
}

return $parser;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace CuyZ\Valinor\Type\Parser\Factory\Specifications;

use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Parser\Lexer\AliasLexer;
use CuyZ\Valinor\Type\Parser\Lexer\TypeLexer;
use CuyZ\Valinor\Type\Parser\TypeParser;
use ReflectionClass;
use ReflectionFunction;
use Reflector;
Expand All @@ -18,8 +20,13 @@ public function __construct(
private Reflector $reflection
) {}

public function transform(TypeLexer $lexer): TypeLexer
public function manipulateLexer(TypeLexer $lexer): TypeLexer
{
return new AliasLexer($lexer, $this->reflection);
}

public function manipulateParser(TypeParser $parser, TypeParserFactory $typeParserFactory): TypeParser
{
return $parser;
}
}
Loading

0 comments on commit ba67703

Please sign in to comment.