From 9138dea99fad2307a16e430d547ee438f855014b Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 12 Jun 2026 20:37:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(php):=20=E5=AE=9E=E7=8E=B0=E8=B5=8B?= =?UTF-8?q?=E5=80=BC=E8=A1=A8=E8=BE=BE=E5=BC=8F=E8=BF=94=E5=9B=9E=E5=80=BC?= =?UTF-8?q?=E5=92=8C=E5=87=BD=E6=95=B0=E5=8F=82=E6=95=B0=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在数组赋值操作中添加临时变量存储以支持返回值 - 为属性赋值操作实现逗号表达式确保右值先计算后赋值 - 添加内部函数参数数量验证机制防止运行时错误 - 优化三元运算符表达式确保类型一致性 - 扩展函数调用优化器以支持参数类型和数量检查 - 添加测试用例验证数组维度赋值返回功能 - 添加测试用例验证属性赋值返回功能 - 添加测试用例验证三元运算符类型转换功能 --- src/Php/CompilerBase.php | 26 +++++++++++- src/Php/Optimizer/FuncCallOptimizer.php | 10 ++++- src/Php/Parser/AssignOpTrait.php | 23 ++++++++--- tests/aot/basic/return-assign-array-dim.phpt | 22 ++++++++++ .../basic/return-assign-set-prop-trait.phpt | 41 +++++++++++++++++++ tests/aot/basic/return-ternary.phpt | 30 ++++++++++++++ 6 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 tests/aot/basic/return-assign-array-dim.phpt create mode 100644 tests/aot/basic/return-assign-set-prop-trait.phpt create mode 100644 tests/aot/basic/return-ternary.phpt diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index a20bd059..a2797587 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -2585,6 +2585,23 @@ class CompilerBase extends \PhpAot\Core\Translator $this->fatalError($node, 'All execution code must be within a function, found stray code'); } + protected function checkInternalFunctionArgCount(string $funcName, Node\Expr\FuncCall $expr): void + { + $ref = Reflection::getFunction($funcName); + if (!$ref) { + return; + } + $minArgs = $ref->getNumberOfRequiredParameters(); + $maxArgs = $ref->getNumberOfParameters(); + $actualArgCount = count($expr->args); + if ($minArgs > 0 && $actualArgCount < $minArgs) { + $this->fatalError($expr, "{$funcName}() expects at least {$minArgs} argument(s), {$actualArgCount} given"); + } + if (!$ref->isVariadic() && $maxArgs > 0 && $actualArgCount > $maxArgs) { + $this->fatalError($expr, "{$funcName}() expects at most {$maxArgs} argument(s), {$actualArgCount} given"); + } + } + protected function parseFuncCall(Expr\FuncCall $expr): string { if ($this->isVarExpr($expr->name)) { @@ -2615,6 +2632,7 @@ class CompilerBase extends \PhpAot\Core\Translator } // 动态调用的函数,转换函数名为带有命名空间的全限定名称 $name = $this->getNamespacedFuncName($name); + $this->checkInternalFunctionArgCount($name, $expr); $code = $this->parseFuncCallWithOptimizer($name, $expr); if ($code !== false) { return $code; @@ -3058,7 +3076,13 @@ class CompilerBase extends \PhpAot\Core\Translator if ($expr->if === null) { return $this->parseValueSelection($expr, $expr->cond, $expr->else, self::OP_NOT_EMPTY); } - return '(' . $this->parseExpr($expr->cond) . ') ? (' . $this->parseExpr($expr->if) . ') : (' . $this->parseExpr($expr->else) . ')'; + $if = $this->parseExpr($expr->if); + $else = $this->parseExpr($expr->else); + if ($this->detectTypeOfExpr($expr->if) !== $this->detectTypeOfExpr($expr->else)) { + $if = 'php::Var(' . $if . ')'; + $else = 'php::Var(' . $else . ')'; + } + return '(' . $this->parseExpr($expr->cond) . ') ? (' . $if . ') : (' . $else . ')'; } protected function parseMatch(Expr\Match_ $expr): string diff --git a/src/Php/Optimizer/FuncCallOptimizer.php b/src/Php/Optimizer/FuncCallOptimizer.php index 998a725b..225b7682 100644 --- a/src/Php/Optimizer/FuncCallOptimizer.php +++ b/src/Php/Optimizer/FuncCallOptimizer.php @@ -265,7 +265,7 @@ trait FuncCallOptimizer $ref = Reflection::getFunction($funcName); if (!$ref) { - return $this->_autoArgTypes[$funcName] = ['args' => '', 'variadic' => false]; + return $this->_autoArgTypes[$funcName] = ['args' => '', 'variadic' => false, 'variadicType' => '', 'minArgs' => 0, 'maxArgs' => 0]; } $types = []; @@ -284,7 +284,13 @@ trait FuncCallOptimizer $types[] = $char; } - return $this->_autoArgTypes[$funcName] = ['args' => implode('_', $types), 'variadic' => $variadic, 'variadicType' => $variadicType]; + return $this->_autoArgTypes[$funcName] = [ + 'args' => implode('_', $types), + 'variadic' => $variadic, + 'variadicType' => $variadicType, + 'minArgs' => $ref->getNumberOfRequiredParameters(), + 'maxArgs' => $ref->getNumberOfParameters(), + ]; } protected function phpParamToArgChar(\ReflectionParameter $param): string diff --git a/src/Php/Parser/AssignOpTrait.php b/src/Php/Parser/AssignOpTrait.php index 9db7f9f7..dd4bbeb5 100644 --- a/src/Php/Parser/AssignOpTrait.php +++ b/src/Php/Parser/AssignOpTrait.php @@ -27,26 +27,33 @@ trait AssignOpTrait $array = $this->parseIdentifier($left->var); $this->context->inAssignExpr = $oriInAssignExpr; $code = ''; - // 这是 PHP 的初始化+赋值写法,需要先创建数组 if (!$this->hasVar($array) and $this->isVarExpr($left->var)) { $this->addLocalVar($array, self::TYPE_ARRAY); } $value = $this->trimBrackets($this->parseExpr($right)); + + $tmp = $this->genTmpVarName(); + $this->addLocalVar($tmp, self::TYPE_VAR); + if ($left->dim === null) { - return $code . "{$array}.offsetSet(" . self::VALUE_NULL . ", {$value})"; + return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.offsetSet(" . self::VALUE_NULL . ", {$tmp})" . '), ' . $tmp . ')'; } $dim = $this->trimBrackets($this->parseIdentifier($left->dim)); - return $code . "{$array}.offsetSet({$dim}, {$value})"; + return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.offsetSet({$dim}, {$tmp})" . '), ' . $tmp . ')'; } protected function parseAssignPropertyFetch(NodeAbstract $left, NodeAbstract $right): string { $array = $this->parseIdentifier($left->var); $propName = $this->identifierToStr($left->name, literal: true); + $rightExpr = $this->trimBrackets($this->parseExpr($right)); - return "{$array}.setProperty({$propName}, " . $this->trimBrackets($this->parseExpr($right)) . ')'; + $tmp = $this->genTmpVarName(); + $this->addLocalVar($tmp, self::TYPE_VAR); + // Comma expression: store RHS → execute side effect → evaluate to stored value + return '((' . $tmp . ' = ' . $rightExpr . ', ' . "{$array}.setProperty({$propName}, {$tmp})" . '), ' . $tmp . ')'; } protected function parseRightAssociativeAssign(NodeAbstract $left, Expr\Assign $right): string @@ -535,12 +542,16 @@ trait AssignOpTrait $propName = $this->identifierToStr($left->var->name); $code = ''; $value = $this->trimBrackets($this->parseExpr($right)); + + $tmp = $this->genTmpVarName(); + $this->addLocalVar($tmp, self::TYPE_VAR); + if ($left->dim === null) { - return $code . "{$obj}.appendArrayProperty({$propName}, {$value})"; + return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$obj}.appendArrayProperty({$propName}, {$tmp})" . '), ' . $tmp . ')'; } $dim = $this->trimBrackets($this->parseIdentifier($left->dim)); - return $code . "{$obj}.updateArrayProperty({$propName}, {$dim}, {$value})"; + return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$obj}.updateArrayProperty({$propName}, {$dim}, {$tmp})" . '), ' . $tmp . ')'; } protected function parseAssignOpCoalesce(Expr\AssignOp\Coalesce $expr): string diff --git a/tests/aot/basic/return-assign-array-dim.phpt b/tests/aot/basic/return-assign-array-dim.phpt new file mode 100644 index 00000000..1460bdd5 --- /dev/null +++ b/tests/aot/basic/return-assign-array-dim.phpt @@ -0,0 +1,22 @@ +--TEST-- +return assign array dim +--SKIPIF-- +--FILE-- +test()); +} +?> +--EXPECT-- +int(999) \ No newline at end of file diff --git a/tests/aot/basic/return-assign-set-prop-trait.phpt b/tests/aot/basic/return-assign-set-prop-trait.phpt new file mode 100644 index 00000000..82f6b653 --- /dev/null +++ b/tests/aot/basic/return-assign-set-prop-trait.phpt @@ -0,0 +1,41 @@ +--TEST-- +return assign array dim +--SKIPIF-- +--FILE-- +array !== null) { + return $this->array; + } + return $this->array = $this->makeArray(); + } +} + +class TestReturnAssignSetProp +{ + use TestReturnTrait; +} + +function main() +{ + $obj = new TestReturnAssignSetProp; + var_dump($obj->test()); +} +?> +--EXPECT-- +array(3) { + [0]=> + int(1) + [1]=> + int(3) + [2]=> + int(4) +} \ No newline at end of file diff --git a/tests/aot/basic/return-ternary.phpt b/tests/aot/basic/return-ternary.phpt new file mode 100644 index 00000000..1dfd64b8 --- /dev/null +++ b/tests/aot/basic/return-ternary.phpt @@ -0,0 +1,30 @@ +--TEST-- +return ternary +--SKIPIF-- +--FILE-- +isA() && $this->isB()) ? 'str' : false; + } +} + +function main() +{ + $obj = new TestReturnTernary; + var_dump($obj->test()); +} +?> +--EXPECT-- +bool(false) \ No newline at end of file