diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 04aefd6a39..9316f3a2a3 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -12,3 +12,4 @@ parameters: strictUnnecessaryNullsafePropertyFetch: true looseComparison: true consistentConstructor: true + checkUnresolvableParameterTypes: true diff --git a/conf/config.neon b/conf/config.neon index 75ed45450b..1e2f29bdd4 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -38,6 +38,7 @@ parameters: strictUnnecessaryNullsafePropertyFetch: false looseComparison: false consistentConstructor: false + checkUnresolvableParameterTypes: false fileExtensions: - php checkAdvancedIsset: false @@ -213,6 +214,7 @@ parametersSchema: strictUnnecessaryNullsafePropertyFetch: bool(), looseComparison: bool(), consistentConstructor: bool() + checkUnresolvableParameterTypes: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() @@ -841,6 +843,7 @@ services: checkArgumentsPassedByReference: %checkArgumentsPassedByReference% checkExtraArguments: %checkExtraArguments% checkMissingTypehints: %checkMissingTypehints% + checkUnresolvableParameterTypes: %featureToggles.checkUnresolvableParameterTypes% - class: PHPStan\Rules\FunctionDefinitionCheck diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 1ed121d5b2..a2a4b06b6b 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -10,6 +10,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\CallableType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\ErrorType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\LateResolvableType; @@ -74,7 +75,7 @@ public static function selectFromArgs( $parameters = $acceptor->getParameters(); $callbackParameters = []; foreach ($arrayMapArgs as $arg) { - $callbackParameters[] = new DummyParameter('item', $scope->getType($arg->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null); + $callbackParameters[] = new DummyParameter('item', self::getIterableValueType($scope->getType($arg->value)), false, PassedByReference::createNo(), false, null); } $parameters[0] = new NativeParameterReflection( $parameters[0]->getName(), @@ -104,12 +105,12 @@ public static function selectFromArgs( if ($mode instanceof ConstantIntegerType) { if ($mode->getValue() === ARRAY_FILTER_USE_KEY) { $arrayFilterParameters = [ - new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), + new DummyParameter('key', self::getIterableKeyType($scope->getType($args[0]->value)), false, PassedByReference::createNo(), false, null), ]; } elseif ($mode->getValue() === ARRAY_FILTER_USE_BOTH) { $arrayFilterParameters = [ - new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), - new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), + new DummyParameter('item', self::getIterableValueType($scope->getType($args[0]->value)), false, PassedByReference::createNo(), false, null), + new DummyParameter('key', self::getIterableKeyType($scope->getType($args[0]->value)), false, PassedByReference::createNo(), false, null), ]; } } @@ -122,7 +123,7 @@ public static function selectFromArgs( $parameters[1]->isOptional(), new CallableType( $arrayFilterParameters ?? [ - new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + new DummyParameter('item', self::getIterableValueType($scope->getType($args[0]->value)), false, PassedByReference::createNo(), false, null), ], new MixedType(), false, @@ -395,4 +396,42 @@ public static function combineAcceptors(array $acceptors): ParametersAcceptor ); } + private static function getIterableValueType(Type $type): Type + { + if ($type instanceof UnionType) { + $types = []; + foreach ($type->getTypes() as $innerType) { + $iterableValueType = $innerType->getIterableValueType(); + if ($iterableValueType instanceof ErrorType) { + continue; + } + + $types[] = $iterableValueType; + } + + return TypeCombinator::union(...$types); + } + + return $type->getIterableValueType(); + } + + private static function getIterableKeyType(Type $type): Type + { + if ($type instanceof UnionType) { + $types = []; + foreach ($type->getTypes() as $innerType) { + $iterableKeyType = $innerType->getIterableKeyType(); + if ($iterableKeyType instanceof ErrorType) { + continue; + } + + $types[] = $iterableKeyType; + } + + return TypeCombinator::union(...$types); + } + + return $type->getIterableKeyType(); + } + } diff --git a/src/Rules/AttributesCheck.php b/src/Rules/AttributesCheck.php index 82ee703d37..9522a5df2a 100644 --- a/src/Rules/AttributesCheck.php +++ b/src/Rules/AttributesCheck.php @@ -124,6 +124,7 @@ public function check( 'Missing parameter $%s in call to ' . $attributeClassName . ' constructor.', 'Unknown parameter $%s in call to ' . $attributeClassName . ' constructor.', 'Return type of call to ' . $attributeClassName . ' constructor contains unresolvable type.', + 'Parameter %s of attribute class ' . $attributeClassName . ' constructor contains unresolvable type.', ], ); diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index 2200f7772d..ce2ff2920b 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -205,6 +205,7 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ 'Missing parameter $%s in call to ' . $classDisplayName . ' constructor.', 'Unknown parameter $%s in call to ' . $classDisplayName . ' constructor.', 'Return type of call to ' . $classDisplayName . ' constructor contains unresolvable type.', + 'Parameter %s of class ' . $classDisplayName . ' constructor contains unresolvable type.', ], )); } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index d47fb95533..cc64028bfd 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -24,6 +24,7 @@ use PHPStan\Type\TypeUtils; use PHPStan\Type\VerbosityLevel; use PHPStan\Type\VoidType; +use function array_fill; use function array_key_exists; use function count; use function is_string; @@ -43,13 +44,14 @@ public function __construct( private bool $checkArgumentsPassedByReference, private bool $checkExtraArguments, private bool $checkMissingTypehints, + private bool $checkUnresolvableParameterTypes, ) { } /** * @param Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $funcCall - * @param array{string, string, string, string, string, string, string, string, string, string, string, string, string} $messages + * @param array{string, string, string, string, string, string, string, string, string, string, string, string, string, string} $messages * @return RuleError[] */ public function check( @@ -223,7 +225,7 @@ public function check( return $errors; } - foreach ($argumentsWithParameters as $i => [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, $parameter]) { + foreach ($argumentsWithParameters as $i => [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, $parameter, $originalParameter]) { if ($this->checkArgumentTypes && $unpack) { $iterableTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, @@ -268,6 +270,24 @@ public function check( ))->line($argumentLine)->build(); } + if ( + $this->checkArgumentTypes + && $this->checkUnresolvableParameterTypes + && $originalParameter !== null + && !$this->unresolvableTypeHelper->containsUnresolvableType($originalParameter->getType()) + && $this->unresolvableTypeHelper->containsUnresolvableType($parameterType) + ) { + $parameterDescription = sprintf('%s$%s', $parameter->isVariadic() ? '...' : '', $parameter->getName()); + $errors[] = RuleErrorBuilder::message(sprintf( + $messages[13], + $argumentName === null ? sprintf( + '#%d %s', + $i + 1, + $parameterDescription, + ) : $parameterDescription, + ))->line($argumentLine)->build(); + } + if ( !$this->checkArgumentsPassedByReference || !$parameter->passedByReference()->yes() @@ -395,7 +415,7 @@ static function (Type $type, callable $traverse) use (&$returnTemplateTypes): Ty /** * @param array $arguments - * @return array{RuleError[], array} + * @return array{RuleError[], array} */ private function processArguments( ParametersAcceptor $parametersAcceptor, @@ -408,11 +428,16 @@ private function processArguments( ): array { $parameters = $parametersAcceptor->getParameters(); + $originalParameters = $parametersAcceptor instanceof ResolvedFunctionVariant + ? $parametersAcceptor->getOriginalParametersAcceptor()->getParameters() + : array_fill(0, count($parameters), null); $parametersByName = []; + $originalParametersByName = []; $unusedParametersByName = []; $errors = []; - foreach ($parametersAcceptor->getParameters() as $parameter) { + foreach ($parameters as $i => $parameter) { $parametersByName[$parameter->getName()] = $parameter; + $originalParametersByName[$parameter->getName()] = $originalParameters[$i]; if ($parameter->isVariadic()) { continue; @@ -428,21 +453,24 @@ private function processArguments( if ($argumentName === null) { if (!isset($parameters[$i])) { if (!$parametersAcceptor->isVariadic() || count($parameters) === 0) { - $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null]; + $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null, null]; break; } $parameter = $parameters[count($parameters) - 1]; + $originalParameter = $originalParameters[count($originalParameters) - 1]; if (!$parameter->isVariadic()) { - $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null]; + $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null, null]; break; // func_get_args } } else { $parameter = $parameters[$i]; + $originalParameter = $originalParameters[$i]; } } elseif (array_key_exists($argumentName, $parametersByName)) { $namedArgumentAlreadyOccurred = true; $parameter = $parametersByName[$argumentName]; + $originalParameter = $originalParametersByName[$argumentName]; } else { $namedArgumentAlreadyOccurred = true; @@ -453,20 +481,21 @@ private function processArguments( || $isBuiltin ) { $errors[] = RuleErrorBuilder::message(sprintf($unknownParameterMessage, $argumentName))->line($argumentLine)->build(); - $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null]; + $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null, null]; continue; } $parameter = $parameters[$parametersCount - 1]; + $originalParameter = $originalParameters[$parametersCount - 1]; } if ($namedArgumentAlreadyOccurred && $argumentName === null && !$unpack) { $errors[] = RuleErrorBuilder::message('Named argument cannot be followed by a positional argument.')->line($argumentLine)->nonIgnorable()->build(); - $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null]; + $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null, null]; continue; } - $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, $parameter]; + $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, $parameter, $originalParameter]; if ( $hasNamedArguments diff --git a/src/Rules/Functions/CallCallablesRule.php b/src/Rules/Functions/CallCallablesRule.php index 0873fdc892..05e1865938 100644 --- a/src/Rules/Functions/CallCallablesRule.php +++ b/src/Rules/Functions/CallCallablesRule.php @@ -125,6 +125,7 @@ public function processNode( 'Missing parameter $%s in call to ' . $callableDescription . '.', 'Unknown parameter $%s in call to ' . $callableDescription . '.', 'Return type of call to ' . $callableDescription . ' contains unresolvable type.', + 'Parameter %s of ' . $callableDescription . ' contains unresolvable type.', ], ), ); diff --git a/src/Rules/Functions/CallToFunctionParametersRule.php b/src/Rules/Functions/CallToFunctionParametersRule.php index 71dd7e06e2..342b79ffc1 100644 --- a/src/Rules/Functions/CallToFunctionParametersRule.php +++ b/src/Rules/Functions/CallToFunctionParametersRule.php @@ -62,6 +62,7 @@ public function processNode(Node $node, Scope $scope): array 'Missing parameter $%s in call to function ' . $functionName . '.', 'Unknown parameter $%s in call to function ' . $functionName . '.', 'Return type of call to function ' . $functionName . ' contains unresolvable type.', + 'Parameter %s of function ' . $functionName . ' contains unresolvable type.', ], ); } diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index 38b3769cd4..dd4adeff0c 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -68,6 +68,7 @@ public function processNode(Node $node, Scope $scope): array 'Missing parameter $%s in call to method ' . $messagesMethodName . '.', 'Unknown parameter $%s in call to method ' . $messagesMethodName . '.', 'Return type of call to method ' . $messagesMethodName . ' contains unresolvable type.', + 'Parameter %s of method ' . $messagesMethodName . ' contains unresolvable type.', ], )); } diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index c53822e7a2..7534017147 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -76,6 +76,7 @@ public function processNode(Node $node, Scope $scope): array 'Missing parameter $%s in call to ' . $lowercasedMethodName . '.', 'Unknown parameter $%s in call to ' . $lowercasedMethodName . '.', 'Return type of call to ' . $lowercasedMethodName . ' contains unresolvable type.', + 'Parameter %s of ' . $lowercasedMethodName . ' contains unresolvable type.', ], )); diff --git a/src/Testing/TestCase.neon b/src/Testing/TestCase.neon index 3f29e76a2d..aa8af6e608 100644 --- a/src/Testing/TestCase.neon +++ b/src/Testing/TestCase.neon @@ -1,5 +1,7 @@ parameters: inferPrivatePropertyTypeFromConstructor: true + featureToggles: + checkUnresolvableParameterTypes: true services: - diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index af8d73a178..ab1e367961 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -780,21 +780,23 @@ public function testBug7215(): void public function testBug7094(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-7094.php'); - $this->assertCount(6, $errors); + $this->assertCount(7, $errors); - $this->assertSame('Parameter #2 $val of method Bug7094\Foo::setAttribute() expects string, int given.', $errors[0]->getMessage()); - $this->assertSame(75, $errors[0]->getLine()); - $this->assertSame('Parameter #2 $val of method Bug7094\Foo::setAttribute() expects 5|6|7, 3 given.', $errors[1]->getMessage()); - $this->assertSame(76, $errors[1]->getLine()); - $this->assertSame('Parameter #2 $val of method Bug7094\Foo::setAttribute() expects string, int given.', $errors[2]->getMessage()); - $this->assertSame(78, $errors[2]->getLine()); - $this->assertSame('Return type of call to method Bug7094\Foo::getAttribute() contains unresolvable type.', $errors[3]->getMessage()); - $this->assertSame(79, $errors[3]->getLine()); + $this->assertSame('Parameter #2 $val of method Bug7094\Foo::setAttribute() contains unresolvable type.', $errors[0]->getMessage()); + $this->assertSame(74, $errors[0]->getLine()); + $this->assertSame('Parameter #2 $val of method Bug7094\Foo::setAttribute() expects string, int given.', $errors[1]->getMessage()); + $this->assertSame(75, $errors[1]->getLine()); + $this->assertSame('Parameter #2 $val of method Bug7094\Foo::setAttribute() expects 5|6|7, 3 given.', $errors[2]->getMessage()); + $this->assertSame(76, $errors[2]->getLine()); + $this->assertSame('Parameter #2 $val of method Bug7094\Foo::setAttribute() expects string, int given.', $errors[3]->getMessage()); + $this->assertSame(78, $errors[3]->getLine()); + $this->assertSame('Return type of call to method Bug7094\Foo::getAttribute() contains unresolvable type.', $errors[4]->getMessage()); + $this->assertSame(79, $errors[4]->getLine()); - $this->assertSame('Parameter #1 $attr of method Bug7094\Foo::setAttributes() expects array{foo?: string, bar?: 5|6|7, baz?: bool}, non-empty-array given.', $errors[4]->getMessage()); - $this->assertSame(29, $errors[4]->getLine()); - $this->assertSame('Parameter #1 $attr of method Bug7094\Foo::setAttributes() expects array{foo?: string, bar?: 5|6|7, baz?: bool}, array<\'bar\'|\'baz\'|\'foo\', 5|6|7|bool|string> given.', $errors[5]->getMessage()); - $this->assertSame(49, $errors[5]->getLine()); + $this->assertSame('Parameter #1 $attr of method Bug7094\Foo::setAttributes() expects array{foo?: string, bar?: 5|6|7, baz?: bool}, non-empty-array given.', $errors[5]->getMessage()); + $this->assertSame(29, $errors[5]->getLine()); + $this->assertSame('Parameter #1 $attr of method Bug7094\Foo::setAttributes() expects array{foo?: string, bar?: 5|6|7, baz?: bool}, array<\'bar\'|\'baz\'|\'foo\', 5|6|7|bool|string> given.', $errors[6]->getMessage()); + $this->assertSame(49, $errors[6]->getLine()); } public function testOffsetAccess(): void @@ -805,6 +807,18 @@ public function testOffsetAccess(): void $this->assertSame(42, $errors[0]->getLine()); } + public function testUnresolvableParameter(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/unresolvable-parameter.php'); + $this->assertCount(3, $errors); + $this->assertSame('Parameter #2 $array of function array_map expects array, array|false given.', $errors[0]->getMessage()); + $this->assertSame(18, $errors[0]->getLine()); + $this->assertSame('Method UnresolvableParameter\Collection::pipeInto() has parameter $class with no type specified.', $errors[1]->getMessage()); + $this->assertSame(30, $errors[1]->getLine()); + $this->assertSame('PHPDoc tag @param for parameter $class contains unresolvable type.', $errors[2]->getMessage()); + $this->assertSame(30, $errors[2]->getLine()); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/unresolvable-parameter.php b/tests/PHPStan/Analyser/data/unresolvable-parameter.php new file mode 100644 index 0000000000..47a3780d69 --- /dev/null +++ b/tests/PHPStan/Analyser/data/unresolvable-parameter.php @@ -0,0 +1,39 @@ +pipeInto(User::class); +}; diff --git a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php index 3c4f39c7ff..90e53baa30 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -36,6 +36,7 @@ protected function getRule(): Rule true, true, true, + true, ), new ClassCaseSensitivityCheck($reflectionProvider, false), ), diff --git a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php index 1f2447c153..fd6d0ade5b 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php @@ -35,6 +35,7 @@ protected function getRule(): Rule true, true, true, + true, ), new ClassCaseSensitivityCheck($reflectionProvider, false), ), diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index aeeb4e836a..036d9c8238 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -24,7 +24,7 @@ protected function getRule(): Rule $broker = $this->createReflectionProvider(); return new InstantiationRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), new ClassCaseSensitivityCheck($broker, true), ); } diff --git a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php index c239ab3910..b917ec14aa 100644 --- a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php +++ b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php @@ -35,6 +35,7 @@ protected function getRule(): Rule true, true, true, + true, ), new ClassCaseSensitivityCheck($reflectionProvider, false), ), diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php index 5ab89c2980..3240c0f596 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php @@ -35,6 +35,7 @@ protected function getRule(): Rule true, true, true, + true, ), new ClassCaseSensitivityCheck($reflectionProvider, false), ), diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 186b438ffb..2912a7b0fb 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -34,6 +34,7 @@ protected function getRule(): Rule true, true, true, + true, ), $ruleLevelHelper, true, diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 5a86c7c503..959431248f 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -26,7 +26,7 @@ protected function getRule(): Rule $broker = $this->createReflectionProvider(); return new CallToFunctionParametersRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), ); } diff --git a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php index c334a8b355..54602988da 100644 --- a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php @@ -35,6 +35,7 @@ protected function getRule(): Rule true, true, true, + true, ), new ClassCaseSensitivityCheck($reflectionProvider, false), ), diff --git a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php index 92d7971ce7..0e7953dc5b 100644 --- a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php @@ -35,6 +35,7 @@ protected function getRule(): Rule true, true, true, + true, ), new ClassCaseSensitivityCheck($reflectionProvider, false), ), diff --git a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php index f9179ee872..3b5ba688cb 100644 --- a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php @@ -35,6 +35,7 @@ protected function getRule(): Rule true, true, true, + true, ), new ClassCaseSensitivityCheck($reflectionProvider, false), ), diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php index 0272593c76..75701abf14 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php @@ -26,7 +26,7 @@ protected function getRule(): Rule $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, $this->checkExplicitMixed); 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), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(PHP_VERSION_ID), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, false), ); } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index b76d6d08ad..177f403a62 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -34,7 +34,7 @@ protected function getRule(): Rule $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed); 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), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), ); } @@ -2451,4 +2451,22 @@ public function testBug3284(): void $this->analyse([__DIR__ . '/data/bug-3284.php'], []); } + public function testUnresolvableParameter(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = false; + $this->analyse([__DIR__ . '/data/unresolvable-parameter.php'], [ + [ + 'Parameter #2 $v of method UnresolvableParameter\HelloWorld::foo() contains unresolvable type.', + 18, + ], + [ + 'Parameter #2 $v of method UnresolvableParameter\HelloWorld::foo() contains unresolvable type.', + 19, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index c3c7694a31..d4bd10c37f 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, true, $this->checkExplicitMixed); return new CallStaticMethodsRule( new StaticMethodCallCheck($reflectionProvider, $ruleLevelHelper, new ClassCaseSensitivityCheck($reflectionProvider, true), true, true), - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), ); } diff --git a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index 6cccaca712..d4b49c4880 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -37,6 +37,7 @@ protected function getRule(): Rule true, true, true, + true, ), new ClassCaseSensitivityCheck($reflectionProvider, false), ), diff --git a/tests/PHPStan/Rules/Methods/data/unresolvable-parameter.php b/tests/PHPStan/Rules/Methods/data/unresolvable-parameter.php new file mode 100644 index 0000000000..9e44d39cf5 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/unresolvable-parameter.php @@ -0,0 +1,23 @@ + $v + */ + public function foo($p, $v): void + { + } +} + +function (HelloWorld $foo) { + $foo->foo(0, 0); + $foo->foo('', 0); + $foo->foo([], 0); + $foo->foo([1], 0); + $foo->foo([1], 1); +}; diff --git a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php index c5be2d0cc0..632e13bf8d 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php @@ -34,6 +34,7 @@ protected function getRule(): Rule true, true, true, + true, ), new ClassCaseSensitivityCheck($reflectionProvider, false), ),