diff --git a/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php b/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php index ab1bd7aebd..ec4a59d5f3 100644 --- a/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php @@ -2,6 +2,7 @@ namespace PHPStan\Type\Php; +use PhpParser\Node\Expr\Cast; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; @@ -38,7 +39,8 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n throw new ShouldNotHappenException(); } - if ($context->true() && $scope->getType($node->getArgs()[0]->value)->isNumericString()->yes()) { + $exprArg = $node->getArgs()[0]->value; + if ($context->true() && $scope->getType($exprArg)->isNumericString()->yes()) { return new SpecifiedTypes(); } @@ -54,7 +56,16 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n ]); } - return $this->typeSpecifier->create($node->getArgs()[0]->value, TypeCombinator::union(...$types), $context, false, $scope); + $unionType = TypeCombinator::union(...$types); + $specifiedTypes = $this->typeSpecifier->create($exprArg, $unionType, $context, false, $scope); + + if ($context->true() && $exprArg instanceof Cast) { + $specifiedTypes = $specifiedTypes->unionWith( + $this->typeSpecifier->create($exprArg->expr, $unionType, $context, false, $scope), + ); + } + + return $specifiedTypes; } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 6b8cbe3a03..49878908f4 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1175,6 +1175,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8621.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8084.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3019.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/callsite-cast-narrowing.php'); } /** diff --git a/tests/PHPStan/Analyser/data/callsite-cast-narrowing.php b/tests/PHPStan/Analyser/data/callsite-cast-narrowing.php new file mode 100644 index 0000000000..576409530e --- /dev/null +++ b/tests/PHPStan/Analyser/data/callsite-cast-narrowing.php @@ -0,0 +1,54 @@ +|int<256, max>|numeric-string', $mixed); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); + + if (ctype_digit((int) $mixed)) { + assertType('int<48, 57>|int<256, max>|numeric-string', $mixed); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); + + if (ctype_digit((string) $int)) { + assertType('int', $int); + } else { + assertType('int', $int); + } + assertType('int', $int); + + if (ctype_digit((int) $int)) { + assertType('int<48, 57>|int<256, max>', $int); + } else { + assertType('int', $int); + } + assertType('int', $int); + + if (ctype_digit((string) $string)) { + assertType('numeric-string', $string); + } else { + assertType('string', $string); + } + assertType('string', $string); + + if (ctype_digit((int) $string)) { + assertType('numeric-string', $string); + } else { + assertType('string', $string); + } + assertType('string', $string); + } + +}