Skip to content

Commit 34bbe41

Browse files
committed
Fix return type of round/ceil/floor in non strict type environment
In non declare strict types the round functions are able to handle numeric strings, null and bools. See also: https://3v4l.org/5io0n And issue phpstan/phpstan#11319
1 parent 165c104 commit 34bbe41

File tree

3 files changed

+95
-15
lines changed

3 files changed

+95
-15
lines changed

src/Type/Php/RoundFunctionReturnTypeExtension.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Php\PhpVersion;
88
use PHPStan\Reflection\FunctionReflection;
9+
use PHPStan\Type\Accessory\AccessoryNumericStringType;
10+
use PHPStan\Type\BooleanType;
911
use PHPStan\Type\Constant\ConstantBooleanType;
1012
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
1113
use PHPStan\Type\FloatType;
1214
use PHPStan\Type\IntegerType;
15+
use PHPStan\Type\IntersectionType;
1316
use PHPStan\Type\MixedType;
1417
use PHPStan\Type\NeverType;
1518
use PHPStan\Type\NullType;
19+
use PHPStan\Type\StringType;
1620
use PHPStan\Type\Type;
1721
use PHPStan\Type\TypeCombinator;
1822
use function count;
@@ -67,6 +71,19 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
6771
new IntegerType(),
6872
new FloatType(),
6973
);
74+
75+
if (!$scope->isDeclareStrictTypes()) {
76+
$allowed = TypeCombinator::union(
77+
$allowed,
78+
new IntersectionType([
79+
new StringType(),
80+
new AccessoryNumericStringType(),
81+
]),
82+
new NullType(),
83+
new BooleanType(),
84+
);
85+
}
86+
7087
if ($allowed->isSuperTypeOf($firstArgType)->no()) {
7188
// PHP 8 fatals if the parameter is not an integer or float.
7289
return new NeverType(true);
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types=1);
4+
5+
namespace RoundFamilyTestPHP8StrictTypes;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
$maybeNull = null;
10+
if (rand(0, 1)) {
11+
$maybeNull = 1.0;
12+
}
13+
14+
// Round
15+
assertType('float', round(123));
16+
assertType('float', round(123.456));
17+
assertType('float', round($_GET['foo'] / 60));
18+
assertType('*NEVER*', round('123'));
19+
assertType('*NEVER*', round('123.456'));
20+
assertType('*NEVER*', round(null));
21+
assertType('float', round($maybeNull));
22+
assertType('*NEVER*', round(true));
23+
assertType('*NEVER*', round(false));
24+
assertType('*NEVER*', round(new \stdClass));
25+
assertType('*NEVER*', round(''));
26+
assertType('*NEVER*', round(array()));
27+
assertType('*NEVER*', round(array(123)));
28+
assertType('*NEVER*', round());
29+
assertType('float', round($_GET['foo']));
30+
31+
// Ceil
32+
assertType('float', ceil(123));
33+
assertType('float', ceil(123.456));
34+
assertType('float', ceil($_GET['foo'] / 60));
35+
assertType('*NEVER*', ceil('123'));
36+
assertType('*NEVER*', ceil('123.456'));
37+
assertType('*NEVER*', ceil(null));
38+
assertType('float', ceil($maybeNull));
39+
assertType('*NEVER*', ceil(true));
40+
assertType('*NEVER*', ceil(false));
41+
assertType('*NEVER*', ceil(new \stdClass));
42+
assertType('*NEVER*', ceil(''));
43+
assertType('*NEVER*', ceil(array()));
44+
assertType('*NEVER*', ceil(array(123)));
45+
assertType('*NEVER*', ceil());
46+
assertType('float', ceil($_GET['foo']));
47+
48+
// Floor
49+
assertType('float', floor(123));
50+
assertType('float', floor(123.456));
51+
assertType('float', floor($_GET['foo'] / 60));
52+
assertType('*NEVER*', floor('123'));
53+
assertType('*NEVER*', floor('123.456'));
54+
assertType('*NEVER*', floor(null));
55+
assertType('float', floor($maybeNull));
56+
assertType('*NEVER*', floor(true));
57+
assertType('*NEVER*', floor(false));
58+
assertType('*NEVER*', floor(new \stdClass));
59+
assertType('*NEVER*', floor(''));
60+
assertType('*NEVER*', floor(array()));
61+
assertType('*NEVER*', floor(array(123)));
62+
assertType('*NEVER*', floor());
63+
assertType('float', floor($_GET['foo']));

tests/PHPStan/Analyser/nsrt/round-php8.php

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
assertType('float', round(123));
1414
assertType('float', round(123.456));
1515
assertType('float', round($_GET['foo'] / 60));
16-
assertType('*NEVER*', round('123'));
17-
assertType('*NEVER*', round('123.456'));
18-
assertType('*NEVER*', round(null));
16+
assertType('float', round('123'));
17+
assertType('float', round('123.456'));
18+
assertType('float', round(null));
1919
assertType('float', round($maybeNull));
20-
assertType('*NEVER*', round(true));
21-
assertType('*NEVER*', round(false));
20+
assertType('float', round(true));
21+
assertType('float', round(false));
2222
assertType('*NEVER*', round(new \stdClass));
2323
assertType('*NEVER*', round(''));
2424
assertType('*NEVER*', round(array()));
@@ -30,12 +30,12 @@
3030
assertType('float', ceil(123));
3131
assertType('float', ceil(123.456));
3232
assertType('float', ceil($_GET['foo'] / 60));
33-
assertType('*NEVER*', ceil('123'));
34-
assertType('*NEVER*', ceil('123.456'));
35-
assertType('*NEVER*', ceil(null));
33+
assertType('float', ceil('123'));
34+
assertType('float', ceil('123.456'));
35+
assertType('float', ceil(null));
3636
assertType('float', ceil($maybeNull));
37-
assertType('*NEVER*', ceil(true));
38-
assertType('*NEVER*', ceil(false));
37+
assertType('float', ceil(true));
38+
assertType('float', ceil(false));
3939
assertType('*NEVER*', ceil(new \stdClass));
4040
assertType('*NEVER*', ceil(''));
4141
assertType('*NEVER*', ceil(array()));
@@ -47,12 +47,12 @@
4747
assertType('float', floor(123));
4848
assertType('float', floor(123.456));
4949
assertType('float', floor($_GET['foo'] / 60));
50-
assertType('*NEVER*', floor('123'));
51-
assertType('*NEVER*', floor('123.456'));
52-
assertType('*NEVER*', floor(null));
50+
assertType('float', floor('123'));
51+
assertType('float', floor('123.456'));
52+
assertType('float', floor(null));
5353
assertType('float', floor($maybeNull));
54-
assertType('*NEVER*', floor(true));
55-
assertType('*NEVER*', floor(false));
54+
assertType('float', floor(true));
55+
assertType('float', floor(false));
5656
assertType('*NEVER*', floor(new \stdClass));
5757
assertType('*NEVER*', floor(''));
5858
assertType('*NEVER*', floor(array()));

0 commit comments

Comments
 (0)