Skip to content

Commit

Permalink
Add support for integer ranges in strtotime() with constant strings
Browse files Browse the repository at this point in the history
  • Loading branch information
Seldaek committed Mar 3, 2022
1 parent 265e181 commit dac1c88
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 32 deletions.
20 changes: 14 additions & 6 deletions src/Type/Php/StrtotimeFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;
use function array_map;
use function array_unique;
use function count;
use function is_int;
use function gettype;
use function min;
use function strtotime;

class StrtotimeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
Expand All @@ -30,20 +31,27 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
{
$defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
if (count($functionCall->getArgs()) === 0) {
if (count($functionCall->getArgs()) !== 1) { // strtotime() & 2nd param baseTimestamp are both unsupported use cases
return $defaultReturnType;
}
$argType = $scope->getType($functionCall->getArgs()[0]->value);
if ($argType instanceof MixedType) {
return TypeUtils::toBenevolentUnion($defaultReturnType);
}
$result = array_unique(array_map(static fn (ConstantStringType $string): bool => is_int(strtotime($string->getValue())), TypeUtils::getConstantStrings($argType)));
$results = array_unique(array_map(static fn (ConstantStringType $string): int|bool => strtotime($string->getValue()), TypeUtils::getConstantStrings($argType)));
$resultTypes = array_unique(array_map(static fn (int|bool $value): string => gettype($value), $results));

if (count($result) !== 1) {
if (count($resultTypes) !== 1) {
return $defaultReturnType;
}

return $result[0] ? new IntegerType() : new ConstantBooleanType(false);
if ($results[0] === false) {
return new ConstantBooleanType(false);
}

$results = array_map('intval', $results);

return IntegerRangeType::createAllGreaterThan(min($results));
}

}
20 changes: 0 additions & 20 deletions tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5249,26 +5249,6 @@ public function dataFunctions(): array
'(float|string)',
'$microtimeBenevolent',
],
[
'int',
'$strtotimeNow',
],
[
'false',
'$strtotimeInvalid',
],
[
'int|false',
'$strtotimeUnknown',
],
[
'(int|false)',
'$strtotimeUnknown2',
],
[
'int|false',
'$strtotimeCrash',
],
[
'-1',
'$versionCompare1',
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public function dataFileAsserts(): iterable
if (PHP_INT_SIZE === 8) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/random-int.php');
}
yield from $this->gatherAssertTypes(__DIR__ . '/data/strtotime-return-type-extensions.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/closure-return-type-extensions.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/array-key.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/intersection-static.php');
Expand Down
6 changes: 0 additions & 6 deletions tests/PHPStan/Analyser/data/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@
$microtimeDefault = microtime(null);
$microtimeBenevolent = microtime($undefined);

$strtotimeNow = strtotime('now');
$strtotimeInvalid = strtotime('4 qm');
$strtotimeUnknown = strtotime(doFoo() ? 'now': '4 qm');
$strtotimeUnknown2 = strtotime($undefined);
$strtotimeCrash = strtotime();

$versionCompare1 = version_compare('7.0.0', '7.0.1');
$versionCompare2 = version_compare('7.0.0', doFoo() ? '7.0.1' : '6.0.0');
$versionCompare3 = version_compare(doFoo() ? '7.0.0' : '6.0.5', doBar() ? '7.0.1' : '6.0.0');
Expand Down
29 changes: 29 additions & 0 deletions tests/PHPStan/Analyser/data/strtotime-return-type-extensions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace ClosureReturnTypeExtensionsNamespace;

use function PHPStan\Testing\assertType;

$strtotimeNow = strtotime('2022-03-03 12:00:00 UTC');
assertType('int<1646308801, max>', $strtotimeNow);

$strtotimeInvalid = strtotime('4 qm');
assertType('false', $strtotimeInvalid);

$strtotimeUnknown = strtotime(rand(0, 1) === 0 ? 'now': '4 qm');
assertType('int|false', $strtotimeUnknown);

$strtotimeUnknown2 = strtotime($undefined);
assertType('(int|false)', $strtotimeUnknown2);

$strtotimeCrash = strtotime();
assertType('int|false', $strtotimeCrash);

$strtotimeWithBase = strtotime('+2 days', time());
assertType('int|false', $strtotimeWithBase);

$strtotimePositiveInt = strtotime('1990-01-01 12:00:00 UTC');
assertType('int<631195201, max>', $strtotimePositiveInt);

$strtotimeNegativeInt = strtotime('1969-12-31 12:00:00 UTC');
assertType('int<-43199, max>', $strtotimeNegativeInt);

0 comments on commit dac1c88

Please sign in to comment.