From 41da4e8126ed86da2bd5b578ffcb12d8e58a09aa Mon Sep 17 00:00:00 2001 From: yavon007 Date: Tue, 22 Sep 2026 18:00:54 +0800 Subject: [PATCH 1/2] fix(compiler): preserve dynamic arithmetic result types --- phpunit/code/dynamic-arithmetic-result.php | 13 +++ phpunit/src/DynamicArithmeticResultTest.php | 31 ++++++ src/CompilerBase.php | 6 ++ tests/compiler/dynamic_arithmetic_result.phpt | 98 +++++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 phpunit/code/dynamic-arithmetic-result.php create mode 100644 phpunit/src/DynamicArithmeticResultTest.php create mode 100644 tests/compiler/dynamic_arithmetic_result.phpt 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..a4e860f6 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -3458,6 +3458,12 @@ 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 || $rightType === Type::VAR) { + 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..9896f5a7 --- /dev/null +++ b/tests/compiler/dynamic_arithmetic_result.phpt @@ -0,0 +1,98 @@ +--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 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)); +} +?> +--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) +} From 5bf95358c9a78a6e955dc0cf3a1f299cb04b3824 Mon Sep 17 00:00:00 2001 From: yavon007 Date: Tue, 22 Sep 2026 18:22:36 +0800 Subject: [PATCH 2/2] fix(compiler): preserve referenced arithmetic result types --- src/CompilerBase.php | 3 +- tests/compiler/dynamic_arithmetic_result.phpt | 41 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/CompilerBase.php b/src/CompilerBase.php index a4e860f6..dd4a447c 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -3461,7 +3461,8 @@ protected function detectTypeOfExpr($expr): string // 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 || $rightType === Type::VAR) { + if ($leftType === Type::VAR || $leftType === Type::REF + || $rightType === Type::VAR || $rightType === Type::REF) { return Type::VAR; } if ($leftType === Type::FLOAT || $rightType === Type::FLOAT) { diff --git a/tests/compiler/dynamic_arithmetic_result.phpt b/tests/compiler/dynamic_arithmetic_result.phpt index 9896f5a7..48255dab 100644 --- a/tests/compiler/dynamic_arithmetic_result.phpt +++ b/tests/compiler/dynamic_arithmetic_result.phpt @@ -38,11 +38,28 @@ function dynamic_kinds(mixed $value, int $n, float $fraction): array $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-- @@ -96,3 +113,27 @@ array(10) { [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) + } +}