Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix returntype for DateIntervall::createFromDateString #2038

Merged
Merged
5 changes: 5 additions & 0 deletions conf/config.neon
Expand Up @@ -1324,6 +1324,11 @@ services:
tags:
- phpstan.dynamicStaticMethodThrowTypeExtension

-
class: PHPStan\Type\Php\DateIntervalDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension

-
class: PHPStan\Type\Php\DateTimeCreateDynamicReturnTypeExtension
tags:
Expand Down
2 changes: 1 addition & 1 deletion resources/functionMap.php
Expand Up @@ -1583,7 +1583,7 @@
'DateInterval::__construct' => ['void', 'spec'=>'string'],
'DateInterval::__set_state' => ['DateInterval', 'array'=>'array'],
'DateInterval::__wakeup' => ['void'],
'DateInterval::createFromDateString' => ['DateInterval', 'time'=>'string'],
'DateInterval::createFromDateString' => ['DateInterval|false', 'time'=>'string'],
'DateInterval::format' => ['string', 'format'=>'string'],
'DatePeriod::__construct' => ['void', 'start'=>'DateTimeInterface', 'interval'=>'DateInterval', 'recur'=>'int', 'options='=>'int'],
'DatePeriod::__construct\'1' => ['void', 'start'=>'DateTimeInterface', 'interval'=>'DateInterval', 'end'=>'DateTimeInterface', 'options='=>'int'],
Expand Down
61 changes: 61 additions & 0 deletions src/Type/Php/DateIntervalDynamicReturnTypeExtension.php
@@ -0,0 +1,61 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use DateInterval;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use function count;
use function in_array;

class DateIntervalDynamicReturnTypeExtension
implements DynamicStaticMethodReturnTypeExtension
{

public function getClass(): string
{
return DateInterval::class;
}

public function isStaticMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'createFromDateString';
}

public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type
{
$arguments = $methodCall->getArgs();

if (!isset($arguments[0])) {
return null;
}

$strings = $scope->getType($arguments[0]->value)->getConstantStrings();

$possibleReturnTypes = [];
foreach ($strings as $string) {
$possibleReturnTypes[] = @DateInterval::createFromDateString($string->getValue()) instanceof DateInterval ? DateInterval::class : false;
}

// the error case, when wrong types are passed
if (count($possibleReturnTypes) === 0) {
return null;
}

if (in_array(false, $possibleReturnTypes, true) && in_array(DateInterval::class, $possibleReturnTypes, true)) {
return null;
}

if (in_array(false, $possibleReturnTypes, true)) {
return new ConstantBooleanType(false);
}

return new ObjectType(DateInterval::class);
}

}
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -1136,6 +1136,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/imagick-pixel.php');
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-8467a.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8467b.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8442.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/PDOStatement.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/discussion-8447.php');
}
Expand Down
41 changes: 41 additions & 0 deletions tests/PHPStan/Analyser/data/bug-8442.php
@@ -0,0 +1,41 @@
<?php declare(strict_types = 1);

namespace Bug8442;

use stdClass;
use function PHPStan\Testing\assertType;
use DateInterval;

function () {
assertType('false', DateInterval::createFromDateString('foo'));
assertType('DateInterval', DateInterval::createFromDateString('1 Day'));

if (rand(0,1)) {
$interval = '1 day';
} else {
$interval = '2 day';
}

assertType('DateInterval', DateInterval::createFromDateString($interval));

if (rand(0,1)) {
$interval = 'foo';
verfriemelt-dot-org marked this conversation as resolved.
Show resolved Hide resolved
} else {
$interval = '2 day';
}

assertType('DateInterval|false', DateInterval::createFromDateString($interval));

if (rand(0,1)) {
$interval = 'foo';
} else {
$interval = 'foo';
}

assertType('false', DateInterval::createFromDateString($interval));

assertType('DateInterval|false',DateInterval::createFromDateString(str_shuffle('1 day')));
assertType('DateInterval|false',DateInterval::createFromDateString());
assertType('DateInterval|false',DateInterval::createFromDateString(new stdClass()));
};