Skip to content

Commit 8c50d88

Browse files
jlherrenondrejmirtes
authored andcommitted
Type cast fixes, add tests for intval(), boolval(), floatval()
1 parent e42ae89 commit 8c50d88

File tree

10 files changed

+115
-42
lines changed

10 files changed

+115
-42
lines changed

conf/config.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1403,7 +1403,7 @@ services:
14031403
- phpstan.broker.dynamicFunctionReturnTypeExtension
14041404

14051405
-
1406-
class: PHPStan\Type\Php\StrvalFunctionReturnTypeExtension
1406+
class: PHPStan\Type\Php\StrvalFamilyFunctionReturnTypeExtension
14071407
tags:
14081408
- phpstan.broker.dynamicFunctionReturnTypeExtension
14091409

src/Type/ArrayType.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPStan\TrinaryLogic;
88
use PHPStan\Type\Accessory\NonEmptyArrayType;
99
use PHPStan\Type\Constant\ConstantArrayType;
10+
use PHPStan\Type\Constant\ConstantFloatType;
1011
use PHPStan\Type\Constant\ConstantIntegerType;
1112
use PHPStan\Type\Constant\ConstantStringType;
1213
use PHPStan\Type\Generic\TemplateMixedType;
@@ -266,12 +267,18 @@ public function toString(): Type
266267

267268
public function toInteger(): Type
268269
{
269-
return new ErrorType();
270+
return TypeCombinator::union(
271+
new ConstantIntegerType(0),
272+
new ConstantIntegerType(1)
273+
);
270274
}
271275

272276
public function toFloat(): Type
273277
{
274-
return new ErrorType();
278+
return TypeCombinator::union(
279+
new ConstantFloatType(0.0),
280+
new ConstantFloatType(1.0)
281+
);
275282
}
276283

277284
public function toArray(): Type

src/Type/Constant/ConstantArrayType.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,16 @@ public function toBoolean(): BooleanType
608608
return $this->count()->toBoolean();
609609
}
610610

