diff --git a/conf/config.neon b/conf/config.neon index 23dce12d8b..3de1ef5ccf 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -962,6 +962,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\DateTimeDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\DsMapDynamicReturnTypeExtension tags: diff --git a/src/Type/Php/DateTimeDynamicReturnTypeExtension.php b/src/Type/Php/DateTimeDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..48e3820856 --- /dev/null +++ b/src/Type/Php/DateTimeDynamicReturnTypeExtension.php @@ -0,0 +1,46 @@ +getName(), ['date_create_from_format', 'date_create_immutable_from_format'], true); + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + + if (count($functionCall->args) < 2) { + return $defaultReturnType; + } + + $format = $scope->getType($functionCall->args[0]->value); + $datetime = $scope->getType($functionCall->args[1]->value); + + if (!$format instanceof ConstantStringType || !$datetime instanceof ConstantStringType) { + return $defaultReturnType; + } + + $isValid = (DateTime::createFromFormat($format->getValue(), $datetime->getValue()) !== false); + + $className = $functionReflection->getName() === 'date_create_from_format' ? DateTime::class : DateTimeImmutable::class; + return $isValid ? new ObjectType($className) : new ConstantBooleanType(false); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 90a012d1fe..836757b48d 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -381,6 +381,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/type-aliases.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4650.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2906.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/DateTimeDynamicReturnTypes.php'); } /** diff --git a/tests/PHPStan/Analyser/data/DateTimeDynamicReturnTypes.php b/tests/PHPStan/Analyser/data/DateTimeDynamicReturnTypes.php new file mode 100644 index 0000000000..33c853a0b1 --- /dev/null +++ b/tests/PHPStan/Analyser/data/DateTimeDynamicReturnTypes.php @@ -0,0 +1,41 @@ +