Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

misc: reorganize type resolver services #519

Merged
merged 1 commit into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,12 @@
$config->skip([
AddLiteralSeparatorToNumberRector::class,
ClassPropertyAssignToConstructorPromotionRector::class,
NullToStrictStringFuncCallArgRector::class,
ReadOnlyPropertyRector::class,
MixedTypeRector::class => [
__DIR__ . '/tests/Unit/Definition/Repository/Reflection/ReflectionClassDefinitionRepositoryTest',
__DIR__ . '/tests/Integration/Mapping/TypeErrorDuringMappingTest.php',
],
NullToStrictStringFuncCallArgRector::class => [
__DIR__ . '/tests/Traits/TestIsSingleton.php',
],
RestoreDefaultNullToNullableTypePropertyRector::class => [
__DIR__ . '/tests/Integration/Mapping/Other/FlexibleCastingMappingTest.php',
__DIR__ . '/tests/Integration/Mapping/SingleNodeMappingTest',
Expand Down
3 changes: 3 additions & 0 deletions src/Definition/Repository/Cache/Compiler/TypeCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ public function compile(Type $type): string
case $type instanceof IntegerRangeType:
return "new $class({$type->min()}, {$type->max()})";
case $type instanceof StringValueType:
$value = var_export($type->toString(), true);

return "$class::from($value)";
case $type instanceof IntegerValueType:
case $type instanceof FloatValueType:
$value = var_export($type->value(), true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,19 @@
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;
use CuyZ\Valinor\Definition\Properties;
use CuyZ\Valinor\Definition\PropertyDefinition;
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ClassImportedTypeAliasResolver;
use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ClassLocalTypeAliasResolver;
use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ClassParentTypeResolver;
use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ReflectionTypeResolver;
use CuyZ\Valinor\Type\GenericType;
use CuyZ\Valinor\Type\ObjectType;
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;
Expand Down Expand Up @@ -89,7 +76,7 @@ private function attributes(ReflectionClass $reflection): array
{
return array_map(
fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute),
Reflection::attributes($reflection)
Reflection::attributes($reflection),
);
}

Expand Down Expand Up @@ -143,13 +130,18 @@ private function typeResolver(ObjectType $type, ReflectionClass $target): Reflec
return $this->typeResolver[$typeKey];
}

$parentTypeResolver = new ClassParentTypeResolver($this->typeParserFactory);

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

$localTypeAliasResolver = new ClassLocalTypeAliasResolver($this->typeParserFactory);
$importedTypeAliasResolver = new ClassImportedTypeAliasResolver($this->typeParserFactory);

$generics = $type instanceof GenericType ? $type->generics() : [];
$localAliases = $this->localTypeAliases($type);
$importedAliases = $this->importedTypeAliases($type);
$localAliases = $localTypeAliasResolver->resolveLocalTypeAliases($type);
$importedAliases = $importedTypeAliasResolver->resolveImportedTypeAliases($type);

$duplicates = [];
$keys = [...array_keys($generics), ...array_keys($localAliases), ...array_keys($importedAliases)];
Expand All @@ -166,128 +158,9 @@ private function typeResolver(ObjectType $type, ReflectionClass $target): Reflec
throw new ClassTypeAliasesDuplication($type->className(), ...array_keys($duplicates));
}

$advancedParser = $this->typeParserFactory->get(
new ClassContextSpecification($type->className()),
new AliasSpecification(Reflection::class($type->className())),
new TypeAliasAssignerSpecification($generics + $localAliases + $importedAliases),
new GenericCheckerSpecification(),
);

$nativeParser = $this->typeParserFactory->get(
new ClassContextSpecification($type->className()),
);
$advancedParser = $this->typeParserFactory->buildAdvancedTypeParserForClass($type, $generics + $localAliases + $importedAliases);
$nativeParser = $this->typeParserFactory->buildNativeTypeParserForClass($type->className());

return $this->typeResolver[$typeKey] = new ReflectionTypeResolver($nativeParser, $advancedParser);
}

/**
* @return array<string, Type>
*/
private function localTypeAliases(ObjectType $type): array
{
$reflection = Reflection::class($type->className());
$rawTypes = DocParser::localTypeAliases($reflection);

$typeParser = $this->typeParser($type);

$types = [];

foreach ($rawTypes as $name => $raw) {
try {
$types[$name] = $typeParser->parse($raw);
} catch (InvalidType $exception) {
$raw = trim($raw);

$types[$name] = UnresolvableType::forLocalAlias($raw, $name, $type, $exception);
}
}

return $types;
}

/**
* @return array<string, Type>
*/
private function importedTypeAliases(ObjectType $type): array
{
$reflection = Reflection::class($type->className());
$importedTypesRaw = DocParser::importedTypeAliases($reflection);

$typeParser = $this->typeParser($type);

$importedTypes = [];

foreach ($importedTypesRaw as $class => $types) {
try {
$classType = $typeParser->parse($class);
} catch (InvalidType) {
throw new InvalidTypeAliasImportClass($type, $class);
}

if (! $classType instanceof ObjectType) {
throw new InvalidTypeAliasImportClassType($type, $classType);
}

$localTypes = $this->localTypeAliases($classType);

foreach ($types as $importedType) {
if (! isset($localTypes[$importedType])) {
throw new UnknownTypeAliasImport($type, $classType->className(), $importedType);
}

$importedTypes[$importedType] = $localTypes[$importedType];
}
}

return $importedTypes;
}

