Skip to content

Commit

Permalink
Improve constant string support in DateTime return type extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
herndlm committed Dec 28, 2022
1 parent c96423e commit 4b26b0a
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 19 deletions.
16 changes: 10 additions & 6 deletions src/Type/Php/DateTimeCreateDynamicReturnTypeExtension.php
Expand Up @@ -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;
Expand All @@ -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);
}

}
29 changes: 16 additions & 13 deletions src/Type/Php/DateTimeDynamicReturnTypeExtension.php
Expand Up @@ -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;

Expand All @@ -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);
}

}
16 changes: 16 additions & 0 deletions tests/PHPStan/Analyser/data/DateTimeCreateDynamicReturnTypes.php
Expand Up @@ -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));
}

}
16 changes: 16 additions & 0 deletions tests/PHPStan/Analyser/data/DateTimeDynamicReturnTypes.php
Expand Up @@ -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));
}

}

0 comments on commit 4b26b0a

Please sign in to comment.