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 75008af

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 - Exceptional case's type should form a Union with others
1 parent 6e498e6 commit 75008af

File tree

2 files changed

+39
-7
lines changed

2 files changed

+39
-7
lines changed

‎src/Type/Php/DateIntervalFormatDynamicReturnTypeExtension.php‎

Lines changed: 35 additions & 5 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,11 +13,13 @@
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;
1820
use PHPStan\Type\Type;
1921
use PHPStan\Type\TypeCombinator;
22+
use PHPStan\Type\UnionType;
2023
use function count;
2124
use function is_numeric;
2225
use function strtolower;
@@ -55,12 +58,12 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
5558
return null;
5659
}
5760

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

6163
$possibleReturnTypes = [];
6264
foreach ($constantStrings as $string) {
63-
$value = $dateInterval->format($string->getValue());
65+
$formatString = $string->getValue();
66+
$value = $dateInterval->format($formatString);
6467

6568
$accessories = [];
6669
if (is_numeric($value)) {
@@ -77,15 +80,42 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
7780
if (strtoupper($value) === $value) {
7881
$accessories[] = new AccessoryUppercaseStringType();
7982
}
83+
$diffInDaysType = $this->diffInDaysTypes($formatString);
8084

81-
if (count($accessories) === 0) {
85+
if (count($accessories) === 0 && $diffInDaysType === null) {
8286
return null;
8387
}
8488

85-
$possibleReturnTypes[] = new IntersectionType([new StringType(), ...$accessories]);
89+
$intersectionType = new IntersectionType([
90+
new StringType(),
91+
...$accessories,
92+
]);
93+
$possibleReturnTypes[] = $diffInDaysType === null
94+
? $intersectionType
95+
: new UnionType([$diffInDaysType, $intersectionType]);
8696
}
8797

8898
return TypeCombinator::union(...$possibleReturnTypes);
8999
}
90100

101+
/**
102+
* The worst case scenario for the non-falsy-string check is that every number is 0.
103+
* We create an interval from a difference of two DateTime instances due to the different behavior for %a
104+
*
105+
* @see https://www.php.net/manual/en/dateinterval.format.php
106+
*
107+
* @return DateInterval
108+
*/
109+
private function referenceDateInterval(): DateInterval
110+
{
111+
return (new DateTimeImmutable('@0'))->diff((new DateTimeImmutable('@0')));
112+
}
113+
114+
private function diffInDaysTypes(string $formatString): ?ConstantStringType
115+
{
116+
return $formatString === '%a'
117+
? new ConstantStringType('(unknown)')
118+
: null;
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 によって変換されたページ (->オリジナル) /