diff --git a/src/Type/Php/DateTimeCreateDynamicReturnTypeExtension.php b/src/Type/Php/DateTimeCreateDynamicReturnTypeExtension.php index 26f726c9716..bd00c0f12aa 100644 --- a/src/Type/Php/DateTimeCreateDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateTimeCreateDynamicReturnTypeExtension.php @@ -8,10 +8,10 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use function count; use function date_create; use function in_array; @@ -30,16 +30,20 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return null; } - $datetime = $scope->getType($functionCall->getArgs()[0]->value); + $datetimes = $scope->getType($functionCall->getArgs()[0]->value)->getConstantStrings(); - if (!$datetime instanceof ConstantStringType) { + if (count($datetimes) === 0) { return null; } - $isValid = date_create($datetime->getValue()) !== false; - + $types = []; $className = $functionReflection->getName() === 'date_create' ? DateTime::class : DateTimeImmutable::class; - return $isValid ? new ObjectType($className) : new ConstantBooleanType(false); + foreach ($datetimes as $constantString) { + $isValid = date_create($constantString->getValue()) !== false; + $types[] = $isValid ? new ObjectType($className) : new ConstantBooleanType(false); + } + + return TypeCombinator::union(...$types); } } diff --git a/src/Type/Php/DateTimeDynamicReturnTypeExtension.php b/src/Type/Php/DateTimeDynamicReturnTypeExtension.php index 22945f9966c..b18c8534dfc 100644 --- a/src/Type/Php/DateTimeDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateTimeDynamicReturnTypeExtension.php @@ -7,12 +7,11 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use function count; use function in_array; @@ -24,25 +23,29 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo return in_array($functionReflection->getName(), ['date_create_from_format', 'date_create_immutable_from_format'], true); } - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if (count($functionCall->getArgs()) < 2) { - return $defaultReturnType; + return null; } - $format = $scope->getType($functionCall->getArgs()[0]->value); - $datetime = $scope->getType($functionCall->getArgs()[1]->value); + $formats = $scope->getType($functionCall->getArgs()[0]->value)->getConstantStrings(); + $datetimes = $scope->getType($functionCall->getArgs()[1]->value)->getConstantStrings(); - if (!$format instanceof ConstantStringType || !$datetime instanceof ConstantStringType) { - return $defaultReturnType; + if (count($formats) === 0 || count($datetimes) === 0) { + return null; } - $isValid = (DateTime::createFromFormat($format->getValue(), $datetime->getValue()) !== false); - + $types = []; $className = $functionReflection->getName() === 'date_create_from_format' ? DateTime::class : DateTimeImmutable::class; - return $isValid ? new ObjectType($className) : new ConstantBooleanType(false); + foreach ($formats as $formatConstantString) { + foreach ($datetimes as $datetimeConstantString) { + $isValid = (DateTime::createFromFormat($formatConstantString->getValue(), $datetimeConstantString->getValue()) !== false); + $types[] = $isValid ? new ObjectType($className) : new ConstantBooleanType(false); + } + } + + return TypeCombinator::union(...$types); } } diff --git a/tests/PHPStan/Analyser/data/DateTimeCreateDynamicReturnTypes.php b/tests/PHPStan/Analyser/data/DateTimeCreateDynamicReturnTypes.php index b2b89ec1b73..f50a903dbf2 100644 --- a/tests/PHPStan/Analyser/data/DateTimeCreateDynamicReturnTypes.php +++ b/tests/PHPStan/Analyser/data/DateTimeCreateDynamicReturnTypes.php @@ -29,4 +29,20 @@ public function localVariables(): void { assertType('DateTimeImmutable', date_create_immutable($datetime)); } + /** + * @param '2020-04-09'|'2022-02-01' $datetimes + * @param '1990-04-11'|'foo' $maybeDatetimes + * @param 'foo'|'bar' $noDatetimes + */ + public function unions(string $datetimes, string $maybeDatetimes, string $noDatetimes): void { + assertType('DateTime', date_create($datetimes)); + assertType('DateTimeImmutable', date_create_immutable($datetimes)); + + assertType('DateTime|false', date_create($maybeDatetimes)); + assertType('DateTimeImmutable|false', date_create_immutable($maybeDatetimes)); + + assertType('false', date_create($noDatetimes)); + assertType('false', date_create_immutable($noDatetimes)); + } + } diff --git a/tests/PHPStan/Analyser/data/DateTimeDynamicReturnTypes.php b/tests/PHPStan/Analyser/data/DateTimeDynamicReturnTypes.php index 33c853a0b11..7c92cb0879b 100644 --- a/tests/PHPStan/Analyser/data/DateTimeDynamicReturnTypes.php +++ b/tests/PHPStan/Analyser/data/DateTimeDynamicReturnTypes.php @@ -38,4 +38,20 @@ public function localVariables(): void { assertType('DateTimeImmutable', date_create_immutable_from_format($format, $datetime)); } + /** + * @param '2020-04-09'|'2022-02-01' $datetimes + * @param '1990-04-11'|'foo' $maybeDatetimes + * @param 'foo'|'bar' $noDatetimes + */ + public function unions(string $datetimes, string $maybeDatetimes, string $noDatetimes): void { + assertType('DateTime', date_create_from_format('Y-m-d', $datetimes)); + assertType('DateTimeImmutable', date_create_immutable_from_format('Y-m-d', $datetimes)); + + assertType('DateTime|false', date_create_from_format('Y-m-d', $maybeDatetimes)); + assertType('DateTimeImmutable|false', date_create_immutable_from_format('Y-m-d', $maybeDatetimes)); + + assertType('false', date_create_from_format('Y-m-d', $noDatetimes)); + assertType('false', date_create_immutable_from_format('Y-m-d', $noDatetimes)); + } + }