From 7509d22454518a406202b01a2149d25d98f48d7c Mon Sep 17 00:00:00 2001 From: Jakub Vrana Date: Thu, 11 Sep 2025 11:19:00 +0200 Subject: [PATCH] Improve idate() return types --- .../Php/IdateFunctionReturnTypeExtension.php | 42 +++++++++++ .../Php/IdateFunctionReturnTypeHelper.php | 73 +++++++++++++++++++ tests/PHPStan/Analyser/nsrt/idate.php | 25 +++++++ 3 files changed, 140 insertions(+) create mode 100644 src/Type/Php/IdateFunctionReturnTypeExtension.php create mode 100644 src/Type/Php/IdateFunctionReturnTypeHelper.php create mode 100644 tests/PHPStan/Analyser/nsrt/idate.php diff --git a/src/Type/Php/IdateFunctionReturnTypeExtension.php b/src/Type/Php/IdateFunctionReturnTypeExtension.php new file mode 100644 index 0000000000..456ff2b227 --- /dev/null +++ b/src/Type/Php/IdateFunctionReturnTypeExtension.php @@ -0,0 +1,42 @@ +getName() === 'idate'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope, + ): ?Type + { + $args = $functionCall->getArgs(); + if ($args === []) { + return new ConstantBooleanType(false); + } + + return $this->idateFunctionReturnTypeHelper->getTypeFromFormatType( + $scope->getType($args[0]->value), + ); + } + +} diff --git a/src/Type/Php/IdateFunctionReturnTypeHelper.php b/src/Type/Php/IdateFunctionReturnTypeHelper.php new file mode 100644 index 0000000000..00541b75b5 --- /dev/null +++ b/src/Type/Php/IdateFunctionReturnTypeHelper.php @@ -0,0 +1,73 @@ +getConstantStrings() as $formatString) { + $types[] = $this->buildReturnTypeFromFormat($formatString->getValue()); + } + + if ($types === []) { + return null; + } + + return TypeCombinator::union(...$types); + } + + public function buildReturnTypeFromFormat(string $formatString): Type + { + // see https://www.php.net/idate + switch ($formatString) { + case 'd': + return IntegerRangeType::fromInterval(1, 31); + case 'h': + return IntegerRangeType::fromInterval(1, 12); + case 'H': + return IntegerRangeType::fromInterval(0, 23); + case 'i': + return IntegerRangeType::fromInterval(0, 59); + case 'I': + return IntegerRangeType::fromInterval(0, 1); + case 'L': + return IntegerRangeType::fromInterval(0, 1); + case 'm': + return IntegerRangeType::fromInterval(1, 12); + case 'N': + return IntegerRangeType::fromInterval(1, 7); + case 's': + return IntegerRangeType::fromInterval(0, 59); + case 't': + return IntegerRangeType::fromInterval(28, 31); + case 'w': + return IntegerRangeType::fromInterval(0, 6); + case 'W': + return IntegerRangeType::fromInterval(1, 53); + case 'y': + return IntegerRangeType::fromInterval(0, 99); + case 'z': + return IntegerRangeType::fromInterval(0, 365); + case 'B': + case 'o': + case 'U': + case 'Y': + case 'Z': + return new IntegerType(); + default: + return new ConstantBooleanType(false); + } + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/idate.php b/tests/PHPStan/Analyser/nsrt/idate.php new file mode 100644 index 0000000000..74b3e363b1 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/idate.php @@ -0,0 +1,25 @@ +', idate('N')); + assertType('int', idate('Y')); + assertType('false', idate('wrong')); + assertType('false', idate('')); + assertType('int|false', idate($string)); + assertType('int<0, 23>', idate($hour)); + assertType('int|false', idate($format)); + } + +}