611+
public function toInteger(): Type
612+
{
613+
return $this->toBoolean()->toInteger();
614+
}
615+
616+
public function toFloat(): Type
617+
{
618+
return $this->toBoolean()->toFloat();
619+
}
620+
611621
public function generalize(): Type
612622
{
613623
if (count($this->keyTypes) === 0) {

src/Type/Constant/ConstantStringType.php

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -226,22 +226,12 @@ public function toNumber(): Type
226226

227227
public function toInteger(): Type
228228
{
229-
$type = $this->toNumber();
230-
if ($type instanceof ErrorType) {
231-
return $type;
232-
}
233-
234-
return $type->toInteger();
229+
return new ConstantIntegerType((int) $this->value);
235230
}
236231

237232
public function toFloat(): Type
238233
{
239-
$type = $this->toNumber();
240-
if ($type instanceof ErrorType) {
241-
return $type;
242-
}
243-
244-
return $type->toFloat();
234+
return new ConstantFloatType((float) $this->value);
245235
}
246236

247237
public function isNumericString(): TrinaryLogic

src/Type/Php/StrvalFunctionReturnTypeExtension.php renamed to src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,20 @@
99
use PHPStan\Type\NullType;
1010
use PHPStan\Type\Type;
1111

12-
class StrvalFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
12+
class StrvalFamilyFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
1313
{
1414

15+
private const FUNCTIONS = [
16+
'strval',
17+
'intval',
18+
'boolval',
19+
'floatval',
20+
'doubleval',
21+
];
22+
1523
public function isFunctionSupported(FunctionReflection $functionReflection): bool
1624
{
17-
return $functionReflection->getName() === 'strval';
25+
return in_array($functionReflection->getName(), self::FUNCTIONS, true);
1826
}
1927

2028
public function getTypeFromFunctionCall(
@@ -26,8 +34,22 @@ public function getTypeFromFunctionCall(
2634
if (count($functionCall->args) === 0) {
2735
return new NullType();
2836
}
37+
2938
$argType = $scope->getType($functionCall->args[0]->value);
30-
return $argType->toString();
39+
40+
switch ($functionReflection->getName()) {
41+
case 'strval':
42+
return $argType->toString();
43+
case 'intval':
44+
return $argType->toInteger();
45+
case 'boolval':
46+
return $argType->toBoolean();
47+
case 'floatval':
48+
case 'doubleval':
49+
return $argType->toFloat();
50+
default:
51+
throw new \PHPStan\ShouldNotHappenException();
52+
}
3153
}
3254

3355
}

tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,11 +1550,11 @@ public function dataCasts(): array
15501550
'(float) "5"',
15511551
],
15521552
[
1553-
'*ERROR*',
1553+
'0',
15541554
'(int) "blabla"',
15551555
],
15561556
[
1557-
'*ERROR*',
1557+
'0.0',
15581558
'(float) "blabla"',
15591559
],
15601560
[
Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,78 @@
11
<?php
22

3-
namespace StrvalTest;
3+
namespace StrvalFamilyTest;
44

55
use function PHPStan\Testing\assertType;
66

77
/**
88
* @param class-string<\stdClass> $class
99
*/
10-
function test(string $class)
10+
function strvalTest(string $string, string $class): void
1111
{
12+
assertType('null', strval());
1213
assertType('\'foo\'', strval('foo'));
14+
assertType('string', strval($string));
1315
assertType('\'\'', strval(null));
16+
assertType('\'\'', strval(false));
17+
assertType('\'1\'', strval(true));
1418
assertType('\'\'|\'1\'', strval(rand(0, 1) === 0));
19+
assertType('\'42\'', strval(42));
1520
assertType('string&numeric', strval(rand()));
1621
assertType('string&numeric', strval(rand() * 0.1));
1722
assertType('string&numeric', strval(strval(rand())));
1823
assertType('class-string<stdClass>', strval($class));
24+
assertType('string', strval(new \Exception()));
25+
assertType('*ERROR*', strval(new \stdClass()));
26+
}
27+
28+
function intvalTest(string $string): void
29+
{
30+
assertType('null', intval());
31+
assertType('42', intval('42'));
32+
assertType('0', intval('foo'));
33+
assertType('int', intval($string));
34+
assertType('0', intval(null));
35+
assertType('0', intval(false));
36+
assertType('1', intval(true));
37+
assertType('0|1', intval(rand(0, 1) === 0));
38+
assertType('42', intval(42));
39+
assertType('int', intval(rand()));
40+
assertType('int', intval(rand() * 0.1));
41+
assertType('0', intval([]));
42+
assertType('1', intval([null]));
43+
}
44+
45+
function floatvalTest(string $string): void
46+
{
47+
assertType('null', floatval());
48+
assertType('3.14', floatval('3.14'));
49+
assertType('0.0', floatval('foo'));
50+
assertType('float', floatval($string));
51+
assertType('0.0', floatval(null));
52+
assertType('0.0', floatval(false));
53+
assertType('1.0', floatval(true));
54+
assertType('0.0|1.0', floatval(rand(0, 1) === 0));
55+
assertType('42.0', floatval(42));
56+
assertType('float', floatval(rand()));
57+
assertType('float', floatval(rand() * 0.1));
58+
assertType('0.0', floatval([]));
59+
assertType('1.0', floatval([null]));
60+
}
61+
62+
function boolvalTest(string $string): void
63+
{
64+
assertType('null', boolval());
65+
assertType('false', boolval(''));
66+
assertType('true', boolval('foo'));
67+
assertType('bool', boolval($string));
68+
assertType('false', boolval(null));
69+
assertType('false', boolval(false));
70+
assertType('true', boolval(true));
71+
assertType('bool', boolval(rand(0, 1) === 0));
72+
assertType('true', boolval(42));
73+
assertType('bool', boolval(rand()));
74+
assertType('bool', boolval(rand() * 0.1));
75+
assertType('false', boolval([]));
76+
assertType('true', boolval([null]));
77+
assertType('true', boolval(new \stdClass()));
1978
}

tests/PHPStan/Levels/data/casts-2.json

Lines changed: 0 additions & 12 deletions
This file was deleted.

tests/PHPStan/Levels/data/casts-7.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
[
2+
{
3+
"message": "Cannot cast array|(callable(): mixed) to int.",
4+
"line": 20,
5+
"ignorable": true
6+
},
27
{
38
"message": "Cannot cast array|float|int to string.",
49
"line": 21,

tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,6 @@ public function testRule(): void
2323
'Cannot cast stdClass to string.',
2424
7,
2525
],
26-
[
27-
'Cannot cast array() to int.',
28-
16,
29-
],
30-
[
31-
'Cannot cast \'blabla\' to int.',
32-
21,
33-
],
3426
[
3527
'Cannot cast stdClass to int.',
3628
23,

0 commit comments

Comments
 (0)