Skip to content

Commit

Permalink
Add DatePeriod dynamic return type
Browse files Browse the repository at this point in the history
  • Loading branch information
Alban-io committed Jan 30, 2022
1 parent da80c29 commit 855a4ab
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 2 deletions.
7 changes: 6 additions & 1 deletion conf/config.neon
Expand Up @@ -22,7 +22,8 @@ parameters:
featureToggles:
bleedingEdge: false
disableRuntimeReflectionProvider: false
skipCheckGenericClasses: []
skipCheckGenericClasses:
- DatePeriod
fileExtensions:
- php
checkAdvancedIsset: false
Expand Down Expand Up @@ -1528,6 +1529,10 @@ services:
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

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

exceptionTypeResolver:
class: PHPStan\Rules\Exceptions\ExceptionTypeResolver
Expand Down
61 changes: 61 additions & 0 deletions src/Type/Php/DatePeriodConstructorReturnTypeExtension.php
@@ -0,0 +1,61 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use DatePeriod;
use DateTimeInterface;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;

class DatePeriodConstructorReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
{

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

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

public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type
{
$thirdArgType = null;
if (isset($methodCall->getArgs()[2])) {
$thirdArgType = $scope->getType($methodCall->getArgs()[2]->value);
}

if (!$thirdArgType instanceof Type) {
return new GenericObjectType(DatePeriod::class, [
new NullType(),
new IntegerType(),
]);
}

if ((new ObjectType(DateTimeInterface::class))->isSuperTypeOf($thirdArgType)->yes()) {
return new GenericObjectType(DatePeriod::class, [
new ObjectType(DateTimeInterface::class),
new NullType(),
]);
}

if ((new IntegerType())->isSuperTypeOf($thirdArgType)->yes()) {
return new GenericObjectType(DatePeriod::class, [
new NullType(),
new IntegerType(),
]);
}

return new ObjectType(DatePeriod::class);
}

}
24 changes: 24 additions & 0 deletions stubs/date.stub
Expand Up @@ -9,3 +9,27 @@ class DateInterval
public $days;

}

/**
* @template TEnd of ?DateTimeInterface
* @template TRecurrences of ?int
*/
class DatePeriod
{

/**
* @return TEnd
*/
public function getEndDate()
{

}

/**
* @return TRecurrences
*/
public function getRecurrences()
{

}
}
5 changes: 4 additions & 1 deletion tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -641,11 +641,14 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5698-php7.php');
}

if (PHP_VERSION_ID >= 70304) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/date-period-return-types.php');
}

yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6404.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6399.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4357.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5817.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/array-column.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/template-null-bound.php');
}
Expand Down
35 changes: 35 additions & 0 deletions tests/PHPStan/Analyser/data/date-period-return-types.php
@@ -0,0 +1,35 @@
<?php declare(strict_types=1);

use function PHPStan\Testing\assertType;

$start = new DateTime('2012-07-01');
$interval = new DateInterval('P7D');
$end = new DateTime('2012-07-31');
$recurrences = 4;
$iso = 'R4/2012-07-01T00:00:00Z/P7D';

$datePeriodList = [];

$datePeriod = new DatePeriod($start, $interval, $end);
assertType(\DatePeriod::class . '<DateTimeInterface, null>', $datePeriod);
assertType(\DateTimeInterface::class, $datePeriod->getEndDate());
assertType('null', $datePeriod->getRecurrences());
$datePeriodList[] = $datePeriod;

$datePeriod = new DatePeriod($start, $interval, $recurrences);
assertType(\DatePeriod::class . '<null, int>', $datePeriod);
assertType('null', $datePeriod->getEndDate());
assertType('int', $datePeriod->getRecurrences());
$datePeriodList[] = $datePeriod;

$datePeriod = new DatePeriod($iso);
assertType(\DatePeriod::class . '<null, int>', $datePeriod);
assertType('null', $datePeriod->getEndDate());
assertType('int', $datePeriod->getRecurrences());
$datePeriodList[] = $datePeriod;

/** @var DatePeriod $datePeriod */
$datePeriod = $datePeriodList[random_int(0, 2)];
assertType(\DatePeriod::class, $datePeriod);
assertType(\DateTimeInterface::class . '|null', $datePeriod->getEndDate());
assertType('int|null', $datePeriod->getRecurrences());

0 comments on commit 855a4ab

Please sign in to comment.