Skip to content

Commit

Permalink
Extracted inheritance resolvers
Browse files Browse the repository at this point in the history
  • Loading branch information
vudaltsov committed Feb 5, 2024
1 parent 6bb63dd commit 536bbf7
Show file tree
Hide file tree
Showing 14 changed files with 756 additions and 280 deletions.
5 changes: 4 additions & 1 deletion .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
->setFinder($finder)
->setCacheFile(__DIR__ . '/var/.php-cs-fixer.cache');

(new PhpCsFixerCodingStandard())->applyTo($config);
(new PhpCsFixerCodingStandard())->applyTo($config, [
/** @see TypeInheritanceResolver::equal() */
'strict_comparison' => false,
]);

return $config;
179 changes: 34 additions & 145 deletions src/ClassReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

use Typhoon\Reflection\ClassReflection\ClassReflector;
use Typhoon\Reflection\ClassReflection\ClassReflectorAwareReflection;
use Typhoon\Reflection\TypeResolver\StaticResolver;
use Typhoon\Reflection\TypeResolver\TemplateResolver;
use Typhoon\Reflection\Inheritance\MethodsInheritanceResolver;
use Typhoon\Reflection\Inheritance\PropertiesInheritanceResolver;
use Typhoon\Type;

/**
Expand Down Expand Up @@ -50,6 +50,7 @@ final class ClassReflection extends ClassReflectorAwareReflection
* @param list<TemplateReflection> $templates
* @param int-mask-of<self::IS_*> $modifiers
* @param list<Type\NamedObjectType> $ownInterfaceTypes
* @param list<Type\NamedObjectType> $ownTraitTypes
* @param list<PropertyReflection> $ownProperties
* @param list<MethodReflection> $ownMethods
* @param ?\ReflectionClass<T> $nativeReflection
Expand All @@ -76,6 +77,7 @@ public function __construct(
private readonly array $ownProperties,
private readonly array $ownMethods,
private ?\ReflectionClass $nativeReflection = null,
private readonly array $ownTraitTypes = [],
) {
$this->name = $name;
}
Expand Down Expand Up @@ -357,7 +359,13 @@ public function getInterfaces(): array
*/
public function implementsInterface(string|self $interface): bool
{
$interface = $this->resolveInterface($interface);
if (\is_string($interface)) {
$interface = $this->classReflector()->reflectClass($interface);
}

if (!$interface->isInterface()) {
throw new ReflectionException();
}

if ($this->name === $interface->name) {
return true;
Expand Down Expand Up @@ -460,34 +468,6 @@ public function getProperties(int $filter = 0): array
));
}

/**
* @return array<non-empty-string, PropertyReflection>
*/
public function getPropertiesIndexedByName(): array
{
if ($this->propertiesIndexedByName !== null) {
return $this->propertiesIndexedByName;
}

$propertiesIndexedByName = array_column($this->ownProperties, null, 'name');

foreach ($this->parentWithResolvedAncestors()?->getPropertiesIndexedByName() ?? [] as $name => $parentProperty) {
if ($parentProperty->isPrivate()) {
continue;
}

if (!isset($propertiesIndexedByName[$name])) {
$propertiesIndexedByName[$name] = $parentProperty;

continue;
}

$propertiesIndexedByName[$name] = PropertyReflection::fromPrototype($parentProperty, $propertiesIndexedByName[$name]);
}

return $this->propertiesIndexedByName = $propertiesIndexedByName;
}

/**
* @psalm-assert non-empty-string $name
*/
Expand Down Expand Up @@ -520,36 +500,6 @@ public function getMethods(int $filter = 0): array
));
}

/**
* @return array<non-empty-string, MethodReflection>
*/
public function getMethodsIndexedByName(): array
{
if ($this->methodsIndexedByName !== null) {
return $this->methodsIndexedByName;
}

$methodsIndexedByName = array_column($this->ownMethods, null, 'name');

foreach ($this->ownAncestorsWithResolvedTemplates() as $ancestor) {
foreach ($ancestor->getMethodsIndexedByName() as $name => $parentMethod) {
if ($parentMethod->isPrivate()) {
continue;
}

if (!isset($methodsIndexedByName[$name])) {
$methodsIndexedByName[$name] = $parentMethod;

continue;
}

$methodsIndexedByName[$name] = MethodReflection::fromPrototype($parentMethod, $methodsIndexedByName[$name]);
}
}

return $this->methodsIndexedByName = $methodsIndexedByName;
}

/**
* @psalm-assert non-empty-string $name
*/
Expand All @@ -563,48 +513,6 @@ public function getConstructor(): ?MethodReflection
return $this->getMethodsIndexedByName()['__construct'] ?? null;
}

/**
* @param array<Type\Type> $templateArguments
* @return self<T>
*/
public function resolveTemplates(array $templateArguments = []): self
{
if ($this->templates === []) {
return $this;
}

/** @var self<T> */
return $this->resolvedTypes(TemplateResolver::create($this->templates, $templateArguments));
}

/**
* @deprecated in favor of resolveTemplates()
* @param array<Type\Type> $templateArguments
* @return self<T>
*/
public function withResolvedTemplates(array $templateArguments = []): self
{
return $this->resolveTemplates($templateArguments);
}

