feat(compiler): 添加对PHP引用参数的支持

- 在ArgInfo类中新增byRef属性用于标识引用参数
- 重命名parseFunctionDeclaration方法为parseFunctionDecl
- 移除引用参数不支持的限制检查
- 在参数解析时添加对byRef的处理逻辑
- 为闭包函数添加引用参数的错误检查
- 在参数类型转换时添加引用参数的特殊处理
- 更新函数调用时参数获取的逻辑以支持引用参数
- 添加引用参数的测试用例
pull/1/head
韩天峰 6 months ago
parent 0e44edd42c
commit 31a3275ecf
  1. 1
      src/Php/ArgInfo.php
  2. 47
      src/Php/CompilerBase.php
  3. 2
      src/Php/Preprocessor.php
  4. 6
      src/Php/Translator.php
  5. 18
      tests/aot/ref-func-param.phpt

@ -13,5 +13,6 @@ class ArgInfo
public string $name;
public string $type;
public string $default = '';
public bool $byRef = false;
public bool $variadic = false;
}

@ -727,7 +727,7 @@ class CompilerBase extends \PhpAot\Core\Translator
}
}
protected function parseFunctionDeclaration(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): FunctionDef
protected function parseFunctionDecl(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): FunctionDef
{
// .stub 存根定义 C++ Native 函数,必须设置返回值类型
if (!$v->returnType && $this->stubFile) {
@ -773,7 +773,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if (isset($this->nativeFunctions[$name])) {
$this->functionDef = $this->nativeFunctions[$name];
} else {
$this->nativeFunctions[$name] = $this->parseFunctionDeclaration($v);
$this->nativeFunctions[$name] = $this->parseFunctionDecl($v);
if (isset($this->redoAfterDeclare[$name])) {
unset($this->redoAfterDeclare[$name]);
$this->climate->cyan('Received redo request, retrying...');
@ -911,14 +911,12 @@ class CompilerBase extends \PhpAot\Core\Translator
$list = [];
$functionDef->argCountRequired = count($params);
$last = array_key_last($params);
foreach ($params as $i => $param) {
// .stub 存根定义 C++ Native 函数,必须设置函数的参数类型
if ($this->stubFile and !$param->type) {
throw new \RuntimeException('No type for ' . $this->parseIdentifier($param->var));
}
if ($param->byRef) {
$this->fatalError($param, 'ByRef parameters are not supported');
}
if ($param->variadic and $i !== $last) {
$this->fatalError($param, 'Variadic parameters must be the last parameter');
}
@ -932,6 +930,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$argInfo = new ArgInfo();
$argInfo->name = $name;
$argInfo->type = $type;
$argInfo->byRef = $param->byRef;
$argInfo->variadic = $param->variadic;
if (isset($param->default)) {
$functionDef->argCountRequired = count($list) - 1;
@ -1263,9 +1262,14 @@ class CompilerBase extends \PhpAot\Core\Translator
return $str === 'true' || $str === 'false';
}
protected function isNativeType(string $var): bool
protected function isNativeType(string $type): bool
{
return in_array($this->getVarType($var), [self::TYPE_INT, self::TYPE_FLOAT, self::TYPE_BOOL]);
return in_array($type, [self::TYPE_INT, self::TYPE_FLOAT, self::TYPE_BOOL]);
}
protected function isNativeTypeVar(string $var): bool
{
return $this->isNativeType($this->getVarType($var));
}
protected function isInternalFunction(string $fname): bool
@ -1335,7 +1339,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->parseBinaryOp($expr->left, $expr->right, '+');
}
protected function parseReturn(mixed $v): string
protected function parseReturn(Node\Stmt\Return_ $v): string
{
if ($v->expr === null) {
if ($this->functionDef->returnType === self::TYPE_VOID and !$this->inClosure) {
@ -1349,10 +1353,10 @@ class CompilerBase extends \PhpAot\Core\Translator
$expr = $this->parseExpr($v->expr);
// 函数定义时没有声明返回值,但函数体中有返回值,修改为实际的返回值类型
if ($this->getReturnType() === 'void') {
$this->resetReturnType($type);
} elseif ($this->getReturnType() !== self::TYPE_VAR and $this->getReturnType() !== $type) {
$this->resetReturnType($v, $type);
} elseif ($this->isNativeType($type) and $this->getReturnType() !== self::TYPE_VAR and $this->getReturnType() !== $type) {
// 返回值类型不一致,说明存在多种类型的返回值,修改为 var 表示 any
$this->resetReturnType(self::TYPE_VAR);
$this->resetReturnType($v, self::TYPE_VAR);
}
$exprCode = $this->convertExprType($expr, $this->getReturnType(), $type);
// return 如果使用了 Indirect 语句,可能会导致变量提前析构,出现悬空指针
@ -1442,11 +1446,11 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->classes[$this->escapeClass($name)];
}
protected function resetReturnType(string $type): void
protected function resetReturnType(Node\Stmt\Return_ $node, string $type): void
{
$this->functionDef->returnType = $type;
// 返回值变更,需要重新解析
$this->climate->cyan('Return type changed, retrying...');
$this->climate->cyan('Return type changed at line ' . $node->getLine() . ', retrying...');
throw new Redo();
}
@ -1562,6 +1566,9 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseParameterType(Node\Param $param, string $var): string
{
if ($param->byRef) {
return self::TYPE_REF;
}
$type = $param->type;
if ($type == null) {
return self::TYPE_VAR;
@ -2713,11 +2720,15 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->functionDef->returnType;
}
protected function getTypeConvertedArg($arg, $argInfo): string
protected function getTypeConvertedArg($arg, ArgInfo $argInfo): string
{
$expr = $this->parseArg($arg);
$type = $this->detectExprType($arg->value);
if ($argInfo->byRef) {
return $this->convertToRef($arg->value);
}
return $this->convertExprType($expr, $argInfo->type, $type);
}
@ -3210,7 +3221,7 @@ class CompilerBase extends \PhpAot\Core\Translator
{
$this->checkLeftValue($expr);
$var = $this->parseIdentifier($expr);
if ($this->isVarExpr($expr) and $this->isNativeType($var)) {
if ($this->isVarExpr($expr) and $this->isNativeTypeVar($var)) {
$this->localVars[$var] = self::TYPE_VAR;
}
return $this->parseIdentifier($expr) . '.toReference()';
@ -3848,6 +3859,9 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->indentLevel++;
foreach ($expr->params as $i => $param) {
if ($param->byRef) {
$this->fatalError($expr, 'Closure cannot use reference parameter');
}
$var = $this->parseIdentifier($param->var);
$fnCode .= 'auto ' . $var . ' = php::getCallArg(' . $i . ');' . PHP_EOL;
$this->addArgument($var, self::TYPE_VAR);
@ -3895,6 +3909,9 @@ class CompilerBase extends \PhpAot\Core\Translator
$fnBodyCode = '';
foreach ($expr->params as $i => $param) {
if ($param->byRef) {
$this->fatalError($expr, 'Closure cannot use reference parameter');
}
$var = $this->parseIdentifier($param->var);
$fnBodyCode .= 'auto ' . $var . ' = php::getCallArg(' . $i . ');' . PHP_EOL;
$this->addArgument($var, self::TYPE_VAR);

@ -159,7 +159,7 @@ class Preprocessor extends CompilerBase
{
$name = $this->getFunctionName($v);
if ($this->stubFile) {
$this->nativeFunctions[$name] = $this->parseFunctionDeclaration($v);
$this->nativeFunctions[$name] = $this->parseFunctionDecl($v);
} else {
$this->functionDeclInFile[$name] = $this->file;
}

@ -755,7 +755,11 @@ class Translator extends Preprocessor
if ($argInfo->default) {
$argExpr = 'php::getCallArg(' . $k . ', ' . $argInfo->default . ')';
} else {
$argExpr = 'php::getCallArg(' . $k . ')';
if ($argInfo->byRef) {
$argExpr = 'php::getCallArgByRef(' . $k . ')';
} else {
$argExpr = 'php::getCallArg(' . $k . ')';
}
}
$expr = $this->convertExprFromType($argInfo->type, $argExpr);
$cppCode .= $this->getIndent() . $argInfo->type . ' ' . $var . ' = ' . $expr . ';' . PHP_EOL;

@ -0,0 +1,18 @@
--TEST--
Ref function parameter
--FILE--
<?php
function test_fn(&$data)
{
$data .= " bar";
}
function main()
{
$s = "foo";
test_fn($s);
var_dump($s);
}
?>
--EXPECT--
string(7) "foo bar"
Loading…
Cancel
Save