From 32aedbac58be50bda6ecf53c73e63414818a7fd8 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 6 Sep 2022 11:05:40 +0200 Subject: [PATCH 1/2] Add dateTimeModify return type provider --- .../Provider/MethodReturnTypeProvider.php | 2 + .../DateTimeModifyReturnTypeProvider.php | 61 ++++++++++++ tests/DateTimeTest.php | 96 +++++++++++++++++++ tests/MethodCallTest.php | 2 +- 4 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php create mode 100644 tests/DateTimeTest.php diff --git a/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php b/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php index f29dc77d6cc..a4203e0d7e1 100644 --- a/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php @@ -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; @@ -58,6 +59,7 @@ public function __construct() $this->registerClass(SimpleXmlElementAsXml::class); $this->registerClass(PdoStatementReturnTypeProvider::class); $this->registerClass(ClosureFromCallableReturnTypeProvider::class); + $this->registerClass(DateTimeModifyReturnTypeProvider::class); } /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php new file mode 100644 index 00000000000..4d474dda537 --- /dev/null +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php @@ -0,0 +1,61 @@ +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; + } +} diff --git a/tests/DateTimeTest.php b/tests/DateTimeTest.php new file mode 100644 index 00000000000..6eb3b572038 --- /dev/null +++ b/tests/DateTimeTest.php @@ -0,0 +1,96 @@ +,error_levels?:string[]}> + */ + public function providerValidCodeParse(): iterable + { + return [ + 'modify' => [ + 'modify(getString()); + $b = $dateTimeImmutable->modify(getString()); + ', + 'assertions' => [ + '$a' => 'DateTime|false', + '$b' => 'DateTimeImmutable|false', + ], + ], + 'modifyWithValidConstant' => [ + 'modify(getString()); + $b = $dateTimeImmutable->modify(getString()); + ', + 'assertions' => [ + '$a' => 'DateTime', + '$b' => 'DateTimeImmutable', + ], + ], + 'modifyWithInvalidConstant' => [ + 'modify(getString()); + $b = $dateTimeImmutable->modify(getString()); + ', + 'assertions' => [ + '$a' => 'false', + '$b' => 'false', + ], + ], + 'modifyWithBothConstant' => [ + 'modify(getString()); + $b = $dateTimeImmutable->modify(getString()); + ', + 'assertions' => [ + '$a' => 'DateTime|false', + '$b' => 'DateTimeImmutable|false', + ], + ], + ]; + } +} diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php index f30f1115fdc..cb3103fdeb8 100644 --- a/tests/MethodCallTest.php +++ b/tests/MethodCallTest.php @@ -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' => [ From fec5c8ab03daaa99c2ece9054cd5ce0599fb5f4f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 14 Sep 2022 00:55:32 +0200 Subject: [PATCH 2/2] Fix cs --- .../ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php index 4d474dda537..1677ba64f6f 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php @@ -2,6 +2,7 @@ namespace Psalm\Internal\Provider\ReturnTypeProvider; +use DateTime; use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface; @@ -21,8 +22,7 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event) $statements_source = $event->getSource(); $call_args = $event->getCallArgs(); $method_name_lowercase = $event->getMethodNameLowercase(); - if ( - !$statements_source instanceof StatementsAnalyzer + if (!$statements_source instanceof StatementsAnalyzer || $method_name_lowercase !== 'modify' || !isset($call_args[0]) ) { @@ -42,7 +42,7 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event) return null; } - if (@(new \DateTime())->modify($type_part->value) === false) { + if (@(new DateTime())->modify($type_part->value) === false) { $has_false = true; } else { $has_date_time = true;