Skip to content

Commit 7375f6c

Browse files
Static analyse if date_create_from_format will fail or not
1 parent 551af77 commit 7375f6c

File tree

4 files changed

+94
-0
lines changed

4 files changed

+94
-0
lines changed

conf/config.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,11 @@ services:
962962
tags:
963963
- phpstan.broker.dynamicFunctionReturnTypeExtension
964964

965+
-
966+
class: PHPStan\Type\Php\DateTimeDynamicReturnTypeExtension
967+
tags:
968+
- phpstan.broker.dynamicFunctionReturnTypeExtension
969+
965970
-
966971
class: PHPStan\Type\Php\DsMapDynamicReturnTypeExtension
967972
tags:
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use DateTime;
6+
use DateTimeImmutable;
7+
use PhpParser\Node\Expr\FuncCall;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Reflection\FunctionReflection;
10+
use PHPStan\Reflection\ParametersAcceptorSelector;
11+
use PHPStan\Type\Constant\ConstantBooleanType;
12+
use PHPStan\Type\Constant\ConstantStringType;
13+
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
14+
use PHPStan\Type\ObjectType;
15+
use PHPStan\Type\Type;
16+
17+
class DateTimeDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
18+
{
19+
20+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
21+
{
22+
return in_array($functionReflection->getName(), ['date_create_from_format', 'date_create_immutable_from_format'], true);
23+
}
24+
25+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
26+
{
27+
$defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
28+
29+
if (count($functionCall->args) < 2) {
30+
return $defaultReturnType;
31+
}
32+
33+
$format = $scope->getType($functionCall->args[0]->value);
34+
$datetime = $scope->getType($functionCall->args[1]->value);
35+
36+
if (!$format instanceof ConstantStringType || !$datetime instanceof ConstantStringType) {
37+
return $defaultReturnType;
38+
}
39+
40+
$isValid = (DateTime::createFromFormat($format->getValue(), $datetime->getValue()) !== false);
41+
42+
$className = $functionReflection->getName() === 'date_create_from_format' ? DateTime::class : DateTimeImmutable::class;
43+
return $isValid ? new ObjectType($className) : new ConstantBooleanType(false);
44+
}
45+
46+
}

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,8 @@ public function dataFileAsserts(): iterable
381381
yield from $this->gatherAssertTypes(__DIR__ . '/data/type-aliases.php');
382382
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4650.php');
383383
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2906.php');
384+
385+
yield from $this->gatherAssertTypes(__DIR__ . '/data/DateTimeDynamicReturnTypes.php');
384386
}
385387

386388
/**
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace DateTimeDynamicReturnTypes;
4+
5+
use DateTime;
6+
use DateTimeImmutable;
7+
use function date_create_from_format;
8+
use function PHPStan\Testing\assertType;
9+
10+
class Foo
11+
{
12+
13+
public function createDynamic(string $format, string $datetime): void {
14+
assertType('DateTime|false', date_create_from_format($format, $datetime));
15+
assertType('DateTimeImmutable|false', date_create_immutable_from_format($format, $datetime));
16+
}
17+
18+
public function staticInvalidFormat(): void {
19+
assertType('false', date_create_from_format('Foobar', '2022-02-20'));
20+
assertType('false', date_create_immutable_from_format('Foobar', '2022-02-20'));
21+
}
22+
23+
public function staticInvalidDatetime(): void {
24+
assertType('false', date_create_from_format('Y-m-d', '2022/02/20'));
25+
assertType('false', date_create_immutable_from_format('Y-m-d', '2022/02/20'));
26+
}
27+
28+
public function staticValidStrings(): void {
29+
assertType('DateTime', date_create_from_format('Y-m-d', '2020-10-12'));
30+
assertType('DateTimeImmutable', date_create_immutable_from_format('Y-m-d', '2020-10-12'));
31+
}
32+
33+
public function localVariables(): void {
34+
$format = 'Y-m-d';
35+
$datetime = '2020-10-12';
36+
37+
assertType('DateTime', date_create_from_format($format, $datetime));
38+
assertType('DateTimeImmutable', date_create_immutable_from_format($format, $datetime));
39+
}
40+
41+
}

0 commit comments

Comments
 (0)