From 9a612d885af750029fe35c41f6866aabe8ee1c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=ABl=20Hagestein?= <6616996+Neol3108@users.noreply.github.com> Date: Wed, 12 Feb 2025 22:47:55 +0100 Subject: [PATCH 1/3] feat: add dynamic parameter type extension --- conf/config.neon | 4 + src/Analyser/NodeScopeResolver.php | 53 +++++ .../DynamicParameterTypeExtensionProvider.php | 21 ++ ...yDynamicParameterTypeExtensionProvider.php | 33 +++ src/Testing/RuleTestCase.php | 2 + src/Testing/TypeInferenceTestCase.php | 2 + .../FunctionDynamicParameterTypeExtension.php | 32 +++ .../MethodDynamicParameterTypeExtension.php | 32 +++ ...ticMethodDynamicParameterTypeExtension.php | 32 +++ tests/PHPStan/Analyser/AnalyserTest.php | 2 + .../DynamicParameterTypeExtensionTest.php | 46 +++++ ...arameter-type-extension-arrow-function.php | 180 +++++++++++++++++ ...namic-parameter-type-extension-closure.php | 188 ++++++++++++++++++ ...c-parameter-type-extension-non-closure.php | 3 + .../dynamic-parameter-type-extension.neon | 30 +++ 15 files changed, 660 insertions(+) create mode 100644 src/DependencyInjection/Type/DynamicParameterTypeExtensionProvider.php create mode 100644 src/DependencyInjection/Type/LazyDynamicParameterTypeExtensionProvider.php create mode 100644 src/Type/FunctionDynamicParameterTypeExtension.php create mode 100644 src/Type/MethodDynamicParameterTypeExtension.php create mode 100644 src/Type/StaticMethodDynamicParameterTypeExtension.php create mode 100644 tests/PHPStan/Analyser/DynamicParameterTypeExtensionTest.php create mode 100644 tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-arrow-function.php create mode 100644 tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-closure.php create mode 100644 tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-non-closure.php create mode 100644 tests/PHPStan/Analyser/dynamic-parameter-type-extension.neon diff --git a/conf/config.neon b/conf/config.neon index b9f6445b08..7733b1b87b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -606,6 +606,10 @@ services: class: PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider factory: PHPStan\DependencyInjection\Type\LazyParameterClosureTypeExtensionProvider + - + class: PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider + factory: PHPStan\DependencyInjection\Type\LazyDynamicParameterTypeExtensionProvider + - class: PHPStan\File\FileHelper arguments: diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index d5fde30813..d85f3c36e7 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -65,6 +65,7 @@ use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; +use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; use PHPStan\File\FileHelper; use PHPStan\File\FileReader; @@ -262,6 +263,7 @@ public function __construct( private readonly DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider, private readonly ReadWritePropertiesExtensionProvider $readWritePropertiesExtensionProvider, private readonly ParameterClosureTypeExtensionProvider $parameterClosureTypeExtensionProvider, + private readonly DynamicParameterTypeExtensionProvider $dynamicParameterTypeExtensionProvider, private readonly ScopeFactory $scopeFactory, private readonly bool $polluteScopeWithLoopInitialAssignments, private readonly bool $polluteScopeWithAlwaysIterableForeach, @@ -5002,6 +5004,12 @@ private function processArgs( if ($overwritingParameterType !== null) { $parameterType = $overwritingParameterType; + } else { + $overwritingParameterType = $this->getDynamicParameterTypeFromParameterTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass); + + if ($overwritingParameterType !== null) { + $parameterType = $overwritingParameterType; + } } } @@ -5054,6 +5062,12 @@ private function processArgs( if ($overwritingParameterType !== null) { $parameterType = $overwritingParameterType; + } else { + $overwritingParameterType = $this->getDynamicParameterTypeFromParameterTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass); + + if ($overwritingParameterType !== null) { + $parameterType = $overwritingParameterType; + } } } @@ -5065,6 +5079,15 @@ private function processArgs( } } else { $exprType = $scope->getType($arg->value); + + if ($parameter !== null) { + $overwritingParameterType = $this->getDynamicParameterTypeFromParameterTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass); + + if ($overwritingParameterType !== null) { + $exprType = $overwritingParameterType; + } + } + $exprResult = $this->processExprNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $context->enterDeep()); $throwPoints = array_merge($throwPoints, $exprResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $exprResult->getImpurePoints()); @@ -5225,6 +5248,36 @@ private function getParameterTypeFromParameterClosureTypeExtension(CallLike $cal return null; } + /** + * @param MethodReflection|FunctionReflection|null $calleeReflection + */ + private function getDynamicParameterTypeFromParameterTypeExtension(CallLike $callLike, $calleeReflection, ParameterReflection $parameter, MutatingScope $scope): ?Type + { + if ($callLike instanceof FuncCall && $calleeReflection instanceof FunctionReflection) { + foreach ($this->dynamicParameterTypeExtensionProvider->getFunctionDynamicParameterTypeExtensions() as $functionDynamicParameterTypeExtension) { + if ($functionDynamicParameterTypeExtension->isFunctionSupported($calleeReflection, $parameter)) { + return $functionDynamicParameterTypeExtension->getTypeFromFunctionCall($calleeReflection, $callLike, $parameter, $scope); + } + } + } elseif ($calleeReflection instanceof MethodReflection) { + if ($callLike instanceof StaticCall) { + foreach ($this->dynamicParameterTypeExtensionProvider->getStaticMethodDynamicParameterTypeExtensions() as $staticMethodDynamicParameterTypeExtension) { + if ($staticMethodDynamicParameterTypeExtension->isStaticMethodSupported($calleeReflection, $parameter)) { + return $staticMethodDynamicParameterTypeExtension->getTypeFromStaticMethodCall($calleeReflection, $callLike, $parameter, $scope); + } + } + } elseif ($callLike instanceof MethodCall) { + foreach ($this->dynamicParameterTypeExtensionProvider->getMethodDynamicParameterTypeExtensions() as $methodDynamicParameterTypeExtension) { + if ($methodDynamicParameterTypeExtension->isMethodSupported($calleeReflection, $parameter)) { + return $methodDynamicParameterTypeExtension->getTypeFromMethodCall($calleeReflection, $callLike, $parameter, $scope); + } + } + } + } + + return null; + } + /** * @param MethodReflection|FunctionReflection|null $calleeReflection */ diff --git a/src/DependencyInjection/Type/DynamicParameterTypeExtensionProvider.php b/src/DependencyInjection/Type/DynamicParameterTypeExtensionProvider.php new file mode 100644 index 0000000000..78801ce192 --- /dev/null +++ b/src/DependencyInjection/Type/DynamicParameterTypeExtensionProvider.php @@ -0,0 +1,21 @@ +container->getServicesByTag(self::FUNCTION_TAG); + } + + public function getMethodDynamicParameterTypeExtensions(): array + { + return $this->container->getServicesByTag(self::METHOD_TAG); + } + + public function getStaticMethodDynamicParameterTypeExtensions(): array + { + return $this->container->getServicesByTag(self::STATIC_METHOD_TAG); + } + +} diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index b40a8ebca0..78b6d126b4 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -17,6 +17,7 @@ use PHPStan\Dependency\DependencyResolver; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; +use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; use PHPStan\File\FileHelper; use PHPStan\Php\PhpVersion; @@ -98,6 +99,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), $readWritePropertiesExtensions !== [] ? new DirectReadWritePropertiesExtensionProvider($readWritePropertiesExtensions) : self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(DynamicParameterTypeExtensionProvider::class), self::createScopeFactory($reflectionProvider, $typeSpecifier), $this->shouldPolluteScopeWithLoopInitialAssignments(), $this->shouldPolluteScopeWithAlwaysIterableForeach(), diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index da7cbe82c2..74cacb07b8 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -10,6 +10,7 @@ use PHPStan\Analyser\ScopeContext; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; +use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; use PHPStan\File\FileHelper; use PHPStan\File\SystemAgnosticSimpleRelativePathHelper; @@ -78,6 +79,7 @@ public static function processFile( self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(DynamicParameterTypeExtensionProvider::class), self::createScopeFactory($reflectionProvider, $typeSpecifier), self::getContainer()->getParameter('polluteScopeWithLoopInitialAssignments'), self::getContainer()->getParameter('polluteScopeWithAlwaysIterableForeach'), diff --git a/src/Type/FunctionDynamicParameterTypeExtension.php b/src/Type/FunctionDynamicParameterTypeExtension.php new file mode 100644 index 0000000000..9d37b8049d --- /dev/null +++ b/src/Type/FunctionDynamicParameterTypeExtension.php @@ -0,0 +1,32 @@ +getByType(DynamicThrowTypeExtensionProvider::class), self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(DynamicParameterTypeExtensionProvider::class), self::createScopeFactory($reflectionProvider, $typeSpecifier), false, true, diff --git a/tests/PHPStan/Analyser/DynamicParameterTypeExtensionTest.php b/tests/PHPStan/Analyser/DynamicParameterTypeExtensionTest.php new file mode 100644 index 0000000000..ab77643b82 --- /dev/null +++ b/tests/PHPStan/Analyser/DynamicParameterTypeExtensionTest.php @@ -0,0 +1,46 @@ +gatherAssertTypes(__DIR__ . '/data/dynamic-parameter-type-extension-arrow-function.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-parameter-type-extension-closure.php'); + // yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-parameter-type-extension-non-closure.php'); + } + + /** + * @dataProvider dataFileAsserts + * @param mixed ...$args + */ + public function testFileAsserts( + string $assertType, + string $file, + ...$args, + ): void + { + if (PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/dynamic-parameter-type-extension.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-arrow-function.php b/tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-arrow-function.php new file mode 100644 index 0000000000..c1590cd202 --- /dev/null +++ b/tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-arrow-function.php @@ -0,0 +1,180 @@ +getName() === 'DynamicParameterTypeExtensionArrowFunction\functionWithCallable'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + ParameterReflection $parameter, + Scope $scope + ): ?Type { + $args = $functionCall->getArgs(); + + if (count($args) < 2) { + return null; + } + + $integer = $scope->getType($args[0]->value)->getConstantScalarValues()[0]; + + if ($integer === 1) { + return new ClosureType( + [ + new NativeParameterReflection('test', false, new GenericObjectType(Generic::class, [new IntegerType()]), PassedByReference::createNo(), false, null), + ], + new MixedType(), + ); + } + + return new ClosureType( + [ + new NativeParameterReflection('test', false, new GenericObjectType(Generic::class, [new StringType()]), PassedByReference::createNo(), false, null), + ], + new MixedType(), + ); + } +} + +class MethodDynamicParameterTypeExtension implements \PHPStan\Type\MethodDynamicParameterTypeExtension +{ + + public function isMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + { + return $methodReflection->getDeclaringClass()->getName() === Foo::class && + $parameter->getName() === 'bar' && + $methodReflection->getName() === 'methodWithCallable'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + ParameterReflection $parameter, + Scope $scope + ): ?Type { + return new ClosureType( + [ + new NativeParameterReflection('bar', false, new GenericObjectType(Generic::class, [new StringType()]), PassedByReference::createNo(), false, null), + ], + new MixedType(), + ); + } +} + +class StaticMethodDynamicParameterTypeExtension implements \PHPStan\Type\StaticMethodDynamicParameterTypeExtension +{ + + public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + { + return $methodReflection->getDeclaringClass()->getName() === Foo::class && $methodReflection->getName() === 'staticMethodWithCallable'; + } + + public function getTypeFromStaticMethodCall( + MethodReflection $methodReflection, + StaticCall $methodCall, + ParameterReflection $parameter, + Scope $scope + ): ?Type { + return new ClosureType( + [ + new NativeParameterReflection('test', false, new FloatType(), PassedByReference::createNo(), false, null), + ], + new MixedType() + ); + } +} + +class Foo +{ + + /** + * @param int $foo + * @param mixed $bar + * + * @return void + */ + public function methodWithCallable(int $foo, mixed $bar) + { + + } + + /** + * @return void + */ + public static function staticMethodWithCallable(callable $callback) + { + + } + +} + +/** + * @template T + */ +class Generic +{ + private $value; + + /** + * @param T $value + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * @return T + */ + public function getValue() + { + return $this->value; + } +} + +/** + * @param int $foo + * @param callable(Generic) $callback + * + * @return void + */ +function functionWithCallable(int $foo, callable $callback) +{ + +} + +function test(Foo $foo): void +{ + + (new Foo)->methodWithCallable(2, fn ($arg) => assertType('string', $arg->getValue())); + + Foo::staticMethodWithCallable(fn ($i) => assertType('float', $i)); + +} + +functionWithCallable(1, fn ($i) => assertType('int', $i->getValue())); +functionWithCallable(2, fn (Generic $i) => assertType('string', $i->getValue())); diff --git a/tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-closure.php b/tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-closure.php new file mode 100644 index 0000000000..dae982b2b8 --- /dev/null +++ b/tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-closure.php @@ -0,0 +1,188 @@ +getName() === 'DynamicParameterTypeExtensionClosure\functionWithCallable'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + ParameterReflection $parameter, + Scope $scope + ): ?Type { + $args = $functionCall->getArgs(); + + if (count($args) < 2) { + return null; + } + + $integer = $scope->getType($args[0]->value)->getConstantScalarValues()[0]; + + if ($integer === 1) { + return new ClosureType( + [ + new NativeParameterReflection('test', false, new GenericObjectType(Generic::class, [new IntegerType()]), PassedByReference::createNo(), false, null), + ], + new MixedType(), + ); + } + + return new ClosureType( + [ + new NativeParameterReflection('test', false, new GenericObjectType(Generic::class, [new StringType()]), PassedByReference::createNo(), false, null), + ], + new MixedType(), + ); + } +} + +class MethodDynamicParameterTypeExtension implements \PHPStan\Type\MethodDynamicParameterTypeExtension +{ + + public function isMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + { + return $methodReflection->getDeclaringClass()->getName() === Foo::class && + $parameter->getName() === 'bar' && + $methodReflection->getName() === 'methodWithCallable'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + ParameterReflection $parameter, + Scope $scope + ): ?Type { + return new ClosureType( + [ + new NativeParameterReflection('bar', false, new GenericObjectType(Generic::class, [new StringType()]), PassedByReference::createNo(), false, null), + ], + new MixedType(), + ); + } +} + +class StaticMethodDynamicParameterTypeExtension implements \PHPStan\Type\StaticMethodDynamicParameterTypeExtension +{ + + public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + { + return $methodReflection->getDeclaringClass()->getName() === Foo::class && $methodReflection->getName() === 'staticMethodWithCallable'; + } + + public function getTypeFromStaticMethodCall( + MethodReflection $methodReflection, + StaticCall $methodCall, + ParameterReflection $parameter, + Scope $scope + ): ?Type { + return new ClosureType( + [ + new NativeParameterReflection('test', false, new FloatType(), PassedByReference::createNo(), false, null), + ], + new MixedType() + ); + } +} + +class Foo +{ + + /** + * @param int $foo + * @param mixed $bar + * + * @return void + */ + public function methodWithCallable(int $foo, mixed $bar) + { + + } + + /** + * @return void + */ + public static function staticMethodWithCallable(callable $callback) + { + + } + +} + +/** + * @template T + */ +class Generic +{ + private $value; + + /** + * @param T $value + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * @return T + */ + public function getValue() + { + return $this->value; + } +} + +/** + * @param int $foo + * @param callable(Generic) $callback + * + * @return void + */ +function functionWithCallable(int $foo, callable $callback) +{ + +} + +function test(Foo $foo): void +{ + + (new Foo)->methodWithCallable(2, function ($arg) { + assertType('string', $arg->getValue()); + }); + + Foo::staticMethodWithCallable(function ($i) { + assertType('float', $i); + }); + +} + +functionWithCallable(1, function ($i) { + assertType('int', $i->getValue()); +}); +functionWithCallable(2, function (Generic $i) { + assertType('string', $i->getValue()); +}); diff --git a/tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-non-closure.php b/tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-non-closure.php new file mode 100644 index 0000000000..5991ab7a3a --- /dev/null +++ b/tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-non-closure.php @@ -0,0 +1,3 @@ + Date: Thu, 13 Feb 2025 12:48:05 +0100 Subject: [PATCH 2/3] chore: fix phpstan and cs --- src/Analyser/NodeScopeResolver.php | 2 +- .../Type/LazyDynamicParameterTypeExtensionProvider.php | 2 +- src/Testing/RuleTestCase.php | 2 +- src/Testing/TypeInferenceTestCase.php | 2 +- tests/PHPStan/Analyser/AnalyserTest.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index d85f3c36e7..10c90f614d 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -63,9 +63,9 @@ use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection; use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource; use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider; +use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; -use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; use PHPStan\File\FileHelper; use PHPStan\File\FileReader; diff --git a/src/DependencyInjection/Type/LazyDynamicParameterTypeExtensionProvider.php b/src/DependencyInjection/Type/LazyDynamicParameterTypeExtensionProvider.php index 43fd17d31e..e860c760ab 100644 --- a/src/DependencyInjection/Type/LazyDynamicParameterTypeExtensionProvider.php +++ b/src/DependencyInjection/Type/LazyDynamicParameterTypeExtensionProvider.php @@ -4,7 +4,7 @@ use PHPStan\DependencyInjection\Container; -class LazyDynamicParameterTypeExtensionProvider implements DynamicParameterTypeExtensionProvider +final class LazyDynamicParameterTypeExtensionProvider implements DynamicParameterTypeExtensionProvider { public const FUNCTION_TAG = 'phpstan.functionDynamicParameterTypeExtension'; diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 78b6d126b4..2b6ab0db46 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -15,9 +15,9 @@ use PHPStan\Collectors\Collector; use PHPStan\Collectors\Registry as CollectorRegistry; use PHPStan\Dependency\DependencyResolver; +use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; -use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; use PHPStan\File\FileHelper; use PHPStan\Php\PhpVersion; diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 74cacb07b8..ce58ff701e 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -8,9 +8,9 @@ use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\ScopeContext; +use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; -use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; use PHPStan\File\FileHelper; use PHPStan\File\SystemAgnosticSimpleRelativePathHelper; diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index d0b6842908..5a648dc7c0 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -10,9 +10,9 @@ use PHPStan\Collectors\Registry as CollectorRegistry; use PHPStan\Dependency\DependencyResolver; use PHPStan\Dependency\ExportedNodeResolver; +use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; -use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Node\Printer\Printer; From 8b5d7dd6ce4bc70582dcc56d9888f264526a272e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=ABl=20Hagestein?= <6616996+Neol3108@users.noreply.github.com> Date: Fri, 14 Feb 2025 21:35:39 +0100 Subject: [PATCH 3/3] chore: add failing test --- ...namic-parameter-type-extension-closure.php | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-closure.php b/tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-closure.php index dae982b2b8..27779b9166 100644 --- a/tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-closure.php +++ b/tests/PHPStan/Analyser/data/dynamic-parameter-type-extension-closure.php @@ -12,9 +12,12 @@ use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\Type\ClosureType; +use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FloatType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\IntegerType; +use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\MixedType; @@ -66,8 +69,8 @@ class MethodDynamicParameterTypeExtension implements \PHPStan\Type\MethodDynamic public function isMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool { return $methodReflection->getDeclaringClass()->getName() === Foo::class && - $parameter->getName() === 'bar' && - $methodReflection->getName() === 'methodWithCallable'; + $parameter->getName() === 'relations' && + $methodReflection->getName() === 'with'; } public function getTypeFromMethodCall( @@ -76,12 +79,25 @@ public function getTypeFromMethodCall( ParameterReflection $parameter, Scope $scope ): ?Type { - return new ClosureType( - [ - new NativeParameterReflection('bar', false, new GenericObjectType(Generic::class, [new StringType()]), PassedByReference::createNo(), false, null), - ], - new MixedType(), - ); + return new ConstantArrayType([ + new ConstantStringType('user'), + ], [ + new ClosureType( + [ + new NativeParameterReflection( + 'callback', + false, + new GenericObjectType('Illuminate\Database\Eloquent\Builder', [ + new ObjectType('Illuminate\Database\Eloquent\Model'), + ]), + PassedByReference::createNo(), + false, + null, + ), + ], + new MixedType(), + ), + ]); } } @@ -130,6 +146,11 @@ public static function staticMethodWithCallable(callable $callback) } + public function with(array $relations) + { + + } + } /** @@ -170,9 +191,11 @@ function functionWithCallable(int $foo, callable $callback) function test(Foo $foo): void { - (new Foo)->methodWithCallable(2, function ($arg) { - assertType('string', $arg->getValue()); - }); + (new Foo)->with([ + 'users' => function ($arg) { + assertType('Illuminate\Database\Eloquent\Builder', $arg); + }, + ]); Foo::staticMethodWithCallable(function ($i) { assertType('float', $i);