From 34bbe41aeb51ba8644c5ff0d5f2890f6f3a02df9 Mon Sep 17 00:00:00 2001 From: gertdepagter Date: Thu, 11 Jul 2024 10:27:28 +0200 Subject: [PATCH] Fix return type of round/ceil/floor in non strict type environment In non declare strict types the round functions are able to handle numeric strings, null and bools. See also: https://3v4l.org/5io0n And issue https://github.com/phpstan/phpstan/issues/11319 --- .../Php/RoundFunctionReturnTypeExtension.php | 17 +++++ .../Analyser/nsrt/round-php8-strict-types.php | 63 +++++++++++++++++++ tests/PHPStan/Analyser/nsrt/round-php8.php | 30 ++++----- 3 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/round-php8-strict-types.php diff --git a/src/Type/Php/RoundFunctionReturnTypeExtension.php b/src/Type/Php/RoundFunctionReturnTypeExtension.php index b68471d04c..e3e3441b3e 100644 --- a/src/Type/Php/RoundFunctionReturnTypeExtension.php +++ b/src/Type/Php/RoundFunctionReturnTypeExtension.php @@ -6,13 +6,17 @@ use PHPStan\Analyser\Scope; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; +use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; +use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use function count; @@ -67,6 +71,19 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, new IntegerType(), new FloatType(), ); + + if (!$scope->isDeclareStrictTypes()) { + $allowed = TypeCombinator::union( + $allowed, + new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]), + new NullType(), + new BooleanType(), + ); + } + if ($allowed->isSuperTypeOf($firstArgType)->no()) { // PHP 8 fatals if the parameter is not an integer or float. return new NeverType(true); diff --git a/tests/PHPStan/Analyser/nsrt/round-php8-strict-types.php b/tests/PHPStan/Analyser/nsrt/round-php8-strict-types.php new file mode 100644 index 0000000000..c618f6c8d9 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/round-php8-strict-types.php @@ -0,0 +1,63 @@ += 8.0 + +declare(strict_types=1); + +namespace RoundFamilyTestPHP8StrictTypes; + +use function PHPStan\Testing\assertType; + +$maybeNull = null; +if (rand(0, 1)) { + $maybeNull = 1.0; +} + +// Round +assertType('float', round(123)); +assertType('float', round(123.456)); +assertType('float', round($_GET['foo'] / 60)); +assertType('*NEVER*', round('123')); +assertType('*NEVER*', round('123.456')); +assertType('*NEVER*', round(null)); +assertType('float', round($maybeNull)); +assertType('*NEVER*', round(true)); +assertType('*NEVER*', round(false)); +assertType('*NEVER*', round(new \stdClass)); +assertType('*NEVER*', round('')); +assertType('*NEVER*', round(array())); +assertType('*NEVER*', round(array(123))); +assertType('*NEVER*', round()); +assertType('float', round($_GET['foo'])); + +// Ceil +assertType('float', ceil(123)); +assertType('float', ceil(123.456)); +assertType('float', ceil($_GET['foo'] / 60)); +assertType('*NEVER*', ceil('123')); +assertType('*NEVER*', ceil('123.456')); +assertType('*NEVER*', ceil(null)); +assertType('float', ceil($maybeNull)); +assertType('*NEVER*', ceil(true)); +assertType('*NEVER*', ceil(false)); +assertType('*NEVER*', ceil(new \stdClass)); +assertType('*NEVER*', ceil('')); +assertType('*NEVER*', ceil(array())); +assertType('*NEVER*', ceil(array(123))); +assertType('*NEVER*', ceil()); +assertType('float', ceil($_GET['foo'])); + +// Floor +assertType('float', floor(123)); +assertType('float', floor(123.456)); +assertType('float', floor($_GET['foo'] / 60)); +assertType('*NEVER*', floor('123')); +assertType('*NEVER*', floor('123.456')); +assertType('*NEVER*', floor(null)); +assertType('float', floor($maybeNull)); +assertType('*NEVER*', floor(true)); +assertType('*NEVER*', floor(false)); +assertType('*NEVER*', floor(new \stdClass)); +assertType('*NEVER*', floor('')); +assertType('*NEVER*', floor(array())); +assertType('*NEVER*', floor(array(123))); +assertType('*NEVER*', floor()); +assertType('float', floor($_GET['foo'])); diff --git a/tests/PHPStan/Analyser/nsrt/round-php8.php b/tests/PHPStan/Analyser/nsrt/round-php8.php index 2d3143ff90..54836b7623 100644 --- a/tests/PHPStan/Analyser/nsrt/round-php8.php +++ b/tests/PHPStan/Analyser/nsrt/round-php8.php @@ -13,12 +13,12 @@ assertType('float', round(123)); assertType('float', round(123.456)); assertType('float', round($_GET['foo'] / 60)); -assertType('*NEVER*', round('123')); -assertType('*NEVER*', round('123.456')); -assertType('*NEVER*', round(null)); +assertType('float', round('123')); +assertType('float', round('123.456')); +assertType('float', round(null)); assertType('float', round($maybeNull)); -assertType('*NEVER*', round(true)); -assertType('*NEVER*', round(false)); +assertType('float', round(true)); +assertType('float', round(false)); assertType('*NEVER*', round(new \stdClass)); assertType('*NEVER*', round('')); assertType('*NEVER*', round(array())); @@ -30,12 +30,12 @@ assertType('float', ceil(123)); assertType('float', ceil(123.456)); assertType('float', ceil($_GET['foo'] / 60)); -assertType('*NEVER*', ceil('123')); -assertType('*NEVER*', ceil('123.456')); -assertType('*NEVER*', ceil(null)); +assertType('float', ceil('123')); +assertType('float', ceil('123.456')); +assertType('float', ceil(null)); assertType('float', ceil($maybeNull)); -assertType('*NEVER*', ceil(true)); -assertType('*NEVER*', ceil(false)); +assertType('float', ceil(true)); +assertType('float', ceil(false)); assertType('*NEVER*', ceil(new \stdClass)); assertType('*NEVER*', ceil('')); assertType('*NEVER*', ceil(array())); @@ -47,12 +47,12 @@ assertType('float', floor(123)); assertType('float', floor(123.456)); assertType('float', floor($_GET['foo'] / 60)); -assertType('*NEVER*', floor('123')); -assertType('*NEVER*', floor('123.456')); -assertType('*NEVER*', floor(null)); +assertType('float', floor('123')); +assertType('float', floor('123.456')); +assertType('float', floor(null)); assertType('float', floor($maybeNull)); -assertType('*NEVER*', floor(true)); -assertType('*NEVER*', floor(false)); +assertType('float', floor(true)); +assertType('float', floor(false)); assertType('*NEVER*', floor(new \stdClass)); assertType('*NEVER*', floor('')); assertType('*NEVER*', floor(array()));