Skip to content

Commit fc35414

Browse files
vranaondrejmirtes
authored andcommitted
Improve idate() return types
1 parent 330ca96 commit fc35414

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-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: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
switch ($formatString) {
34+
case 'd':
35+
return IntegerRangeType::fromInterval(1, 31);
36+
case 'h':
37+
return IntegerRangeType::fromInterval(1, 12);
38+
case 'H':
39+
return IntegerRangeType::fromInterval(0, 23);
40+
case 'i':
41+
return IntegerRangeType::fromInterval(0, 59);
42+
case 'I':
43+
return IntegerRangeType::fromInterval(0, 1);
44+
case 'L':
45+
return IntegerRangeType::fromInterval(0, 1);
46+
case 'm':
47+
return IntegerRangeType::fromInterval(1, 12);
48+
case 'N':
49+
return IntegerRangeType::fromInterval(1, 7);
50+
case 's':
51+
return IntegerRangeType::fromInterval(0, 59);
52+
case 't':
53+
return IntegerRangeType::fromInterval(28, 31);
54+
case 'w':
55+
return IntegerRangeType::fromInterval(0, 6);
56+
case 'W':
57+
return IntegerRangeType::fromInterval(1, 53);
58+
case 'y':
59+
return IntegerRangeType::fromInterval(0, 99);
60+
case 'z':
61+
return IntegerRangeType::fromInterval(0, 365);
62+
case 'B':
63+
case 'o':
64+
case 'U':
65+
case 'Y':
66+
case 'Z':
67+
return new IntegerType();
68+
default:
69+
return new ConstantBooleanType(false);
70+
}
71+
}
72+
73+
}
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)