Skip to content

Commit

Permalink
Merge pull request #8462 from VincentLanglet/dateTimeModify
Browse files Browse the repository at this point in the history
Add dateTimeModify return type provider
  • Loading branch information
orklah committed Sep 14, 2022
2 parents be3a88d + fec5c8a commit 9ed9c4b
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/Psalm/Internal/Provider/MethodReturnTypeProvider.php
Expand Up @@ -7,6 +7,7 @@
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\Provider\ReturnTypeProvider\ClosureFromCallableReturnTypeProvider;
use Psalm\Internal\Provider\ReturnTypeProvider\DateTimeModifyReturnTypeProvider;
use Psalm\Internal\Provider\ReturnTypeProvider\DomNodeAppendChild;
use Psalm\Internal\Provider\ReturnTypeProvider\ImagickPixelColorReturnTypeProvider;
use Psalm\Internal\Provider\ReturnTypeProvider\PdoStatementReturnTypeProvider;
Expand Down Expand Up @@ -58,6 +59,7 @@ public function __construct()
$this->registerClass(SimpleXmlElementAsXml::class);
$this->registerClass(PdoStatementReturnTypeProvider::class);
$this->registerClass(ClosureFromCallableReturnTypeProvider::class);
$this->registerClass(DateTimeModifyReturnTypeProvider::class);
}

/**
Expand Down
@@ -0,0 +1,61 @@
<?php

namespace Psalm\Internal\Provider\ReturnTypeProvider;

use DateTime;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface;
use Psalm\Type;
use Psalm\Type\Atomic\TLiteralString;
use Psalm\Type\Union;

class DateTimeModifyReturnTypeProvider implements MethodReturnTypeProviderInterface
{
public static function getClassLikeNames(): array
{
return ['DateTime', 'DateTimeImmutable'];
}

public static function getMethodReturnType(MethodReturnTypeProviderEvent $event): ?Union
{
$statements_source = $event->getSource();
$call_args = $event->getCallArgs();
$method_name_lowercase = $event->getMethodNameLowercase();
if (!$statements_source instanceof StatementsAnalyzer
|| $method_name_lowercase !== 'modify'
|| !isset($call_args[0])
) {
return null;
}

$first_arg = $call_args[0]->value;
$first_arg_type = $statements_source->node_data->getType($first_arg);
if (!$first_arg_type) {
return null;
}

$has_date_time = false;
$has_false = false;
foreach ($first_arg_type->getAtomicTypes() as $type_part) {
if (!$type_part instanceof TLiteralString) {
return null;
}

if (@(new DateTime())->modify($type_part->value) === false) {
$has_false = true;
} else {
$has_date_time = true;
}
}

if ($has_false && !$has_date_time) {
return Type::getFalse();
}
if ($has_date_time && !$has_false) {
return Type::parseString($event->getFqClasslikeName());
}

return null;
}
}
96 changes: 96 additions & 0 deletions tests/DateTimeTest.php
@@ -0,0 +1,96 @@
<?php

namespace Psalm\Tests;

use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;

class DateTimeTest extends TestCase
{
use ValidCodeAnalysisTestTrait;

/**
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
*/
public function providerValidCodeParse(): iterable
{
return [
'modify' => [
'<?php
function getString(): string
{
return "";
}
$datetime = new DateTime();
$dateTimeImmutable = new DateTimeImmutable();
$a = $datetime->modify(getString());
$b = $dateTimeImmutable->modify(getString());
',
'assertions' => [
'$a' => 'DateTime|false',
'$b' => 'DateTimeImmutable|false',
],
],
'modifyWithValidConstant' => [
'<?php
/**
* @return "+1 day"|"+2 day"
*/
function getString(): string
{
return "+1 day";
}
$datetime = new DateTime();
$dateTimeImmutable = new DateTimeImmutable();
$a = $datetime->modify(getString());
$b = $dateTimeImmutable->modify(getString());
',
'assertions' => [
'$a' => 'DateTime',
'$b' => 'DateTimeImmutable',
],
],
'modifyWithInvalidConstant' => [
'<?php
/**
* @return "foo"|"bar"
*/
function getString(): string
{
return "foo";
}
$datetime = new DateTime();
$dateTimeImmutable = new DateTimeImmutable();
$a = $datetime->modify(getString());
$b = $dateTimeImmutable->modify(getString());
',
'assertions' => [
'$a' => 'false',
'$b' => 'false',
],
],
'modifyWithBothConstant' => [
'<?php
/**
* @return "+1 day"|"bar"
*/
function getString(): string
{
return "+1 day";
}
$datetime = new DateTime();
$dateTimeImmutable = new DateTimeImmutable();
$a = $datetime->modify(getString());
$b = $dateTimeImmutable->modify(getString());
',
'assertions' => [
'$a' => 'DateTime|false',
'$b' => 'DateTimeImmutable|false',
],
],
];
}
}
2 changes: 1 addition & 1 deletion tests/MethodCallTest.php
Expand Up @@ -272,7 +272,7 @@ final class MyDate extends DateTimeImmutable {}
$b = (new DateTimeImmutable())->modify("+3 hours");',
'assertions' => [
'$yesterday' => 'MyDate|false',
'$b' => 'DateTimeImmutable|false',
'$b' => 'DateTimeImmutable',
],
],
'magicCall' => [
Expand Down

0 comments on commit 9ed9c4b

Please sign in to comment.