Skip to content

Commit

Permalink
Add ClassReflection::typeArgumentsOf($class) and resolvedTypeArgument…
Browse files Browse the repository at this point in the history
…sOf($class)
  • Loading branch information
vudaltsov committed Aug 12, 2024
1 parent ee694a7 commit 1e9ce3b
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/Reflection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add `ConstantReflection` and `TyphoonReflector::reflectConstant()`.
- Add `ClassReflection::typeArgumentsOf($class)`.
- Add `ClassReflection::resolvedTypeArgumentsOf($class)`.

## [0.4.2] 2024-08-05

Expand Down
47 changes: 47 additions & 0 deletions src/Reflection/ClassReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Typhoon\Reflection\Internal\Misc\NonSerializable;
use Typhoon\Reflection\Internal\NativeAdapter\ClassAdapter;
use Typhoon\Type\Type;
use Typhoon\Type\types;
use Typhoon\Type\Visitor\TemplateTypeResolver;
use Typhoon\TypedMap\TypedMap;

Expand Down Expand Up @@ -199,6 +200,52 @@ public function isInstanceOf(string|NamedClassId|AnonymousClassId $class): bool
|| \array_key_exists($class->name, $this->data[Data::Interfaces]);
}

/**
* @param non-empty-string|NamedClassId|AnonymousClassId $class
* @return list<Type>
*/
public function typeArgumentsOf(string|NamedClassId|AnonymousClassId $class): array
{
if (\is_string($class)) {
$class = Id::class($class);
}

if ($this->id->equals($class)) {
return $this
->templates()
->map(static fn(TemplateReflection $template): Type => types::template($template->id))
->toList();
}

if ($class instanceof AnonymousClassId) {
return [];

Check warning on line 221 in src/Reflection/ClassReflection.php

View check run for this annotation

Codecov / codecov/patch

src/Reflection/ClassReflection.php#L221

Added line #L221 was not covered by tests
}

/** @psalm-suppress PossiblyInvalidArrayOffset */
return $this->data[Data::Parents][$class->name] ?? $this->data[Data::Interfaces][$class->name] ?? [];
}

/**
* @param non-empty-string|NamedClassId|AnonymousClassId $class
* @param list<Type> $typeArguments
* @return list<Type>
*/
public function resolvedTypeArgumentsOf(string|NamedClassId|AnonymousClassId $class, array $typeArguments = []): array
{
$classTypeArguments = $this->typeArgumentsOf($class);

if ($classTypeArguments === []) {
return [];

Check warning on line 238 in src/Reflection/ClassReflection.php

View check run for this annotation

Codecov / codecov/patch

src/Reflection/ClassReflection.php#L238

Added line #L238 was not covered by tests
}

$templateResolver = $this->createTemplateResolver($typeArguments);

return array_map(
static fn(Type $type): Type => $type->accept($templateResolver),
$classTypeArguments,
);
}

public function isClass(): bool
{
return $this->data[Data::ClassKind] === ClassKind::Class_;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection;

use Typhoon\Reflection\Locator\Resource;
use Typhoon\Type\types;
use function PHPUnit\Framework\assertEquals;

return static function (TyphoonReflector $reflector): void {
$iterator = $reflector->reflectClass(\Iterator::class);
assertEquals([types::mixed, types::mixed], $iterator->resolvedTypeArgumentsOf(\Iterator::class));
assertEquals([types::int, types::string], $iterator->resolvedTypeArgumentsOf(\Iterator::class, [types::int, types::string]));
assertEquals([types::mixed, types::mixed], $iterator->resolvedTypeArgumentsOf(\Traversable::class));
assertEquals([types::int, types::string], $iterator->resolvedTypeArgumentsOf(\Traversable::class, [types::int, types::string]));

$a = $reflector
->withResource(Resource::fromCode(
<<<'PHP'
<?php
/**
* @implements Iterator<string>
*/
abstract class A implements Iterator {}
PHP,
))
->reflectClass('A');
assertEquals([types::mixed, types::string], $a->resolvedTypeArgumentsOf(\Iterator::class));
assertEquals([types::mixed, types::string], $a->resolvedTypeArgumentsOf(\Traversable::class));
};
42 changes: 42 additions & 0 deletions tests/Reflection/functional_tests/class/type_arguments_of.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection;

use Typhoon\Reflection\Locator\Resource;
use Typhoon\Type\types;
use function PHPUnit\Framework\assertEquals;

return static function (TyphoonReflector $reflector): void {
$iterator = $reflector->reflectClass(\Iterator::class);
assertEquals(
[types::classTemplate(\Iterator::class, 'TKey'), types::classTemplate(\Iterator::class, 'TValue')],
$iterator->typeArgumentsOf(\Iterator::class),
);
assertEquals(
[types::classTemplate(\Iterator::class, 'TKey'), types::classTemplate(\Iterator::class, 'TValue')],
$iterator->typeArgumentsOf(\Traversable::class),
);

$a = $reflector
->withResource(Resource::fromCode(
<<<'PHP'
<?php
/**
* @implements Iterator<string>
*/
abstract class A implements Iterator {}
PHP,
))
->reflectClass('A');
assertEquals(
[types::mixed, types::string],
$a->typeArgumentsOf(\Iterator::class),
);
assertEquals(
[types::mixed, types::string],
$a->typeArgumentsOf(\Traversable::class),
);
};

0 comments on commit 1e9ce3b

Please sign in to comment.