Skip to content

Commit

Permalink
Add ClosureTypeFactory
Browse files Browse the repository at this point in the history
  • Loading branch information
canvural committed Jun 30, 2022
1 parent 59fb0a3 commit 49966a9
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 0 deletions.
3 changes: 3 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1747,6 +1747,9 @@ services:
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension

-
class: PHPStan\Type\ClosureTypeFactory

exceptionTypeResolver:
class: PHPStan\Rules\Exceptions\ExceptionTypeResolver
factory: @PHPStan\Rules\Exceptions\DefaultExceptionTypeResolver
Expand Down
78 changes: 78 additions & 0 deletions src/Type/ClosureTypeFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type;

use Closure;
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter;
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionType;
use PHPStan\BetterReflection\Reflection\ReflectionFunction as BetterReflectionFunction;
use PHPStan\BetterReflection\Reflection\ReflectionParameter as BetterReflectionParameter;
use PHPStan\Reflection\InitializerExprContext;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\PassedByReference;
use function array_map;

/** @api */
class ClosureTypeFactory
{

public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver)
{
}

/**
* @param Closure(): mixed $closure
*/
public function fromClosureObject(Closure $closure): ClosureType
{
$betterReflectionFunction = BetterReflectionFunction::createFromClosure($closure);

$parameters = array_map(fn (BetterReflectionParameter $parameter) => new class($parameter, $this->initializerExprTypeResolver) implements ParameterReflection {

public function __construct(private BetterReflectionParameter $reflection, private InitializerExprTypeResolver $initializerExprTypeResolver)
{
}

public function getName(): string
{
return $this->reflection->getName();
}

public function isOptional(): bool
{
return $this->reflection->isOptional();
}

public function getType(): Type
{
return TypehintHelper::decideTypeFromReflection(ReflectionType::fromTypeOrNull($this->reflection->getType()), null, null, $this->reflection->isVariadic());
}

public function passedByReference(): PassedByReference
{
return $this->reflection->isPassedByReference()
? PassedByReference::createCreatesNewVariable()
: PassedByReference::createNo();
}

public function isVariadic(): bool
{
return $this->reflection->isVariadic();
}

public function getDefaultValue(): ?Type
{
if (! $this->reflection->isDefaultValueAvailable()) {
return null;
}

return $this->initializerExprTypeResolver->getType($this->reflection->getDefaultValueExpr(), InitializerExprContext::fromReflectionParameter(new ReflectionParameter($this->reflection)));
}

}, $betterReflectionFunction->getParameters());

return new ClosureType($parameters, TypehintHelper::decideTypeFromReflection(ReflectionType::fromTypeOrNull($betterReflectionFunction->getReturnType())), $betterReflectionFunction->isVariadic());
}

}
69 changes: 69 additions & 0 deletions tests/PHPStan/Type/ClosureTypeFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type;

use Closure;
use PHPStan\Testing\PHPStanTestCase;

class ClosureTypeFactoryTest extends PHPStanTestCase
{

public function dataFromClosureObjectReturnType(): array
{
return [
[static function (): void {
}, 'void'],
[static function () { // @phpcs:ignore
}, 'mixed'],
[static fn (): int => 5, 'int'],
];
}

/**
* @param Closure(): mixed $closure
* @dataProvider dataFromClosureObjectReturnType
*/
public function testFromClosureObjectReturnType(Closure $closure, string $returnType): void
{
$closureType = $this->getClosureType($closure);

$this->assertSame($returnType, $closureType->getReturnType()->describe(VerbosityLevel::precise()));
}

public function dataFromClosureObjectParameter(): array
{
return [
[static function (string $foo): void {
}, 0, 'string'],
[static function (string $foo = 'boo'): void {
}, 0, 'string'],
[static function (string $foo = 'foo', int $bar = 5): void {
}, 1, 'int'],
[static function (array $foo): void {
}, 0, 'array'],
[static function (array $foo = [1]): void {
}, 0, 'array'],
];
}

/**
* @param Closure(): mixed $closure
* @dataProvider dataFromClosureObjectParameter
*/
public function testFromClosureObjectParameter(Closure $closure, int $index, string $type): void
{
$closureType = $this->getClosureType($closure);

$this->assertArrayHasKey($index, $closureType->getParameters());
$this->assertSame($type, $closureType->getParameters()[$index]->getType()->describe(VerbosityLevel::precise()));
}

/**
* @param Closure(): mixed $closure
*/
private function getClosureType(Closure $closure): ClosureType
{
return self::getContainer()->getByType(ClosureTypeFactory::class)->fromClosureObject($closure);
}

}

0 comments on commit 49966a9

Please sign in to comment.