feat(aot): 添加对 PHP Trait 特性的支持

- 实现了 Trait 的基本语法解析和处理功能
- 添加了 trait 属性、方法、常量的继承机制
- 实现了 trait 使用时的命名空间解析
- 添加了类与 trait 同名成员的冲突处理逻辑
- 新增 trait 相关的测试用例
- 重构了类定义结构以支持 trait 类型判断
pull/1/head
韩天峰 4 months ago
parent f4b8d6c1f3
commit 88693dcc21
  1. 3
      src/Php/Entity/ClassDef.php
  2. 2
      src/Php/Preprocessor.php
  3. 31
      src/Php/Translator.php
  4. 5
      src/gen_stub.php
  5. 40
      tests/aot/trait/001.phpt
  6. 17
      tests/aot/trait/002.phpt

@ -9,6 +9,7 @@
namespace PhpAot\Php\Entity;
use PhpAot\Php\Context\FunctionContext;
use PhpParser\Node\Stmt\Trait_;
class ClassDef extends ClassLikeDef
{
@ -30,7 +31,7 @@ class ClassDef extends ClassLikeDef
public string $extends = '';
public bool $requireCtor = false;
public bool $enum = false;
public bool $trait = false;
public ?Trait_ $trait = null;
public int $flags;
public bool $inheritedFromInternalClass = false;
public string $ctorInit = '';

@ -422,7 +422,7 @@ class Preprocessor extends CompilerBase
if (!$class instanceof Node\Stmt\Trait_) {
$this->classDef->implements = $this->parseImplements($class->implements);
} else {
$this->classDef->trait = true;
$this->classDef->trait = $class;
}
if (isset($this->symbolDeclInFile[$fullClassNameLower])) {
$this->fatalError($class, "Duplicate class `{$fullClassName}`");

@ -920,6 +920,27 @@ class Translator extends Preprocessor
$this->argInfoHeaderFiles[] = $headerFile;
}
public function parseTraitUseForStub(Node\Stmt\ClassLike $stmt): void
{
foreach ($stmt->stmts as $classStmt) {
if (!$classStmt instanceof Node\Stmt\TraitUse) {
continue;
}
foreach ($classStmt->traits as $trait) {
$traitFullName = $this->getNamespacedClassName($trait);
if (!$this->hasClass($traitFullName)) {
$this->fatalError($classStmt, "Trait `{$traitFullName}` not found");
}
$classDef = $this->getClass($traitFullName);
if (!$classDef->trait) {
$this->fatalError($classStmt, "Trait `{$traitFullName}` not found");
}
// 务必注意顺序,如果类和 Trait 存在同名的方法,那么将使用类定义的方法
$stmt->stmts = array_merge($classDef->trait->stmts, $stmt->stmts);
}
}
}
protected function parseClass(Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $class): string
{
$this->class = $this->parseIdentifier($class->name);
@ -1184,12 +1205,22 @@ class Translator extends Preprocessor
$traitDef = $this->getClass($traitFullName);
// 将 Trait 中定义的 常量、静态常量、属性、方法、静态属性复制到当前类中
foreach ($traitDef->constants as $const) {
if ($classDef->hasConstant($const->name)) {
continue;
}
$classDef->constants[$const->name] = $const;
}
foreach ($traitDef->properties as $prop) {
if ($classDef->hasProperty($prop->name)) {
continue;
}
$classDef->properties[$prop->name] = $prop;
}
foreach ($traitDef->methods as $methodDef) {
// 类中已经有同名方法,则不使用 Trait 中的方法
if ($classDef->hasMethod($methodDef->name)) {
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);

@ -4437,6 +4437,9 @@ class FileInfo {
$propertyInfos = [];
$methodInfos = [];
$enumCaseInfos = [];
getTranslator()->parseTraitUseForStub($stmt);
foreach ($stmt->stmts as $classStmt) {
$cond = self::handlePreprocessorConditions($conds, $classStmt);
if ($classStmt instanceof Stmt\Nop) {
@ -4496,6 +4499,8 @@ class FileInfo {
} else if ($classStmt instanceof Stmt\EnumCase) {
$enumCaseInfos[] = new EnumCaseInfo(
$classStmt->name->toString(), $classStmt->expr);
} else if ($classStmt instanceof Stmt\TraitUse) {
continue;
} else {
throw new Exception("Not implemented {$classStmt->getType()}");
}

@ -0,0 +1,40 @@
--TEST--
Single Trait with simple trait method
--FILE--
<?php
trait THello {
protected string $name = 'world';
static public int $count = 0;
public const BAZ = 'baz';
public function hello() {
echo 'Hello ' . $this->name . PHP_EOL;
$this->foo();
}
public function foo(): void {
$array = [
'count' => self::$count,
'baz' => self::BAZ,
];
var_dump($array);
}
}
class TraitsTest {
use THello;
}
function main() {
$test = new TraitsTest();
$test->hello();
}
?>
--EXPECT--
Hello world
array(2) {
["count"]=>
int(0)
["baz"]=>
string(3) "baz"
}

@ -0,0 +1,17 @@
--TEST--
Single Trait with simple trait method
--FILE--
<?php
trait HelloTrait {
public $name = 'world';
public function hello() {
echo 'Hello ' . $this->name . PHP_EOL;
}
}
function main() {
eval("(new class { use HelloTrait;})->hello();");
}
?>
--EXPECT--
Hello world
Loading…
Cancel
Save