33namespace PHPStan \Type \Php ;
44
55use DateInterval ;
6+ use DateTimeImmutable ;
67use PhpParser \Node \Expr \MethodCall ;
78use PHPStan \Analyser \Scope ;
89use PHPStan \DependencyInjection \AutowiredService ;
1213use PHPStan \Type \Accessory \AccessoryNonFalsyStringType ;
1314use PHPStan \Type \Accessory \AccessoryNumericStringType ;
1415use PHPStan \Type \Accessory \AccessoryUppercaseStringType ;
16+ use PHPStan \Type \Constant \ConstantStringType ;
1517use PHPStan \Type \DynamicMethodReturnTypeExtension ;
1618use PHPStan \Type \IntersectionType ;
1719use 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}
0 commit comments