From 642604685752fccad7b68d61f6d25176ed0643d9 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 14 Apr 2026 19:11:59 +0800 Subject: [PATCH] =?UTF-8?q?fix(generator):=20=E8=A7=A3=E5=86=B3=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E4=B8=ADtrait=E4=BD=BF=E7=94=A8=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E5=B9=B6=E5=AE=8C=E5=96=84trait=E5=88=AB?= =?UTF-8?q?=E5=90=8D=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在gen_stub.php中添加对Interface_类型的检查,避免接口中使用trait时报错 - 移除Translator.php中generateStubFile的异常捕获逻辑,直接抛出异常 - 重构parseTraitUseForStub方法,支持完整的trait别名和冲突解决语法 - 添加getFullMethodName和parseTraitUseOptions辅助方法处理trait方法映射 - 完善trait属性、常量和方法的合并逻辑,支持insteadof和as关键字 - 更新parseTraitUse方法以正确处理trait别名映射关系 - 修复trait方法名称映射和代码生成中的多个边界情况 --- examples/error/trait_conflict001.php | 37 +++++++ src/Php/Translator.php | 150 ++++++++++++++++++++++++--- src/gen_stub.php | 4 +- tests/aot/trait/003.phpt | 35 +++++++ 4 files changed, 208 insertions(+), 18 deletions(-) create mode 100644 examples/error/trait_conflict001.php create mode 100644 tests/aot/trait/003.phpt diff --git a/examples/error/trait_conflict001.php b/examples/error/trait_conflict001.php new file mode 100644 index 00000000..b1448a67 --- /dev/null +++ b/examples/error/trait_conflict001.php @@ -0,0 +1,37 @@ +hello2(); +} diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 8c366b55..0c9eecbb 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -902,11 +902,7 @@ class Translator extends Preprocessor $stubFilenameWithoutExtension = str_replace(['.stub.php', '.php'], '', $file); $headerFile = $this->getArgInfoHeaderFile($stubFilenameWithoutExtension, true); - try { - generateStubFile($file, $this->getIncludeDir() . '/' . $headerFile, true); - } catch (\Throwable $e) { - $this->error("failed to generate arginfo header file: `{$headerFile}`, Error: {$e->getMessage()}"); - } + generateStubFile($file, $this->getIncludeDir() . '/' . $headerFile, true); $this->climate->info('generate stub file: ' . $this->getRelativePath($file)); if ($this->useRegisterSymbolsFn) { @@ -920,23 +916,128 @@ class Translator extends Preprocessor $this->argInfoHeaderFiles[] = $headerFile; } - public function parseTraitUseForStub(Node\Stmt\ClassLike $stmt): void + protected function getFullMethodName(string $fullClassName, string $method): string + { + return strtolower($fullClassName . '::' . $method); + } + + protected function parseTraitUseOptions(Node\Stmt\TraitUse $traitUse, array &$aliases, array &$insteadof) + { + foreach ($traitUse->adaptations as $adaptation) { + if ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) { + $trait1 = $this->getNamespacedClassName($adaptation->trait); + $methodName = $adaptation->method->toString(); + if ($adaptation->newModifier) { + $this->fatalError($traitUse, "Trait `{$trait1}` cannot use `newModifier`"); + } + $aliases[$this->getFullMethodName($trait1, $methodName)] = $adaptation->newName->toString(); + } + if ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Precedence) { + $trait1 = $this->getNamespacedClassName($adaptation->trait); + if (count($adaptation->insteadof) > 1) { + $this->fatalError($traitUse, "Trait `{$trait1}` cannot use `insteadof` for multiple traits"); + } + $trait2 = $this->getNamespacedClassName($adaptation->insteadof[0]); + $methodName = $adaptation->method->toString(); + $insteadof[$this->getFullMethodName($trait1, $methodName)] = $this->getFullMethodName($trait2, $methodName); + } + } + } + + public function parseTraitUseForStub(Node\Stmt\ClassLike $stmt, Node\Name $className): void { + $methods = []; + $constants = []; + $properties = []; + $traitMethods = []; + $traitConstants = []; + $traitProperties = []; + $aliases = []; + $insteadof = []; + + foreach ($stmt->stmts as $classStmt) { + if ($classStmt instanceof Node\Stmt\ClassMethod) { + $name = strtolower($classStmt->name->toString()); + $methods[$name] = $classStmt; + } + if ($classStmt instanceof Node\Stmt\Property) { + foreach ($classStmt->props as $prop) { + $name = strtolower($prop->name->toString()); + $properties[$name] = $prop; + } + } + if ($classStmt instanceof Node\Stmt\ClassConst) { + foreach ($classStmt->consts as $const) { + $name = strtolower($const->name->toString()); + $constants[$name] = $const; + } + } + if ($classStmt instanceof Node\Stmt\TraitUse and $classStmt->adaptations) { + $this->parseTraitUseOptions($classStmt, $aliases, $insteadof); + } + } + foreach ($stmt->stmts as $classStmt) { if (!$classStmt instanceof Node\Stmt\TraitUse) { continue; } - foreach ($classStmt->traits as $trait) { - $traitFullName = $this->getNamespacedClassName($trait); + + foreach ($classStmt->traits as $trait1) { + $traitFullName = $this->getNamespacedClassName($trait1); if (!$this->hasClass($traitFullName)) { $this->fatalError($classStmt, "Trait `{$traitFullName}` not found"); } - $classDef = $this->getClass($traitFullName); - if (!$classDef->trait) { + + $traitDef = $this->getClass($traitFullName); + if (!$traitDef->trait) { $this->fatalError($classStmt, "Trait `{$traitFullName}` not found"); } - // 务必注意顺序,如果类和 Trait 存在同名的方法,那么将使用类定义的方法 - $stmt->stmts = array_merge($classDef->trait->stmts, $stmt->stmts); + + $traitAst = clone $traitDef->trait; + $traitStmts = $traitAst->stmts; + foreach ($traitStmts as $k1 => $traitStmt) { + if ($traitStmt instanceof Node\Stmt\ClassMethod) { + $methodName = strtolower($traitStmt->name->toString()); + if (isset($traitMethods[$methodName])) { + $this->fatalError($classStmt, "Trait `{$traitFullName}` method `{$methodName}` already exists"); + } + if (isset($aliases[$this->getFullMethodName($traitFullName, $methodName)])) { + $alias = $aliases[$this->getFullMethodName($traitFullName, $methodName)]; + $traitStmt->name = new Node\Identifier($alias); + $methodName = $alias; + } + if (isset($methods[$methodName])) { + unset($traitStmts[$k1]); + } + $traitMethods[$methodName] = $traitStmt; + } + if ($traitStmt instanceof Node\Stmt\ClassConst) { + foreach ($traitStmt->consts as $k2 => $const) { + $constName = strtolower($const->name->toString()); + if (isset($constants[$constName])) { + unset($traitStmts[$k1][$k2]); + } + if (isset($traitConstants[$constName])) { + $this->fatalError($classStmt, "Trait `{$traitFullName}` constant `{$constName}` already exists"); + } + $traitConstants[$constName] = $const; + } + } + if ($traitStmt instanceof Node\Stmt\Property) { + foreach ($traitStmt->props as $k2 => $prop) { + $propName = strtolower($prop->name->toString()); + if (isset($properties[$propName])) { + unset($traitStmts[$k1][$k2]); + } + if (isset($traitProperties[$propName])) { + $this->fatalError($classStmt, "Trait `{$traitFullName}` property `{$propName}` already exists"); + } + $traitProperties[$propName] = $prop; + } + } + } + + $stmt->stmts = array_merge($stmt->stmts, $traitStmts); } } } @@ -1196,6 +1297,13 @@ class Translator extends Preprocessor protected function parseTraitUse(Node\Stmt\TraitUse $v, array &$methodCodes): void { $classDef = $this->classDef; + $aliases = []; + $insteadof = []; + + if ($v->adaptations) { + $this->parseTraitUseOptions($v, $aliases, $insteadof); + } + foreach ($v->traits as $trait) { $traitName = $this->parseIdentifier($trait); $traitFullName = $this->getNamespacedClassName($traitName); @@ -1217,13 +1325,21 @@ class Translator extends Preprocessor $classDef->properties[$prop->name] = $prop; } foreach ($traitDef->methods as $methodDef) { + $classMethodName = $traitMethodName = $methodDef->name; + // Trait 设置了别名 + if (isset($aliases[$this->getFullMethodName($traitFullName, $classMethodName)])) { + $classMethodName = $aliases[$this->getFullMethodName($traitFullName, $classMethodName)]; + $methodDef = clone $methodDef; + $methodDef->name = $classMethodName; + } // 类中已经有同名方法,则不使用 Trait 中的方法 - if ($classDef->hasMethod($methodDef->name)) { + if ($classDef->hasMethod($classMethodName)) { continue; } - $classDef->methods[$methodDef->name] = $methodDef; - $traitMethodNativeName = self::PREFIX . $this->getNativeName($methodDef->name, $traitDef->namespace, $traitDef->name); - $classMethodNativeName = self::PREFIX . $this->getNativeName($methodDef->name, $classDef->namespace, $classDef->name); + + $classDef->addMethod($methodDef); + $traitMethodNativeName = self::PREFIX . $this->getNativeName($traitMethodName, $traitDef->namespace, $traitDef->name); + $classMethodNativeName = self::PREFIX . $this->getNativeName($classMethodName, $classDef->namespace, $classDef->name); $argList = ['this_']; foreach ($methodDef->functionDef->argInfoList as $argInfo) { $argList[] = $argInfo->name; @@ -1248,7 +1364,7 @@ class Translator extends Preprocessor $code .= $this->getIndent() . $methodCall . ';' . PHP_EOL; $this->indentLevel--; $code .= $this->getIndent() . '}' . PHP_EOL; - $methodCodes[$methodDef->name] = $code; + $methodCodes[$classMethodName] = $code; } } } diff --git a/src/gen_stub.php b/src/gen_stub.php index df061f82..866ad9a6 100755 --- a/src/gen_stub.php +++ b/src/gen_stub.php @@ -4438,7 +4438,9 @@ class FileInfo { $methodInfos = []; $enumCaseInfos = []; - getTranslator()->parseTraitUseForStub($stmt); + if (!$stmt instanceof PhpParser\Node\Stmt\Interface_) { + getTranslator()->parseTraitUseForStub($stmt, $className); + } foreach ($stmt->stmts as $classStmt) { $cond = self::handlePreprocessorConditions($conds, $classStmt); diff --git a/tests/aot/trait/003.phpt b/tests/aot/trait/003.phpt new file mode 100644 index 00000000..13c94498 --- /dev/null +++ b/tests/aot/trait/003.phpt @@ -0,0 +1,35 @@ +--TEST-- +Method conflict in traits +--FILE-- +hello2(); + $o->hello(); +} + +?> +--EXPECT-- +Hello 2 +Hello 1 \ No newline at end of file