TypePHP 编译器 https://swoole.com/aot/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

269 lines
9.7 KiB

<?php
/**
* This file is part of TypePHP.
*
* @link https://www.swoole.com/
* @contact service@swoole.com
*/
namespace TypePhp\Generator;
use TypePhp\Type;
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\Node\Stmt\TraitUse;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use TypePhp\Resolver\Reflection;
trait AnonClassGenerator
{
public function genAnonClassName(): string
{
return self::ANON_CLASS . $this->anonClassIndex++;
}
/** Flatten trait templates before an anonymous class is evaluated by ZendVM. */
protected function flattenEmbeddedClassTraits(Class_ $class): void
{
$declaredMethods = [];
foreach ($class->stmts as $stmt) {
if ($stmt instanceof ClassMethod) {
$declaredMethods[strtolower($stmt->name->toString())] = true;
}
}
$injected = [];
foreach ($class->stmts as $stmt) {
if (!$stmt instanceof TraitUse) {
continue;
}
foreach ($stmt->traits as $traitName) {
$fullName = $this->getNamespacedClassName($traitName->toString());
if (!$this->hasClass($fullName)) {
$this->fatalError($stmt, "Trait `{$fullName}` not found");
}
$traitDef = $this->getClass($fullName);
$traitAst = clone $traitDef->trait;
foreach ($traitAst->stmts as $traitStmt) {
if ($traitStmt instanceof ClassMethod) {
$name = strtolower($traitStmt->name->toString());
if (isset($declaredMethods[$name])) {
continue;
}
$declaredMethods[$name] = true;
}
if (!$traitStmt instanceof TraitUse) {
$injected[] = $traitStmt;
}
}
}
}
$class->stmts = array_values(array_filter(
$class->stmts,
static fn (Node $stmt): bool => !$stmt instanceof TraitUse,
));
array_push($class->stmts, ...$injected);
}
/** Resolve imported names in an anonymous class before evaluating it in the root namespace. */
protected function resolveAnonClassNames(Class_ $classDef): void
{
// Anonymous classes are emitted through eval() in the root namespace. Names
// resolved from the declaring file's namespace and imports must therefore be
// embedded as fully-qualified names, including names used inside method bodies.
$traverser = new NodeTraverser();
$traverser->addVisitor(new class extends NodeVisitorAbstract {
public function enterNode(Node $node): ?Node
{
if (!$node instanceof Name || $node->isSpecialClassName()) {
return null;
}
$resolvedName = $node->getAttribute('resolvedName');
if (!$resolvedName instanceof Name) {
return null;
}
return new Name\FullyQualified($resolvedName->toString(), $node->getAttributes());
}
});
$traverser->traverse([$classDef]);
// Lowering may synthesize type nodes after name resolution, so retain the
// explicit signature pass for nodes which do not carry resolvedName metadata.
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): NodeAbstract
{
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;
$traverser = new NodeTraverser();
$traverser->addVisitor(new class($this) extends NodeVisitorAbstract {
/** @var list<Class_> */
private array $classStack = [];
public function __construct(private object $compiler)
{
}
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->compiler->shouldAddMixedReturnToEmbeddedClassMethod($class, $node)) {
$node->returnType = new Identifier('mixed');
// An implicit fallthrough returns null in PHP. Once the
// compatibility return type is made explicit, PHP requires
// an explicit value on that path as well.
if ($node->stmts !== null) {
$node->stmts[] = new Node\Stmt\Return_(
new Node\Expr\ConstFetch(new Name('null')),
);
}
}
}
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]);
}
public 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 === 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;
}
}