feat(trait): 实现 trait 使用适配器功能支持别名和冲突解决

- 添加 traitAliases 和 traitIgnored 属性到 ClassDef 存储适配器配置
- 实现 trait 方法别名映射功能 (as)
- 实现 trait 方法冲突解决机制 (insteadof)
- 在预处理器中解析 trait use 语句的适配器选项
- 在翻译器中应用 trait 别名和忽略规则
- 添加完整的 trait 冲突测试用例验证功能正确性
pull/1/head v0.0.6
韩天峰 4 months ago
parent 6426046857
commit 57220d63b9
  1. 5
      src/Php/CompilerBase.php
  2. 10
      src/Php/Entity/ClassDef.php
  3. 48
      src/Php/Preprocessor.php
  4. 58
      src/Php/Translator.php
  5. 37
      tests/aot/trait/004.phpt

@ -644,6 +644,11 @@ class CompilerBase extends \PhpAot\Core\Translator
return ltrim($this->namespace . '\\' . $this->class, '\\');
}
protected function getFullMethodName(string $fullClassName, string $method): string
{
return strtolower($fullClassName . '::' . $method);
}
protected function getNamespacedClassName(string $class): string
{
if ($class === '') {

@ -32,6 +32,16 @@ class ClassDef extends ClassLikeDef
public bool $requireCtor = false;
public bool $enum = false;
public ?Trait_ $trait = null;
/**
* FullMethodName -> NewMethodName
* @var array<string, string>
*/
public array $traitAliases = [];
/**
* FullMethodName -> true
* @var array<string, bool>
*/
public array $traitIgnored = [];
public int $flags;
public bool $inheritedFromInternalClass = false;
public string $ctorInit = '';

@ -440,8 +440,10 @@ class Preprocessor extends CompilerBase
case 'Stmt_Property':
$this->parseClassPropertyDef($v);
break;
case 'Stmt_Nop':
case 'Stmt_TraitUse':
$this->prepareTraitUse($v);
break;
case 'Stmt_Nop':
case 'Stmt_EnumCase':
break;
case 'Stmt_ClassMethod':
@ -604,4 +606,48 @@ class Preprocessor extends CompilerBase
}
$this->resetMethod();
}
protected function parseTraitUseOptions(Node\Stmt\TraitUse $traitUse, array &$aliases, array &$ignored): void
{
foreach ($traitUse->adaptations as $adaptation) {
if (!$adaptation->trait) {
$this->fatalError($traitUse, "Trait `use` cannot use `use` without `as`");
}
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`");
}
/**
* 例如:
* use TraitA { TraitA::method as newMethod}
* 这表示 TraitA::method() 会被重命名为 TraitA::newMethod()
*/
$aliases[$this->getFullMethodName($trait1, $methodName)] = $adaptation->newName->toString();
}
if ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Precedence) {
$methodName = $adaptation->method->toString();
/**
* 例如:
* use TraitA { TraitA::method insteadof TraitB}
* 这表示 TraitB::method() 将会被忽略,真正执行的是 TraitA::method()
*/
foreach($adaptation->insteadof as $trait2) {
$ignored[$this->getFullMethodName($trait2, $methodName)] = true;
}
}
}
}
protected function prepareTraitUse(Node\Stmt\TraitUse $v): void
{
$aliases = [];
$ignored = [];
if ($v->adaptations) {
$this->parseTraitUseOptions($v, $aliases, $ignored);
}
$this->classDef->traitAliases = array_merge($this->classDef->traitAliases, $aliases);
$this->classDef->traitIgnored = array_merge($this->classDef->traitIgnored, $ignored);
}
}

@ -916,34 +916,6 @@ class Translator extends Preprocessor
$this->argInfoHeaderFiles[] = $headerFile;
}
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 = [];
@ -952,8 +924,7 @@ class Translator extends Preprocessor
$traitMethods = [];
$traitConstants = [];
$traitProperties = [];
$aliases = [];
$insteadof = [];
$classDef = $this->getClass($className);
foreach ($stmt->stmts as $classStmt) {
if ($classStmt instanceof Node\Stmt\ClassMethod) {
@ -972,9 +943,6 @@ class Translator extends Preprocessor
$constants[$name] = $const;
}
}
if ($classStmt instanceof Node\Stmt\TraitUse and $classStmt->adaptations) {
$this->parseTraitUseOptions($classStmt, $aliases, $insteadof);
}
}
foreach ($stmt->stmts as $classStmt) {
@ -1001,11 +969,16 @@ class Translator extends Preprocessor
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)];
$fullMethodName = $this->getFullMethodName($traitFullName, $methodName);
if (isset($classDef->traitAliases[$fullMethodName])) {
$alias = $classDef->traitAliases[$fullMethodName];
$traitStmt->name = new Node\Identifier($alias);
$methodName = $alias;
}
if (isset($classDef->traitIgnored[$fullMethodName])) {
unset($traitStmts[$k1]);
continue;
}
if (isset($methods[$methodName])) {
unset($traitStmts[$k1]);
}
@ -1297,12 +1270,6 @@ 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);
@ -1326,12 +1293,17 @@ class Translator extends Preprocessor
}
foreach ($traitDef->methods as $methodDef) {
$classMethodName = $traitMethodName = $methodDef->name;
$fullMethodName = $this->getFullMethodName($traitFullName, $traitMethodName);
// Trait 设置了别名
if (isset($aliases[$this->getFullMethodName($traitFullName, $classMethodName)])) {
$classMethodName = $aliases[$this->getFullMethodName($traitFullName, $classMethodName)];
if (isset($classDef->traitAliases[$fullMethodName])) {
$classMethodName = $classDef->traitAliases[$fullMethodName];
$methodDef = clone $methodDef;
$methodDef->name = $classMethodName;
}
// 设置了 insteadof 选项,此 Trait 的方法将不会被使用
if (isset($classDef->traitIgnored[$fullMethodName])) {
continue;
}
// 类中已经有同名方法,则不使用 Trait 中的方法
if ($classDef->hasMethod($classMethodName)) {
continue;

@ -0,0 +1,37 @@
--TEST--
Method conflict in traits
--FILE--
<?php
trait THello1
{
public function hello()
{
echo 'Hello 2', PHP_EOL;
}
}
trait THello2
{
public function hello()
{
echo 'Hello 3', PHP_EOL;
}
}
class TraitsTest
{
use THello1;
use THello2 {
THello2::hello insteadof THello1;
}
}
function main()
{
$o = new TraitsTest;
$o->hello();
}
?>
--EXPECT--
Hello 3
Loading…
Cancel
Save