From d34baca7dcd92df530457e49e07ebaf0ad5f0456 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 31 Jul 2026 18:26:01 +0800 Subject: [PATCH] fix(optimizer): parenthesize null-check expressions --- phpunit/code/assign-not-identical-null.php | 17 +++++++++++++++++ phpunit/src/OperatorTest.php | 17 +++++++++++++++++ src/Optimizer/FuncCallOptimizer.php | 2 +- src/Parser/BinaryOpTrait.php | 5 ++++- 4 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 phpunit/code/assign-not-identical-null.php diff --git a/phpunit/code/assign-not-identical-null.php b/phpunit/code/assign-not-identical-null.php new file mode 100644 index 00000000..67d377fb --- /dev/null +++ b/phpunit/code/assign-not-identical-null.php @@ -0,0 +1,17 @@ +isFatal($error['type'])) { + return true; + } + return false; + } + + private function isFatal(int $type): bool + { + return $type > 0; + } +} diff --git a/phpunit/src/OperatorTest.php b/phpunit/src/OperatorTest.php index 04f2c2bf..468f7d29 100644 --- a/phpunit/src/OperatorTest.php +++ b/phpunit/src/OperatorTest.php @@ -22,6 +22,23 @@ class OperatorTest extends \BaseTest $this->assertStringContainsString('php::same(php::true_, value)', $cpp); } + public function testAssignedValueNotIdenticalToNullIsParenthesized(): void + { + global $translator; + $compiler = \TypePhp\CompilerTest::create(ROOT_PATH); + $translator = $compiler; + $testFile = __DIR__ . '/../code/assign-not-identical-null.php'; + $compiler->addFiles([$testFile]); + $compiler->prepareFile($testFile); + $cppFile = $compiler->convertFile($testFile); + $cpp = file_get_contents($cppFile); + + $this->assertMatchesRegularExpression( + '/!\(\(error = php::call\([^\n]+\)\)\.isNull\(\)\)/', + $cpp, + ); + } + public function testLiteralIntDivideByZeroDoesNotCompile(): void { $this->exec('Cannot divide or modulo by zero', 'divide-by-zero-int.php'); diff --git a/src/Optimizer/FuncCallOptimizer.php b/src/Optimizer/FuncCallOptimizer.php index 5e3e4d46..2c1c865d 100644 --- a/src/Optimizer/FuncCallOptimizer.php +++ b/src/Optimizer/FuncCallOptimizer.php @@ -612,7 +612,7 @@ trait FuncCallOptimizer protected function genIsNull(string $n, Node\Expr\FuncCall $e, array $c): string { - return $this->parseIdentifier($e->args[0]->value) . '.isNull()'; + return '(' . $this->parseExprAsValue($e->args[0]->value) . ').isNull()'; } protected function genIsCallable(string $n, Node\Expr\FuncCall $e, array $c): string|false diff --git a/src/Parser/BinaryOpTrait.php b/src/Parser/BinaryOpTrait.php index feae8dcb..b17f1eee 100644 --- a/src/Parser/BinaryOpTrait.php +++ b/src/Parser/BinaryOpTrait.php @@ -388,7 +388,10 @@ trait BinaryOpTrait $left = $this->parseCompareExpr($expr->left); $right = $this->parseCompareExpr($expr->right); if ($right === 'nullptr') { - return $left . '.isNull()'; + // The left operand may itself be an assignment or another compound + // expression. Parenthesize it before invoking Variant::isNull(), or + // C++ binds the member access to the assignment's RHS instead. + return '(' . $left . ').isNull()'; } if ($optimized = $this->optimizeIdenticalOp($expr->left, $expr->right, $left, $right)) { return $optimized;