private function typeParser(ObjectType $type): TypeParser
{
$specs = [
new ClassContextSpecification($type->className()),
new AliasSpecification(Reflection::class($type->className())),
new GenericCheckerSpecification(),
];

if ($type instanceof GenericType) {
$specs[] = new TypeAliasAssignerSpecification($type->generics());
}

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

private function parentType(ObjectType $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 @@ -10,10 +10,10 @@
use CuyZ\Valinor\Definition\Parameters;
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
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\Definition\Repository\Reflection\TypeResolver\FunctionReturnTypeResolver;
use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ReflectionTypeResolver;
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionAttribute;
use ReflectionFunction;
Expand Down Expand Up @@ -42,60 +42,71 @@ public function for(callable $function): FunctionDefinition
{
$reflection = Reflection::function($function);

$typeResolver = $this->typeResolver($reflection);
$nativeParser = $this->typeParserFactory->buildNativeTypeParserForFunction($reflection);
$advancedParser = $this->typeParserFactory->buildAdvancedTypeParserForFunction($reflection);

$typeResolver = new ReflectionTypeResolver($nativeParser, $advancedParser);

$returnTypeResolver = new FunctionReturnTypeResolver($typeResolver);

$parameters = array_map(
fn (ReflectionParameter $parameter) => $this->parameterBuilder->for($parameter, $typeResolver),
$reflection->getParameters()
$reflection->getParameters(),
);

$name = $reflection->getName();
$signature = $this->signature($reflection);
$class = $reflection->getClosureScopeClass();
$returnType = $typeResolver->resolveType($reflection);
$returnType = $returnTypeResolver->resolveReturnTypeFor($reflection);
$nativeReturnType = $returnTypeResolver->resolveNativeReturnTypeFor($reflection);
$isClosure = $name === '{closure}' || str_ends_with($name, '\\{closure}');

if ($returnType instanceof UnresolvableType) {
$returnType = $returnType->forFunctionReturnType($signature);
} elseif (! $returnType->matches($nativeReturnType)) {
$returnType = UnresolvableType::forNonMatchingFunctionReturnTypes($name, $nativeReturnType, $returnType);
}

return new FunctionDefinition(
$name,
Reflection::signature($reflection),
$signature,
new Attributes(...$this->attributes($reflection)),
$reflection->getFileName() ?: null,
$class?->name,
$reflection->getClosureThis() === null,
$isClosure,
new Parameters(...$parameters),
$returnType
$returnType,
);
}

private function typeResolver(ReflectionFunction $reflection): ReflectionTypeResolver
{
$class = $reflection->getClosureScopeClass();

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

if ($class !== null) {
$nativeSpecifications[] = new ClassContextSpecification($class->name);
$advancedSpecification[] = new ClassContextSpecification($class->name);
}

$nativeParser = $this->typeParserFactory->get(...$nativeSpecifications);
$advancedParser = $this->typeParserFactory->get(...$advancedSpecification);

return new ReflectionTypeResolver($nativeParser, $advancedParser);
}

/**
* @return list<AttributeDefinition>
*/
private function attributes(ReflectionFunction $reflection): array
{
return array_map(
fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute),
Reflection::attributes($reflection)
Reflection::attributes($reflection),
);
}

/**
* @return non-empty-string
*/
private function signature(ReflectionFunction $reflection): string
{
if (str_contains($reflection->name, '{closure}')) {
$startLine = $reflection->getStartLine();
$endLine = $reflection->getEndLine();

return $startLine === $endLine
? "Closure (line $startLine of {$reflection->getFileName()})"
: "Closure (lines $startLine to $endLine of {$reflection->getFileName()})";
}

return $reflection->getClosureScopeClass()
? $reflection->getClosureScopeClass()->name . '::' . $reflection->name . '()'
: $reflection->name . '()';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
use CuyZ\Valinor\Definition\MethodDefinition;
use CuyZ\Valinor\Definition\Parameters;
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\FunctionReturnTypeResolver;
use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ReflectionTypeResolver;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionAttribute;
use ReflectionMethod;
Expand All @@ -32,6 +35,7 @@ public function for(ReflectionMethod $reflection, ReflectionTypeResolver $typeRe
{
/** @var non-empty-string $name */
$name = $reflection->name;
$signature = $reflection->getDeclaringClass()->name . '::' . $reflection->name . '()';

$attributes = array_map(
fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute),
Expand All @@ -43,11 +47,20 @@ public function for(ReflectionMethod $reflection, ReflectionTypeResolver $typeRe
$reflection->getParameters()
);

$returnType = $typeResolver->resolveType($reflection);
$returnTypeResolver = new FunctionReturnTypeResolver($typeResolver);

$returnType = $returnTypeResolver->resolveReturnTypeFor($reflection);
$nativeReturnType = $returnTypeResolver->resolveNativeReturnTypeFor($reflection);

if ($returnType instanceof UnresolvableType) {
$returnType = $returnType->forMethodReturnType($signature);
} elseif (! $returnType->matches($nativeReturnType)) {
$returnType = UnresolvableType::forNonMatchingMethodReturnTypes($name, $nativeReturnType, $returnType);
}

return new MethodDefinition(
$name,
Reflection::signature($reflection),
$signature,
new Attributes(...$attributes),
new Parameters(...$parameters),
$reflection->isStatic(),
Expand Down
Loading
Loading