From fdf7eb976586de2139fb0edf1a670030e51dd0d1 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Wed, 1 Apr 2026 21:00:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(compiler):=20=E5=AE=9E=E7=8E=B0=E7=A9=BA?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E8=BF=90=E7=AE=97=E7=AC=A6=E7=9A=84=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了对 ?? 运算符的完整编译支持 - 实现了变量存在性检查逻辑 - 添加了链式表达式解析功能 - 集成了测试用例验证功能 - 优化了错误处理机制 - 完善了代码生成流程 --- src/Php/CompilerBase.php | 44 +++++++++++++++-- src/Php/Generator/ClosureGenerator.php | 4 +- tests/aot/coalesce/001.phpt | 66 ++++++++++++++++++++++++++ tests/aot/coalesce/002.phpt | 23 +++++++++ 4 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 tests/aot/coalesce/001.phpt create mode 100644 tests/aot/coalesce/002.phpt diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 561675f5..3e08ec31 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -2920,9 +2920,21 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseBinaryOpCoalesce(Node\Expr\BinaryOp\Coalesce $expr): string { $left = $this->parseIdentifier($expr->left); + if ($this->isVarExpr($expr->left)) { + $this->checkVarMustExist($expr->left, $left); + } + + $this->mustNoCall($expr->right); $right = $this->parseIdentifier($expr->right); + $this->checkVarMustExist($expr->right, $right); + + $isset = $this->parseChainedExpr($expr->left, self::OP_ISSET, true); + $chainOpResult = $expr->left->getAttribute('chainOpResult'); + if ($chainOpResult) { + $left = $chainOpResult; + } - return $this->parseChainedExpr($expr->left, 'isset') . ' ? ' . $left . ' : ' . $right; + return $isset . ' ? ' . $left . ' : ' . $right; } protected function parseBinaryOpNotIdentical(Node\Expr\BinaryOp $expr): string @@ -3597,8 +3609,9 @@ class CompilerBase extends \PhpAot\Core\Translator } } - protected function parseChainedExpr(NodeAbstract $expr, string $op): string + protected function parseChainedExpr(NodeAbstract $node, string $op, bool $getValue = false): string { + $expr = $node; if ($this->isVarExpr($expr)) { if ($op === 'isset') { $var = $this->parseIdentifier($expr); @@ -3631,7 +3644,14 @@ class CompilerBase extends \PhpAot\Core\Translator } $list = array_reverse($list); $fn = $op === 'isset' ? 'exists' : $op; - return 'php::' . $fn . '(' . $var . ', {' . implode(', ', $list) . '})'; + + if ($getValue) { + $result = $this->addTmpVar(self::TYPE_VAR); + $node->setAttribute('chainOpResult', $result); + return 'php::' . $fn . '(' . $var . ', {' . implode(', ', $list) . '}, ' . $result . ')'; + } else { + return 'php::' . $fn . '(' . $var . ', {' . implode(', ', $list) . '})'; + } } protected function parseCastArray(Node\Expr\Cast\Array_ $expr): string @@ -4295,6 +4315,24 @@ class CompilerBase extends \PhpAot\Core\Translator } } + protected function checkVarMustExist(NodeAbstract $node, string $name): void + { + if ($this->isVarExpr($node) and !$this->hasVar($name)) { + $this->errorUndefinedVariable($node); + } + } + + protected function mustNoCall(NodeAbstract $node): void + { + $nodeFinder = new NodeFinder(); + $r1 = $nodeFinder->findInstanceOf($node, Node\Expr\StaticCall::class); + $r2 = $nodeFinder->findInstanceOf($node, Node\Expr\MethodCall::class); + $r3 = $nodeFinder->findInstanceOf($node, Node\Expr\FuncCall::class); + if (count($r1) + count($r2) + count($r3) > 0) { + $this->fatalError($node, 'Calling function or method is not allowed'); + } + } + protected function checkAccessible(ClassDef $classDef, int $flags): bool { // 在当前类中,允许调用所有方法 diff --git a/src/Php/Generator/ClosureGenerator.php b/src/Php/Generator/ClosureGenerator.php index 811684b3..c9c69628 100644 --- a/src/Php/Generator/ClosureGenerator.php +++ b/src/Php/Generator/ClosureGenerator.php @@ -78,9 +78,7 @@ trait ClosureGenerator } $useVars[] = $this->convertToRef($useItem->var); } else { - if (!$this->hasVar($var)) { - $this->errorUndefinedVariable($useItem->var); - } + $this->checkVarMustExist($useItem->var, $var); $useVars[] = $var; } } diff --git a/tests/aot/coalesce/001.phpt b/tests/aot/coalesce/001.phpt new file mode 100644 index 00000000..03e40b6f --- /dev/null +++ b/tests/aot/coalesce/001.phpt @@ -0,0 +1,66 @@ +--TEST-- +Test ?? operator +--FILE-- +boo = 7; + + $arr = [ + 2 => 7, + "foo" => "bar", + "foobar" => NULL, + "qux" => $obj, + "bing" => [ + "bang" + ] + ]; + + var_dump($var ?? 3); + var_dump($var2 ?? 3); + echo PHP_EOL; + var_dump($obj->boo ?? 3); + var_dump($obj->bing ?? 3); + var_dump($arr["qux"]->boo ?? 3); + var_dump($arr["qux"]->bing ?? 3); + echo PHP_EOL; + var_dump($arr[2] ?? 3); + var_dump($arr["foo"] ?? 3); + var_dump($arr["foobar"] ?? 3); + var_dump($arr["qux"] ?? 3); + var_dump($arr["bing"][0] ?? 3); + var_dump($arr["bing"][1] ?? 3); + echo PHP_EOL; + var_dump(foobar()[0] ?? false); +} +?> +--EXPECTF-- +int(7) +int(3) + +int(7) +int(3) +int(7) +int(3) + +int(7) +string(3) "bar" +int(3) +object(stdClass)#%d (%d) { + ["boo"]=> + int(7) +} +string(4) "bang" +int(3) + +called +string(1) "a" \ No newline at end of file diff --git a/tests/aot/coalesce/002.phpt b/tests/aot/coalesce/002.phpt new file mode 100644 index 00000000..28ac8a0e --- /dev/null +++ b/tests/aot/coalesce/002.phpt @@ -0,0 +1,23 @@ +--TEST-- +Test ?? operator +--FILE-- + +--EXPECTF-- +f(1) +f(2) +f(0) +int(1)