diff --git a/src/Rules/Functions/ParameterCastableToNumberRule.php b/src/Rules/Functions/ParameterCastableToNumberRule.php index 3137688835..98584e45f3 100644 --- a/src/Rules/Functions/ParameterCastableToNumberRule.php +++ b/src/Rules/Functions/ParameterCastableToNumberRule.php @@ -11,6 +11,7 @@ use PHPStan\Rules\ParameterCastableToStringCheck; use PHPStan\Rules\Rule; use PHPStan\Type\ErrorType; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; use function count; use function in_array; @@ -69,7 +70,7 @@ public function processNode(Node $node, Scope $scope): array $castFn = $this->phpVersion->supportsObjectsInArraySumProduct() ? static fn (Type $t) => $t->toNumber() - : static fn (Type $t) => !$t->isObject()->no() ? new ErrorType() : $t->toNumber(); + : static fn (Type $t) => $t instanceof MixedType || $t->isObject()->no() ? $t->toNumber() : new ErrorType(); $error = $this->parameterCastableToStringCheck->checkParameter( $origArgs[0], diff --git a/src/Rules/ParameterCastableToStringCheck.php b/src/Rules/ParameterCastableToStringCheck.php index 244cd1ba4f..28a24997fe 100644 --- a/src/Rules/ParameterCastableToStringCheck.php +++ b/src/Rules/ParameterCastableToStringCheck.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\ArgumentsNormalizer; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredService; +use PHPStan\Node\Expr\TypeExpr; use PHPStan\Reflection\ParameterReflection; use PHPStan\Type\ErrorType; use PHPStan\Type\Type; @@ -34,22 +35,31 @@ public function checkParameter( return null; } - $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $arrayTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, $parameter->value, '', - static fn (Type $type): bool => $type->isArray()->yes() && !$castFn($type->getIterableValueType()) instanceof ErrorType, + static fn (Type $type): bool => $type->isArray()->yes(), + ); + + $arrayType = $arrayTypeResult->getType(); + if (!$arrayType->isArray()->yes()) { + return null; + } + + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + new TypeExpr($arrayType->getIterableValueType()), + '', + static fn (Type $type): bool => !$castFn($type) instanceof ErrorType, ); - if ( - ! $typeResult->getType()->isArray()->yes() - || !$castFn($typeResult->getType()->getIterableValueType()) instanceof ErrorType - ) { + if (!$castFn($typeResult->getType()) instanceof ErrorType) { return null; } return RuleErrorBuilder::message( - sprintf($errorMessageTemplate, $parameterName, $functionName, $typeResult->getType()->describe(VerbosityLevel::typeOnly())), + sprintf($errorMessageTemplate, $parameterName, $functionName, $arrayTypeResult->getType()->describe(VerbosityLevel::typeOnly())), )->identifier('argument.type')->build(); } diff --git a/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php b/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php index e4ebd5c149..48522ef8e4 100644 --- a/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php @@ -18,12 +18,18 @@ class ParameterCastableToNumberRuleTest extends RuleTestCase { + private bool $checkUnion = true; + + private bool $checkExplicitMixed = true; + + private bool $checkImplicitMixed = true; + protected function getRule(): Rule { $broker = self::createReflectionProvider(); return new ParameterCastableToNumberRule( $broker, - new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false, true)), + new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, $this->checkUnion, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true)), self::getContainer()->getByType(PhpVersion::class), ); } @@ -213,6 +219,39 @@ public function testBug13775Bis(): void $this->analyse([__DIR__ . '/data/bug-13775-bis.php'], $this->hackPhp74ErrorMessages($errors)); } + public function testBug13775TerWithoutMixed(): void + { + $this->checkExplicitMixed = false; + $this->checkImplicitMixed = false; + + $this->analyse([__DIR__ . '/data/bug-13775-ter.php'], $this->hackPhp74ErrorMessages([ + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 9, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 10, + ], + ])); + } + + public function testBug13775TerWithoutUnion(): void + { + $this->checkUnion = false; + + $this->analyse([__DIR__ . '/data/bug-13775-ter.php'], $this->hackPhp74ErrorMessages([ + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 5, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 6, + ], + ])); + } + public function testBug12146(): void { $this->analyse([__DIR__ . '/data/bug-12146.php'], $this->hackPhp74ErrorMessages([ diff --git a/tests/PHPStan/Rules/Functions/data/bug-13775-ter.php b/tests/PHPStan/Rules/Functions/data/bug-13775-ter.php new file mode 100644 index 0000000000..acda0c1d71 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-13775-ter.php @@ -0,0 +1,10 @@ +