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

Reflection-based TypeResolver #66

Draft
wants to merge 1 commit into
base: 0.4.x
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion docs/reflection/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ final class A

$reflector = TyphoonReflector::build();

$class = $reflector->reflectClass(A::class);
$class = $reflector->reflectClass(ChildOfArrayObject::class);

$constant = $class->constants()['CONSTANT'];

Expand Down
56 changes: 56 additions & 0 deletions src/Reflection/Visitor/ClassIdResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection\Visitor;

use Typhoon\DeclarationId\AnonymousClassId;
use Typhoon\DeclarationId\NamedClassId;
use Typhoon\Type\Type;
use Typhoon\Type\Visitor\DefaultTypeVisitor;
use function Typhoon\Type\stringify;

/**
* @internal
* @psalm-internal Typhoon\Reflection\Visitor
* @extends DefaultTypeVisitor<NamedClassId|AnonymousClassId>
*/
final class ClassIdResolver extends DefaultTypeVisitor
{
public function namedObject(Type $type, NamedClassId|AnonymousClassId $classId, array $typeArguments): mixed
{
return $classId;
}

public function self(Type $type, array $typeArguments, null|NamedClassId|AnonymousClassId $resolvedClassId): mixed
{
if ($resolvedClassId === null) {
return $this->default($type);
}

return $resolvedClassId;
}

public function parent(Type $type, array $typeArguments, ?NamedClassId $resolvedClassId): mixed
{
if ($resolvedClassId === null) {
return $this->default($type);
}

return $resolvedClassId;
}

public function static(Type $type, array $typeArguments, null|NamedClassId|AnonymousClassId $resolvedClassId): mixed
{
if ($resolvedClassId === null) {
return $this->default($type);
}

return $resolvedClassId;
}

protected function default(Type $type): mixed
{
throw new \LogicException(\sprintf('Cannot resolve %s type as class', stringify($type)));
}
}
49 changes: 49 additions & 0 deletions src/Reflection/Visitor/ExternalTypeResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection\Visitor;

use Typhoon\DeclarationId\AliasId;
use Typhoon\Reflection\ClassConstantReflection;
use Typhoon\Reflection\TyphoonReflector;
use Typhoon\Type\Type;
use Typhoon\Type\types;
use Typhoon\Type\Visitor\RecursiveTypeReplacer;

/**
* @api
*/
final class ExternalTypeResolver extends RecursiveTypeReplacer
{
public function __construct(
private readonly TyphoonReflector $reflector,
) {}

public function alias(Type $type, AliasId $aliasId, array $typeArguments): mixed
{
// todo apply $typeArguments https://github.com/typhoon-php/typhoon/issues/65
return $this->reflector->reflect($aliasId)->type()->accept($this);
}

public function classConstant(Type $type, Type $classType, string $name): mixed
{
$classId = $classType->accept($this)->accept(new ClassIdResolver());

return $this->reflector->reflect($classId)->constants()[$name]->type()->accept($this);
}

public function classConstantMask(Type $type, Type $classType, string $namePrefix): mixed
{
$classId = $classType->accept($this)->accept(new ClassIdResolver());

$types = $this->reflector
->reflect($classId)
->constants()
->filter(static fn(ClassConstantReflection $_constant, string $name): bool => str_starts_with($name, $namePrefix))
->map(static fn(ClassConstantReflection $constant): Type => $constant->type())
->toList();

return types::union(...$types);
}
}
32 changes: 32 additions & 0 deletions src/Reflection/Visitor/FloatLimitResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection\Visitor;

use Typhoon\Type\Type;
use Typhoon\Type\Visitor\DefaultTypeVisitor;
use function Typhoon\Type\stringify;

/**
* @internal
* @psalm-internal Typhoon\Reflection\Visitor
* @extends DefaultTypeVisitor<int|float>
*/
final class FloatLimitResolver extends DefaultTypeVisitor
{
public function intValue(Type $type, int $value): mixed
{
return $value;
}

public function floatValue(Type $type, float $value): mixed
{
return $value;
}

protected function default(Type $type): mixed
{
throw new \LogicException(\sprintf('Cannot resolve %s type as int range limit', stringify($type)));
}
}
27 changes: 27 additions & 0 deletions src/Reflection/Visitor/IntLimitResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection\Visitor;

