Skip to content

Commit

Permalink
Add DateTimeModifyReturnTypeExtension
Browse files Browse the repository at this point in the history
  • Loading branch information
VincentLanglet committed May 4, 2022
1 parent e40474b commit a8a37ed
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 2 deletions.
14 changes: 14 additions & 0 deletions conf/config.neon
Expand Up @@ -1193,6 +1193,20 @@ services:
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension

-
class: PHPStan\Type\Php\DateTimeModifyReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
arguments:
dateTimeClass: DateTime

-
class: PHPStan\Type\Php\DateTimeModifyReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
arguments:
dateTimeClass: DateTimeImmutable

-
class: PHPStan\Type\Php\DateTimeConstructorThrowTypeExtension
tags:
Expand Down
4 changes: 2 additions & 2 deletions resources/functionMap.php
Expand Up @@ -1604,7 +1604,7 @@
'DateTime::getOffset' => ['int'],
'DateTime::getTimestamp' => ['int'],
'DateTime::getTimezone' => ['DateTimeZone'],
'DateTime::modify' => ['static', 'modify'=>'string'],
'DateTime::modify' => ['static|false', 'modify'=>'string'],
'DateTime::setDate' => ['static', 'year'=>'int', 'month'=>'int', 'day'=>'int'],
'DateTime::setISODate' => ['static', 'year'=>'int', 'week'=>'int', 'day='=>'int'],
'DateTime::setTime' => ['static', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'],
Expand All @@ -1623,7 +1623,7 @@
'DateTimeImmutable::getOffset' => ['int'],
'DateTimeImmutable::getTimestamp' => ['int'],
'DateTimeImmutable::getTimezone' => ['DateTimeZone'],
'DateTimeImmutable::modify' => ['static', 'modify'=>'string'],
'DateTimeImmutable::modify' => ['static|false', 'modify'=>'string'],
'DateTimeImmutable::setDate' => ['static', 'year'=>'int', 'month'=>'int', 'day'=>'int'],
'DateTimeImmutable::setISODate' => ['static', 'year'=>'int', 'week'=>'int', 'day='=>'int'],
'DateTimeImmutable::setTime' => ['static', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'],
Expand Down
79 changes: 79 additions & 0 deletions src/Type/Php/DateTimeModifyReturnTypeExtension.php
@@ -0,0 +1,79 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use DateTime;
use DateTimeInterface;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\NeverType;
use PHPStan\Type\StaticType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
use function count;

class DateTimeModifyReturnTypeExtension implements DynamicMethodReturnTypeExtension
{

/** @var class-string<DateTimeInterface> */
private $dateTimeClass;

/** @param class-string<DateTimeInterface> $dateTimeClass */
public function __construct(string $dateTimeClass = DateTime::class)
{
$this->dateTimeClass = $dateTimeClass;
}

public function getClass(): string
{
return $this->dateTimeClass;
}

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

public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
$defaultReturnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
if (count($methodCall->getArgs()) < 1) {
return $defaultReturnType;
}

$valueType = $scope->getType($methodCall->getArgs()[0]->value);
$constantStrings = TypeUtils::getConstantStrings($valueType);

$hasFalse = false;
$hasDateTime = false;

foreach ($constantStrings as $constantString) {
if (@(new DateTime())->modify($constantString->getValue()) === false) {
$hasFalse = true;
} else {
$hasDateTime = true;
}

$valueType = TypeCombinator::remove($valueType, $constantString);
}

if (!$valueType instanceof NeverType) {
return $defaultReturnType;
}

if ($hasFalse && !$hasDateTime) {
return new ConstantBooleanType(false);
}
if ($hasDateTime && !$hasFalse) {
return new StaticType($methodReflection->getDeclaringClass());
}

return $defaultReturnType;
}

}
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -435,6 +435,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2906.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/DateTimeDynamicReturnTypes.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/DateTimeModifyReturnTypes.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4821.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4838.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4879.php');
Expand Down
39 changes: 39 additions & 0 deletions tests/PHPStan/Analyser/data/DateTimeModifyReturnTypes.php
@@ -0,0 +1,39 @@
<?php declare(strict_types = 1);

namespace DateTimeModifyReturnTypes;

use DateTime;
use DateTimeImmutable;

class Foo
{
public function modify(DateTime $datetime, DateTimeImmutable $dateTimeImmutable, string $modify): void {
assertType('DateTime|false', $datetime->modify($modify));
assertType('DateTimeImmutable|false', $dateTimeImmutable->modify($modify));
}

/**
* @param '+1 day'|'+2 day' $modify
*/
public function modifyWithValidConstant(DateTime $datetime, DateTimeImmutable $dateTimeImmutable, string $modify): void {
assertType('DateTime', $datetime->modify($modify));
assertType('DateTimeImmutable', $dateTimeImmutable->modify($modify));
}

/**
* @param 'kewk'|'koko' $modify
*/
public function modifyWithInvalidConstant(DateTime $datetime, DateTimeImmutable $dateTimeImmutable, string $modify): void {
assertType('false', $datetime->modify($modify));
assertType('false', $dateTimeImmutable->modify($modify));
}

/**
* @param '+1 day'|'koko' $modify
*/
public function modifyWithBothConstant(DateTime $datetime, DateTimeImmutable $dateTimeImmutable, string $modify): void {
assertType('DateTime|false', $datetime->modify($modify));
assertType('DateTimeImmutable|false', $dateTimeImmutable->modify($modify));
}

}

0 comments on commit a8a37ed

Please sign in to comment.