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/AttributesCheck.php b/src/Rules/AttributesCheck.php index e04381033e..cb10a6dcf6 100644 --- a/src/Rules/AttributesCheck.php +++ b/src/Rules/AttributesCheck.php @@ -155,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 8994a4754b..3b68ae02df 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -220,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 4f0d2ae447..d09184fc5f 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -7,6 +7,8 @@ use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\Scope; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Reflection\ParametersAcceptor; @@ -20,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; @@ -42,6 +45,7 @@ public function __construct( private PhpVersion $phpVersion, private UnresolvableTypeHelper $unresolvableTypeHelper, private PropertyReflectionFinder $propertyReflectionFinder, + private ParameterClosureTypeHelper $parameterClosureTypeHelper, private bool $checkArgumentTypes, private bool $checkArgumentsPassedByReference, private bool $checkExtraArguments, @@ -55,6 +59,7 @@ public function __construct( * @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( @@ -65,6 +70,7 @@ public function check( array $messages, string $nodeType = 'function', bool $acceptsNamedArguments = true, + $callReflection = null, ): array { $functionParametersMinCount = 0; @@ -311,7 +317,13 @@ public function check( } if ($this->checkArgumentTypes) { - $parameterType = TypeUtils::resolveLateResolvableTypes($parameter->getType()); + $parameterType = $this->parameterClosureTypeHelper->getParameterTypeFromParameterClosureTypeExtension( + $funcCall, + $callReflection, + $parameter, + $scope, + ) ?? $parameter->getType(); + $parameterType = TypeUtils::resolveLateResolvableTypes($parameterType); if ( !$parameter->passedByReference()->createsNewVariable() diff --git a/src/Rules/Functions/CallToFunctionParametersRule.php b/src/Rules/Functions/CallToFunctionParametersRule.php index d1ca216791..02c16e9f4e 100644 --- a/src/Rules/Functions/CallToFunctionParametersRule.php +++ b/src/Rules/Functions/CallToFunctionParametersRule.php @@ -68,6 +68,7 @@ public function processNode(Node $node, Scope $scope): array ], 'function', $function->acceptsNamedArguments(), + $function, ); } diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index 4f45dbf9fd..3464df8f90 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -74,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 33612ff02c..950edb7870 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -82,6 +82,7 @@ public function processNode(Node $node, Scope $scope): array ], 'staticMethod', $method->acceptsNamedArguments(), + $method, )); return $errors; 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..4c2403fd3a --- /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/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/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 ff0a0daa9e..8448a0d648 100644 --- a/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php +++ b/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php @@ -12,6 +12,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use const PHP_VERSION_ID; /** @@ -26,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(), 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 6fa6252277..5fb166b5ae 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -14,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use const PHP_VERSION_ID; /** @@ -34,6 +35,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + 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 b132e3fe08..80e1d3e546 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php @@ -14,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -33,6 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + 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 9d3ea64034..2821ab40cf 100644 --- a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php @@ -13,6 +13,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use function array_merge; /** @@ -26,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(), 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 36dc32c352..a53fa5dc0c 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -13,6 +13,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use const PHP_VERSION_ID; /** @@ -26,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(), 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 26643e506c..035501dd96 100644 --- a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php +++ b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php @@ -14,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -33,6 +34,7 @@ protected function getRule(): Rule new PhpVersion(80100), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + 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 7f3aab6ac2..5c90b83a20 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php @@ -14,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -33,6 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + 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 bcd1c9ee87..fe6a37f6a2 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -10,6 +10,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use const PHP_VERSION_ID; /** @@ -30,6 +31,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + 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 a47624e8e8..ddd586f4bd 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -10,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; @@ -26,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(), 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, + ), ); } @@ -658,19 +671,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 callable(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 callable(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 callable(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 callable(array{string}): string, Closure(): void given.', 25, ], ]); @@ -1753,6 +1766,29 @@ public function testBug11506(): void $this->analyse([__DIR__ . '/data/bug-11506.php'], []); } + 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 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, + ], + ]); + } + + 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/CallUserFuncRuleTest.php b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php index d10eee8e48..c89b900f9e 100644 --- a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php @@ -10,6 +10,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use const PHP_VERSION_ID; /** @@ -21,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(), 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 f4a3d5a1bb..bac481b179 100644 --- a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php @@ -14,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -33,6 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + 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 553ab6c462..2253323c0e 100644 --- a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php @@ -14,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -33,6 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + 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 60d620474c..f569c10314 100644 --- a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php @@ -14,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -33,6 +34,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), true, true, true, 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]; +} 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..c9c621a75a 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php @@ -10,6 +10,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; use const PHP_VERSION_ID; /** @@ -26,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(), 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 8bb53330d4..382bf55624 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2,7 +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; @@ -10,6 +16,13 @@ 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\ParameterClosureTypeHelper; +use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use const PHP_VERSION_ID; /** @@ -34,9 +47,69 @@ 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(), true, true, true, true, true), + new FunctionCallParametersCheck( + $ruleLevelHelper, + new NullsafeCheck(), + new PhpVersion($this->phpVersion), + new UnresolvableTypeHelper(), + new PropertyReflectionFinder(), + new ParameterClosureTypeHelper( + 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, + ), ); } @@ -3345,6 +3418,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 27718dc9c7..7c76ed6867 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -2,7 +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; @@ -13,6 +19,13 @@ 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\ParameterClosureTypeHelper; +use PHPStan\Type\StaticMethodParameterClosureTypeExtension; +use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use function array_merge; use function usort; use const PHP_VERSION_ID; @@ -50,6 +63,54 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + new ParameterClosureTypeHelper( + 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, @@ -841,4 +902,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/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index 9ce75af6e6..ad6fa4af5d 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -14,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -35,6 +36,7 @@ protected function getRule(): Rule new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), true, true, true, 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; + } +} diff --git a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php index b6d394d30b..f0b2f831d0 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php @@ -13,6 +13,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\ParameterClosureTypeHelper; /** * @extends RuleTestCase @@ -32,6 +33,7 @@ protected function getRule(): Rule new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), + self::getContainer()->getByType(ParameterClosureTypeHelper::class), true, true, true,