From b11f3f3f4dbac8ab50ad0c0870982986e78a3242 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 30 Apr 2022 16:47:46 +0200 Subject: [PATCH 1/4] 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 84a957bf87..1b628b1301 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1193,6 +1193,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 86638947e2..be4268e791 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 0000000000..70e3562180 --- /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 008456768f..1fe124010f 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -435,6 +435,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/DateTimeModifyReturnTypes.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 0000000000..578010cf17 --- /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)); + } + +} From 7ee30b962d65be6795e4e69ab2a381b4db449d3a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 3 May 2022 15:12:32 +0200 Subject: [PATCH 2/4] Rework --- conf/config.neon | 6 +++++- ...teTimeImmutableModifyReturnTypeExtension.php | 15 --------------- .../Php/DateTimeModifyReturnTypeExtension.php | 17 +++++++++++++++-- 3 files changed, 20 insertions(+), 18 deletions(-) delete mode 100644 src/Type/Php/DateTimeImmutableModifyReturnTypeExtension.php diff --git a/conf/config.neon b/conf/config.neon index 1b628b1301..55de411e10 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1194,14 +1194,18 @@ services: - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\DateTimeImmutableModifyReturnTypeExtension + class: PHPStan\Type\Php\DateTimeModifyReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension + arguments: + dateTimeClass: \DateTime - class: PHPStan\Type\Php\DateTimeModifyReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension + arguments: + dateTimeClass: \DateTimeImmutable - class: PHPStan\Type\Php\DateTimeConstructorThrowTypeExtension diff --git a/src/Type/Php/DateTimeImmutableModifyReturnTypeExtension.php b/src/Type/Php/DateTimeImmutableModifyReturnTypeExtension.php deleted file mode 100644 index 70e3562180..0000000000 --- a/src/Type/Php/DateTimeImmutableModifyReturnTypeExtension.php +++ /dev/null @@ -1,15 +0,0 @@ - */ + private $dateTimeClass; + + /** @param class-string $dateTimeClass */ + public function __construct($dateTimeClass = DateTime::class) + { + $this->dateTimeClass = $dateTimeClass; + } + public function getClass(): string { - return DateTime::class; + return $this->dateTimeClass; } public function isMethodSupported(MethodReflection $methodReflection): bool @@ -31,6 +41,9 @@ public function isMethodSupported(MethodReflection $methodReflection): bool public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { $defaultReturnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + if (count($methodCall->getArgs()) < 1) { + return $defaultReturnType; + } $valueType = $scope->getType($methodCall->getArgs()[0]->value); $constantStrings = TypeUtils::getConstantStrings($valueType); @@ -39,7 +52,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method $hasDateTime = false; foreach ($constantStrings as $constantString) { - if ((new DateTime())->modify($constantString->getValue()) === false) { + if (@(new DateTime())->modify($constantString->getValue()) === false) { $hasFalse = true; } else { $hasDateTime = true; From a7ff3e6ec09d18144805ae8d0fd3fef23cd1be6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mirtes?= Date: Tue, 3 May 2022 17:25:41 +0200 Subject: [PATCH 3/4] Update config.neon --- conf/config.neon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 55de411e10..4789d17753 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1198,14 +1198,14 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension arguments: - dateTimeClass: \DateTime + dateTimeClass: DateTime - class: PHPStan\Type\Php\DateTimeModifyReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension arguments: - dateTimeClass: \DateTimeImmutable + dateTimeClass: DateTimeImmutable - class: PHPStan\Type\Php\DateTimeConstructorThrowTypeExtension From 5b980c18b8064f3d82e882df2d2e77a133258acb Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 4 May 2022 13:01:17 +0200 Subject: [PATCH 4/4] Fix cs --- src/Type/Php/DateTimeModifyReturnTypeExtension.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Type/Php/DateTimeModifyReturnTypeExtension.php b/src/Type/Php/DateTimeModifyReturnTypeExtension.php index 106b47735e..a6dbb4133d 100644 --- a/src/Type/Php/DateTimeModifyReturnTypeExtension.php +++ b/src/Type/Php/DateTimeModifyReturnTypeExtension.php @@ -15,6 +15,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; +use function count; class DateTimeModifyReturnTypeExtension implements DynamicMethodReturnTypeExtension { @@ -23,7 +24,7 @@ class DateTimeModifyReturnTypeExtension implements DynamicMethodReturnTypeExtens private $dateTimeClass; /** @param class-string $dateTimeClass */ - public function __construct($dateTimeClass = DateTime::class) + public function __construct(string $dateTimeClass = DateTime::class) { $this->dateTimeClass = $dateTimeClass; }