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)) { if (empty($expr->args)) {
return $call . '(' . $fn . ')'; 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 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 protected function parseCallArgs(array $args, string $funcName = '', string $className = ''): string
{ {
$list_args = []; $list_args = [];
$last = array_key_last($args);
foreach ($args as $i => $arg) { foreach ($args as $i => $arg) {
if ($arg->name !== null) { if ($arg->name !== null) {
$this->fatalError($arg, 'Named arguments are not supported'); $this->fatalError($arg, 'Named arguments are not supported');
@ -2050,21 +2051,42 @@ class CompilerBase extends \PhpAot\Core\Translator
continue; continue;
} }
} }
// 不支持变长参数展开的语法,例如:array_merge(...$arr) // 变长参数展开的语法,例如:array_merge(...$arr)
if ($arg->unpack) { 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); $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); 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 protected function parsePostOp($expr, string $op): string
{ {
if ($this->isVarExpr($expr->var) or $this->isPropertyFetch($expr->var) or $this->isArrayDimFetch($expr->var)) { 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)) { if (empty($args)) {
return 'php::newObject(' . $cePtr . ')'; 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 protected function parseClone(Node\Expr\Clone_ $expr): string
@ -3198,7 +3220,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if (empty($expr->args)) { if (empty($expr->args)) {
return $object . '.exec(' . $method . ')'; 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 protected function identifierToStr(NodeAbstract $node, bool $require = true): string
@ -3268,7 +3290,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if (empty($expr->args)) { if (empty($expr->args)) {
return $call . '(' . $fn . ')'; 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 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)) { if (empty($expr->args)) {
return 'this_.callParentMethod(' . $method . ')'; 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 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_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); 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) { 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); return php::call(php_get_func(func_id, func_name), args);
} }
static inline php::Variant CALL(int func_id, const php::Str &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)); 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) { 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); 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) { static inline php::Variant CALL_SILENT(int func_id, const php::Str &func_name, php::Array &args) {
return php::silentCall(php_get_func(func_id, func_name)); 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