/**
* @return self<T>
*/
public function resolveStatic(): self
{
/** @var self<T> */
return $this->resolvedTypes(new StaticResolver($this->name));
}

/**
* @deprecated in favor of resolveStatic()
* @return self<T>
*/
public function withResolvedStatic(): self
{
return $this->resolveStatic();
}

/**
* @return T
*/
Expand Down Expand Up @@ -673,63 +581,44 @@ public function __clone()
}
}

private function resolvedTypes(TemplateResolver|StaticResolver $typeResolver): self
{
$class = clone $this;
$class->propertiesIndexedByName = array_map(
static fn(PropertyReflection $property): PropertyReflection => $property->resolveTypes($typeResolver),
$this->getPropertiesIndexedByName(),
);
$class->methodsIndexedByName = array_map(
static fn(MethodReflection $method): MethodReflection => $method->resolveTypes($typeResolver),
$this->getMethodsIndexedByName(),
);

return $class;
}

/**
* @param interface-string|self $interface
* @return array<non-empty-string, PropertyReflection>
*/
private function resolveInterface(string|self $interface): self
private function getPropertiesIndexedByName(): array
{
if (\is_string($interface)) {
$interface = $this->classReflector()->reflectClass($interface);
}

if (!$interface->isInterface()) {
throw new ReflectionException();
if ($this->propertiesIndexedByName !== null) {
return $this->propertiesIndexedByName;
}

return $interface;
}
$resolver = new PropertiesInheritanceResolver($this->classReflector());
$resolver->setOwn($this->ownProperties);
$resolver->addInherited(...$this->ownTraitTypes);

private function parentWithResolvedAncestors(): ?self
{
if ($this->parentType === null) {
return null;
if ($this->parentType !== null) {
$resolver->addInherited($this->parentType);
}

return $this->classReflector()
->reflectClass($this->parentType->class)
->resolveTemplates($this->parentType->templateArguments);
return $this->propertiesIndexedByName = $resolver->resolve();
}

/**
* @return \Generator<self>
* @return array<non-empty-string, MethodReflection>
*/
private function ownAncestorsWithResolvedTemplates(): \Generator
private function getMethodsIndexedByName(): array
{
$parentWithResolvedAncestors = $this->parentWithResolvedAncestors();

if ($parentWithResolvedAncestors !== null) {
yield $parentWithResolvedAncestors;
if ($this->methodsIndexedByName !== null) {
return $this->methodsIndexedByName;
}

foreach ($this->ownInterfaceTypes as $interfaceType) {
yield $this->classReflector()
->reflectClass($interfaceType->class)
->resolveTemplates($interfaceType->templateArguments);
$resolver = new MethodsInheritanceResolver($this->classReflector());
$resolver->setOwn($this->ownMethods);
$resolver->addInherited(...$this->ownInterfaceTypes);
$resolver->addInherited(...$this->ownTraitTypes);

if ($this->parentType !== null) {
$resolver->addInherited($this->parentType);
}

return $this->methodsIndexedByName = $resolver->resolve();
}
}
83 changes: 83 additions & 0 deletions src/Inheritance/MethodInheritanceResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection\Inheritance;

use Typhoon\Reflection\MethodReflection;
use Typhoon\Reflection\TypeReflection;
use Typhoon\Type\Type;
use Typhoon\Type\TypeVisitor;

/**
* @internal
* @psalm-internal Typhoon\Reflection\Inheritance
*/
final class MethodInheritanceResolver
{
private ?MethodReflection $method = null;

/**
* @var array<non-empty-string, TypeInheritanceResolver>
*/
private array $parameterTypes = [];

private TypeInheritanceResolver $returnType;

public function __construct()
{
$this->returnType = new TypeInheritanceResolver();
}

public function setOwn(MethodReflection $method): void
{
$this->method = $method;

foreach ($method->getParameters() as $parameter) {
$this->parameterTypes[$parameter->name] = new TypeInheritanceResolver();
$this->parameterTypes[$parameter->name]->setOwn($parameter->getType());
}

$this->returnType->setOwn($method->getReturnType());
}

/**
* @param TypeVisitor<Type> $templateResolver
*/
public function addInherited(MethodReflection $method, TypeVisitor $templateResolver): void
{
if ($this->method !== null) {
foreach ($this->parameterTypes as $name => $parameter) {
if ($method->hasParameterWithName($name)) {
$parameter->addInherited($method->getParameterByName($name)->getType(), $templateResolver);
}
}

$this->returnType->addInherited($method->getReturnType(), $templateResolver);

return;
}

$this->method = $method;

foreach ($method->getParameters() as $parameter) {
$this->parameterTypes[$parameter->name] = new TypeInheritanceResolver();
$this->parameterTypes[$parameter->name]->addInherited($parameter->getType(), $templateResolver);
}

$this->returnType->addInherited($method->getReturnType(), $templateResolver);
}

public function resolve(): MethodReflection
{
\assert($this->method !== null);

return $this->method->withTypes(
array_map(
static fn(TypeInheritanceResolver $resolver): TypeReflection => $resolver->resolve(),
$this->parameterTypes,
),
$this->returnType->resolve(),
);
}
}
Loading

0 comments on commit 536bbf7

Please sign in to comment.