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.
 
 

199 lines
6.5 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\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use TypePhp\Resolver\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): 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');
}
}
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;
}
}