Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Php/PhpVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -492,4 +492,9 @@ public function deprecatesIncOnNonNumericString(): bool
return $this->versionId >= 80500;
}

public function supportsObjectsInArraySumProduct(): bool
{
return $this->versionId >= 80300;
}

}
10 changes: 9 additions & 1 deletion src/Rules/Functions/ParameterCastableToNumberRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,6 +24,7 @@ final class ParameterCastableToNumberRule implements Rule
public function __construct(
private ReflectionProvider $reflectionProvider,
private ParameterCastableToStringCheck $parameterCastableToStringCheck,
private PhpVersion $phpVersion,
)
{
}
Expand Down Expand Up @@ -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],
Expand Down
5 changes: 4 additions & 1 deletion src/Type/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Rules\Functions;

use PHPStan\Php\PhpVersion;
use PHPStan\Rules\ParameterCastableToStringCheck;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
Expand All @@ -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
Expand Down Expand Up @@ -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<int, string> given.',
13,
],
[
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, string> given.',
19,
],
[
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, string> given.',
22,
],
[
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, string> given.',
25,
],
[
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, array> given.',
28,
],
[
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, stdClass> given.',
31,
],
[
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, GMP|stdClass> given.',
34,
],
];

if (PHP_VERSION_ID < 80300) {
$errors[] = [
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, GMP> given.',
37,
];
}

$this->analyse([__DIR__ . '/data/bug-13775.php'], $errors);
}

public function testBug13775Bis(): void
{
$errors = [
[
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, string> given.',
13,
],
[
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, string> given.',
19,
],
[
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, string> given.',
22,
],
[
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, string> given.',
25,
],
[
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, array> given.',
28,
],
[
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, stdClass> given.',
31,
],
[
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, GMP|stdClass> given.',
34,
],
];

if (PHP_VERSION_ID < 80300) {
$errors[] = [
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, GMP> given.',
37,
];
}

$this->analyse([__DIR__ . '/data/bug-13775-bis.php'], $errors);
}

public function testBug12146(): void
{
$this->analyse([__DIR__ . '/data/bug-12146.php'], $this->hackPhp74ErrorMessages([
Expand Down
37 changes: 37 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-13775-bis.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types = 1);

$a = [null];
echo 'null, valid cast: ' . array_sum($a) . "\n\n";

$a = [false];
echo 'false, valid cast: ' . array_sum($a) . "\n\n";

$a = [true];
echo 'true, valid cast: ' . array_sum($a) . "\n\n";

$a = [''];
echo 'empty string, invalid cast: ' . array_sum($a) . "\n\n";

$a = ['42.5'];
echo 'string of a float, valid cast: ' . array_sum($a) . "\n\n";

$a = ['42,5'];
echo 'string of comma-separated float, valid cast but not desirable (discards trailing chars): ' . array_sum($a) . "\n\n";

$a = ['42a'];
echo 'string of int with trailing alpha char, valid cast but not desirable (discards trailing chars): ' . array_sum($a) . "\n\n";

$a = ['a42'];
echo 'string of int with leading alpha char, invalid cast: ' . array_sum($a) . "\n\n";

$a = [[]];
echo 'array, invalid cast: ' . array_sum($a) . "\n\n";

$a = [new stdClass()];
echo 'class that does not auto-cast, invalid cast: ' . array_sum($a) . "\n\n";

$a = [rand(0, 1) ? new stdClass() : gmp_init(42)];
echo 'possibly invalid cast: ' . array_sum($a) . "\n\n";

$a = [gmp_init(42)];
echo 'gmp, valid cast: ' . array_sum($a) . "\n\n";
37 changes: 37 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-13775.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types = 1);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue 13775 also talks about array_sum which is not yet covered in tests

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


$a = [null];
echo 'null, valid cast: ' . array_product($a) . "\n\n";

$a = [false];
echo 'false, valid cast: ' . array_product($a) . "\n\n";

$a = [true];
echo 'true, valid cast: ' . array_product($a) . "\n\n";

$a = [''];
echo 'empty string, invalid cast: ' . array_product($a) . "\n\n";

$a = ['42.5'];
echo 'string of a float, valid cast: ' . array_product($a) . "\n\n";

$a = ['42,5'];
echo 'string of comma-separated float, valid cast but not desirable (discards trailing chars): ' . array_product($a) . "\n\n";

$a = ['42a'];
echo 'string of int with trailing alpha char, valid cast but not desirable (discards trailing chars): ' . array_product($a) . "\n\n";

$a = ['a42'];
echo 'string of int with leading alpha char, invalid cast: ' . array_product($a) . "\n\n";

$a = [[]];
echo 'array, invalid cast: ' . array_product($a) . "\n\n";

$a = [new stdClass()];
echo 'class that does not auto-cast, invalid cast: ' . array_product($a) . "\n\n";

$a = [rand(0, 1) ? new stdClass() : gmp_init(42)];
echo 'possibly invalid cast: ' . array_product($a) . "\n\n";

$a = [gmp_init(42)];
echo 'gmp, valid cast: ' . array_product($a) . "\n\n";
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ function wrongNumberOfArguments(): void

function validUsages(): void
{
var_dump(array_sum(['5.5', false, true, new \SimpleXMLElement('<a>7.7</a>'), 5, 5.5, null]));
var_dump(array_product(['5.5', false, true, new \SimpleXMLElement('<a>7.7</a>'), 5, 5.5, null]));
Comment on lines -43 to -44
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't a valid usage, see
https://3v4l.org/GNJiY#v8.2.30

The SimpleXMLElement is silently ignored

var_dump(array_sum(['5.5', false, true, 5, 5.5, null]));
var_dump(array_product(['5.5', false, true, 5, 5.5, null]));
}
Loading