From 49966a91725d8e059155eab163dfc61d281ad36f Mon Sep 17 00:00:00 2001 From: Can Vural Date: Thu, 30 Jun 2022 14:19:43 +0200 Subject: [PATCH] Add ClosureTypeFactory --- conf/config.neon | 3 + src/Type/ClosureTypeFactory.php | 78 +++++++++++++++++++ tests/PHPStan/Type/ClosureTypeFactoryTest.php | 69 ++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 src/Type/ClosureTypeFactory.php create mode 100644 tests/PHPStan/Type/ClosureTypeFactoryTest.php diff --git a/conf/config.neon b/conf/config.neon index 38f8410ca3..34c08f4352 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1747,6 +1747,9 @@ services: tags: - phpstan.broker.dynamicStaticMethodReturnTypeExtension + - + class: PHPStan\Type\ClosureTypeFactory + exceptionTypeResolver: class: PHPStan\Rules\Exceptions\ExceptionTypeResolver factory: @PHPStan\Rules\Exceptions\DefaultExceptionTypeResolver diff --git a/src/Type/ClosureTypeFactory.php b/src/Type/ClosureTypeFactory.php new file mode 100644 index 0000000000..dc090d55ad --- /dev/null +++ b/src/Type/ClosureTypeFactory.php @@ -0,0 +1,78 @@ + 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()); + } + +} diff --git a/tests/PHPStan/Type/ClosureTypeFactoryTest.php b/tests/PHPStan/Type/ClosureTypeFactoryTest.php new file mode 100644 index 0000000000..7e3a4537b3 --- /dev/null +++ b/tests/PHPStan/Type/ClosureTypeFactoryTest.php @@ -0,0 +1,69 @@ + 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); + } + +}