diff --git a/phpunit/code/dynamic-arithmetic-result.php b/phpunit/code/dynamic-arithmetic-result.php new file mode 100644 index 00000000..1d53d8e3 --- /dev/null +++ b/phpunit/code/dynamic-arithmetic-result.php @@ -0,0 +1,13 @@ +addFiles([$source]); + $compiler->prepareFile($source); + $code = file_get_contents($compiler->convertFile($source)); + self::assertIsString($code); + $dynamic = substr($code, strpos($code, 'php_dynamicresult(')); + $dynamic = substr($dynamic, 0, strpos($dynamic, "\n}") + 3); + self::assertDoesNotMatchRegularExpression('/php::toInt\([^;]*divisor/s', $dynamic); + self::assertMatchesRegularExpression('/php::Int php_nativeintresult\(php::Int left, php::Int right\)/', $code); + self::assertMatchesRegularExpression('/php::Float php_nativefloatresult\(php::Float left, php::Float right\)/', $code); + foreach (['php_nativeintresult(', 'php_nativefloatresult('] as $name) { + $body = substr($code, strpos($code, $name)); + $body = substr($body, 0, strpos($body, "\n}") + 3); + self::assertStringContainsString('((left) / (right))', $body); + self::assertStringNotContainsString('php::Var', $body); + } + self::assertMatchesRegularExpression('/php::Var (tmp_var_\d+);.*?\1 = \(+tmp_var_\d+\) \/ \(divisor\)/s', $dynamic); + } +} diff --git a/src/CompilerBase.php b/src/CompilerBase.php index d28dc0b1..dd4a447c 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -3458,6 +3458,13 @@ protected function detectTypeOfExpr($expr): string } return Type::BIGINT; } + // A dynamic operand keeps the runtime result type. Inferring + // Int from the other operand would truncate floating results + // when ordered evaluation materializes this expression. + if ($leftType === Type::VAR || $leftType === Type::REF + || $rightType === Type::VAR || $rightType === Type::REF) { + return Type::VAR; + } if ($leftType === Type::FLOAT || $rightType === Type::FLOAT) { return Type::FLOAT; } diff --git a/tests/compiler/dynamic_arithmetic_result.phpt b/tests/compiler/dynamic_arithmetic_result.phpt new file mode 100644 index 00000000..48255dab --- /dev/null +++ b/tests/compiler/dynamic_arithmetic_result.phpt @@ -0,0 +1,139 @@ +--TEST-- +Dynamic arithmetic values keep PHP numeric semantics in nested array returns +--FILE-- + [ + 'add' => $values['float'] + $n, + 'subtract' => $values['float'] - $n, + 'multiply' => $values['float'] * $n, + 'divide' => $values['float'] / $n, + 'integer_divide' => $values['integer'] / $n, + ], + 'reverse' => [ + 'add' => $n + $values['float'], + 'subtract' => $n - $values['float'], + 'multiply' => $n * $values['float'], + 'divide' => $n / $values['float'], + ], + ]; +} + +function dynamic_return(mixed $value, int $n): mixed +{ + return $value / $n; +} + +function dynamic_nested(array $values, int $n): mixed +{ + return ($values['float'] + $n) * $n; +} + +function dynamic_kinds(mixed $value, int $n, float $fraction): array +{ + return [$value / $n, $value + $fraction, $fraction - $value, + $value ** $n, $value % $n, $value << $n, $value >> $n, + $value & $n, $value | $n, $value ^ $n]; +} + +function divisor(): int +{ + return 2; +} + +function dynamic_reference_arithmetic(): array +{ + $values = [5.5]; + foreach ($values as &$value) { + return [ + 'forward' => [$value / divisor(), $value + divisor(), $value - divisor(), $value * divisor()], + 'reverse' => [divisor() / $value, divisor() + $value, divisor() - $value, divisor() * $value], + ]; + } +} + +function main() +{ + var_dump(dynamic_arithmetic(['float' => 5.5, 'integer' => 5], 2)); + var_dump(dynamic_return(5.5, 2), dynamic_nested(['float' => 5.5], 2)); + var_dump(dynamic_kinds(4, 2, 0.5)); + var_dump(dynamic_reference_arithmetic()); +} +?> +--EXPECT-- +array(2) { + ["forward"]=> + array(5) { + ["add"]=> + float(7.5) + ["subtract"]=> + float(3.5) + ["multiply"]=> + float(11) + ["divide"]=> + float(2.75) + ["integer_divide"]=> + float(2.5) + } + ["reverse"]=> + array(4) { + ["add"]=> + float(7.5) + ["subtract"]=> + float(-3.5) + ["multiply"]=> + float(11) + ["divide"]=> + float(0.36363636363636365) + } +} +float(2.75) +float(15) +array(10) { + [0]=> + int(2) + [1]=> + float(4.5) + [2]=> + float(-3.5) + [3]=> + int(16) + [4]=> + int(0) + [5]=> + int(16) + [6]=> + int(1) + [7]=> + int(0) + [8]=> + int(6) + [9]=> + int(6) +} +array(2) { + ["forward"]=> + array(4) { + [0]=> + float(2.75) + [1]=> + float(7.5) + [2]=> + float(3.5) + [3]=> + float(11) + } + ["reverse"]=> + array(4) { + [0]=> + float(0.36363636363636365) + [1]=> + float(7.5) + [2]=> + float(-3.5) + [3]=> + float(11) + } +}