Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 8740766

Browse files
Add exceptional case for DateInterval::format return type inference
Difference in days might behave differently when the DateInterval is created from scratch or from a diff. Extend returned type information for DateInterval::format method
1 parent 6e498e6 commit 8740766

File tree

2 files changed

+38
-6
lines changed

2 files changed

+38
-6
lines changed

‎src/Type/Php/DateIntervalFormatDynamicReturnTypeExtension.php‎

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Type\Php;
44

55
use DateInterval;
6+
use DateTimeImmutable;
67
use PhpParser\Node\Expr\MethodCall;
78
use PHPStan\Analyser\Scope;
89
use PHPStan\DependencyInjection\AutowiredService;
@@ -12,6 +13,7 @@
1213
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
1314
use PHPStan\Type\Accessory\AccessoryNumericStringType;
1415
use PHPStan\Type\Accessory\AccessoryUppercaseStringType;
16+
use PHPStan\Type\Constant\ConstantStringType;
1517
use PHPStan\Type\DynamicMethodReturnTypeExtension;
1618
use PHPStan\Type\IntersectionType;
1719
use PHPStan\Type\StringType;
@@ -55,12 +57,12 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
5557
return null;
5658
}
5759

58-
// The worst case scenario for the non-falsy-string check is that every number is 0.
59-
$dateInterval = new DateInterval('P0D');
60+
$dateInterval = $this->referenceDateInterval();
6061

6162
$possibleReturnTypes = [];
6263
foreach ($constantStrings as $string) {
63-
$value = $dateInterval->format($string->getValue());
64+
$formatString = $string->getValue();
65+
$value = $dateInterval->format($formatString);
6466

6567
$accessories = [];
6668
if (is_numeric($value)) {
@@ -82,10 +84,38 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
8284
return null;
8385
}
8486

85-
$possibleReturnTypes[] = new IntersectionType([new StringType(), ...$accessories]);
87+
$possibleReturnTypes[] = new IntersectionType([
88+
new StringType(),
89+
...$this->diffInDaysTypes($formatString),
90+
...$accessories,
91+
]);
8692
}
8793

8894
return TypeCombinator::union(...$possibleReturnTypes);
8995
}
9096

97+
/**
98+
* The worst case scenario for the non-falsy-string check is that every number is 0.
99+
* We create an interval from a difference of two DateTime instances due to the different behavior for %a
100+
*
101+
* @see https://www.php.net/manual/en/dateinterval.format.php
102+
*
103+
* @return DateInterval
104+
*/
105+
private function referenceDateInterval(): DateInterval
106+
{
107+
return (new DateTimeImmutable('@0'))->diff((new DateTimeImmutable('@0')));
108+
}
109+
110+
/**
111+
* @return array<ConstantStringType>
112+
*/
113+
private function diffInDaysTypes(string $formatString): array
114+
{
115+
if ($formatString === '%a') {
116+
return [new ConstantStringType('(unknown)')];
117+
}
118+
return [];
119+
}
120+
91121
}

‎tests/PHPStan/Analyser/nsrt/bug-1452.php‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@
66

77
$dateInterval = (new \DateTimeImmutable('now -60 minutes'))->diff(new \DateTimeImmutable('now'));
88

9-
// Could be lowercase-string&non-falsy-string&numeric-string&uppercase-string
10-
assertType('lowercase-string&non-falsy-string', $dateInterval->format('%a'));
9+
assertType(
10+
"'(unknown)'&lowercase-string&non-empty-string&numeric-string&uppercase-string",
11+
$dateInterval->format('%a')
12+
);

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /