From d22c59319c6a7515b8e2f95fc026e5701785bebe Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 30 Apr 2022 16:47:46 +0200 Subject: [PATCH] Add DateTimeModifyReturnTypeExtension --- conf/config.neon | 10 +++ resources/functionMap.php | 4 +- ...TimeImmutableModifyReturnTypeExtension.php | 15 +++++ .../Php/DateTimeModifyReturnTypeExtension.php | 65 +++++++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 1 + .../data/DateTimeModifyReturnTypes.php | 39 +++++++++++ 6 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 src/Type/Php/DateTimeImmutableModifyReturnTypeExtension.php create mode 100644 src/Type/Php/DateTimeModifyReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/data/DateTimeModifyReturnTypes.php diff --git a/conf/config.neon b/conf/config.neon index 1210d69efc8..17bc45bc240 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1174,6 +1174,16 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\DateTimeImmutableModifyReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: PHPStan\Type\Php\DateTimeModifyReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - class: PHPStan\Type\Php\DateTimeConstructorThrowTypeExtension tags: diff --git a/resources/functionMap.php b/resources/functionMap.php index 9d149d920b7..3e0d9dfc128 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -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'], @@ -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'], diff --git a/src/Type/Php/DateTimeImmutableModifyReturnTypeExtension.php b/src/Type/Php/DateTimeImmutableModifyReturnTypeExtension.php new file mode 100644 index 00000000000..70e35621803 --- /dev/null +++ b/src/Type/Php/DateTimeImmutableModifyReturnTypeExtension.php @@ -0,0 +1,15 @@ +getName() === 'modify'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $defaultReturnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + + $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; + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index b023dc74d19..f8c9682762a 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -429,6 +429,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/DateTimeModifyDynamicReturnTypes.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'); diff --git a/tests/PHPStan/Analyser/data/DateTimeModifyReturnTypes.php b/tests/PHPStan/Analyser/data/DateTimeModifyReturnTypes.php new file mode 100644 index 00000000000..578010cf171 --- /dev/null +++ b/tests/PHPStan/Analyser/data/DateTimeModifyReturnTypes.php @@ -0,0 +1,39 @@ +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)); + } + +}