diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 6a555dff73..65db8bdae6 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -492,4 +492,9 @@ public function deprecatesIncOnNonNumericString(): bool return $this->versionId >= 80500; } + public function supportsObjectsInArraySumProduct(): bool + { + return $this->versionId >= 80300; + } + } diff --git a/src/Rules/Functions/ParameterCastableToNumberRule.php b/src/Rules/Functions/ParameterCastableToNumberRule.php index 640c73a440..3137688835 100644 --- a/src/Rules/Functions/ParameterCastableToNumberRule.php +++ b/src/Rules/Functions/ParameterCastableToNumberRule.php @@ -5,10 +5,12 @@ use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ParameterCastableToStringCheck; use PHPStan\Rules\Rule; +use PHPStan\Type\ErrorType; use PHPStan\Type\Type; use function count; use function in_array; @@ -22,6 +24,7 @@ final class ParameterCastableToNumberRule implements Rule public function __construct( private ReflectionProvider $reflectionProvider, private ParameterCastableToStringCheck $parameterCastableToStringCheck, + private PhpVersion $phpVersion, ) { } @@ -63,11 +66,16 @@ public function processNode(Node $node, Scope $scope): array $errorMessage = 'Parameter %s of function %s expects an array of values castable to number, %s given.'; $functionParameters = $parametersAcceptor->getParameters(); + + $castFn = $this->phpVersion->supportsObjectsInArraySumProduct() + ? static fn (Type $t) => $t->toNumber() + : static fn (Type $t) => !$t->isObject()->no() ? new ErrorType() : $t->toNumber(); + $error = $this->parameterCastableToStringCheck->checkParameter( $origArgs[0], $scope, $errorMessage, - static fn (Type $t) => $t->toNumber(), + $castFn, $functionName, $this->parameterCastableToStringCheck->getParameterName( $origArgs[0], diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 85fcc4bf05..8e295b9fc4 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -752,7 +752,10 @@ private function describeCache(): string public function toNumber(): Type { - if ($this->isInstanceOf('SimpleXMLElement')->yes()) { + if ( + $this->isInstanceOf('SimpleXMLElement')->yes() + || $this->isInstanceOf('GMP')->yes() + ) { return new UnionType([ new FloatType(), new IntegerType(), diff --git a/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php b/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php index 588c845952..e4ebd5c149 100644 --- a/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Functions; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\ParameterCastableToStringCheck; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; @@ -20,7 +21,11 @@ class ParameterCastableToNumberRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = self::createReflectionProvider(); - return new ParameterCastableToNumberRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false, true))); + return new ParameterCastableToNumberRule( + $broker, + new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false, true)), + self::getContainer()->getByType(PhpVersion::class), + ); } public function testRule(): void @@ -122,6 +127,92 @@ public function testBug11883(): void ]); } + public function testBug13775(): void + { + $errors = [ + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 13, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 19, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 22, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 25, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 28, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 31, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 34, + ], + ]; + + if (PHP_VERSION_ID < 80300) { + $errors[] = [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 37, + ]; + } + + $this->analyse([__DIR__ . '/data/bug-13775.php'], $this->hackPhp74ErrorMessages($errors)); + } + + public function testBug13775Bis(): void + { + $errors = [ + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 13, + ], + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 19, + ], + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 22, + ], + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 25, + ], + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 28, + ], + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 31, + ], + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 34, + ], + ]; + + if (PHP_VERSION_ID < 80300) { + $errors[] = [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 37, + ]; + } + + $this->analyse([__DIR__ . '/data/bug-13775-bis.php'], $this->hackPhp74ErrorMessages($errors)); + } + public function testBug12146(): void { $this->analyse([__DIR__ . '/data/bug-12146.php'], $this->hackPhp74ErrorMessages([ diff --git a/tests/PHPStan/Rules/Functions/data/bug-13775-bis.php b/tests/PHPStan/Rules/Functions/data/bug-13775-bis.php new file mode 100644 index 0000000000..69890599fa --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-13775-bis.php @@ -0,0 +1,37 @@ +7.7'), 5, 5.5, null])); - var_dump(array_product(['5.5', false, true, new \SimpleXMLElement('7.7'), 5, 5.5, null])); + var_dump(array_sum(['5.5', false, true, 5, 5.5, null])); + var_dump(array_product(['5.5', false, true, 5, 5.5, null])); } diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index bfca28ee4f..fb1b119745 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -795,6 +795,13 @@ public function testBenevolentUnion(): void ]); } + public function testBug12123(): void + { + $this->checkImplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-12123.php'], []); + } + public function testBug7863(): void { $this->checkImplicitMixed = true; diff --git a/tests/PHPStan/Rules/Operators/data/bug-12123.php b/tests/PHPStan/Rules/Operators/data/bug-12123.php new file mode 100644 index 0000000000..13ba245955 --- /dev/null +++ b/tests/PHPStan/Rules/Operators/data/bug-12123.php @@ -0,0 +1,7 @@ +