Skip to content

Commit

Permalink
Date format return type extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 28, 2022
1 parent 6f9749a commit b49df58
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 10 deletions.
10 changes: 10 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,16 @@ services:
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension

-
class: PHPStan\Type\Php\DateFormatFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension

-
class: PHPStan\Type\Php\DateFormatMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

-
class: PHPStan\Type\Php\DateFunctionReturnTypeExtension
tags:
Expand Down
39 changes: 39 additions & 0 deletions src/Type/Php/DateFormatFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use function count;

class DateFormatFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
{

public function isFunctionSupported(FunctionReflection $functionReflection): bool
{
return $functionReflection->getName() === 'date_format';
}

public function getTypeFromFunctionCall(
FunctionReflection $functionReflection,
FuncCall $functionCall,
Scope $scope,
): Type
{
if (count($functionCall->getArgs()) < 2) {
return new StringType();
}

return $scope->getType(
new FuncCall(new FullyQualified('date'), [
$functionCall->getArgs()[1],
]),
);
}

}
42 changes: 42 additions & 0 deletions src/Type/Php/DateFormatMethodReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use DateTimeInterface;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use function count;

class DateFormatMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension
{

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

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

public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
if (count($methodCall->getArgs()) === 0) {
return new StringType();
}

return $scope->getType(
new FuncCall(new FullyQualified('date'), [
$methodCall->getArgs()[0],
]),
);
}

}
34 changes: 25 additions & 9 deletions src/Type/Php/DateFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\Accessory\AccessoryNumericStringType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\ConstantTypeHelper;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\UnionType;
use function count;
use function date;
use function is_numeric;
use function sprintf;

class DateFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
Expand Down Expand Up @@ -76,17 +78,31 @@ public function getTypeFromFunctionCall(
}
}

$types = [];
foreach ($constantStrings as $constantString) {
$formattedDate = date($constantString->getValue());
if (!is_numeric($formattedDate)) {
return new StringType();
}
$types[] = ConstantTypeHelper::getTypeFromValue(date($constantString->getValue()));
}

$type = TypeCombinator::union(...$types);
if ($type->isNumericString()->yes()) {
return new IntersectionType([
new StringType(),
new AccessoryNumericStringType(),
]);
}

if ($type->isNonEmptyString()->yes()) {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}

if ($type->isNonEmptyString()->no()) {
return new ConstantStringType('');
}

return new IntersectionType([
new StringType(),
new AccessoryNumericStringType(),
]);
return new StringType();
}

private function buildNumericRangeType(int $min, int $max, bool $zeroPad): Type
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,7 @@ public function dataFileAsserts(): iterable
}

yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6698.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/date-format.php');
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-2899.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Foo
public function doFoo(string $s, $mixed)
{
assertType('numeric-string', date('Y'));
assertType('string', date('Y.m.d'));
assertType('non-empty-string', date('Y.m.d'));
assertType('string', date($s));
assertType('string', date($mixed));
}
Expand Down
45 changes: 45 additions & 0 deletions tests/PHPStan/Analyser/data/date-format.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace DateFormatReturnType;

use function PHPStan\Testing\assertType;

function (string $s): void {
assertType('\'\'', date(''));
assertType('string', date($s));
assertType('non-empty-string', date('D'));
assertType('numeric-string', date('Y'));
assertType('numeric-string', date('Ghi'));
};

function (\DateTime $dt, string $s): void {
assertType('\'\'', date_format($dt, ''));
assertType('string', date_format($dt, $s));
assertType('non-empty-string', date_format($dt, 'D'));
assertType('numeric-string', date_format($dt, 'Y'));
assertType('numeric-string', date_format($dt, 'Ghi'));
};

function (\DateTimeInterface $dt, string $s): void {
assertType('\'\'', $dt->format(''));
assertType('string', $dt->format($s));
assertType('non-empty-string', $dt->format('D'));
assertType('numeric-string', $dt->format('Y'));
assertType('numeric-string', $dt->format('Ghi'));
};

function (\DateTime $dt, string $s): void {
assertType('\'\'', $dt->format(''));
assertType('string', $dt->format($s));
assertType('non-empty-string', $dt->format('D'));
assertType('numeric-string', $dt->format('Y'));
assertType('numeric-string', $dt->format('Ghi'));
};

function (\DateTimeImmutable $dt, string $s): void {
assertType('\'\'', $dt->format(''));
assertType('string', $dt->format($s));
assertType('non-empty-string', $dt->format('D'));
assertType('numeric-string', $dt->format('Y'));
assertType('numeric-string', $dt->format('Ghi'));
};

0 comments on commit b49df58

Please sign in to comment.