Skip to content

Commit

Permalink
fix: correctly handle type inferring for method coming from interface
Browse files Browse the repository at this point in the history
Fixes an error that occurred when trying to resolve types of a method
referencing self, coming from an interface, in an abstract class.
  • Loading branch information
simPod authored Aug 18, 2023
1 parent f260cfb commit 2657f8b
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use CuyZ\Valinor\Utility\Reflection\DocParser;
Expand Down Expand Up @@ -77,7 +78,7 @@ private function properties(ClassType $type): array
{
return array_map(
function (ReflectionProperty $property) use ($type) {
$typeResolver = $this->typeResolver($type, $property->class);
$typeResolver = $this->typeResolver($type, $property->getDeclaringClass());

return $this->propertyBuilder->for($property, $typeResolver);
},
Expand All @@ -101,21 +102,26 @@ private function methods(ClassType $type): array
}

return array_map(function (ReflectionMethod $method) use ($type) {
$typeResolver = $this->typeResolver($type, $method->class);
$typeResolver = $this->typeResolver($type, $method->getDeclaringClass());

return $this->methodBuilder->for($method, $typeResolver);
}, $methods);
}

private function typeResolver(ClassType $type, string $targetClass): ReflectionTypeResolver
/**
* @param ReflectionClass<object> $target
*/
private function typeResolver(ClassType $type, ReflectionClass $target): ReflectionTypeResolver
{
$typeKey = "{$type->toString()}/$targetClass";
$typeKey = $target->isInterface()
? "{$type->toString()}/{$type->className()}"
: "{$type->toString()}/$target->name";

if (isset($this->typeResolver[$typeKey])) {
return $this->typeResolver[$typeKey];
}

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

Expand Down
10 changes: 10 additions & 0 deletions tests/Fixture/Object/AbstractObjectWithInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Tests\Fixture\Object;

abstract class AbstractObjectWithInterface implements \JsonSerializable
{
public static function of(AbstractObjectWithInterface $value): void {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use CuyZ\Valinor\Tests\Fake\Definition\Repository\FakeAttributesRepository;
use CuyZ\Valinor\Tests\Fake\Type\FakeType;
use CuyZ\Valinor\Tests\Fake\Type\Parser\Factory\FakeTypeParserFactory;
use CuyZ\Valinor\Tests\Fixture\Object\AbstractObjectWithInterface;
use CuyZ\Valinor\Type\StringType;
use CuyZ\Valinor\Type\Types\NativeClassType;
use CuyZ\Valinor\Type\Types\MixedType;
Expand Down Expand Up @@ -145,6 +146,15 @@ public function publicMethodWithNativeAndDocBlockReturnTypes(): string
self::assertSame('Optional parameter value', $optionalParameter->defaultValue());
}

public function test_methods_can_be_retrieved_from_abstract_object_with_interface_and_with_method_referencing_self(): void
{
$type = new NativeClassType(AbstractObjectWithInterface::class);
$methods = $this->repository->for($type)->methods();

self::assertTrue($methods->has('of'));
self::assertTrue($methods->has('jsonSerialize'));
}

public function test_private_parent_constructor_is_listed_in_methods(): void
{
$type = new NativeClassType(ClassWithInheritedPrivateConstructor::class, parent: new NativeClassType(AbstractClassWithPrivateConstructor::class));
Expand Down

0 comments on commit 2657f8b

Please sign in to comment.