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.
 
 

177 lines
6.7 KiB

<?php
/**
* This file is part of TypePHP.
*
* @link https://www.swoole.com/
* @contact service@swoole.com
*/
namespace TypePhp\Transform;
use Closure;
use PhpParser\Node;
use PhpParser\Node\Stmt;
use PhpParser\NodeVisitorAbstract;
use TypePhp\Exception\CompileTimeAttributeError;
use TypePhp\Exception\SyntaxError;
use TypePhp\Diagnostics\CompileTimeAttributeDiagnostic;
class Visitor extends NodeVisitorAbstract
{
/** @param null|Closure(Node, string): void $warning */
public function __construct(
private readonly ?Closure $warning = null,
private readonly string $sourceFile = '',
) {
}
public function enterNode(Node $node): null
{
$this->guard($node, static fn () => CompileTimeAttribute::validateNode($node));
$this->guard($node, static fn () => NativeClassAttributeLowering::lower($node), 'Native');
$this->guard($node, static fn () => FunctionAttributeLowering::lower($node));
$this->guard($node, static fn () => GetterLowering::validateTarget($node), 'Getter');
$this->guard($node, static fn () => PropertyMethodLowering::validateTarget($node));
$this->guard($node, static fn () => ConstructorLowering::validateTarget($node), 'Constructor');
return null;
}
public function leaveNode(Node $node): null
{
if ($node instanceof Stmt\Function_ || $node instanceof Stmt\ClassMethod || $node instanceof Node\Expr\Closure) {
$this->guard(
$node,
fn () => ParameterValidationLowering::lowerFunction($node, $this->warning),
);
} elseif ($node instanceof Node\Expr\ArrowFunction) {
$this->guard($node, static fn () => ParameterValidationLowering::rejectArrowFunction($node));
}
if ($node instanceof Stmt\Interface_) {
foreach ($node->stmts as $stmt) {
if ($stmt instanceof Stmt\Property) {
PropertyHookLowering::markAbstractInterfaceProperty($stmt);
}
}
return null;
}
if (!$node instanceof Stmt\Class_ && !$node instanceof Stmt\Trait_ && !$node instanceof Stmt\Enum_) {
return null;
}
$methods = [];
$classReadonly = $node instanceof Stmt\Class_ && $node->isReadonly();
foreach ($node->stmts as $stmt) {
if ($stmt instanceof Stmt\Property) {
foreach (PropertyHookLowering::lowerProperty($stmt) as $method) {
$methods[] = $method;
}
foreach ($this->guard(
$stmt,
static fn () => GetterLowering::lowerProperty($stmt),
'Getter',
) as $method) {
$methods[] = $method;
}
foreach ($this->guard(
$stmt,
static fn () => PropertyMethodLowering::lowerProperty($stmt, $classReadonly),
) as $method) {
$methods[] = $method;
}
} elseif ($stmt instanceof Stmt\ClassMethod && $stmt->name->toLowerString() === '__construct') {
foreach ($stmt->params as $param) {
$marker = PropertyHookLowering::lowerPromotedProperty($param);
if ($marker !== null) {
$methods[] = $marker;
}
$getter = $this->guard(
$param,
static fn () => GetterLowering::lowerPromotedProperty($param),
'Getter',
);
if ($getter !== null) {
$methods[] = $getter;
}
foreach ($this->guard(
$param,
static fn () => PropertyMethodLowering::lowerPromotedProperty($param, $classReadonly),
) as $method) {
$methods[] = $method;
}
}
}
}
if ($methods !== []) {
foreach ($methods as $method) {
$node->stmts[] = $method;
}
}
$this->guard($node, static fn () => ConstructorLowering::lowerClassLike($node), 'Constructor');
if ($node instanceof Stmt\Class_) {
if (CompileTimeAttribute::find($node, 'Printer') !== null) {
$this->guard($node, static fn () => PrinterLowering::lowerClass($node), 'Printer');
}
if (CompileTimeAttribute::find($node, 'Arrayable') !== null) {
$this->guard($node, static fn () => ArrayableLowering::lowerClass($node), 'Arrayable');
}
}
return null;
}
private function guard(Node $target, Closure $operation, ?string $attribute = null): mixed
{
try {
return $operation();
} catch (SyntaxError $error) {
if (str_contains($error->getMessage(), '[compile-time attribute:')) {
throw $error;
}
$source = $target;
$conflictAttribute = null;
$conflictSource = null;
if ($error instanceof CompileTimeAttributeError) {
$target = $error->target;
$attribute = $error->attribute ?? $attribute;
$source = $error->attributeSource ?? $target;
$conflictAttribute = $error->conflictAttribute;
$conflictSource = $error->conflictSource;
} else {
[$detected, $attributeSource] = $this->detectAttribute($target);
$attribute ??= $detected;
$source = $attributeSource ?? $target;
}
$attribute ??= 'unknown';
$file = $this->sourceFile !== '' ? $this->sourceFile : '<unknown>';
throw new SyntaxError(CompileTimeAttributeDiagnostic::format(
$error->getMessage(),
$attribute,
$target,
$file,
$source,
$conflictAttribute,
$conflictSource,
), 0, $error);
}
}
/** @return array{?string, ?Node} */
private function detectAttribute(Node $node): array
{
if (!property_exists($node, 'attrGroups')) {
return [null, null];
}
foreach ($node->attrGroups as $group) {
foreach ($group->attrs as $attribute) {
$definition = CompileTimeAttributeRegistry::get(CompileTimeAttribute::resolvedName($attribute));
if ($definition !== null) {
return [$definition['name'], $attribute];
}
}
}
return [null, null];
}
}