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 fede695

Browse files
Add DateIntervalFormatDynamicReturnTypeExtension
1 parent b7b57c1 commit fede695

File tree

3 files changed

+141
-0
lines changed

3 files changed

+141
-0
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use DateInterval;
6+
use PhpParser\Node\Expr\MethodCall;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\DependencyInjection\AutowiredService;
9+
use PHPStan\Reflection\MethodReflection;
10+
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
11+
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
12+
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
13+
use PHPStan\Type\Accessory\AccessoryNumericStringType;
14+
use PHPStan\Type\Accessory\AccessoryUppercaseStringType;
15+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
16+
use PHPStan\Type\IntersectionType;
17+
use PHPStan\Type\StringType;
18+
use PHPStan\Type\Type;
19+
use PHPStan\Type\TypeCombinator;
20+
use function count;
21+
use function is_numeric;
22+
use function strtolower;
23+
use function strtoupper;
24+
25+
#[AutowiredService]
26+
final class DateIntervalFormatDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
27+
{
28+
29+
public function getClass(): string
30+
{
31+
return DateInterval::class;
32+
}
33+
34+
public function isMethodSupported(MethodReflection $methodReflection): bool
35+
{
36+
return $methodReflection->getName() === 'format';
37+
}
38+
39+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
40+
{
41+
$arguments = $methodCall->getArgs();
42+
43+
if (!isset($arguments[0])) {
44+
return null;
45+
}
46+
47+
$arg = $scope->getType($arguments[0]->value);
48+
49+
$constantStrings = $arg->getConstantStrings();
50+
if (count($constantStrings) === 0) {
51+
if ($arg->isNonEmptyString()->yes()) {
52+
return new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]);
53+
}
54+
55+
return null;
56+
}
57+
58+
// The worst case scenario for the non-falsy-string check is that every number is 0.
59+
$dateInterval = new DateInterval('P0D');
60+
61+
$possibleReturnTypes = [];
62+
foreach ($constantStrings as $string) {
63+
$value = $dateInterval->format($string->getValue());
64+
65+
$accessories = [];
66+
if (is_numeric($value)) {
67+
$accessories[] = new AccessoryNumericStringType();
68+
}
69+
if ($value !== '0' && $value !== '') {
70+
$accessories[] = new AccessoryNonFalsyStringType();
71+
} elseif ($value !== '') {
72+
$accessories[] = new AccessoryNonEmptyStringType();
73+
}
74+
if (strtolower($value) === $value) {
75+
$accessories[] = new AccessoryLowercaseStringType();
76+
}
77+
if (strtoupper($value) === $value) {
78+
$accessories[] = new AccessoryUppercaseStringType();
79+
}
80+
81+
if (count($accessories) === 0) {
82+
return null;
83+
}
84+
85+
$possibleReturnTypes[] = new IntersectionType([new StringType(), ...$accessories]);
86+
}
87+
88+
return TypeCombinator::union(...$possibleReturnTypes);
89+
}
90+
91+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug1452;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
$dateInterval = (new \DateTimeImmutable('now -60 minutes'))->diff(new \DateTimeImmutable('now'));
8+
9+
// Could be lowercase-string&non-falsy-string&numeric-string&uppercase-string
10+
assertType('lowercase-string&non-falsy-string', $dateInterval->format('%a'));
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace DateIntervalFormat;
4+
5+
use DateInterval;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
class Foo
10+
{
11+
/**
12+
* @param string $string
13+
* @param non-empty-string $nonEmptyString
14+
* @param '%Y'|'%D' $unionString1
15+
* @param '%Y'|'%y' $unionString2
16+
*
17+
* @return void
18+
*/
19+
public function test(
20+
DateInterval $dateInterval,
21+
string $string,
22+
string $nonEmptyString,
23+
string $unionString1,
24+
string $unionString2,
25+
): void {
26+
assertType('string', $dateInterval->format($string));
27+
assertType('non-empty-string', $dateInterval->format($nonEmptyString));
28+
29+
assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $dateInterval->format('%Y')); // '00'
30+
assertType('lowercase-string&non-empty-string&numeric-string&uppercase-string', $dateInterval->format('%y')); // '0'
31+
assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $dateInterval->format($unionString1));
32+
assertType('lowercase-string&non-empty-string&numeric-string&uppercase-string', $dateInterval->format($unionString2));
33+
34+
assertType('non-falsy-string&uppercase-string', $dateInterval->format('%Y DAYS'));
35+
assertType('non-falsy-string&uppercase-string', $dateInterval->format($unionString1. ' DAYS'));
36+
37+
assertType('lowercase-string&non-falsy-string', $dateInterval->format('%Y days'));
38+
assertType('lowercase-string&non-falsy-string', $dateInterval->format($unionString1. ' days'));
39+
}
40+
}

0 commit comments

Comments
(0)

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