From 9beb618b68e32bd4c8fd85d41a1f95384449a94d Mon Sep 17 00:00:00 2001 From: schlndh Date: Sun, 25 Aug 2024 13:46:50 +0200 Subject: [PATCH 1/8] WIP: apply parameter closure type extension in FunctionCallParametersCheck --- src/Rules/AttributesCheck.php | 1 + src/Rules/Classes/InstantiationRule.php | 1 + src/Rules/FunctionCallParametersCheck.php | 17 +++++++ src/Rules/Functions/CallCallablesRule.php | 1 + .../CallToFunctionParametersRule.php | 1 + src/Rules/Functions/CallUserFuncRule.php | 2 +- src/Rules/Methods/CallMethodsRule.php | 1 + src/Rules/Methods/CallStaticMethodsRule.php | 1 + .../Analyser/Bug9307CallMethodsRuleTest.php | 3 +- .../Rules/Classes/ClassAttributesRuleTest.php | 2 + .../ClassConstantAttributesRuleTest.php | 2 + .../ForbiddenNameCheckExtensionRuleTest.php | 3 +- .../Rules/Classes/InstantiationRuleTest.php | 3 +- .../EnumCases/EnumCaseAttributesRuleTest.php | 2 + .../ArrowFunctionAttributesRuleTest.php | 2 + .../Rules/Functions/CallCallablesRuleTest.php | 2 + .../CallToFunctionParametersRuleTest.php | 21 ++++++-- .../Rules/Functions/CallUserFuncRuleTest.php | 3 +- .../Functions/ClosureAttributesRuleTest.php | 2 + .../Functions/FunctionAttributesRuleTest.php | 2 + .../Functions/ParamAttributesRuleTest.php | 2 + ...ction-parameter-closure-type-extension.php | 50 +++++++++++++++++++ .../CallMethodsRuleNoBleedingEdgeTest.php | 3 +- .../Rules/Methods/CallMethodsRuleTest.php | 3 +- .../Methods/CallStaticMethodsRuleTest.php | 2 + .../Methods/MethodAttributesRuleTest.php | 2 + .../Properties/PropertyAttributesRuleTest.php | 2 + 27 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/function-parameter-closure-type-extension.php diff --git a/src/Rules/AttributesCheck.php b/src/Rules/AttributesCheck.php index e04381033e..4980e72e1e 100644 --- a/src/Rules/AttributesCheck.php +++ b/src/Rules/AttributesCheck.php @@ -127,6 +127,7 @@ public function check( $nodeAttributes['isAttribute'] = true; $parameterErrors = $this->functionCallParametersCheck->check( + $attributeConstructor, ParametersAcceptorSelector::selectFromArgs( $scope, $attribute->args, diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index 8994a4754b..bbddfbc2c4 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -192,6 +192,7 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ $classDisplayName = SprintfHelper::escapeFormatString($classReflection->getDisplayName()); return array_merge($messages, $this->check->check( + $constructorReflection, ParametersAcceptorSelector::selectFromArgs( $scope, $node->getArgs(), diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 4f0d2ae447..29865dd550 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -6,7 +6,10 @@ use PhpParser\Node\Expr; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Reflection\ParametersAcceptor; @@ -42,6 +45,7 @@ public function __construct( private PhpVersion $phpVersion, private UnresolvableTypeHelper $unresolvableTypeHelper, private PropertyReflectionFinder $propertyReflectionFinder, + private ParameterClosureTypeExtensionProvider $parameterClosureTypeExtensionProvider, private bool $checkArgumentTypes, private bool $checkArgumentsPassedByReference, private bool $checkExtraArguments, @@ -52,12 +56,14 @@ public function __construct( } /** + * @param MethodReflection|FunctionReflection|null $callReflection * @param Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $funcCall * @param array{0: string, 1: string, 2: string, 3: string, 4: string, 5: string, 6: string, 7: string, 8: string, 9: string, 10: string, 11: string, 12: string, 13?: string, 14?: string} $messages * @param 'attribute'|'callable'|'method'|'staticMethod'|'function'|'new' $nodeType * @return list */ public function check( + $callReflection, ParametersAcceptor $parametersAcceptor, Scope $scope, bool $isBuiltin, @@ -313,6 +319,17 @@ public function check( if ($this->checkArgumentTypes) { $parameterType = TypeUtils::resolveLateResolvableTypes($parameter->getType()); + // TODO: handle other types of extensions + if ($funcCall instanceof Expr\FuncCall && $callReflection instanceof FunctionReflection) { + foreach ($this->parameterClosureTypeExtensionProvider->getFunctionParameterClosureTypeExtensions() as $functionParameterClosureTypeExtension) { + if (!$functionParameterClosureTypeExtension->isFunctionSupported($callReflection, $parameter)) { + continue; + } + $parameterType = $functionParameterClosureTypeExtension->getTypeFromFunctionCall($callReflection, $funcCall, $parameter, $scope) ?? $parameterType; + + } + } + if ( !$parameter->passedByReference()->createsNewVariable() || (!$isBuiltin && $this->checkUnresolvableParameterTypes) // bleeding edge only diff --git a/src/Rules/Functions/CallCallablesRule.php b/src/Rules/Functions/CallCallablesRule.php index d8fb352e52..0bb5155812 100644 --- a/src/Rules/Functions/CallCallablesRule.php +++ b/src/Rules/Functions/CallCallablesRule.php @@ -113,6 +113,7 @@ public function processNode( return array_merge( $messages, $this->check->check( + null, $parametersAcceptor, $scope, false, diff --git a/src/Rules/Functions/CallToFunctionParametersRule.php b/src/Rules/Functions/CallToFunctionParametersRule.php index d1ca216791..0137a21d52 100644 --- a/src/Rules/Functions/CallToFunctionParametersRule.php +++ b/src/Rules/Functions/CallToFunctionParametersRule.php @@ -40,6 +40,7 @@ public function processNode(Node $node, Scope $scope): array $functionName = SprintfHelper::escapeFormatString($function->getName()); return $this->check->check( + $function, ParametersAcceptorSelector::selectFromArgs( $scope, $node->getArgs(), diff --git a/src/Rules/Functions/CallUserFuncRule.php b/src/Rules/Functions/CallUserFuncRule.php index c4030961cf..3f311f1de5 100644 --- a/src/Rules/Functions/CallUserFuncRule.php +++ b/src/Rules/Functions/CallUserFuncRule.php @@ -60,7 +60,7 @@ public function processNode(Node $node, Scope $scope): array $callableDescription = 'callable passed to call_user_func()'; - return $this->check->check($parametersAcceptor, $scope, false, $funcCall, [ + return $this->check->check(null, $parametersAcceptor, $scope, false, $funcCall, [ ucfirst($callableDescription) . ' invoked with %d parameter, %d required.', ucfirst($callableDescription) . ' invoked with %d parameters, %d required.', ucfirst($callableDescription) . ' invoked with %d parameter, at least %d required.', diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index 4f45dbf9fd..8d988e7d61 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -46,6 +46,7 @@ public function processNode(Node $node, Scope $scope): array $messagesMethodName = SprintfHelper::escapeFormatString($declaringClass->getDisplayName() . '::' . $methodReflection->getName() . '()'); return array_merge($errors, $this->parametersCheck->check( + $methodReflection, ParametersAcceptorSelector::selectFromArgs( $scope, $node->getArgs(), diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 33612ff02c..9eb52d4841 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -54,6 +54,7 @@ public function processNode(Node $node, Scope $scope): array )); $errors = array_merge($errors, $this->parametersCheck->check( + $method, ParametersAcceptorSelector::selectFromArgs( $scope, $node->getArgs(), diff --git a/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php b/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php index ff0a0daa9e..ca8be58131 100644 --- a/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php +++ b/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Analyser; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\Methods\CallMethodsRule; @@ -26,7 +27,7 @@ protected function getRule(): Rule $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, true, false, true, false); return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(PHP_VERSION_ID), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(PHP_VERSION_ID), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, true), ); } diff --git a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php index 6fa6252277..5a39fbb0a6 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Classes; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -34,6 +35,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, diff --git a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php index b132e3fe08..d4469a886b 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Classes; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -33,6 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, diff --git a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php index 9d3ea64034..1c62c80cdb 100644 --- a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Classes; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -26,7 +27,7 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( $reflectionProvider, - new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 36dc32c352..c0bc966864 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Classes; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -26,7 +27,7 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( $reflectionProvider, - new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php index 26643e506c..7c2f173e8e 100644 --- a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php +++ b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\EnumCases; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -33,6 +34,7 @@ protected function getRule(): Rule new PhpVersion(80100), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php index 7f3aab6ac2..35fcb2e7ec 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Functions; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -33,6 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index bcd1c9ee87..52bfdcc97b 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Functions; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; @@ -30,6 +31,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index a47624e8e8..d94d0873d2 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Functions; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; @@ -26,7 +27,7 @@ protected function getRule(): Rule $broker = $this->createReflectionProvider(); return new CallToFunctionParametersRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, true), ); } @@ -658,19 +659,19 @@ public function testPregReplaceCallback(): void { $this->analyse([__DIR__ . '/data/preg_replace_callback.php'], [ [ - 'Parameter #2 $callback of function preg_replace_callback expects callable(array): string, Closure(string): string given.', + 'Parameter #2 $callback of function preg_replace_callback expects Closure(array{string}): string, Closure(string): string given.', 6, ], [ - 'Parameter #2 $callback of function preg_replace_callback expects callable(array): string, Closure(string): string given.', + 'Parameter #2 $callback of function preg_replace_callback expects Closure(array{string}): string, Closure(string): string given.', 13, ], [ - 'Parameter #2 $callback of function preg_replace_callback expects callable(array): string, Closure(array): void given.', + 'Parameter #2 $callback of function preg_replace_callback expects Closure(array{string}): string, Closure(array): void given.', 20, ], [ - 'Parameter #2 $callback of function preg_replace_callback expects callable(array): string, Closure(): void given.', + 'Parameter #2 $callback of function preg_replace_callback expects Closure(array{string}): string, Closure(): void given.', 25, ], ]); @@ -1753,6 +1754,16 @@ public function testBug11506(): void $this->analyse([__DIR__ . '/data/bug-11506.php'], []); } + public function testFunctionParameterClosureTypeExtension(): void + { + $this->analyse([__DIR__ . '/data/function-parameter-closure-type-extension.php'], [ + [ + 'Parameter #2 $callback of function preg_replace_callback expects Closure(array{0: array{string, int<-1, max>}, 1?: array{\'\'|\'foo\', int<-1, max>}, 2?: array{\'\'|\'bar\', int<-1, max>}, 3?: array{\'baz\', int<-1, max>}}): string, Closure(array): string given.', + 44, + ], + ]); + } + public function testBug11559(): void { $this->analyse([__DIR__ . '/data/bug-11559.php'], []); diff --git a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php index d10eee8e48..2f86cd6d94 100644 --- a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Functions; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; @@ -21,7 +22,7 @@ class CallUserFuncRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - return new CallUserFuncRule($reflectionProvider, new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true)); + return new CallUserFuncRule($reflectionProvider, new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php index f4a3d5a1bb..9af4a49947 100644 --- a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Functions; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -33,6 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php index 553ab6c462..bbc748ec27 100644 --- a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Functions; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -33,6 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php index 60d620474c..ddaf10d3c6 100644 --- a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Functions; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -33,6 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/data/function-parameter-closure-type-extension.php b/tests/PHPStan/Rules/Functions/data/function-parameter-closure-type-extension.php new file mode 100644 index 0000000000..39f8661e18 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/function-parameter-closure-type-extension.php @@ -0,0 +1,50 @@ += 8.1 + +namespace FunctionParameterClosureTypeExtension; + +/** @param array{0: array{string, int<-1, max>}, 1?: array{''|'foo', int<-1, max>}, 2?: array{''|'bar', int<-1, max>}, 3?: array{'baz', int<-1, max>}} $matches */ +function foo(array $matches): string +{ + return ''; +} + +/** @param array $matches */ +function bar(array $matches): string +{ + return ''; +} + +/** @param array $matches */ +function baz(array $matches): string +{ + return ''; +} + +function (string $s): void { + preg_replace_callback( + '/(foo)?(bar)?(baz)?/', + foo(...), + $s, + -1, + $count, + PREG_OFFSET_CAPTURE + ); + + preg_replace_callback( + '/(foo)?(bar)?(baz)?/', + bar(...), + $s, + -1, + $count, + PREG_OFFSET_CAPTURE + ); + + preg_replace_callback( + '/(foo)?(bar)?(baz)?/', + baz(...), + $s, + -1, + $count, + PREG_OFFSET_CAPTURE + ); +}; diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php index 71678d99ff..e326414910 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Methods; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; @@ -26,7 +27,7 @@ protected function getRule(): Rule $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, $this->checkExplicitMixed, false, true, false); return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(PHP_VERSION_ID), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, false), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(PHP_VERSION_ID), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, false), ); } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 8bb53330d4..9c8ec94759 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Methods; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; @@ -36,7 +37,7 @@ protected function getRule(): Rule $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false); return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, true), ); } diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 27718dc9c7..d43a08c18f 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Methods; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -50,6 +51,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, diff --git a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index 9ce75af6e6..6616696fc0 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Methods; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -35,6 +36,7 @@ protected function getRule(): Rule new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, diff --git a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php index b6d394d30b..ce3faa7ec2 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Properties; +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -32,6 +33,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, From 3d0107ca5b86af6279db8a96540cb4e58159a673 Mon Sep 17 00:00:00 2001 From: schlndh Date: Mon, 26 Aug 2024 17:29:54 +0200 Subject: [PATCH 2/8] apply all closure type parameter extensions in parameters check --- src/Rules/FunctionCallParametersCheck.php | 51 ++++++++--- .../CallToFunctionParametersRuleTest.php | 6 +- .../Rules/Methods/CallMethodsRuleTest.php | 91 ++++++++++++++++++- .../Methods/CallStaticMethodsRuleTest.php | 76 +++++++++++++++- ...ethod-parameter-closure-type-extension.php | 35 +++++++ ...ethod-parameter-closure-type-extension.php | 35 +++++++ 6 files changed, 280 insertions(+), 14 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/method-parameter-closure-type-extension.php create mode 100644 tests/PHPStan/Rules/Methods/data/static-method-parameter-closure-type-extension.php diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 29865dd550..26c4c00579 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -4,6 +4,10 @@ use PhpParser\Node; use PhpParser\Node\Expr; +use PhpParser\Node\Expr\CallLike; +use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; @@ -318,17 +322,12 @@ public function check( if ($this->checkArgumentTypes) { $parameterType = TypeUtils::resolveLateResolvableTypes($parameter->getType()); - - // TODO: handle other types of extensions - if ($funcCall instanceof Expr\FuncCall && $callReflection instanceof FunctionReflection) { - foreach ($this->parameterClosureTypeExtensionProvider->getFunctionParameterClosureTypeExtensions() as $functionParameterClosureTypeExtension) { - if (!$functionParameterClosureTypeExtension->isFunctionSupported($callReflection, $parameter)) { - continue; - } - $parameterType = $functionParameterClosureTypeExtension->getTypeFromFunctionCall($callReflection, $funcCall, $parameter, $scope) ?? $parameterType; - - } - } + $parameterType = $this->getParameterTypeFromParameterClosureTypeExtension( + $funcCall, + $callReflection, + $parameter, + $scope, + ) ?? $parameterType; if ( !$parameter->passedByReference()->createsNewVariable() @@ -650,4 +649,34 @@ private function describeParameter(ParameterReflection $parameter, ?int $positio return implode(' ', $parts); } + /** + * @param MethodReflection|FunctionReflection|null $calleeReflection + */ + private function getParameterTypeFromParameterClosureTypeExtension(CallLike $callLike, $calleeReflection, ParameterReflection $parameter, Scope $scope): ?Type + { + if ($callLike instanceof FuncCall && $calleeReflection instanceof FunctionReflection) { + foreach ($this->parameterClosureTypeExtensionProvider->getFunctionParameterClosureTypeExtensions() as $functionParameterClosureTypeExtension) { + if ($functionParameterClosureTypeExtension->isFunctionSupported($calleeReflection, $parameter)) { + return $functionParameterClosureTypeExtension->getTypeFromFunctionCall($calleeReflection, $callLike, $parameter, $scope); + } + } + } elseif ($calleeReflection instanceof MethodReflection) { + if ($callLike instanceof StaticCall) { + foreach ($this->parameterClosureTypeExtensionProvider->getStaticMethodParameterClosureTypeExtensions() as $staticMethodParameterClosureTypeExtension) { + if ($staticMethodParameterClosureTypeExtension->isStaticMethodSupported($calleeReflection, $parameter)) { + return $staticMethodParameterClosureTypeExtension->getTypeFromStaticMethodCall($calleeReflection, $callLike, $parameter, $scope); + } + } + } elseif ($callLike instanceof MethodCall) { + foreach ($this->parameterClosureTypeExtensionProvider->getMethodParameterClosureTypeExtensions() as $methodParameterClosureTypeExtension) { + if ($methodParameterClosureTypeExtension->isMethodSupported($calleeReflection, $parameter)) { + return $methodParameterClosureTypeExtension->getTypeFromMethodCall($calleeReflection, $callLike, $parameter, $scope); + } + } + } + } + + return null; + } + } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index d94d0873d2..130e4abc81 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1754,8 +1754,12 @@ public function testBug11506(): void $this->analyse([__DIR__ . '/data/bug-11506.php'], []); } - public function testFunctionParameterClosureTypeExtension(): void + public function testParameterClosureTypeExtension(): void { + if (PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4'); + } + $this->analyse([__DIR__ . '/data/function-parameter-closure-type-extension.php'], [ [ 'Parameter #2 $callback of function preg_replace_callback expects Closure(array{0: array{string, int<-1, max>}, 1?: array{\'\'|\'foo\', int<-1, max>}, 2?: array{\'\'|\'bar\', int<-1, max>}, 3?: array{\'baz\', int<-1, max>}}): string, Closure(array): string given.', diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 9c8ec94759..afcf743fea 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2,8 +2,13 @@ namespace PHPStan\Rules\Methods; +use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\Native\NativeParameterReflection; +use PHPStan\Reflection\ParameterReflection; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; @@ -11,6 +16,12 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ClosureType; +use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\IntegerType; +use PHPStan\Type\MethodParameterClosureTypeExtension; +use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use const PHP_VERSION_ID; /** @@ -35,9 +46,67 @@ protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false); + return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, true), + new FunctionCallParametersCheck( + $ruleLevelHelper, + new NullsafeCheck(), + new PhpVersion($this->phpVersion), + new UnresolvableTypeHelper(), + new PropertyReflectionFinder(), + new class implements ParameterClosureTypeExtensionProvider + { + + public function getFunctionParameterClosureTypeExtensions(): array + { + return []; + } + + public function getMethodParameterClosureTypeExtensions(): array + { + return [ + new class implements MethodParameterClosureTypeExtension + { + + public function isMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + { + return $methodReflection->getName() === 'foo' && $methodReflection->getDeclaringClass()->getName() === 'MethodParameterClosureTypeExtension\\Foo'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, ParameterReflection $parameter, Scope $scope): Type + { + return new ClosureType( + [ + new NativeParameterReflection( + $parameter->getName(), + $parameter->isOptional(), + TypeCombinator::union(new ConstantIntegerType(5), new ConstantIntegerType(7)), + $parameter->passedByReference(), + $parameter->isVariadic(), + $parameter->getDefaultValue(), + ), + ], + new IntegerType(), + ); + } + + }, + ]; + } + + public function getStaticMethodParameterClosureTypeExtensions(): array + { + return []; + } + + }, + true, + true, + true, + true, + true, + ), ); } @@ -3346,6 +3415,26 @@ public function testNoNamedArguments(): void ]); } + public function testParameterClosureTypeExtension(): void + { + if (PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/method-parameter-closure-type-extension.php'], [ + [ + 'Parameter #1 $fn of method MethodParameterClosureTypeExtension\Foo::foo() expects Closure(5|7): int, Closure(5): int given.', + 16, + 'Type 5 of parameter #1 $a of passed callable needs to be same or wider than parameter type 5|7 of accepting callable.', + ], + ]); + } + public function testTraitMixin(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index d43a08c18f..e390be232b 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -2,8 +2,13 @@ namespace PHPStan\Rules\Methods; +use PhpParser\Node\Expr\StaticCall; +use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\Native\NativeParameterReflection; +use PHPStan\Reflection\ParameterReflection; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; use PHPStan\Rules\ClassNameCheck; @@ -14,6 +19,12 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ClosureType; +use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\IntegerType; +use PHPStan\Type\StaticMethodParameterClosureTypeExtension; +use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use function array_merge; use function usort; use const PHP_VERSION_ID; @@ -51,7 +62,52 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + new class implements ParameterClosureTypeExtensionProvider + { + + public function getFunctionParameterClosureTypeExtensions(): array + { + return []; + } + + public function getMethodParameterClosureTypeExtensions(): array + { + return []; + } + + public function getStaticMethodParameterClosureTypeExtensions(): array + { + return [ + new class implements StaticMethodParameterClosureTypeExtension + { + + public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + { + return $methodReflection->getName() === 'foo' && $methodReflection->getDeclaringClass()->getName() === 'StaticMethodParameterClosureTypeExtension\\Foo'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): Type + { + return new ClosureType( + [ + new NativeParameterReflection( + $parameter->getName(), + $parameter->isOptional(), + TypeCombinator::union(new ConstantIntegerType(5), new ConstantIntegerType(7)), + $parameter->passedByReference(), + $parameter->isVariadic(), + $parameter->getDefaultValue(), + ), + ], + new IntegerType(), + ); + } + + }, + ]; + } + + }, true, true, true, @@ -843,4 +899,22 @@ public function testClosureBind(): void ]); } + public function testParameterClosureTypeExtension(): void + { + if (PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4'); + } + + $this->checkThisOnly = false; + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + $this->analyse([__DIR__ . '/data/static-method-parameter-closure-type-extension.php'], [ + [ + 'Parameter #1 $fn of static method StaticMethodParameterClosureTypeExtension\Foo::foo() expects Closure(5|7): int, Closure(5): int given.', + 16, + 'Type 5 of parameter #1 $a of passed callable needs to be same or wider than parameter type 5|7 of accepting callable.', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/method-parameter-closure-type-extension.php b/tests/PHPStan/Rules/Methods/data/method-parameter-closure-type-extension.php new file mode 100644 index 0000000000..55bca4fa24 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/method-parameter-closure-type-extension.php @@ -0,0 +1,35 @@ += 8.1 + +namespace MethodParameterClosureTypeExtension; + +class Foo +{ + /** @param callable(int): int $fn */ + public function foo(callable $fn): void + { + } + + public function bar(): void + { + $this->foo($this->callback1(...)); + $this->foo($this->callback2(...)); + $this->foo($this->callback3(...)); + } + + private function callback1(int $a): int + { + return $a; + } + + /** @param 5|7 $a */ + private function callback2(int $a): int + { + return $a; + } + + /** @param 5 $a */ + private function callback3(int $a): int + { + return $a; + } +} diff --git a/tests/PHPStan/Rules/Methods/data/static-method-parameter-closure-type-extension.php b/tests/PHPStan/Rules/Methods/data/static-method-parameter-closure-type-extension.php new file mode 100644 index 0000000000..d52073b608 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/static-method-parameter-closure-type-extension.php @@ -0,0 +1,35 @@ +callback1(...)); + self::foo($this->callback2(...)); + self::foo($this->callback3(...)); + } + + private function callback1(int $a): int + { + return $a; + } + + /** @param 5|7 $a */ + private function callback2(int $a): int + { + return $a; + } + + /** @param 5 $a */ + private function callback3(int $a): int + { + return $a; + } +} From b6fcf5ec5f528c0f0a5bd62954ccc97c37586db3 Mon Sep 17 00:00:00 2001 From: schlndh Date: Mon, 26 Aug 2024 17:49:51 +0200 Subject: [PATCH 3/8] extract common code to helper --- conf/config.neon | 2 + src/Analyser/NodeScopeResolver.php | 38 +-------- src/Rules/FunctionCallParametersCheck.php | 40 +-------- src/Testing/RuleTestCase.php | 4 +- src/Testing/TypeInferenceTestCase.php | 4 +- src/Type/ParameterClosureTypeHelper.php | 52 ++++++++++++ tests/PHPStan/Analyser/AnalyserTest.php | 4 +- .../Analyser/Bug9307CallMethodsRuleTest.php | 16 +++- .../Rules/Classes/ClassAttributesRuleTest.php | 4 +- .../ClassConstantAttributesRuleTest.php | 4 +- .../ForbiddenNameCheckExtensionRuleTest.php | 16 +++- .../Rules/Classes/InstantiationRuleTest.php | 16 +++- .../EnumCases/EnumCaseAttributesRuleTest.php | 4 +- .../ArrowFunctionAttributesRuleTest.php | 4 +- .../Rules/Functions/CallCallablesRuleTest.php | 4 +- .../CallToFunctionParametersRuleTest.php | 16 +++- .../Rules/Functions/CallUserFuncRuleTest.php | 19 ++++- .../Functions/ClosureAttributesRuleTest.php | 4 +- .../Functions/FunctionAttributesRuleTest.php | 4 +- .../Functions/ParamAttributesRuleTest.php | 4 +- .../CallMethodsRuleNoBleedingEdgeTest.php | 16 +++- .../Rules/Methods/CallMethodsRuleTest.php | 85 ++++++++++--------- .../Methods/CallStaticMethodsRuleTest.php | 83 +++++++++--------- .../Methods/MethodAttributesRuleTest.php | 4 +- .../Properties/PropertyAttributesRuleTest.php | 4 +- 25 files changed, 261 insertions(+), 190 deletions(-) create mode 100644 src/Type/ParameterClosureTypeHelper.php diff --git a/conf/config.neon b/conf/config.neon index 05a71dbe97..c8e9dd0b39 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -672,6 +672,8 @@ services: - class: PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider factory: PHPStan\DependencyInjection\Type\LazyParameterClosureTypeExtensionProvider + - + class: PHPStan\Type\ParameterClosureTypeHelper - class: PHPStan\File\FileHelper diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 6871211346..ef5b1c3ebf 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -63,7 +63,6 @@ use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource; use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; use PHPStan\File\FileHelper; use PHPStan\File\FileReader; @@ -171,6 +170,7 @@ use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; +use PHPStan\Type\ParameterClosureTypeHelper; use PHPStan\Type\ResourceType; use PHPStan\Type\StaticType; use PHPStan\Type\StaticTypeFactory; @@ -250,7 +250,7 @@ public function __construct( private readonly TypeSpecifier $typeSpecifier, private readonly DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider, private readonly ReadWritePropertiesExtensionProvider $readWritePropertiesExtensionProvider, - private readonly ParameterClosureTypeExtensionProvider $parameterClosureTypeExtensionProvider, + private readonly ParameterClosureTypeHelper $parameterClosureTypeHelper, private readonly ScopeFactory $scopeFactory, private readonly bool $polluteScopeWithLoopInitialAssignments, private readonly bool $polluteScopeWithAlwaysIterableForeach, @@ -4621,7 +4621,7 @@ private function processArgs( } if ($parameter !== null) { - $overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass); + $overwritingParameterType = $this->parameterClosureTypeHelper->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass); if ($overwritingParameterType !== null) { $parameterType = $overwritingParameterType; @@ -4673,7 +4673,7 @@ private function processArgs( } if ($parameter !== null) { - $overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass); + $overwritingParameterType = $this->parameterClosureTypeHelper->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass); if ($overwritingParameterType !== null) { $parameterType = $overwritingParameterType; @@ -4820,36 +4820,6 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { return new ExpressionResult($scope, $hasYield, $throwPoints, $impurePoints); } - /** - * @param MethodReflection|FunctionReflection|null $calleeReflection - */ - private function getParameterTypeFromParameterClosureTypeExtension(CallLike $callLike, $calleeReflection, ParameterReflection $parameter, MutatingScope $scope): ?Type - { - if ($callLike instanceof FuncCall && $calleeReflection instanceof FunctionReflection) { - foreach ($this->parameterClosureTypeExtensionProvider->getFunctionParameterClosureTypeExtensions() as $functionParameterClosureTypeExtension) { - if ($functionParameterClosureTypeExtension->isFunctionSupported($calleeReflection, $parameter)) { - return $functionParameterClosureTypeExtension->getTypeFromFunctionCall($calleeReflection, $callLike, $parameter, $scope); - } - } - } elseif ($calleeReflection instanceof MethodReflection) { - if ($callLike instanceof StaticCall) { - foreach ($this->parameterClosureTypeExtensionProvider->getStaticMethodParameterClosureTypeExtensions() as $staticMethodParameterClosureTypeExtension) { - if ($staticMethodParameterClosureTypeExtension->isStaticMethodSupported($calleeReflection, $parameter)) { - return $staticMethodParameterClosureTypeExtension->getTypeFromStaticMethodCall($calleeReflection, $callLike, $parameter, $scope); - } - } - } elseif ($callLike instanceof MethodCall) { - foreach ($this->parameterClosureTypeExtensionProvider->getMethodParameterClosureTypeExtensions() as $methodParameterClosureTypeExtension) { - if ($methodParameterClosureTypeExtension->isMethodSupported($calleeReflection, $parameter)) { - return $methodParameterClosureTypeExtension->getTypeFromMethodCall($calleeReflection, $callLike, $parameter, $scope); - } - } - } - } - - return null; - } - /** * @param MethodReflection|FunctionReflection|null $calleeReflection */ diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 26c4c00579..39dea164b9 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -4,13 +4,8 @@ use PhpParser\Node; use PhpParser\Node\Expr; -use PhpParser\Node\Expr\CallLike; -use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\MethodCall; -use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\Scope; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\MethodReflection; @@ -27,6 +22,7 @@ use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\NeverType; +use PHPStan\Type\ParameterClosureTypeHelper; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeTraverser; @@ -49,7 +45,7 @@ public function __construct( private PhpVersion $phpVersion, private UnresolvableTypeHelper $unresolvableTypeHelper, private PropertyReflectionFinder $propertyReflectionFinder, - private ParameterClosureTypeExtensionProvider $parameterClosureTypeExtensionProvider, + private ParameterClosureTypeHelper $parameterClosureTypeHelper, private bool $checkArgumentTypes, private bool $checkArgumentsPassedByReference, private bool $checkExtraArguments, @@ -322,7 +318,7 @@ public function check( if ($this->checkArgumentTypes) { $parameterType = TypeUtils::resolveLateResolvableTypes($parameter->getType()); - $parameterType = $this->getParameterTypeFromParameterClosureTypeExtension( + $parameterType = $this->parameterClosureTypeHelper->getParameterTypeFromParameterClosureTypeExtension( $funcCall, $callReflection, $parameter, @@ -649,34 +645,4 @@ private function describeParameter(ParameterReflection $parameter, ?int $positio return implode(' ', $parts); } - /** - * @param MethodReflection|FunctionReflection|null $calleeReflection - */ - private function getParameterTypeFromParameterClosureTypeExtension(CallLike $callLike, $calleeReflection, ParameterReflection $parameter, Scope $scope): ?Type - { - if ($callLike instanceof FuncCall && $calleeReflection instanceof FunctionReflection) { - foreach ($this->parameterClosureTypeExtensionProvider->getFunctionParameterClosureTypeExtensions() as $functionParameterClosureTypeExtension) { - if ($functionParameterClosureTypeExtension->isFunctionSupported($calleeReflection, $parameter)) { - return $functionParameterClosureTypeExtension->getTypeFromFunctionCall($calleeReflection, $callLike, $parameter, $scope); - } - } - } elseif ($calleeReflection instanceof MethodReflection) { - if ($callLike instanceof StaticCall) { - foreach ($this->parameterClosureTypeExtensionProvider->getStaticMethodParameterClosureTypeExtensions() as $staticMethodParameterClosureTypeExtension) { - if ($staticMethodParameterClosureTypeExtension->isStaticMethodSupported($calleeReflection, $parameter)) { - return $staticMethodParameterClosureTypeExtension->getTypeFromStaticMethodCall($calleeReflection, $callLike, $parameter, $scope); - } - } - } elseif ($callLike instanceof MethodCall) { - foreach ($this->parameterClosureTypeExtensionProvider->getMethodParameterClosureTypeExtensions() as $methodParameterClosureTypeExtension) { - if ($methodParameterClosureTypeExtension->isMethodSupported($calleeReflection, $parameter)) { - return $methodParameterClosureTypeExtension->getTypeFromMethodCall($calleeReflection, $callLike, $parameter, $scope); - } - } - } - } - - return null; - } - } diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index b507b05ae6..02ba2cf475 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -16,7 +16,6 @@ use PHPStan\Collectors\Registry as CollectorRegistry; use PHPStan\Dependency\DependencyResolver; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; use PHPStan\File\FileHelper; use PHPStan\Php\PhpVersion; @@ -30,6 +29,7 @@ use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; use PHPStan\Rules\Rule; use PHPStan\Type\FileTypeMapper; +use PHPStan\Type\ParameterClosureTypeHelper; use function array_map; use function count; use function implode; @@ -95,7 +95,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser $typeSpecifier, self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), $readWritePropertiesExtensions !== [] ? new DirectReadWritePropertiesExtensionProvider($readWritePropertiesExtensions) : self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), self::createScopeFactory($reflectionProvider, $typeSpecifier), $this->shouldPolluteScopeWithLoopInitialAssignments(), $this->shouldPolluteScopeWithAlwaysIterableForeach(), diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 4194d4f4d3..544cc67547 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -9,7 +9,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Analyser\ScopeContext; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; use PHPStan\File\FileHelper; use PHPStan\File\SystemAgnosticSimpleRelativePathHelper; @@ -22,6 +21,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\FileTypeMapper; +use PHPStan\Type\ParameterClosureTypeHelper; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use Symfony\Component\Finder\Finder; @@ -75,7 +75,7 @@ public static function processFile( $typeSpecifier, self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), self::createScopeFactory($reflectionProvider, $typeSpecifier), self::getContainer()->getParameter('polluteScopeWithLoopInitialAssignments'), self::getContainer()->getParameter('polluteScopeWithAlwaysIterableForeach'), diff --git a/src/Type/ParameterClosureTypeHelper.php b/src/Type/ParameterClosureTypeHelper.php new file mode 100644 index 0000000000..b3cf027639 --- /dev/null +++ b/src/Type/ParameterClosureTypeHelper.php @@ -0,0 +1,52 @@ +parameterClosureTypeExtensionProvider->getFunctionParameterClosureTypeExtensions() as $functionParameterClosureTypeExtension) { + if ($functionParameterClosureTypeExtension->isFunctionSupported($calleeReflection, $parameter)) { + return $functionParameterClosureTypeExtension->getTypeFromFunctionCall($calleeReflection, $callLike, $parameter, $scope); + } + } + } elseif ($calleeReflection instanceof MethodReflection) { + if ($callLike instanceof StaticCall) { + foreach ($this->parameterClosureTypeExtensionProvider->getStaticMethodParameterClosureTypeExtensions() as $staticMethodParameterClosureTypeExtension) { + if ($staticMethodParameterClosureTypeExtension->isStaticMethodSupported($calleeReflection, $parameter)) { + return $staticMethodParameterClosureTypeExtension->getTypeFromStaticMethodCall($calleeReflection, $callLike, $parameter, $scope); + } + } + } elseif ($callLike instanceof MethodCall) { + foreach ($this->parameterClosureTypeExtensionProvider->getMethodParameterClosureTypeExtensions() as $methodParameterClosureTypeExtension) { + if ($methodParameterClosureTypeExtension->isMethodSupported($calleeReflection, $parameter)) { + return $methodParameterClosureTypeExtension->getTypeFromMethodCall($calleeReflection, $callLike, $parameter, $scope); + } + } + } + } + + return null; + } + +} diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index f820c64aff..788b5692b2 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -11,7 +11,6 @@ use PHPStan\Dependency\DependencyResolver; use PHPStan\Dependency\ExportedNodeResolver; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Node\Printer\Printer; @@ -26,6 +25,7 @@ use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\FileTypeMapper; +use PHPStan\Type\ParameterClosureTypeHelper; use stdClass; use function array_map; use function array_merge; @@ -731,7 +731,7 @@ private function createAnalyser(bool $enableIgnoreErrorsWithinPhpDocs): Analyser $typeSpecifier, self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), self::createScopeFactory($reflectionProvider, $typeSpecifier), false, true, diff --git a/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php b/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php index ca8be58131..8448a0d648 100644 --- a/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php +++ b/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Analyser; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\Methods\CallMethodsRule; @@ -13,6 +12,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use const PHP_VERSION_ID; /** @@ -27,7 +27,19 @@ protected function getRule(): Rule $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, true, false, true, false); return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(PHP_VERSION_ID), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, true), + new FunctionCallParametersCheck( + $ruleLevelHelper, + new NullsafeCheck(), + new PhpVersion(PHP_VERSION_ID), + new UnresolvableTypeHelper(), + new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), + true, + true, + true, + true, + true, + ), ); } diff --git a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php index 5a39fbb0a6..5fb166b5ae 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Classes; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -15,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use const PHP_VERSION_ID; /** @@ -35,7 +35,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), true, true, true, diff --git a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php index d4469a886b..80e1d3e546 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Classes; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -15,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -34,7 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), true, true, true, diff --git a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php index 1c62c80cdb..2821ab40cf 100644 --- a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Classes; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -14,6 +13,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use function array_merge; /** @@ -27,7 +27,19 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( $reflectionProvider, - new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, true), + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new NullsafeCheck(), + new PhpVersion(80000), + new UnresolvableTypeHelper(), + new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), + true, + true, + true, + true, + true, + ), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index c0bc966864..a53fa5dc0c 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Classes; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -14,6 +13,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use const PHP_VERSION_ID; /** @@ -27,7 +27,19 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( $reflectionProvider, - new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, true), + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new NullsafeCheck(), + new PhpVersion(80000), + new UnresolvableTypeHelper(), + new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), + true, + true, + true, + true, + true, + ), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php index 7c2f173e8e..035501dd96 100644 --- a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php +++ b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\EnumCases; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -15,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -34,7 +34,7 @@ protected function getRule(): Rule new PhpVersion(80100), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php index 35fcb2e7ec..5c90b83a20 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -15,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -34,7 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 52bfdcc97b..fe6a37f6a2 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; @@ -11,6 +10,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use const PHP_VERSION_ID; /** @@ -31,7 +31,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 130e4abc81..ceb44ecc24 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; @@ -11,6 +10,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use function sprintf; use const PHP_VERSION_ID; @@ -27,7 +27,19 @@ protected function getRule(): Rule $broker = $this->createReflectionProvider(); return new CallToFunctionParametersRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, true), + new FunctionCallParametersCheck( + new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, false, true, false), + new NullsafeCheck(), + new PhpVersion(80000), + new UnresolvableTypeHelper(), + new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), + true, + true, + true, + true, + true, + ), ); } diff --git a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php index 2f86cd6d94..c89b900f9e 100644 --- a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; @@ -11,6 +10,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use const PHP_VERSION_ID; /** @@ -22,7 +22,22 @@ class CallUserFuncRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - return new CallUserFuncRule($reflectionProvider, new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, true)); + return new CallUserFuncRule( + $reflectionProvider, + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true, true, false, true, false), + new NullsafeCheck(), + new PhpVersion(80000), + new UnresolvableTypeHelper(), + new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), + true, + true, + true, + true, + true, + ), + ); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php index 9af4a49947..bac481b179 100644 --- a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -15,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -34,7 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php index bbc748ec27..2253323c0e 100644 --- a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -15,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -34,7 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php index ddaf10d3c6..f569c10314 100644 --- a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -15,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -34,7 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), true, true, true, diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php index e326414910..c9c621a75a 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Methods; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; @@ -11,6 +10,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use const PHP_VERSION_ID; /** @@ -27,7 +27,19 @@ protected function getRule(): Rule $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, $this->checkExplicitMixed, false, true, false); return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(PHP_VERSION_ID), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), true, true, true, true, false), + new FunctionCallParametersCheck( + $ruleLevelHelper, + new NullsafeCheck(), + new PhpVersion(PHP_VERSION_ID), + new UnresolvableTypeHelper(), + new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), + true, + true, + true, + true, + false, + ), ); } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index afcf743fea..382bf55624 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -20,6 +20,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\IntegerType; use PHPStan\Type\MethodParameterClosureTypeExtension; +use PHPStan\Type\ParameterClosureTypeHelper; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use const PHP_VERSION_ID; @@ -55,52 +56,54 @@ protected function getRule(): Rule new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), - new class implements ParameterClosureTypeExtensionProvider - { - - public function getFunctionParameterClosureTypeExtensions(): array + new ParameterClosureTypeHelper( + new class implements ParameterClosureTypeExtensionProvider { - return []; - } - public function getMethodParameterClosureTypeExtensions(): array - { - return [ - new class implements MethodParameterClosureTypeExtension - { + public function getFunctionParameterClosureTypeExtensions(): array + { + return []; + } - public function isMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + public function getMethodParameterClosureTypeExtensions(): array + { + return [ + new class implements MethodParameterClosureTypeExtension { - return $methodReflection->getName() === 'foo' && $methodReflection->getDeclaringClass()->getName() === 'MethodParameterClosureTypeExtension\\Foo'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, ParameterReflection $parameter, Scope $scope): Type - { - return new ClosureType( - [ - new NativeParameterReflection( - $parameter->getName(), - $parameter->isOptional(), - TypeCombinator::union(new ConstantIntegerType(5), new ConstantIntegerType(7)), - $parameter->passedByReference(), - $parameter->isVariadic(), - $parameter->getDefaultValue(), - ), - ], - new IntegerType(), - ); - } - - }, - ]; - } - - public function getStaticMethodParameterClosureTypeExtensions(): array - { - return []; - } - }, + public function isMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + { + return $methodReflection->getName() === 'foo' && $methodReflection->getDeclaringClass()->getName() === 'MethodParameterClosureTypeExtension\\Foo'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, ParameterReflection $parameter, Scope $scope): Type + { + return new ClosureType( + [ + new NativeParameterReflection( + $parameter->getName(), + $parameter->isOptional(), + TypeCombinator::union(new ConstantIntegerType(5), new ConstantIntegerType(7)), + $parameter->passedByReference(), + $parameter->isVariadic(), + $parameter->getDefaultValue(), + ), + ], + new IntegerType(), + ); + } + + }, + ]; + } + + public function getStaticMethodParameterClosureTypeExtensions(): array + { + return []; + } + + }, + ), true, true, true, diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index e390be232b..7c76ed6867 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -22,6 +22,7 @@ use PHPStan\Type\ClosureType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\IntegerType; +use PHPStan\Type\ParameterClosureTypeHelper; use PHPStan\Type\StaticMethodParameterClosureTypeExtension; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -62,52 +63,54 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), - new class implements ParameterClosureTypeExtensionProvider - { - - public function getFunctionParameterClosureTypeExtensions(): array + new ParameterClosureTypeHelper( + new class implements ParameterClosureTypeExtensionProvider { - return []; - } - public function getMethodParameterClosureTypeExtensions(): array - { - return []; - } + public function getFunctionParameterClosureTypeExtensions(): array + { + return []; + } - public function getStaticMethodParameterClosureTypeExtensions(): array - { - return [ - new class implements StaticMethodParameterClosureTypeExtension - { + public function getMethodParameterClosureTypeExtensions(): array + { + return []; + } - public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + public function getStaticMethodParameterClosureTypeExtensions(): array + { + return [ + new class implements StaticMethodParameterClosureTypeExtension { - return $methodReflection->getName() === 'foo' && $methodReflection->getDeclaringClass()->getName() === 'StaticMethodParameterClosureTypeExtension\\Foo'; - } - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): Type - { - return new ClosureType( - [ - new NativeParameterReflection( - $parameter->getName(), - $parameter->isOptional(), - TypeCombinator::union(new ConstantIntegerType(5), new ConstantIntegerType(7)), - $parameter->passedByReference(), - $parameter->isVariadic(), - $parameter->getDefaultValue(), - ), - ], - new IntegerType(), - ); - } - - }, - ]; - } - - }, + public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + { + return $methodReflection->getName() === 'foo' && $methodReflection->getDeclaringClass()->getName() === 'StaticMethodParameterClosureTypeExtension\\Foo'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): Type + { + return new ClosureType( + [ + new NativeParameterReflection( + $parameter->getName(), + $parameter->isOptional(), + TypeCombinator::union(new ConstantIntegerType(5), new ConstantIntegerType(7)), + $parameter->passedByReference(), + $parameter->isVariadic(), + $parameter->getDefaultValue(), + ), + ], + new IntegerType(), + ); + } + + }, + ]; + } + + }, + ), true, true, true, diff --git a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index 6616696fc0..ad6fa4af5d 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Methods; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -15,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -36,7 +36,7 @@ protected function getRule(): Rule new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), true, true, true, diff --git a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php index ce3faa7ec2..f0b2f831d0 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Properties; -use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -14,6 +13,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -33,7 +33,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), - self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), true, true, true, From eea74d2c982109866e969234893b43dd81904e22 Mon Sep 17 00:00:00 2001 From: schlndh Date: Mon, 26 Aug 2024 17:55:40 +0200 Subject: [PATCH 4/8] resolve late resolvable types from parameter closure type extension --- src/Rules/FunctionCallParametersCheck.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 39dea164b9..7a13d8a889 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -317,13 +317,13 @@ public function check( } if ($this->checkArgumentTypes) { - $parameterType = TypeUtils::resolveLateResolvableTypes($parameter->getType()); $parameterType = $this->parameterClosureTypeHelper->getParameterTypeFromParameterClosureTypeExtension( $funcCall, $callReflection, $parameter, $scope, - ) ?? $parameterType; + ) ?? $parameter->getType(); + $parameterType = TypeUtils::resolveLateResolvableTypes($parameterType); if ( !$parameter->passedByReference()->createsNewVariable() From 081ea60d27f4721cac06364fbbf2f34be19da63b Mon Sep 17 00:00:00 2001 From: schlndh Date: Mon, 26 Aug 2024 18:49:11 +0200 Subject: [PATCH 5/8] fix larastan --- src/Rules/AttributesCheck.php | 2 +- src/Rules/Classes/InstantiationRule.php | 2 +- src/Rules/FunctionCallParametersCheck.php | 4 ++-- src/Rules/Functions/CallCallablesRule.php | 1 - src/Rules/Functions/CallToFunctionParametersRule.php | 2 +- src/Rules/Functions/CallUserFuncRule.php | 2 +- src/Rules/Methods/CallMethodsRule.php | 2 +- src/Rules/Methods/CallStaticMethodsRule.php | 2 +- 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Rules/AttributesCheck.php b/src/Rules/AttributesCheck.php index 4980e72e1e..cb10a6dcf6 100644 --- a/src/Rules/AttributesCheck.php +++ b/src/Rules/AttributesCheck.php @@ -127,7 +127,6 @@ public function check( $nodeAttributes['isAttribute'] = true; $parameterErrors = $this->functionCallParametersCheck->check( - $attributeConstructor, ParametersAcceptorSelector::selectFromArgs( $scope, $attribute->args, @@ -156,6 +155,7 @@ public function check( ], 'attribute', $attributeConstructor->acceptsNamedArguments(), + $attributeConstructor, ); foreach ($parameterErrors as $error) { diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index bbddfbc2c4..3b68ae02df 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -192,7 +192,6 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ $classDisplayName = SprintfHelper::escapeFormatString($classReflection->getDisplayName()); return array_merge($messages, $this->check->check( - $constructorReflection, ParametersAcceptorSelector::selectFromArgs( $scope, $node->getArgs(), @@ -221,6 +220,7 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ ], 'new', $constructorReflection->acceptsNamedArguments(), + $constructorReflection, )); } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 7a13d8a889..d09184fc5f 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -56,14 +56,13 @@ public function __construct( } /** - * @param MethodReflection|FunctionReflection|null $callReflection * @param Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $funcCall * @param array{0: string, 1: string, 2: string, 3: string, 4: string, 5: string, 6: string, 7: string, 8: string, 9: string, 10: string, 11: string, 12: string, 13?: string, 14?: string} $messages * @param 'attribute'|'callable'|'method'|'staticMethod'|'function'|'new' $nodeType + * @param MethodReflection|FunctionReflection|null $callReflection * @return list */ public function check( - $callReflection, ParametersAcceptor $parametersAcceptor, Scope $scope, bool $isBuiltin, @@ -71,6 +70,7 @@ public function check( array $messages, string $nodeType = 'function', bool $acceptsNamedArguments = true, + $callReflection = null, ): array { $functionParametersMinCount = 0; diff --git a/src/Rules/Functions/CallCallablesRule.php b/src/Rules/Functions/CallCallablesRule.php index 0bb5155812..d8fb352e52 100644 --- a/src/Rules/Functions/CallCallablesRule.php +++ b/src/Rules/Functions/CallCallablesRule.php @@ -113,7 +113,6 @@ public function processNode( return array_merge( $messages, $this->check->check( - null, $parametersAcceptor, $scope, false, diff --git a/src/Rules/Functions/CallToFunctionParametersRule.php b/src/Rules/Functions/CallToFunctionParametersRule.php index 0137a21d52..02c16e9f4e 100644 --- a/src/Rules/Functions/CallToFunctionParametersRule.php +++ b/src/Rules/Functions/CallToFunctionParametersRule.php @@ -40,7 +40,6 @@ public function processNode(Node $node, Scope $scope): array $functionName = SprintfHelper::escapeFormatString($function->getName()); return $this->check->check( - $function, ParametersAcceptorSelector::selectFromArgs( $scope, $node->getArgs(), @@ -69,6 +68,7 @@ public function processNode(Node $node, Scope $scope): array ], 'function', $function->acceptsNamedArguments(), + $function, ); } diff --git a/src/Rules/Functions/CallUserFuncRule.php b/src/Rules/Functions/CallUserFuncRule.php index 3f311f1de5..c4030961cf 100644 --- a/src/Rules/Functions/CallUserFuncRule.php +++ b/src/Rules/Functions/CallUserFuncRule.php @@ -60,7 +60,7 @@ public function processNode(Node $node, Scope $scope): array $callableDescription = 'callable passed to call_user_func()'; - return $this->check->check(null, $parametersAcceptor, $scope, false, $funcCall, [ + return $this->check->check($parametersAcceptor, $scope, false, $funcCall, [ ucfirst($callableDescription) . ' invoked with %d parameter, %d required.', ucfirst($callableDescription) . ' invoked with %d parameters, %d required.', ucfirst($callableDescription) . ' invoked with %d parameter, at least %d required.', diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index 8d988e7d61..3464df8f90 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -46,7 +46,6 @@ public function processNode(Node $node, Scope $scope): array $messagesMethodName = SprintfHelper::escapeFormatString($declaringClass->getDisplayName() . '::' . $methodReflection->getName() . '()'); return array_merge($errors, $this->parametersCheck->check( - $methodReflection, ParametersAcceptorSelector::selectFromArgs( $scope, $node->getArgs(), @@ -75,6 +74,7 @@ public function processNode(Node $node, Scope $scope): array ], 'method', $methodReflection->acceptsNamedArguments(), + $methodReflection, )); } diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 9eb52d4841..950edb7870 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -54,7 +54,6 @@ public function processNode(Node $node, Scope $scope): array )); $errors = array_merge($errors, $this->parametersCheck->check( - $method, ParametersAcceptorSelector::selectFromArgs( $scope, $node->getArgs(), @@ -83,6 +82,7 @@ public function processNode(Node $node, Scope $scope): array ], 'staticMethod', $method->acceptsNamedArguments(), + $method, )); return $errors; From 7d109510462b9e7ca262512133f5d1f899dc1180 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 30 Aug 2024 12:37:26 +0200 Subject: [PATCH 6/8] make ParameterClosureTypeHelper final --- src/Type/ParameterClosureTypeHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/ParameterClosureTypeHelper.php b/src/Type/ParameterClosureTypeHelper.php index b3cf027639..4c2403fd3a 100644 --- a/src/Type/ParameterClosureTypeHelper.php +++ b/src/Type/ParameterClosureTypeHelper.php @@ -12,7 +12,7 @@ use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParameterReflection; -class ParameterClosureTypeHelper +final class ParameterClosureTypeHelper { public function __construct(private readonly ParameterClosureTypeExtensionProvider $parameterClosureTypeExtensionProvider) From 022a1e1dcb12fa9a19b5248bcf3033925fc72021 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 30 Aug 2024 12:46:07 +0200 Subject: [PATCH 7/8] accept callable as callback to preg_replace_callback --- .../Php/PregReplaceCallbackClosureTypeExtension.php | 4 ++-- .../Functions/CallToFunctionParametersRuleTest.php | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Type/Php/PregReplaceCallbackClosureTypeExtension.php b/src/Type/Php/PregReplaceCallbackClosureTypeExtension.php index a7ea4dc133..de5abfae80 100644 --- a/src/Type/Php/PregReplaceCallbackClosureTypeExtension.php +++ b/src/Type/Php/PregReplaceCallbackClosureTypeExtension.php @@ -8,7 +8,7 @@ use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\TrinaryLogic; -use PHPStan\Type\ClosureType; +use PHPStan\Type\CallableType; use PHPStan\Type\FunctionParameterClosureTypeExtension; use PHPStan\Type\StringType; use PHPStan\Type\Type; @@ -49,7 +49,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return null; } - return new ClosureType( + return new CallableType( [ new NativeParameterReflection($parameter->getName(), $parameter->isOptional(), $matchesType, $parameter->passedByReference(), $parameter->isVariadic(), $parameter->getDefaultValue()), ], diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index ceb44ecc24..e4e5b8d0df 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -671,19 +671,19 @@ public function testPregReplaceCallback(): void { $this->analyse([__DIR__ . '/data/preg_replace_callback.php'], [ [ - 'Parameter #2 $callback of function preg_replace_callback expects Closure(array{string}): string, Closure(string): string given.', + 'Parameter #2 $callback of function preg_replace_callback expects callable(array{string}): string, Closure(string): string given.', 6, ], [ - 'Parameter #2 $callback of function preg_replace_callback expects Closure(array{string}): string, Closure(string): string given.', + 'Parameter #2 $callback of function preg_replace_callback expects callable(array{string}): string, Closure(string): string given.', 13, ], [ - 'Parameter #2 $callback of function preg_replace_callback expects Closure(array{string}): string, Closure(array): void given.', + 'Parameter #2 $callback of function preg_replace_callback expects callable(array{string}): string, Closure(array): void given.', 20, ], [ - 'Parameter #2 $callback of function preg_replace_callback expects Closure(array{string}): string, Closure(): void given.', + 'Parameter #2 $callback of function preg_replace_callback expects callable(array{string}): string, Closure(): void given.', 25, ], ]); @@ -1774,7 +1774,7 @@ public function testParameterClosureTypeExtension(): void $this->analyse([__DIR__ . '/data/function-parameter-closure-type-extension.php'], [ [ - 'Parameter #2 $callback of function preg_replace_callback expects Closure(array{0: array{string, int<-1, max>}, 1?: array{\'\'|\'foo\', int<-1, max>}, 2?: array{\'\'|\'bar\', int<-1, max>}, 3?: array{\'baz\', int<-1, max>}}): string, Closure(array): string given.', + 'Parameter #2 $callback of function preg_replace_callback expects callable(array{0: array{string, int<-1, max>}, 1?: array{\'\'|\'foo\', int<-1, max>}, 2?: array{\'\'|\'bar\', int<-1, max>}, 3?: array{\'baz\', int<-1, max>}}): string, Closure(array): string given.', 44, ], ]); From f63df3b2e04c20c4b83d8c57eedecd1a9a8e6a22 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 30 Aug 2024 12:50:58 +0200 Subject: [PATCH 8/8] add test for bug 10396 --- .../CallToFunctionParametersRuleTest.php | 9 +++++++++ tests/PHPStan/Rules/Functions/data/bug-10396.php | 16 ++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-10396.php diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index e4e5b8d0df..ddd586f4bd 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1780,6 +1780,15 @@ public function testParameterClosureTypeExtension(): void ]); } + public function testBug10396(): void + { + if (PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4'); + } + + $this->analyse([__DIR__ . '/data/bug-10396.php'], []); + } + public function testBug11559(): void { $this->analyse([__DIR__ . '/data/bug-11559.php'], []); diff --git a/tests/PHPStan/Rules/Functions/data/bug-10396.php b/tests/PHPStan/Rules/Functions/data/bug-10396.php new file mode 100644 index 0000000000..32f1bf1b12 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-10396.php @@ -0,0 +1,16 @@ += 8.1 + +namespace Bug10396; + +function test(): string|null { + $flags = \PREG_OFFSET_CAPTURE | \PREG_UNMATCHED_AS_NULL; + return preg_replace_callback('/bar/', callback(...), 'foobar', -1, $count, $flags); +} + +/** + * @param array $match + * @return string + */ +function callback(array $match): string { + return (string) $match[0][1]; +}