Skip to content

Commit f2015d4

Browse files
Fix GMP::toNumber (#4867)
1 parent 707fbe1 commit f2015d4

File tree

9 files changed

+200
-5
lines changed

9 files changed

+200
-5
lines changed

src/Php/PhpVersion.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,4 +496,9 @@ public function deprecatesIncOnNonNumericString(): bool
496496
return $this->versionId >= 80500;
497497
}
498498

499+
public function supportsObjectsInArraySumProduct(): bool
500+
{
501+
return $this->versionId >= 80300;
502+
}
503+
499504
}

src/Rules/Functions/ParameterCastableToNumberRule.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
use PhpParser\Node;
66
use PhpParser\Node\Expr\FuncCall;
77
use PHPStan\Analyser\Scope;
8+
use PHPStan\Php\PhpVersion;
89
use PHPStan\Reflection\ParametersAcceptorSelector;
910
use PHPStan\Reflection\ReflectionProvider;
1011
use PHPStan\Rules\ParameterCastableToStringCheck;
1112
use PHPStan\Rules\Rule;
13+
use PHPStan\Type\ErrorType;
1214
use PHPStan\Type\Type;
1315
use function count;
1416
use function in_array;
@@ -22,6 +24,7 @@ final class ParameterCastableToNumberRule implements Rule
2224
public function __construct(
2325
private ReflectionProvider $reflectionProvider,
2426
private ParameterCastableToStringCheck $parameterCastableToStringCheck,
27+
private PhpVersion $phpVersion,
2528
)
2629
{
2730
}
@@ -63,11 +66,16 @@ public function processNode(Node $node, Scope $scope): array
6366

6467
$errorMessage = 'Parameter %s of function %s expects an array of values castable to number, %s given.';
6568
$functionParameters = $parametersAcceptor->getParameters();
69+
70+
$castFn = $this->phpVersion->supportsObjectsInArraySumProduct()
71+
? static fn (Type $t) => $t->toNumber()
72+
: static fn (Type $t) => !$t->isObject()->no() ? new ErrorType() : $t->toNumber();
73+
6674
$error = $this->parameterCastableToStringCheck->checkParameter(
6775
$origArgs[0],
6876
$scope,
6977
$errorMessage,
70-
static fn (Type $t) => $t->toNumber(),
78+
$castFn,
7179
$functionName,
7280
$this->parameterCastableToStringCheck->getParameterName(
7381
$origArgs[0],

src/Type/ObjectType.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,10 @@ private function describeCache(): string
752752

753753
public function toNumber(): Type
754754
{
755-
if ($this->isInstanceOf('SimpleXMLElement')->yes()) {
755+
if (
756+
$this->isInstanceOf('SimpleXMLElement')->yes()
757+
|| $this->isInstanceOf('GMP')->yes()
758+
) {
756759
return new UnionType([
757760
new FloatType(),
758761
new IntegerType(),

tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Rules\Functions;
44

5+
use PHPStan\Php\PhpVersion;
56
use PHPStan\Rules\ParameterCastableToStringCheck;
67
use PHPStan\Rules\Rule;
78
use PHPStan\Rules\RuleLevelHelper;
@@ -20,7 +21,11 @@ class ParameterCastableToNumberRuleTest extends RuleTestCase
2021
protected function getRule(): Rule
2122
{
2223
$broker = self::createReflectionProvider();
23-
return new ParameterCastableToNumberRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false, true)));
24+
return new ParameterCastableToNumberRule(
25+
$broker,
26+
new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false, true)),
27+
self::getContainer()->getByType(PhpVersion::class),
28+
);
2429
}
2530

2631
public function testRule(): void
@@ -122,6 +127,92 @@ public function testBug11883(): void
122127
]);
123128
}
124129

