feat(compiler): 支持PHP变长参数展开语法

- 修改parseCallArgs方法以支持...操作符参数展开
- 添加对变长参数展开位置限制的验证逻辑
- 实现变长参数展开的临时变量生成机制
- 更新函数调用参数解析以兼容数组参数传递
- 添加parseArrayArg辅助方法处理数组参数解析
- 为变长参数展开功能增加单元测试用例
- 修复多处函数调用参数格式化问题
pull/1/head
韩天峰 6 months ago
parent 54c43dc691
commit ac3351b5f4
  1. 9
      examples/vcall.php
  2. 40
      src/Php/CompilerBase.php
  3. 16
      src/cpp/php_aot_helper.h
  4. 18
      tests/aot/args-unpack.phpt

@ -0,0 +1,9 @@
<?php
function main()
{
$a = 100;
$b = 'hello';
$c = [12.34, true, null];
var_dump($a, $b, ...$c);
}

@ -1956,7 +1956,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if (empty($expr->args)) {
return $call . '(' . $fn . ')';
}
return $call . '(' . $fn . ', {' . $this->parseCallArgs($expr->args, $name) . '})';
return $call . '(' . $fn . ', ' . $this->parseCallArgs($expr->args, $name) . ')';
}
protected function parseNativeCallArgs(array $callArgs, string $nativeFunc): string
@ -2005,6 +2005,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseCallArgs(array $args, string $funcName = '', string $className = ''): string
{
$list_args = [];
$last = array_key_last($args);
foreach ($args as $i => $arg) {
if ($arg->name !== null) {
$this->fatalError($arg, 'Named arguments are not supported');
@ -2050,21 +2051,42 @@ class CompilerBase extends \PhpAot\Core\Translator
continue;
}
}
// 不支持变长参数展开的语法,例如:array_merge(...$arr)
// 变长参数展开的语法,例如:array_merge(...$arr)
if ($arg->unpack) {
$this->fatalError($arg, 'The syntax for variable parameter expansion is not supported');
if ($i !== $last) {
$this->fatalError($arg, 'The unpack expression for variadic arguments must be the last');
}
$tmpVar = $this->genTmpVarName();
$this->beforeStmtLines[] = self::TYPE_ARRAY . ' ' . $tmpVar . '{' . implode(', ', $list_args) . '};';
$this->beforeStmtLines[] = $tmpVar . '.merge(' . $this->parseArrayArg($arg) . ');';
return $tmpVar;
}
$list_args[] = $this->parseArg($arg);
}
return implode(', ', $list_args);
return '{' . implode(', ', $list_args) . '}';
}
protected function parseArg($arg): string
protected function parseArg(Node\Arg $arg): string
{
return $this->parseIdentifier($arg->value);
}
protected function parseArrayArg(Node\Arg $expr): string
{
$value = $expr->value;
if ($this->isVarExpr($value)) {
$var = $this->parseIdentifier($value);
if (!$this->hasVar($var)) {
$this->errorUndefinedVariable($value);
}
if ($this->getVarType($var) === self::TYPE_ARRAY) {
return $var;
}
}
return $this->parseExpr($value);
}
protected function parsePostOp($expr, string $op): string
{
if ($this->isVarExpr($expr->var) or $this->isPropertyFetch($expr->var) or $this->isArrayDimFetch($expr->var)) {
@ -2448,7 +2470,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if (empty($args)) {
return 'php::newObject(' . $cePtr . ')';
}
return 'php::newObject(' . $cePtr . ', {' . $this->parseCallArgs($args) . '})';
return 'php::newObject(' . $cePtr . ', ' . $this->parseCallArgs($args) . ')';
}
protected function parseClone(Node\Expr\Clone_ $expr): string
@ -3198,7 +3220,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if (empty($expr->args)) {
return $object . '.exec(' . $method . ')';
}
return $object . '.exec(' . $method . ', {' . $this->parseCallArgs($expr->args, $funcName, $class) . '})';
return $object . '.exec(' . $method . ', ' . $this->parseCallArgs($expr->args, $funcName, $class) . ')';
}
protected function identifierToStr(NodeAbstract $node, bool $require = true): string
@ -3268,7 +3290,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if (empty($expr->args)) {
return $call . '(' . $fn . ')';
}
return $call . '(' . $fn . ', {' . $this->parseCallArgs($expr->args) . '})';
return $call . '(' . $fn . ', ' . $this->parseCallArgs($expr->args) . ')';
}
protected function findNativeStaticProperty(Node\Expr\StaticPropertyFetch $expr, ?string &$class, ?string &$namespace): ?PropertyDef
@ -3655,7 +3677,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if (empty($expr->args)) {
return 'this_.callParentMethod(' . $method . ')';
}
return 'this_.callParentMethod(' . $method . ', {' . $this->parseCallArgs($expr->args) . '})';
return 'this_.callParentMethod(' . $method . ', ' . $this->parseCallArgs($expr->args) . ')';
}
protected function genDebugInfo(?NodeAbstract $stmt = null): string

@ -4,18 +4,26 @@ extern zend_class_entry *php_get_class(int class_id, const php::Str &class_name)
extern zend_function *php_get_func(int func_id, const php::Str &func_name);
extern zend_function *php_get_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name);
static inline php::Variant CALL(int func_id, const php::Str &func_name) {
return php::call(php_get_func(func_id, func_name));
}
static inline php::Variant CALL(int func_id, const php::Str &func_name, const php::ArgList &args) {
return php::call(php_get_func(func_id, func_name), args);
}
static inline php::Variant CALL(int func_id, const php::Str &func_name) {
return php::call(php_get_func(func_id, func_name));
static inline php::Variant CALL(int func_id, const php::Str &func_name, php::Array &args) {
return php::call(php_get_func(func_id, func_name), args);
}
static inline php::Variant CALL_SILENT(int func_id, const php::Str &func_name) {
return php::silentCall(php_get_func(func_id, func_name));
}
static inline php::Variant CALL_SILENT(int func_id, const php::Str &func_name, const php::ArgList &args) {
return php::silentCall(php_get_func(func_id, func_name), args);
}
static inline php::Variant CALL_SILENT(int func_id, const php::Str &func_name) {
return php::silentCall(php_get_func(func_id, func_name));
static inline php::Variant CALL_SILENT(int func_id, const php::Str &func_name, php::Array &args) {
return php::call(php_get_func(func_id, func_name), args);
}

@ -0,0 +1,18 @@
--TEST--
args unpack
--FILE--
<?php
function main()
{
$a = 100;
$b = 'hello';
$c = [12.34, true, null];
var_dump($a, $b, ...$c);
}
?>
--EXPECT--
int(100)
string(5) "hello"
float(12.34)
bool(true)
NULL
Loading…
Cancel
Save