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
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;
|
|
}
|
|
}
|
|
|