130+
public function testBug13775(): void
131+
{
132+
$errors = [
133+
[
134+
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, string> given.',
135+
13,
136+
],
137+
[
138+
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, string> given.',
139+
19,
140+
],
141+
[
142+
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, string> given.',
143+
22,
144+
],
145+
[
146+
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, string> given.',
147+
25,
148+
],
149+
[
150+
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, array> given.',
151+
28,
152+
],
153+
[
154+
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, stdClass> given.',
155+
31,
156+
],
157+
[
158+
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, GMP|stdClass> given.',
159+
34,
160+
],
161+
];
162+
163+
if (PHP_VERSION_ID < 80300) {
164+
$errors[] = [
165+
'Parameter #1 $array of function array_product expects an array of values castable to number, array<int, GMP> given.',
166+
37,
167+
];
168+
}
169+
170+
$this->analyse([__DIR__ . '/data/bug-13775.php'], $this->hackPhp74ErrorMessages($errors));
171+
}
172+
173+
public function testBug13775Bis(): void
174+
{
175+
$errors = [
176+
[
177+
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, string> given.',
178+
13,
179+
],
180+
[
181+
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, string> given.',
182+
19,
183+
],
184+
[
185+
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, string> given.',
186+
22,
187+
],
188+
[
189+
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, string> given.',
190+
25,
191+
],
192+
[
193+
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, array> given.',
194+
28,
195+
],
196+
[
197+
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, stdClass> given.',
198+
31,
199+
],
200+
[
201+
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, GMP|stdClass> given.',
202+
34,
203+
],
204+
];
205+
206+
if (PHP_VERSION_ID < 80300) {
207+
$errors[] = [
208+
'Parameter #1 $array of function array_sum expects an array of values castable to number, array<int, GMP> given.',
209+
37,
210+
];
211+
}
212+
213+
$this->analyse([__DIR__ . '/data/bug-13775-bis.php'], $this->hackPhp74ErrorMessages($errors));
214+
}
215+
125216
public function testBug12146(): void
126217
{
127218
$this->analyse([__DIR__ . '/data/bug-12146.php'], $this->hackPhp74ErrorMessages([
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php declare(strict_types = 1);
2+
3+
$a = [null];
4+
echo 'null, valid cast: ' . array_sum($a) . "\n\n";
5+
6+
$a = [false];
7+
echo 'false, valid cast: ' . array_sum($a) . "\n\n";
8+
9+
$a = [true];
10+
echo 'true, valid cast: ' . array_sum($a) . "\n\n";
11+
12+
$a = [''];
13+
echo 'empty string, invalid cast: ' . array_sum($a) . "\n\n";
14+
15+
$a = ['42.5'];
16+
echo 'string of a float, valid cast: ' . array_sum($a) . "\n\n";
17+
18+
$a = ['42,5'];
19+
echo 'string of comma-separated float, valid cast but not desirable (discards trailing chars): ' . array_sum($a) . "\n\n";
20+
21+
$a = ['42a'];
22+
echo 'string of int with trailing alpha char, valid cast but not desirable (discards trailing chars): ' . array_sum($a) . "\n\n";
23+
24+
$a = ['a42'];
25+
echo 'string of int with leading alpha char, invalid cast: ' . array_sum($a) . "\n\n";
26+
27+
$a = [[]];
28+
echo 'array, invalid cast: ' . array_sum($a) . "\n\n";
29+
30+
$a = [new stdClass()];
31+
echo 'class that does not auto-cast, invalid cast: ' . array_sum($a) . "\n\n";
32+
33+
$a = [rand(0, 1) ? new stdClass() : gmp_init(42)];
34+
echo 'possibly invalid cast: ' . array_sum($a) . "\n\n";
35+
36+
$a = [gmp_init(42)];
37+
echo 'gmp, valid cast: ' . array_sum($a) . "\n\n";
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php declare(strict_types = 1);
2+
3+
$a = [null];
4+
echo 'null, valid cast: ' . array_product($a) . "\n\n";
5+
6+
$a = [false];
7+
echo 'false, valid cast: ' . array_product($a) . "\n\n";
8+
9+
$a = [true];
10+
echo 'true, valid cast: ' . array_product($a) . "\n\n";
11+
12+
$a = [''];
13+
echo 'empty string, invalid cast: ' . array_product($a) . "\n\n";
14+
15+
$a = ['42.5'];
16+
echo 'string of a float, valid cast: ' . array_product($a) . "\n\n";
17+
18+
$a = ['42,5'];
19+
echo 'string of comma-separated float, valid cast but not desirable (discards trailing chars): ' . array_product($a) . "\n\n";
20+
21+
$a = ['42a'];
22+
echo 'string of int with trailing alpha char, valid cast but not desirable (discards trailing chars): ' . array_product($a) . "\n\n";
23+
24+
$a = ['a42'];
25+
echo 'string of int with leading alpha char, invalid cast: ' . array_product($a) . "\n\n";
26+
27+
$a = [[]];
28+
echo 'array, invalid cast: ' . array_product($a) . "\n\n";
29+
30+
$a = [new stdClass()];
31+
echo 'class that does not auto-cast, invalid cast: ' . array_product($a) . "\n\n";
32+
33+
$a = [rand(0, 1) ? new stdClass() : gmp_init(42)];
34+
echo 'possibly invalid cast: ' . array_product($a) . "\n\n";
35+
36+
$a = [gmp_init(42)];
37+
echo 'gmp, valid cast: ' . array_product($a) . "\n\n";

tests/PHPStan/Rules/Functions/data/param-castable-to-number-functions.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ function wrongNumberOfArguments(): void
4040

4141
function validUsages(): void
4242
{
43-
var_dump(array_sum(['5.5', false, true, new \SimpleXMLElement('<a>7.7</a>'), 5, 5.5, null]));
44-
var_dump(array_product(['5.5', false, true, new \SimpleXMLElement('<a>7.7</a>'), 5, 5.5, null]));
43+
var_dump(array_sum(['5.5', false, true, 5, 5.5, null]));
44+
var_dump(array_product(['5.5', false, true, 5, 5.5, null]));
4545
}

tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,13 @@ public function testBenevolentUnion(): void
795795
]);
796796
}
797797

798+
public function testBug12123(): void
799+
{
800+
$this->checkImplicitMixed = true;
801+
802+
$this->analyse([__DIR__ . '/data/bug-12123.php'], []);
803+
}
804+
798805
public function testBug7863(): void
799806
{
800807
$this->checkImplicitMixed = true;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12123;
4+
5+
$x = gmp_init('1');
6+
$y = $x * 2;
7+
var_dump($y);

0 commit comments

Comments
 (0)