Skip to content

Commit eca0078

Browse files
committed
Improve idate() return types
1 parent 6f87293 commit eca0078

File tree

3 files changed

+120
-0
lines changed

3 files changed

+120
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PhpParser\Node\Expr\FuncCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\DependencyInjection\AutowiredService;
8+
use PHPStan\Reflection\FunctionReflection;
9+
use PHPStan\Type\Constant\ConstantBooleanType;
10+
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
11+
use PHPStan\Type\Type;
12+
13+
#[AutowiredService]
14+
final class IdateFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
15+
{
16+
17+
public function __construct(private IdateFunctionReturnTypeHelper $idateFunctionReturnTypeHelper)
18+
{
19+
}
20+
21+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
22+
{
23+
return $functionReflection->getName() === 'idate';
24+
}
25+
26+
public function getTypeFromFunctionCall(
27+
FunctionReflection $functionReflection,
28+
FuncCall $functionCall,
29+
Scope $scope,
30+
): ?Type
31+
{
32+
$args = $functionCall->getArgs();
33+
if ($args === []) {
34+
return new ConstantBooleanType(false);
35+
}
36+
37+
return $this->idateFunctionReturnTypeHelper->getTypeFromFormatType(
38+
$scope->getType($args[0]->value),
39+
);
40+
}
41+
42+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PHPStan\DependencyInjection\AutowiredService;
6+
use PHPStan\Type\Constant\ConstantBooleanType;
7+
use PHPStan\Type\IntegerRangeType;
8+
use PHPStan\Type\IntegerType;
9+
use PHPStan\Type\Type;
10+
use PHPStan\Type\TypeCombinator;
11+
12+
#[AutowiredService]
13+
final class IdateFunctionReturnTypeHelper
14+
{
15+
16+
public function getTypeFromFormatType(Type $formatType): ?Type
17+
{
18+
$types = [];
19+
foreach ($formatType->getConstantStrings() as $formatString) {
20+
$types[] = $this->buildReturnTypeFromFormat($formatString->getValue());
21+
}
22+
23+
if ($types === []) {
24+
return null;
25+
}
26+
27+
return TypeCombinator::union(...$types);
28+
}
29+
30+
public function buildReturnTypeFromFormat(string $formatString): Type
31+
{
32+
// see https://www.php.net/idate
33+
return match ($formatString) {
34+
'd' => IntegerRangeType::fromInterval(1, 31),
35+
'h' => IntegerRangeType::fromInterval(1, 12),
36+
'H' => IntegerRangeType::fromInterval(0, 23),
37+
'i' => IntegerRangeType::fromInterval(0, 59),
38+
'I' => IntegerRangeType::fromInterval(0, 1),
39+
'L' => IntegerRangeType::fromInterval(0, 1),
40+
'm' => IntegerRangeType::fromInterval(1, 12),
41+
'N' => IntegerRangeType::fromInterval(1, 7),
42+
's' => IntegerRangeType::fromInterval(0, 59),
43+
't' => IntegerRangeType::fromInterval(28, 31),
44+
'w' => IntegerRangeType::fromInterval(0, 6),
45+
'W' => IntegerRangeType::fromInterval(1, 53),
46+
'y' => IntegerRangeType::fromInterval(0, 99),
47+
'z' => IntegerRangeType::fromInterval(0, 365),
48+
'B', 'o', 'U', 'Y', 'Z' => new IntegerType(),
49+
default => new ConstantBooleanType(false),
50+
};
51+
}
52+
53+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace PHPStan\Analyser\nsrt;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
/**
11+
* @param 'h'|'H' $hour
12+
* @param 'm'|string $format
13+
*/
14+
public function doFoo(string $string, string $hour, string $format): void
15+
{
16+
assertType('int<1, 7>', idate('N'));
17+
assertType('int', idate('Y'));
18+
assertType('false', idate('wrong'));
19+
assertType('false', idate(''));
20+
assertType('int|false', idate($string));
21+
assertType('int<0, 23>', idate($hour));
22+
assertType('int|false', idate($format));
23+
}
24+
25+
}

0 commit comments

Comments
 (0)