diff --git a/conf/config.neon b/conf/config.neon index 1cc26c1664..0d954bd6b1 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1305,6 +1305,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\RoundFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\StrtotimeFunctionReturnTypeExtension tags: diff --git a/resources/functionMap.php b/resources/functionMap.php index f55becba80..959f9866c2 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -932,7 +932,7 @@ 'CallbackFilterIterator::next' => ['void'], 'CallbackFilterIterator::rewind' => ['void'], 'CallbackFilterIterator::valid' => ['bool'], -'ceil' => ['float', 'number'=>'float'], +'ceil' => ['float|false', 'number'=>'float'], 'chdb::__construct' => ['void', 'pathname'=>'string'], 'chdb::get' => ['string', 'key'=>'string'], 'chdb_create' => ['bool', 'pathname'=>'string', 'data'=>'array'], @@ -2990,7 +2990,7 @@ 'finfo_set_flags' => ['bool', 'finfo'=>'resource', 'options'=>'int'], 'floatval' => ['float', 'var'=>'mixed'], 'flock' => ['bool', 'fp'=>'resource', 'operation'=>'int', '&w_wouldblock='=>'int'], -'floor' => ['float', 'number'=>'float'], +'floor' => ['float|false', 'number'=>'float'], 'flush' => ['void'], 'fmod' => ['float', 'x'=>'float', 'y'=>'float'], 'fnmatch' => ['bool', 'pattern'=>'string', 'filename'=>'string', 'flags='=>'int'], @@ -9889,7 +9889,7 @@ 'rewind' => ['bool', 'fp'=>'resource'], 'rewinddir' => ['null|false', 'dir_handle='=>'resource'], 'rmdir' => ['bool', 'dirname'=>'string', 'context='=>'resource'], -'round' => ['float', 'number'=>'float', 'precision='=>'int', 'mode='=>'int'], +'round' => ['float|false', 'number'=>'float', 'precision='=>'int', 'mode='=>'int'], 'rpm_close' => ['bool', 'rpmr'=>'resource'], 'rpm_get_tag' => ['mixed', 'rpmr'=>'resource', 'tagnum'=>'int'], 'rpm_is_valid' => ['bool', 'filename'=>'string'], diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index a1fc40fe26..b236a79122 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -156,6 +156,11 @@ public function supportsCaseInsensitiveConstantNames(): bool return $this->versionId < 80000; } + public function hasStricterRoundFunctions(): bool + { + return $this->versionId >= 80000; + } + public function hasTentativeReturnTypes(): bool { return $this->versionId >= 80100; diff --git a/src/Type/Php/RoundFunctionReturnTypeExtension.php b/src/Type/Php/RoundFunctionReturnTypeExtension.php new file mode 100644 index 0000000000..4d75ec260f --- /dev/null +++ b/src/Type/Php/RoundFunctionReturnTypeExtension.php @@ -0,0 +1,89 @@ +getName(), + [ + 'round', + 'ceil', + 'floor', + ], + true, + ); + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if ($this->phpVersion->hasStricterRoundFunctions()) { + // PHP 8 fatals with a missing parameter. + $noArgsReturnType = new NeverType(true); + // PHP 8 can either return a float or fatal. + $defaultReturnType = new BenevolentUnionType([ + new FloatType(), + new NeverType(true), + ]); + } else { + // PHP 7 returns null with a missing parameter. + $noArgsReturnType = new NullType(); + // PHP 7 can return either a float or false. + $defaultReturnType = new BenevolentUnionType([ + new FloatType(), + new ConstantBooleanType(false), + ]); + } + + if (count($functionCall->getArgs()) < 1) { + return $noArgsReturnType; + } + + $firstArgType = $scope->getType($functionCall->getArgs()[0]->value); + + if ($firstArgType instanceof MixedType) { + return $defaultReturnType; + } + + if ($this->phpVersion->hasStricterRoundFunctions()) { + $allowed = TypeCombinator::union( + new IntegerType(), + new FloatType(), + ); + if (!$allowed->accepts($firstArgType, true)->yes()) { + // PHP 8 fatals if the parameter is not an integer or float. + return new NeverType(true); + } + } elseif ($firstArgType->isArray()->yes()) { + // PHP 7 returns false if the parameter is an array. + return new ConstantBooleanType(false); + } + + return new FloatType(); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 97bbc2d1fb..aaf6bc04b3 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -552,6 +552,12 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5992.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6001.php'); + if (PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/round-php8.php'); + } else { + yield from $this->gatherAssertTypes(__DIR__ . '/data/round.php'); + } + if (PHP_VERSION_ID >= 80100) { yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5287-php81.php'); } else { diff --git a/tests/PHPStan/Analyser/data/round-php8.php b/tests/PHPStan/Analyser/data/round-php8.php new file mode 100644 index 0000000000..68a0c97768 --- /dev/null +++ b/tests/PHPStan/Analyser/data/round-php8.php @@ -0,0 +1,53 @@ +