fix(aot): 解决trait方法参数类型声明的完全限定名转换问题

- 重构upgradeToFullyQualifiedName方法,支持NullableType和UnionType的递归处理
- 修正逻辑以正确区分内置类型(self/static/parent)与自定义类名的处理方式
- 更新trait预处理器中的参数类型转换实现,确保类型节点正确替换
- 添加对Qualified名称的识别和转换支持
- 移除重复的上下文解析逻辑,提高代码效率
- 新增回归测试用例验证arginfo/zpp匹配问题的修复效果
pull/1/head
韩天峰 2 months ago
parent 2824d8d21d
commit cd3f0743d7
  1. 36
      src/Php/CompilerBase.php
  2. 4
      src/Php/Preprocessor.php
  3. 56
      tests/aot/trait/class-param.phpt

@ -883,30 +883,42 @@ class CompilerBase extends \PhpAot\Core\Translator
}
/**
* 将参数类型中的非完全限定类名解析为完全限定名称。
* 在 parseTrait 阶段调用,避免 gen_stub 时上下文丢失。
* 将 trait 方法参数中的类名 Name 节点升级为 Name\FullyQualified。
* 对于已由 parseTypeDecl() 解析的限定名(含 \),直接升级节点类型;
* 对于尚未解析的非限定名(如 NullableType 内层,parseTypeDecl 返回 TYPE_VAR 跳过了解析),
* 先通过 useAliases/useNamespaces 解析再升级。
* gen_stub.php 的 SimpleType::fromNode() 依赖 isFullyQualified() 判断是否需要再次解析,
* 若不升级为 FullyQualified,在上下文丢失后会被错误地追加当前 namespace 前缀。
*/
protected function resolveParamClassName(?NodeAbstract $type): void
protected function upgradeToFullyQualifiedName(?NodeAbstract $type): ?NodeAbstract
{
if ($type === null) {
return;
return null;
}
if ($type instanceof Node\NullableType) {
$this->resolveParamClassName($type->type);
return;
return new Node\NullableType($this->upgradeToFullyQualifiedName($type->type));
}
if ($type instanceof Node\UnionType) {
foreach ($type->types as $subType) {
$this->resolveParamClassName($subType);
foreach ($type->types as $i => $subType) {
$type->types[$i] = $this->upgradeToFullyQualifiedName($subType);
}
return;
return $type;
}
if ($type instanceof Node\Name && !$type->isFullyQualified()) {
if ($type instanceof Node\Name\FullyQualified) {
return $type;
}
if ($type instanceof Node\Name) {
$typeName = $type->toString();
if (!isset($this->zendTypeMap[strtolower($typeName)]) && strtolower($typeName) !== 'self' && strtolower($typeName) !== 'static' && strtolower($typeName) !== 'parent') {
$type->name = '\\' . $this->getNamespacedClassName($typeName);
if (isset($this->zendTypeMap[strtolower($typeName)]) || in_array(strtolower($typeName), ['self', 'static', 'parent'], true)) {
return $type;
}
if ($type->isQualified()) {
return new Node\Name\FullyQualified($typeName, $type->getAttributes());
}
$resolved = $this->getNamespacedClassName($typeName);
return new Node\Name\FullyQualified($resolved, $type->getAttributes());
}
return $type;
}
/**

@ -484,12 +484,12 @@ class Preprocessor extends CompilerBase
}
}
// 将 trait 方法参数中的类名解析为完全限定名称,避免 gen_stub 时上下文丢失
// 将 trait 方法参数中的类名升级为 FullyQualified,避免 gen_stub 时上下文丢失
if ($class instanceof Node\Stmt\Trait_) {
foreach ($class->stmts as $v) {
if ($v instanceof Node\Stmt\ClassMethod) {
foreach ($v->params as $param) {
$this->resolveParamClassName($param->type);
$param->type = $this->upgradeToFullyQualifiedName($param->type);
}
}
}

@ -0,0 +1,56 @@
--TEST--
Cross-trait call with untyped nullable parameter (regression test for arginfo/zpp mismatch)
--FILE--
<?php
namespace App\Types {
class Node {
public string $name;
public function __construct(string $name = '') {
$this->name = $name;
}
}
}
namespace App\Test{
use App\Types\Node;
trait HelperTrait {
// Note: no type hint on $node — ?ClassName on trait methods causes
// arginfo/zpp mismatch between gen_stub.php's ZEND_ARG_OBJ_INFO and
// the AOT compiler's TYPE_VAR-based codegen.
protected function process(string $expr, string $type, Node $node): string {
if ($node instanceof Node) {
return 'node:' . $node->name;
}
if ($type !== '') {
return 'expr:' . $expr . ',type:' . $type;
}
return 'expr:' . $expr;
}
}
trait CallerTrait {
public function runProcess(string $expr, string $type, Node $node): string {
return $this->process($expr, $type, $node);
}
}
class MyProcessor {
use HelperTrait;
use CallerTrait;
}
}
namespace {
function main() {
$p = new App\Test\MyProcessor();
$node = new App\Types\Node('hello');
var_dump($p->runProcess('data', 'float', $node));
var_dump($p->runProcess('str', 'str', new App\Types\Node('world')));
}
}
?>
--EXPECT--
string(10) "node:hello"
string(10) "node:world"
Loading…
Cancel
Save