Skip to content

Commit 279c781

Browse files
authored
ctype_digit - support type narrowing with cast parameters
1 parent 980551b commit 279c781

File tree

3 files changed

+69
-2
lines changed

3 files changed

+69
-2
lines changed

src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Type\Php;
44

5+
use PhpParser\Node\Expr\Cast;
56
use PhpParser\Node\Expr\FuncCall;
67
use PHPStan\Analyser\Scope;
78
use PHPStan\Analyser\SpecifiedTypes;
@@ -38,7 +39,8 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
3839
throw new ShouldNotHappenException();
3940
}
4041

41-
if ($context->true() && $scope->getType($node->getArgs()[0]->value)->isNumericString()->yes()) {
42+
$exprArg = $node->getArgs()[0]->value;
43+
if ($context->true() && $scope->getType($exprArg)->isNumericString()->yes()) {
4244
return new SpecifiedTypes();
4345
}
4446

@@ -54,7 +56,16 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
5456
]);
5557
}
5658

57-
return $this->typeSpecifier->create($node->getArgs()[0]->value, TypeCombinator::union(...$types), $context, false, $scope);
59+
$unionType = TypeCombinator::union(...$types);
60+
$specifiedTypes = $this->typeSpecifier->create($exprArg, $unionType, $context, false, $scope);
61+
62+
if ($context->true() && $exprArg instanceof Cast) {
63+
$specifiedTypes = $specifiedTypes->unionWith(
64+
$this->typeSpecifier->create($exprArg->expr, $unionType, $context, false, $scope),
65+
);
66+
}
67+
68+
return $specifiedTypes;
5869
}
5970

6071
public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,8 @@ public function dataFileAsserts(): iterable
11751175
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8621.php');
11761176
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8084.php');
11771177
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3019.php');
1178+
1179+
yield from $this->gatherAssertTypes(__DIR__ . '/data/callsite-cast-narrowing.php');
11781180
}
11791181

11801182
/**
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace CallsiteCastNarrowing;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld
8+
{
9+
public function sayHello($mixed, int $int, string $string): void
10+
{
11+
if (ctype_digit((string) $mixed)) {
12+
assertType('int<48, 57>|int<256, max>|numeric-string', $mixed);
13+
} else {
14+
assertType('mixed', $mixed);
15+
}
16+
assertType('mixed', $mixed);
17+
18+
if (ctype_digit((int) $mixed)) {
19+
assertType('int<48, 57>|int<256, max>|numeric-string', $mixed);
20+
} else {
21+
assertType('mixed', $mixed);
22+
}
23+
assertType('mixed', $mixed);
24+
25+
if (ctype_digit((string) $int)) {
26+
assertType('int', $int);
27+
} else {
28+
assertType('int', $int);
29+
}
30+
assertType('int', $int);
31+
32+
if (ctype_digit((int) $int)) {
33+
assertType('int<48, 57>|int<256, max>', $int);
34+
} else {
35+
assertType('int', $int);
36+
}
37+
assertType('int', $int);
38+
39+
if (ctype_digit((string) $string)) {
40+
assertType('numeric-string', $string);
41+
} else {
42+
assertType('string', $string);
43+
}
44+
assertType('string', $string);
45+
46+
if (ctype_digit((int) $string)) {
47+
assertType('numeric-string', $string);
48+
} else {
49+
assertType('string', $string);
50+
}
51+
assertType('string', $string);
52+
}
53+
54+
}

0 commit comments

Comments
 (0)