From 399f77d5c57e37f62f0dac9b32f2d55a45e00de4 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 30 Jun 2026 17:48:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(php):=20=E5=AE=9E=E7=8E=B0=E5=AF=B9?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E5=8F=82=E6=95=B0=E5=BC=95=E7=94=A8=E4=BC=A0?= =?UTF-8?q?=E9=80=92=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了 AOT 调用参数信息获取功能 - 实现了按索引和名称获取参数信息的方法 - 增加了对命名参数引用传递的判断支持 - 添加了引用参数值解析功能 - 优化了新对象构造函数参数传递逻辑 - 完善了对动态调用和继承链的参数分析 - 新增静态函数、方法和构造函数引用参数测试用例 --- src/Php/CompilerBase.php | 179 ++++++++++++++++++++++++++- tests/aot/ref/static-byref-call.phpt | 54 ++++++++ 2 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 tests/aot/ref/static-byref-call.phpt diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index e374ec1f..0cc2070d 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -3298,6 +3298,11 @@ class CompilerBase extends \PhpAot\Core\Translator protected function isReferenceArgument($funcName, $className, $argIndex): bool { + $argInfo = $this->getAotCallArgInfo($funcName, $className, $argIndex); + if ($argInfo !== null) { + return $argInfo->byRef; + } + if ($className) { // 动态调用类方法,无法判断参数是否为引用 if ($className === self::DYNAMIC_CALLED_CLASS) { @@ -3317,6 +3322,119 @@ class CompilerBase extends \PhpAot\Core\Translator return $variadicParam !== null && $variadicParam->isPassedByReference(); } + protected function getAotCallArgInfo(string $funcName, string $className, int $argIndex): ?ArgInfo + { + if ($className !== '') { + if ($className === self::DYNAMIC_CALLED_CLASS || !$this->hasClass($className)) { + return null; + } + $classDef = $this->getClass($className); + while (true) { + if ($classDef->hasMethod($funcName)) { + return $this->getArgInfoByIndex($classDef->getMethod($funcName)->functionDef, $argIndex); + } + if (!$classDef->extends || !$this->hasClass($classDef->extends)) { + return null; + } + $classDef = $this->getClass($classDef->extends); + } + } + + if (!$this->hasFunction($funcName)) { + return null; + } + return $this->getArgInfoByIndex($this->getFunction($funcName), $argIndex); + } + + protected function getAotCallArgInfoByName(string $funcName, string $className, string $argName): ?ArgInfo + { + $functionDef = null; + if ($className !== '') { + if ($className === self::DYNAMIC_CALLED_CLASS || !$this->hasClass($className)) { + return null; + } + $classDef = $this->getClass($className); + while (true) { + if ($classDef->hasMethod($funcName)) { + $functionDef = $classDef->getMethod($funcName)->functionDef; + break; + } + if (!$classDef->extends || !$this->hasClass($classDef->extends)) { + return null; + } + $classDef = $this->getClass($classDef->extends); + } + } elseif ($this->hasFunction($funcName)) { + $functionDef = $this->getFunction($funcName); + } + + if ($functionDef === null) { + return null; + } + + $variadicArgInfo = null; + foreach ($functionDef->argInfoList as $argInfo) { + if ($argInfo->variadic) { + $variadicArgInfo = $argInfo; + } + if (($argInfo->phpName ?: $this->unescapeVarName($argInfo->name)) === $argName) { + return $argInfo; + } + } + return $variadicArgInfo; + } + + protected function getArgInfoByIndex(FunctionDef $functionDef, int $argIndex): ?ArgInfo + { + if (array_key_exists($argIndex, $functionDef->argInfoList)) { + return $functionDef->argInfoList[$argIndex]; + } + if ($functionDef->hasVariadicArg()) { + return $functionDef->argInfoList[array_key_last($functionDef->argInfoList)]; + } + return null; + } + + protected function isReferenceNamedArgument(string $funcName, string $className, string $argName): bool + { + $argInfo = $this->getAotCallArgInfoByName($funcName, $className, $argName); + if ($argInfo !== null) { + return $argInfo->byRef; + } + + if ($className) { + if ($className === self::DYNAMIC_CALLED_CLASS) { + return false; + } + $ref = Reflection::getClass($className); + if (!$ref) { + return false; + } + try { + $params = $ref->getMethod($funcName)->getParameters(); + } catch (\ReflectionException) { + return false; + } + } else { + $ref = Reflection::getFunction($funcName); + if (!$ref) { + return false; + } + $params = $ref->getParameters(); + } + + $variadicParam = null; + foreach ($params as $param) { + if ($param->isVariadic()) { + $variadicParam = $param; + } + if ($param->getName() === $argName) { + return $param->isPassedByReference(); + } + } + return $variadicParam !== null && $variadicParam->isPassedByReference(); + } + protected function parseCallArgs( array $args, string $funcName = '', @@ -3384,12 +3502,16 @@ class CompilerBase extends \PhpAot\Core\Translator $this->fatalError($arg, "Duplicate named argument `{$arg->name->name}`"); } $namedArgs[$arg->name->name] = true; + $byRef = $funcName && $this->isReferenceNamedArgument($funcName, $className, $arg->name->name); + $value = ($byRef || $this->isRefvalCall($arg->value)) + ? $this->parseReferenceCallArgValue($arg) + : $this->parseCallArgValue($arg); if ($separateNamedArgs) { $namedArgsArray = $ensureNamedArgs(); - $this->context->beforeStmtLines[] = $namedArgsArray . '.set(' . $this->getLiteralString($arg->name->name) . ', ' . $this->parseCallArgValue($arg) . ');'; + $this->context->beforeStmtLines[] = $namedArgsArray . '.set(' . $this->getLiteralString($arg->name->name) . ', ' . $value . ');'; } else { $arrayArgs = $ensureArrayArgs(); - $this->context->beforeStmtLines[] = $arrayArgs . '.set(' . $this->getLiteralString($arg->name->name) . ', ' . $this->parseCallArgValue($arg) . ');'; + $this->context->beforeStmtLines[] = $arrayArgs . '.set(' . $this->getLiteralString($arg->name->name) . ', ' . $value . ');'; } continue; } @@ -3507,6 +3629,54 @@ class CompilerBase extends \PhpAot\Core\Translator return $value instanceof Expr\PropertyFetch; } + protected function parseReferenceCallArgValue(Node\Arg $arg): string + { + if ($this->isRefvalCall($arg->value)) { + if (count($arg->value->args) !== 1) { + $this->fatalError($arg, 'The refval function only accepts one parameter'); + } + $arg->value = $arg->value->args[0]->value; + } + + if ($this->isVarExpr($arg->value)) { + return $this->parseArgRefVar($arg, $this->parseIdentifier($arg->value)); + } + + if ($this->isPropertyFetch($arg->value) and $this->isVarExpr($arg->value->var)) { + $obj = $this->parseIdentifier($arg->value->var); + if (!$this->hasVar($obj)) { + $this->fatalError($arg, 'Undefined variable `$' . $obj . '`'); + } + return $obj . '.attrRef(' . $this->identifierToStr($arg->value->name) . ')'; + } + + if ($this->isArrayDimFetch($arg->value) and $this->isVarExpr($arg->value->var)) { + $array = $this->parseIdentifier($arg->value->var); + if ($array === 'GLOBALS') { + $globalVar = $this->parseGlobalsArrayDimFetch($arg->value); + $ref = $this->addTmpVar(self::TYPE_REF); + $this->context->beforeStmtLines[] = $ref . ' = ' . $globalVar . '.toReference();'; + return '&' . $ref; + } + if (!$this->hasVar($array)) { + $this->fatalError($arg, 'Undefined variable `$' . $array . '`'); + } + if ($arg->value->dim === null) { + $this->fatalError($arg, 'Array dimension must be a constant expression'); + } + return $array . '.itemRef(' . $this->identifierToStr($arg->value->dim) . ')'; + } + + if ($this->isScalar($arg->value)) { + $this->fatalError($arg, 'The constants cannot be used as an argument for a reference-type parameter'); + } + + $tmpRef = $this->genTmpVarName(); + $this->addLocalVar($tmpRef, self::TYPE_REF); + $this->context->beforeStmtLines[] = $tmpRef . ' = ' . $this->parseChainedExpr($arg->value, self::OP_REFVAL) . ';'; + return '&' . $tmpRef; + } + /** * 展开 refval() 调用中的数组元素或对象属性,返回对应的 C++ 引用表达式。 * 若为普通变量则返回 null,由调用方自行处理。 @@ -4017,6 +4187,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseNew(Expr\New_ $expr): string { + $ctorClassName = ''; // 匿名类 if ($expr->class instanceof Node\Stmt\Class_) { if ($expr->class->name === null) { @@ -4043,6 +4214,7 @@ class CompilerBase extends \PhpAot\Core\Translator . $className . '_defined = true; php::eval((const char *)' . $className . '_code);}'; $className = '\\' . $className; $cePtr = $this->getClassEntryPtr($className); + $ctorClassName = $className; } else { $this->fatalError($expr, 'must be anonymous class'); } @@ -4062,6 +4234,7 @@ class CompilerBase extends \PhpAot\Core\Translator } else { $className = $this->getNamespacedClassName($className); } + $ctorClassName = $className; if ($this->hasClass($className)) { $classDef = $this->getClass($className); if ($classDef->flags & Modifiers::ABSTRACT) { @@ -4079,7 +4252,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, '__construct', $ctorClassName) . ')'; } protected function parseClone(Expr\Clone_ $expr): string diff --git a/tests/aot/ref/static-byref-call.phpt b/tests/aot/ref/static-byref-call.phpt new file mode 100644 index 00000000..a0bfbc74 --- /dev/null +++ b/tests/aot/ref/static-byref-call.phpt @@ -0,0 +1,54 @@ +--TEST-- +Static function, method, and constructor calls pass by-reference args automatically +--FILE-- +mutate($value); + new RefChild($value); + mutate_named_arg(...['named-function'], value: $value); + $target->mutateNamed(...['named-method'], value: $value); + $fn = 'mutate_named_arg'; + $fn(...['dynamic-refval'], value: refval($value)); + echo $value, PHP_EOL; +} + +?> +--EXPECT-- +start:function:ctor:method:ctor:named-function:named-method:dynamic-refval