- 新增 AnonClassGenerator trait 处理匿名类类型名称解析 - 实现匿名类内部类型引用转为全限定名称的功能 - 添加处理命名空间 use 导入的类型匹配逻辑 - 重构嵌入代码生成方法到独立 trait 中 - 添加匿名类继承和接口实现的全限定名称转换 - 新增测试用例验证匿名类在命名空间中的行为pull/3/head
parent
601b985600
commit
cd37d26e64
5 changed files with 283 additions and 115 deletions
@ -0,0 +1,199 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* This file is part of Swoole-Compiler(AOT). |
||||||
|
* |
||||||
|
* @link https://www.swoole.com/ |
||||||
|
* @contact service@swoole.com |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace PhpAot\Php\Generator; |
||||||
|
|
||||||
|
use PhpParser\Node; |
||||||
|
use PhpParser\NodeAbstract; |
||||||
|
use PhpParser\Node\Identifier; |
||||||
|
use PhpParser\Node\IntersectionType; |
||||||
|
use PhpParser\Node\Name; |
||||||
|
use PhpParser\Node\NullableType; |
||||||
|
use PhpParser\Node\UnionType; |
||||||
|
use PhpParser\Node\Stmt\Class_; |
||||||
|
use PhpParser\Node\Stmt\ClassConst; |
||||||
|
use PhpParser\Node\Stmt\ClassMethod; |
||||||
|
use PhpParser\Node\Stmt\Property; |
||||||
|
use PhpParser\NodeTraverser; |
||||||
|
use PhpParser\NodeVisitorAbstract; |
||||||
|
use PhpAot\Php\Reflection; |
||||||
|
|
||||||
|
trait AnonClassGenerator |
||||||
|
{ |
||||||
|
public function genAnonClassName(): string |
||||||
|
{ |
||||||
|
return self::ANON_CLASS . $this->anonClassIndex++; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Resolve all relative type names in an anonymous class to fully qualified names. |
||||||
|
* The generated eval code runs without use imports, so all type references must be FQN. |
||||||
|
*/ |
||||||
|
protected function resolveAnonClassTypeNames(Class_ $classDef): void |
||||||
|
{ |
||||||
|
foreach ($classDef->stmts as $stmt) { |
||||||
|
if ($stmt instanceof ClassMethod) { |
||||||
|
foreach ($stmt->params as $param) { |
||||||
|
if ($param->type !== null) { |
||||||
|
$param->type = $this->resolveTypeNode($param->type); |
||||||
|
} |
||||||
|
} |
||||||
|
if ($stmt->returnType !== null) { |
||||||
|
$stmt->returnType = $this->resolveTypeNode($stmt->returnType); |
||||||
|
} |
||||||
|
} elseif ($stmt instanceof Property) { |
||||||
|
if ($stmt->type !== null) { |
||||||
|
$stmt->type = $this->resolveTypeNode($stmt->type); |
||||||
|
} |
||||||
|
} elseif ($stmt instanceof ClassConst) { |
||||||
|
if ($stmt->type !== null) { |
||||||
|
$stmt->type = $this->resolveTypeNode($stmt->type); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Resolve a single type node, converting relative Name to FullyQualified. |
||||||
|
*/ |
||||||
|
protected function resolveTypeNode(Node $type): Node |
||||||
|
{ |
||||||
|
if ($type instanceof Name) { |
||||||
|
if ($type->isFullyQualified()) { |
||||||
|
return $type; |
||||||
|
} |
||||||
|
$resolved = $this->getNamespacedClassName($type->toString()); |
||||||
|
return new Name\FullyQualified($resolved); |
||||||
|
} |
||||||
|
if ($type instanceof NullableType) { |
||||||
|
$type->type = $this->resolveTypeNode($type->type); |
||||||
|
return $type; |
||||||
|
} |
||||||
|
if ($type instanceof UnionType) { |
||||||
|
foreach ($type->types as $i => $subType) { |
||||||
|
$type->types[$i] = $this->resolveTypeNode($subType); |
||||||
|
} |
||||||
|
return $type; |
||||||
|
} |
||||||
|
if ($type instanceof IntersectionType) { |
||||||
|
foreach ($type->types as $i => $subType) { |
||||||
|
$type->types[$i] = $this->resolveTypeNode($subType); |
||||||
|
} |
||||||
|
return $type; |
||||||
|
} |
||||||
|
return $type; |
||||||
|
} |
||||||
|
|
||||||
|
protected function genEmbeddedCode(NodeAbstract $stmt): string |
||||||
|
{ |
||||||
|
if ($stmt instanceof Class_) { |
||||||
|
$stmt = clone $stmt; |
||||||
|
$shouldAddMixedReturn = fn (Class_ $class, ClassMethod $method): bool => |
||||||
|
$this->shouldAddMixedReturnToEmbeddedClassMethod($class, $method); |
||||||
|
$traverser = new NodeTraverser(); |
||||||
|
$traverser->addVisitor(new class($shouldAddMixedReturn) extends NodeVisitorAbstract { |
||||||
|
/** @var list<Class_> */ |
||||||
|
private array $classStack = []; |
||||||
|
|
||||||
|
public function __construct(private \Closure $shouldAddMixedReturn) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function enterNode(Node $node) |
||||||
|
{ |
||||||
|
if ($node instanceof Class_) { |
||||||
|
$this->classStack[] = $node; |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
if ($node instanceof ClassMethod && $node->returnType === null) { |
||||||
|
$class = $this->classStack[count($this->classStack) - 1] ?? null; |
||||||
|
if ($class !== null && ($this->shouldAddMixedReturn)($class, $node)) { |
||||||
|
$node->returnType = new Identifier('mixed'); |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public function leaveNode(Node $node) |
||||||
|
{ |
||||||
|
if ($node instanceof Class_) { |
||||||
|
array_pop($this->classStack); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
}); |
||||||
|
$stmt = $traverser->traverse([$stmt])[0]; |
||||||
|
} |
||||||
|
return $this->printer->prettyPrint([$stmt]); |
||||||
|
} |
||||||
|
|
||||||
|
protected function shouldAddMixedReturnToEmbeddedClassMethod(Class_ $class, ClassMethod $method): bool |
||||||
|
{ |
||||||
|
$methodName = strtolower($method->name->toString()); |
||||||
|
if ($this->isEmbeddedMagicMethodReturnSensitive($methodName)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if (!empty($class->implements)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
return $class->extends !== null |
||||||
|
&& $this->ancestorMethodMayRequireMixedReturn($class->extends, $methodName); |
||||||
|
} |
||||||
|
|
||||||
|
protected function isEmbeddedMagicMethodReturnSensitive(string $methodName): bool |
||||||
|
{ |
||||||
|
return in_array($methodName, [ |
||||||
|
'__construct', |
||||||
|
'__destruct', |
||||||
|
'__clone', |
||||||
|
'__debuginfo', |
||||||
|
'__isset', |
||||||
|
'__serialize', |
||||||
|
'__set', |
||||||
|
'__set_state', |
||||||
|
'__sleep', |
||||||
|
'__tostring', |
||||||
|
'__unserialize', |
||||||
|
'__unset', |
||||||
|
'__wakeup', |
||||||
|
], true); |
||||||
|
} |
||||||
|
|
||||||
|
protected function ancestorMethodMayRequireMixedReturn(Name $extends, string $methodName): bool |
||||||
|
{ |
||||||
|
$className = ltrim($extends->toString(), '\\'); |
||||||
|
|
||||||
|
while ($className !== '') { |
||||||
|
if ($this->hasClass($className)) { |
||||||
|
$classDef = $this->getClass($className); |
||||||
|
if ($classDef->hasMethod($methodName)) { |
||||||
|
$functionDef = $classDef->getMethod($methodName)->functionDef; |
||||||
|
return $functionDef !== null |
||||||
|
&& ($functionDef->returnTypeUndeclared || $functionDef->returnType === self::TYPE_VAR); |
||||||
|
} |
||||||
|
$className = $classDef->extends; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if ($this->isInternalClass($className) || $this->isInternalInterface($className)) { |
||||||
|
if (!Reflection::hasMethod($className, $methodName)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
$returnType = Reflection::getMethodReturnType($className, $methodName); |
||||||
|
return $returnType === null || strtolower($returnType) === 'mixed'; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
--TEST-- |
||||||
|
Anonymous class inside namespace |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
namespace Foo\App { |
||||||
|
class Node { |
||||||
|
public string $name; |
||||||
|
} |
||||||
|
|
||||||
|
abstract class VisitorAbstract { |
||||||
|
abstract public function test(Node $node): string; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
namespace Bar\App { |
||||||
|
use Foo\App\Node; |
||||||
|
use Foo\App\VisitorAbstract; |
||||||
|
|
||||||
|
function test() { |
||||||
|
$node = new Node(); |
||||||
|
$node->name = "World"; |
||||||
|
$obj = new class() extends VisitorAbstract { |
||||||
|
public function test(Node $node): string { |
||||||
|
return 'Hello ' . $node->name . " !"; |
||||||
|
} |
||||||
|
}; |
||||||
|
var_dump($obj->test($node)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
namespace { |
||||||
|
function main() { |
||||||
|
Bar\App\test(); |
||||||
|
echo "done\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(13) "Hello World !" |
||||||
|
done |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
--TEST-- |
||||||
|
Namespace with constants defined via const keyword |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
namespace Foo\App { |
||||||
|
class Printer { |
||||||
|
public function print() { |
||||||
|
echo "Hello World!\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
namespace Bar\App { |
||||||
|
use Foo\App; |
||||||
|
function test(){ |
||||||
|
$o = new App\Printer; |
||||||
|
$o->print(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
namespace { |
||||||
|
function main() { |
||||||
|
Bar\App\test(); |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
Hello World! |
||||||
@ -1 +1 @@ |
|||||||
1069 |
1070 |
||||||
|
|||||||
Loading…
Reference in new issue