diff --git a/src/Type/Php/RoundFunctionReturnTypeExtension.php b/src/Type/Php/RoundFunctionReturnTypeExtension.php index 2bc17560339..b1dfe7f63bc 100644 --- a/src/Type/Php/RoundFunctionReturnTypeExtension.php +++ b/src/Type/Php/RoundFunctionReturnTypeExtension.php @@ -2,6 +2,7 @@ namespace PHPStan\Type\Php; +use PhpParser\Node\Arg; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredService; @@ -10,6 +11,7 @@ use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; @@ -19,9 +21,21 @@ use PHPStan\Type\NullType; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; +use function ceil; use function count; +use function floor; use function in_array; +use function is_float; +use function is_int; +use function is_numeric; +use function is_string; +use function round; +use const PHP_ROUND_HALF_DOWN; +use const PHP_ROUND_HALF_EVEN; +use const PHP_ROUND_HALF_ODD; +use const PHP_ROUND_HALF_UP; #[AutowiredService] final class RoundFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension @@ -58,16 +72,23 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $noArgsReturnType = new NullType(); } - if (count($functionCall->getArgs()) < 1) { + $args = $functionCall->getArgs(); + + if (count($args) < 1) { return $noArgsReturnType; } - $firstArgType = $scope->getType($functionCall->getArgs()[0]->value); - + $firstArgType = $scope->getType($args[0]->value); if ($firstArgType instanceof MixedType) { return $defaultReturnType; } + $functionName = $functionReflection->getName(); + $returnConstantType = $this->resolveConstantType($functionName, $args, $scope, $firstArgType); + if ($returnConstantType !== null) { + return $returnConstantType; + } + if ($this->phpVersion->hasStricterRoundFunctions()) { if (!$scope->isDeclareStrictTypes()) { $allowed = new UnionType([ @@ -99,4 +120,118 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return new FloatType(); } + /** + * @return 1|2|3|4|null + * + * Returns null for RoundingMode cases without a corresponding + * PHP_ROUND_HALF_* constant, so constant evaluation falls back + * to the existing return-type logic. + */ + private function resolveRoundMode(Type $modeType): ?int + { + $mode = $modeType->getConstantScalarValues(); + + if (count($mode) === 1 && in_array($mode[0], [ + PHP_ROUND_HALF_UP, + PHP_ROUND_HALF_DOWN, + PHP_ROUND_HALF_EVEN, + PHP_ROUND_HALF_ODD, + ], true)) { + return $mode[0]; + } + + $enumCase = $modeType->getEnumCaseObject(); + + if ($enumCase === null || $enumCase->getClassName() !== 'RoundingMode') { + return null; + } + + switch ($enumCase->getEnumCaseName()) { + case 'HalfAwayFromZero': + return PHP_ROUND_HALF_UP; + case 'HalfTowardsZero': + return PHP_ROUND_HALF_DOWN; + case 'HalfEven': + return PHP_ROUND_HALF_EVEN; + case 'HalfOdd': + return PHP_ROUND_HALF_ODD; + default: + return null; + } + } + + /** + * @param Arg[] $args + */ + public function resolveConstantType(string $functionName, array $args, Scope $scope, Type $argType): ?Type + { + $proc = null; + + if ($functionName === 'floor') { + $proc = static fn ($name) => floor($name); + } elseif ($functionName === 'ceil') { + $proc = static fn ($name) => ceil($name); + } elseif ($functionName === 'round') { + if (count($args) === 1) { + $proc = static fn ($name) => round($name); + } else { + if (isset($args[1]->value)) { + $precisionArg = $args[1]->value; + $precisionType = $scope->getType($precisionArg); + $precisions = $precisionType->getConstantScalarValues(); + if (count($precisions) !== 1 || !is_int($precisions[0])) { + return null; + } + $precision = $precisions[0]; + } else { + $precision = 0; + } + + if (!isset($args[2]->value)) { + $proc = static fn ($name) => round($name, $precision); + } else { + $modeType = $scope->getType($args[2]->value); + $mode = $this->resolveRoundMode($modeType); + + if ($mode === null) { + return null; + } + + $proc = static fn ($name) => round($name, $precision, $mode); + } + } + } + + if ($proc === null) { + return null; + } + + $constantScalarValues = $argType->getConstantScalarValues(); + $returnValueTypes = []; + + foreach ($constantScalarValues as $constantScalarValue) { + if (is_int($constantScalarValue) || is_float($constantScalarValue)) { + $returnValueTypes[] = new ConstantFloatType($proc($constantScalarValue)); + continue; + } + + if ( + !$scope->isDeclareStrictTypes() + && is_string($constantScalarValue) + && is_numeric($constantScalarValue) + ) { + $returnValueTypes[] = new ConstantFloatType($proc((float) $constantScalarValue)); + continue; + } + + return null; + } + + if (count($returnValueTypes) >= 1) { + return TypeCombinator::union(...$returnValueTypes); + } + + return null; + } + } diff --git a/tests/PHPStan/Analyser/nsrt/round-php8-strict-types.php b/tests/PHPStan/Analyser/nsrt/round-php8-strict-types.php index c618f6c8d9a..05252ab6efd 100644 --- a/tests/PHPStan/Analyser/nsrt/round-php8-strict-types.php +++ b/tests/PHPStan/Analyser/nsrt/round-php8-strict-types.php @@ -12,8 +12,8 @@ } // Round -assertType('float', round(123)); -assertType('float', round(123.456)); +assertType('123.0', round(123)); +assertType('123.0', round(123.456)); assertType('float', round($_GET['foo'] / 60)); assertType('*NEVER*', round('123')); assertType('*NEVER*', round('123.456')); @@ -29,8 +29,8 @@ assertType('float', round($_GET['foo'])); // Ceil -assertType('float', ceil(123)); -assertType('float', ceil(123.456)); +assertType('123.0', ceil(123)); +assertType('124.0', ceil(123.456)); assertType('float', ceil($_GET['foo'] / 60)); assertType('*NEVER*', ceil('123')); assertType('*NEVER*', ceil('123.456')); @@ -46,8 +46,8 @@ assertType('float', ceil($_GET['foo'])); // Floor -assertType('float', floor(123)); -assertType('float', floor(123.456)); +assertType('123.0', floor(123)); +assertType('123.0', floor(123.456)); assertType('float', floor($_GET['foo'] / 60)); assertType('*NEVER*', floor('123')); assertType('*NEVER*', floor('123.456')); @@ -61,3 +61,87 @@ assertType('*NEVER*', floor(array(123))); assertType('*NEVER*', floor()); assertType('float', floor($_GET['foo'])); + +/** + * @param 1.11|2.22 $floatUnionA + * @param 1.5|2.5 $floatUnionB + * @param 1.1|2.2|5.5|6.6 $floatUnionC + */ +function constant(float $floatUnionA, float $floatUnionB, float $floatUnionC) +{ + assertType('3.0', round(3.4)); + assertType('4.0', round(3.5)); + assertType('4.0', round(3.6)); + assertType('4.0', round(3.6, 0)); + assertType('5.05', round(5.045, 2)); + assertType('5.06', round(5.055, 2)); + assertType('300.0', round(345, -2)); + assertType('0.0', round(345, -3)); + assertType('700.0', round(678, -2)); + assertType('1000.0', round(678, -3)); + + assertType('1.1|2.2', round($floatUnionA, 1)); + assertType('1.1|2.2', round($floatUnionA, 1, PHP_ROUND_HALF_UP)); + assertType('1.0|2.0', round($floatUnionA, mode: PHP_ROUND_HALF_UP)); + assertType('2.0|3.0', round($floatUnionB, mode: PHP_ROUND_HALF_UP)); + assertType('1.0|2.0', round($floatUnionB, mode: PHP_ROUND_HALF_DOWN)); + assertType('1.0|2.0|5.0|6.0', floor($floatUnionC)); + + $number = 135.79; + assertType('135.79', round($number, 3)); + assertType('135.79', round($number, 2)); + assertType('135.8', round($number, 1)); + assertType('136.0', round($number, 0)); + assertType('140.0', round($number, -1)); + assertType('100.0', round($number, -2)); + assertType('0.0', round($number, -3)); + + // Rounding modes with 9.5 + assertType('10.0', round(9.5, 0, PHP_ROUND_HALF_UP)); + assertType('9.0', round(9.5, 0, PHP_ROUND_HALF_DOWN)); + assertType('10.0', round(9.5, 0, PHP_ROUND_HALF_EVEN)); + assertType('9.0', round(9.5, 0, PHP_ROUND_HALF_ODD)); + + // Rounding modes with 8.5 + assertType('9.0', round(8.5, 0, PHP_ROUND_HALF_UP)); + assertType('8.0', round(8.5, 0, PHP_ROUND_HALF_DOWN)); + assertType('8.0', round(8.5, 0, PHP_ROUND_HALF_EVEN)); + assertType('9.0', round(8.5, 0, PHP_ROUND_HALF_ODD)); + + // Using PHP_ROUND_HALF_UP with 1 decimal digit precision + assertType('1.6', round( 1.55, 1, PHP_ROUND_HALF_UP)); + assertType('-1.6', round(-1.55, 1, PHP_ROUND_HALF_UP)); + + // Using PHP_ROUND_HALF_DOWN with 1 decimal digit precision + assertType('1.5', round( 1.55, 1, PHP_ROUND_HALF_DOWN)); + assertType('-1.5', round(-1.55, 1, PHP_ROUND_HALF_DOWN)); + + // Using PHP_ROUND_HALF_EVEN with 1 decimal digit precision + assertType('1.6', round( 1.55, 1, PHP_ROUND_HALF_EVEN)); + assertType('-1.6', round(-1.55, 1, PHP_ROUND_HALF_EVEN)); + + // Using PHP_ROUND_HALF_ODD with 1 decimal digit precision + assertType('1.5', round( 1.55, 1, PHP_ROUND_HALF_ODD)); + assertType('-1.5', round(-1.55, 1, PHP_ROUND_HALF_ODD)); +} + +/** + * @param 1.11|2.22 $floatUnion + * @param 2|3 $precisionUnion + * @param 2|4 $modeUnion + * @param 1|'2.5' $IntOrNumStr + * @param 1.11|'2.22' $floatOrNumStr + */ +function notConstant(float $floatUnion, float $precisionUnion, float $modeUnion, $IntOrNumStr, $floatOrNumStr) +{ + assertType('float', round($floatUnion, $precisionUnion, PHP_ROUND_HALF_UP)); + assertType('float', round($floatUnion, 0, $modeUnion)); + + assertType('float', round($IntOrNumStr)); + assertType('float', round($IntOrNumStr, mode: PHP_ROUND_HALF_UP)); + assertType('float', round($IntOrNumStr, mode: PHP_ROUND_HALF_DOWN)); + + assertType('float', round($floatOrNumStr)); + assertType('float', round($floatOrNumStr, mode: PHP_ROUND_HALF_UP)); + assertType('float', round($floatOrNumStr, mode: PHP_ROUND_HALF_DOWN)); +} diff --git a/tests/PHPStan/Analyser/nsrt/round-php8.php b/tests/PHPStan/Analyser/nsrt/round-php8.php index 54836b7623c..ad1a162b05c 100644 --- a/tests/PHPStan/Analyser/nsrt/round-php8.php +++ b/tests/PHPStan/Analyser/nsrt/round-php8.php @@ -10,11 +10,11 @@ } // Round -assertType('float', round(123)); -assertType('float', round(123.456)); +assertType('123.0', round(123)); +assertType('123.0', round(123.456)); assertType('float', round($_GET['foo'] / 60)); -assertType('float', round('123')); -assertType('float', round('123.456')); +assertType('123.0', round('123')); +assertType('123.0', round('123.456')); assertType('float', round(null)); assertType('float', round($maybeNull)); assertType('float', round(true)); @@ -27,11 +27,11 @@ assertType('float', round($_GET['foo'])); // Ceil -assertType('float', ceil(123)); -assertType('float', ceil(123.456)); +assertType('123.0', ceil(123)); +assertType('124.0', ceil(123.456)); assertType('float', ceil($_GET['foo'] / 60)); -assertType('float', ceil('123')); -assertType('float', ceil('123.456')); +assertType('123.0', ceil('123')); +assertType('124.0', ceil('123.456')); assertType('float', ceil(null)); assertType('float', ceil($maybeNull)); assertType('float', ceil(true)); @@ -44,11 +44,11 @@ assertType('float', ceil($_GET['foo'])); // Floor -assertType('float', floor(123)); -assertType('float', floor(123.456)); +assertType('123.0', floor(123)); +assertType('123.0', floor(123.456)); assertType('float', floor($_GET['foo'] / 60)); -assertType('float', floor('123')); -assertType('float', floor('123.456')); +assertType('123.0', floor('123')); +assertType('123.0', floor('123.456')); assertType('float', floor(null)); assertType('float', floor($maybeNull)); assertType('float', floor(true)); @@ -59,3 +59,87 @@ assertType('*NEVER*', floor(array(123))); assertType('*NEVER*', floor()); assertType('float', floor($_GET['foo'])); + +/** + * @param 1.11|2.22 $floatUnionA + * @param 1.5|2.5 $floatUnionB + * @param 1.1|2.2|5.5|6.6 $floatUnionC + */ +function constant(float $floatUnionA, float $floatUnionB, float $floatUnionC) +{ + assertType('3.0', round(3.4)); + assertType('4.0', round(3.5)); + assertType('4.0', round(3.6)); + assertType('4.0', round(3.6, 0)); + assertType('5.05', round(5.045, 2)); + assertType('5.06', round(5.055, 2)); + assertType('300.0', round(345, -2)); + assertType('0.0', round(345, -3)); + assertType('700.0', round(678, -2)); + assertType('1000.0', round(678, -3)); + + assertType('1.1|2.2', round($floatUnionA, 1)); + assertType('1.1|2.2', round($floatUnionA, 1, PHP_ROUND_HALF_UP)); + assertType('1.0|2.0', round($floatUnionA, mode: PHP_ROUND_HALF_UP)); + assertType('2.0|3.0', round($floatUnionB, mode: PHP_ROUND_HALF_UP)); + assertType('1.0|2.0', round($floatUnionB, mode: PHP_ROUND_HALF_DOWN)); + assertType('1.0|2.0|5.0|6.0', floor($floatUnionC)); + + $number = 135.79; + assertType('135.79', round($number, 3)); + assertType('135.79', round($number, 2)); + assertType('135.8', round($number, 1)); + assertType('136.0', round($number, 0)); + assertType('140.0', round($number, -1)); + assertType('100.0', round($number, -2)); + assertType('0.0', round($number, -3)); + + // Rounding modes with 9.5 + assertType('10.0', round(9.5, 0, PHP_ROUND_HALF_UP)); + assertType('9.0', round(9.5, 0, PHP_ROUND_HALF_DOWN)); + assertType('10.0', round(9.5, 0, PHP_ROUND_HALF_EVEN)); + assertType('9.0', round(9.5, 0, PHP_ROUND_HALF_ODD)); + + // Rounding modes with 8.5 + assertType('9.0', round(8.5, 0, PHP_ROUND_HALF_UP)); + assertType('8.0', round(8.5, 0, PHP_ROUND_HALF_DOWN)); + assertType('8.0', round(8.5, 0, PHP_ROUND_HALF_EVEN)); + assertType('9.0', round(8.5, 0, PHP_ROUND_HALF_ODD)); + + // Using PHP_ROUND_HALF_UP with 1 decimal digit precision + assertType('1.6', round( 1.55, 1, PHP_ROUND_HALF_UP)); + assertType('-1.6', round(-1.55, 1, PHP_ROUND_HALF_UP)); + + // Using PHP_ROUND_HALF_DOWN with 1 decimal digit precision + assertType('1.5', round( 1.55, 1, PHP_ROUND_HALF_DOWN)); + assertType('-1.5', round(-1.55, 1, PHP_ROUND_HALF_DOWN)); + + // Using PHP_ROUND_HALF_EVEN with 1 decimal digit precision + assertType('1.6', round( 1.55, 1, PHP_ROUND_HALF_EVEN)); + assertType('-1.6', round(-1.55, 1, PHP_ROUND_HALF_EVEN)); + + // Using PHP_ROUND_HALF_ODD with 1 decimal digit precision + assertType('1.5', round( 1.55, 1, PHP_ROUND_HALF_ODD)); + assertType('-1.5', round(-1.55, 1, PHP_ROUND_HALF_ODD)); +} + +/** + * @param 1.11|2.22 $floatUnion + * @param 2|3 $precisionUnion + * @param 2|4 $modeUnion + * @param 1|'2.5' $IntOrNumStr + * @param 1.11|'2.22' $floatOrNumStr + */ +function notConstant(float $floatUnion, float $precisionUnion, float $modeUnion, $IntOrNumStr, $floatOrNumStr) +{ + assertType('float', round($floatUnion, $precisionUnion, PHP_ROUND_HALF_UP)); + assertType('float', round($floatUnion, 0, $modeUnion)); + + assertType('1.0|3.0', round($IntOrNumStr)); + assertType('1.0|3.0', round($IntOrNumStr, mode: PHP_ROUND_HALF_UP)); + assertType('1.0|2.0', round($IntOrNumStr, mode: PHP_ROUND_HALF_DOWN)); + + assertType('1.0|2.0', round($floatOrNumStr)); + assertType('1.0|2.0', round($floatOrNumStr, mode: PHP_ROUND_HALF_UP)); + assertType('1.0|2.0', round($floatOrNumStr, mode: PHP_ROUND_HALF_DOWN)); +} diff --git a/tests/PHPStan/Analyser/nsrt/round-php84.php b/tests/PHPStan/Analyser/nsrt/round-php84.php new file mode 100644 index 00000000000..b45eb1f3b02 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/round-php84.php @@ -0,0 +1,29 @@ += 8.4 + +namespace RoundFamilyTestPHP84; + +use function PHPStan\Testing\assertType; + +function constantRoundingMode(): void +{ + assertType('10.0', round(9.5, 0, \RoundingMode::HalfAwayFromZero)); + assertType('9.0', round(9.5, 0, \RoundingMode::HalfTowardsZero)); + assertType('10.0', round(9.5, 0, \RoundingMode::HalfEven)); + assertType('9.0', round(9.5, 0, \RoundingMode::HalfOdd)); + + assertType('float', round(9.5, 0, \RoundingMode::TowardsZero)); + assertType('float', round(9.5, 0, \RoundingMode::AwayFromZero)); + assertType('float', round(9.5, 0, \RoundingMode::NegativeInfinity)); + assertType('float', round(9.5, 0, \RoundingMode::PositiveInfinity)); + + assertType('10.0', round(9.5, mode: \RoundingMode::HalfAwayFromZero)); +} + + +/** + * @param \RoundingMode::HalfEven|\RoundingMode::HalfOdd $mode + */ +function nonConstantRoundingMode(\RoundingMode $mode): void +{ + assertType('float', round(9.5, 0, $mode)); +} \ No newline at end of file diff --git a/tests/PHPStan/Analyser/nsrt/round.php b/tests/PHPStan/Analyser/nsrt/round.php index 3d181ca50a6..6a28f4ad045 100644 --- a/tests/PHPStan/Analyser/nsrt/round.php +++ b/tests/PHPStan/Analyser/nsrt/round.php @@ -10,11 +10,11 @@ } // Round -assertType('float', round(123)); -assertType('float', round(123.456)); +assertType('123.0', round(123)); +assertType('123.0', round(123.456)); assertType('float', round($_GET['foo'] / 60)); -assertType('float', round('123')); -assertType('float', round('123.456')); +assertType('123.0', round('123')); +assertType('123.0', round('123.456')); assertType('float', round(null)); assertType('float', round($maybeNull)); assertType('float', round(true)); @@ -27,11 +27,11 @@ assertType('(float|false)', round($_GET['foo'])); // Ceil -assertType('float', ceil(123)); -assertType('float', ceil(123.456)); +assertType('123.0', ceil(123)); +assertType('124.0', ceil(123.456)); assertType('float', ceil($_GET['foo'] / 60)); -assertType('float', ceil('123')); -assertType('float', ceil('123.456')); +assertType('123.0', ceil('123')); +assertType('124.0', ceil('123.456')); assertType('float', ceil(null)); assertType('float', ceil($maybeNull)); assertType('float', ceil(true)); @@ -44,11 +44,11 @@ assertType('(float|false)', ceil($_GET['foo'])); // Floor -assertType('float', floor(123)); -assertType('float', floor(123.456)); +assertType('123.0', floor(123)); +assertType('123.0', floor(123.456)); assertType('float', floor($_GET['foo'] / 60)); -assertType('float', floor('123')); -assertType('float', floor('123.456')); +assertType('123.0', floor('123')); +assertType('123.0', floor('123.456')); assertType('float', floor(null)); assertType('float', floor($maybeNull)); assertType('float', floor(true)); @@ -59,3 +59,79 @@ assertType('false', floor(array(123))); assertType('null', floor()); assertType('(float|false)', floor($_GET['foo'])); + +/** + * @param 1.11|2.22 $floatUnionA + * @param 1.5|2.5 $floatUnionB + * @param 1.1|2.2|5.5|6.6 $floatUnionC + */ +function constant(float $floatUnionA, float $floatUnionB, float $floatUnionC) +{ + assertType('3.0', round(3.4)); + assertType('4.0', round(3.5)); + assertType('4.0', round(3.6)); + assertType('4.0', round(3.6, 0)); + assertType('5.05', round(5.045, 2)); + assertType('5.06', round(5.055, 2)); + assertType('300.0', round(345, -2)); + assertType('0.0', round(345, -3)); + assertType('700.0', round(678, -2)); + assertType('1000.0', round(678, -3)); + + assertType('1.1|2.2', round($floatUnionA, 1)); + assertType('1.1|2.2', round($floatUnionA, 1, PHP_ROUND_HALF_UP)); + assertType('1.0|2.0|5.0|6.0', floor($floatUnionC)); + + $number = 135.79; + assertType('135.79', round($number, 3)); + assertType('135.79', round($number, 2)); + assertType('135.8', round($number, 1)); + assertType('136.0', round($number, 0)); + assertType('140.0', round($number, -1)); + assertType('100.0', round($number, -2)); + assertType('0.0', round($number, -3)); + + // Rounding modes with 9.5 + assertType('10.0', round(9.5, 0, PHP_ROUND_HALF_UP)); + assertType('9.0', round(9.5, 0, PHP_ROUND_HALF_DOWN)); + assertType('10.0', round(9.5, 0, PHP_ROUND_HALF_EVEN)); + assertType('9.0', round(9.5, 0, PHP_ROUND_HALF_ODD)); + + // Rounding modes with 8.5 + assertType('9.0', round(8.5, 0, PHP_ROUND_HALF_UP)); + assertType('8.0', round(8.5, 0, PHP_ROUND_HALF_DOWN)); + assertType('8.0', round(8.5, 0, PHP_ROUND_HALF_EVEN)); + assertType('9.0', round(8.5, 0, PHP_ROUND_HALF_ODD)); + + // Using PHP_ROUND_HALF_UP with 1 decimal digit precision + assertType('1.6', round( 1.55, 1, PHP_ROUND_HALF_UP)); + assertType('-1.6', round(-1.55, 1, PHP_ROUND_HALF_UP)); + + // Using PHP_ROUND_HALF_DOWN with 1 decimal digit precision + assertType('1.5', round( 1.55, 1, PHP_ROUND_HALF_DOWN)); + assertType('-1.5', round(-1.55, 1, PHP_ROUND_HALF_DOWN)); + + // Using PHP_ROUND_HALF_EVEN with 1 decimal digit precision + assertType('1.6', round( 1.55, 1, PHP_ROUND_HALF_EVEN)); + assertType('-1.6', round(-1.55, 1, PHP_ROUND_HALF_EVEN)); + + // Using PHP_ROUND_HALF_ODD with 1 decimal digit precision + assertType('1.5', round( 1.55, 1, PHP_ROUND_HALF_ODD)); + assertType('-1.5', round(-1.55, 1, PHP_ROUND_HALF_ODD)); +} + +/** + * @param 1.11|2.22 $floatUnion + * @param 2|3 $precisionUnion + * @param 2|4 $modeUnion + * @param 1|'2.5' $IntOrNumStr + * @param 1.11|'2.22' $floatOrNumStr + */ +function notConstant(float $floatUnion, float $precisionUnion, float $modeUnion, $IntOrNumStr, $floatOrNumStr) +{ + assertType('float', round($floatUnion, $precisionUnion, PHP_ROUND_HALF_UP)); + assertType('float', round($floatUnion, 0, $modeUnion)); + + assertType('1.0|3.0', round($IntOrNumStr)); + assertType('1.0|2.0', round($floatOrNumStr)); +}