Skip to content

Commit

Permalink
fix returntype for DateIntervall::createFromDateString
Browse files Browse the repository at this point in the history
  • Loading branch information
verfriemelt-dot-org committed Dec 30, 2022
1 parent 8c2be72 commit 97efd4d
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 1 deletion.
5 changes: 5 additions & 0 deletions conf/config.neon
Expand Up @@ -1328,6 +1328,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 @@ -1135,6 +1135,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');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7805.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';
} 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()));
};

0 comments on commit 97efd4d

Please sign in to comment.