use Typhoon\Type\Type;
use Typhoon\Type\Visitor\DefaultTypeVisitor;
use function Typhoon\Type\stringify;

/**
* @internal
* @psalm-internal Typhoon\Reflection\Visitor
* @extends DefaultTypeVisitor<int>
*/
final class IntLimitResolver extends DefaultTypeVisitor
{
public function intValue(Type $type, int $value): mixed
{
return $value;
}

protected function default(Type $type): mixed
{
throw new \LogicException(\sprintf('Cannot resolve %s type as int range limit', stringify($type)));
}
}
46 changes: 46 additions & 0 deletions src/Reflection/Visitor/IntMaskResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection\Visitor;

use Typhoon\Type\Type;
use Typhoon\Type\Visitor\DefaultTypeVisitor;
use function Typhoon\Type\stringify;

/**
* @internal
* @psalm-internal Typhoon\Reflection\Visitor
* @extends DefaultTypeVisitor<positive-int>
*/
final class IntMaskResolver extends DefaultTypeVisitor
{
public function intValue(Type $type, int $value): mixed
{
if ($value <= 0) {
return $this->default($type);
}

return $value;
}

public function union(Type $type, array $ofTypes): mixed
{
$mask = 0;

foreach ($ofTypes as $ofType) {
$mask |= $ofType->accept($this);
}

if ($mask <= 0) {
return $this->default($type);
}

return $mask;
}

protected function default(Type $type): mixed
{
throw new \LogicException(\sprintf('Cannot resolve %s type as int-mask type argument', stringify($type)));
}
}
26 changes: 26 additions & 0 deletions src/Reflection/Visitor/IsNever.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection\Visitor;

use Typhoon\Type\Type;
use Typhoon\Type\Visitor\DefaultTypeVisitor;

/**
* @internal
* @psalm-internal Typhoon\Reflection\Visitor
* @extends DefaultTypeVisitor<bool>
*/
final class IsNever extends DefaultTypeVisitor
{
public function never(Type $type): mixed
{
return true;
}

protected function default(Type $type): mixed
{
return false;
}
}
41 changes: 41 additions & 0 deletions src/Reflection/Visitor/KeyOfResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection\Visitor;

use Typhoon\Type\Type;
use Typhoon\Type\types;
use Typhoon\Type\Visitor\DefaultTypeVisitor;
use function Typhoon\Type\stringify;

/**
* @internal
* @psalm-internal Typhoon\Reflection\Visitor
* @extends DefaultTypeVisitor<Type>
*/
final class KeyOfResolver extends DefaultTypeVisitor

Check failure on line 17 in src/Reflection/Visitor/KeyOfResolver.php

View workflow job for this annotation

GitHub Actions / psalm (lowest)

UnusedClass

src/Reflection/Visitor/KeyOfResolver.php:17:13: UnusedClass: Class Typhoon\Reflection\Visitor\KeyOfResolver is never used (see https://psalm.dev/075)
{
public function list(Type $type, Type $valueType, array $elements): mixed
{
if ($valueType->accept(new IsNever())) {
return types::listShape(array_map(types::int(...), array_keys($elements)));
}

return types::nonNegativeInt;
}

public function array(Type $type, Type $keyType, Type $valueType, array $elements): mixed
{
if ($valueType->accept(new IsNever())) {
return types::listShape(array_map(types::int(...), array_keys($elements)));

Check failure on line 31 in src/Reflection/Visitor/KeyOfResolver.php

View workflow job for this annotation

GitHub Actions / psalm (lowest)

MixedArgumentTypeCoercion

src/Reflection/Visitor/KeyOfResolver.php:31:47: MixedArgumentTypeCoercion: Parameter 1 of closure passed to function array_map expects int, but parent type array-key provided (see https://psalm.dev/194)
}

return types::nonNegativeInt;
}

protected function default(Type $type): mixed
{
throw new \LogicException(\sprintf('Cannot resolve %s type as int range limit', stringify($type)));
}
}
Loading
Loading