diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 6eb8500a..2eb46437 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -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 === '') { diff --git a/src/Php/Entity/ClassDef.php b/src/Php/Entity/ClassDef.php index eedfa679..409f16ca 100644 --- a/src/Php/Entity/ClassDef.php +++ b/src/Php/Entity/ClassDef.php @@ -32,6 +32,16 @@ class ClassDef extends ClassLikeDef public bool $requireCtor = false; public bool $enum = false; public ?Trait_ $trait = null; + /** + * FullMethodName -> NewMethodName + * @var array + */ + public array $traitAliases = []; + /** + * FullMethodName -> true + * @var array + */ + public array $traitIgnored = []; public int $flags; public bool $inheritedFromInternalClass = false; public string $ctorInit = ''; diff --git a/src/Php/Preprocessor.php b/src/Php/Preprocessor.php index ba7db888..08b74ae2 100644 --- a/src/Php/Preprocessor.php +++ b/src/Php/Preprocessor.php @@ -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); + } } diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 0c9eecbb..4f58b29f 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -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; diff --git a/tests/aot/trait/004.phpt b/tests/aot/trait/004.phpt new file mode 100644 index 00000000..5b4facad --- /dev/null +++ b/tests/aot/trait/004.phpt @@ -0,0 +1,37 @@ +--TEST-- +Method conflict in traits +--FILE-- +hello(); +} + +?> +--EXPECT-- +Hello 3 \ No newline at end of file