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.
222 lines
7.5 KiB
222 lines
7.5 KiB
<?php
|
|
/**
|
|
* This file is part of TypePHP.
|
|
*
|
|
* @link https://www.swoole.com/
|
|
* @contact service@swoole.com
|
|
*/
|
|
|
|
namespace TypePhp\Generator;
|
|
|
|
use PhpParser\Modifiers;
|
|
use PhpParser\Node;
|
|
use PhpParser\NodeTraverser;
|
|
use PhpParser\NodeVisitor\NameResolver;
|
|
use PhpParser\Parser;
|
|
use PhpParser\PrettyPrinter;
|
|
use TypePhp\Exception\SyntaxError;
|
|
use TypePhp\Transform\CompileTimeAttribute;
|
|
use TypePhp\Transform\CompileTimeAttributeRegistry;
|
|
|
|
final class LibraryImportStubGenerator
|
|
{
|
|
public function __construct(
|
|
private readonly Parser $parser,
|
|
private readonly PrettyPrinter $printer,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @param array<string> $files
|
|
* @param array<string, true> $externalImportStubFiles
|
|
*/
|
|
public function generate(array $files, array $externalImportStubFiles): string
|
|
{
|
|
/** @var array<string, array<Node\Stmt>> $namespaces */
|
|
$namespaces = [];
|
|
|
|
foreach ($files as $file) {
|
|
$realFile = realpath($file);
|
|
if ($realFile === false || isset($externalImportStubFiles[$realFile])) {
|
|
continue;
|
|
}
|
|
if (pathinfo($realFile, PATHINFO_EXTENSION) !== 'php') {
|
|
continue;
|
|
}
|
|
|
|
$code = file_get_contents($realFile);
|
|
if ($code === false) {
|
|
throw new \RuntimeException('Can not read file: ' . $realFile);
|
|
}
|
|
$ast = $this->parser->parse($code) ?? [];
|
|
$traverser = new NodeTraverser();
|
|
$traverser->addVisitor(new NameResolver(null, ['preserveOriginalNames' => true]));
|
|
$ast = $traverser->traverse($ast);
|
|
|
|
foreach ($ast as $stmt) {
|
|
if ($stmt instanceof Node\Stmt\Namespace_) {
|
|
$namespace = $stmt->name?->toString() ?? '';
|
|
$this->appendDeclarations($namespaces, $namespace, $stmt->stmts);
|
|
continue;
|
|
}
|
|
$this->appendDeclarations($namespaces, '', [$stmt]);
|
|
}
|
|
}
|
|
|
|
$namespaceNodes = [];
|
|
foreach ($namespaces as $namespace => $stmts) {
|
|
if ($stmts === []) {
|
|
continue;
|
|
}
|
|
$namespaceNodes[] = new Node\Stmt\Namespace_(
|
|
$namespace === '' ? null : new Node\Name($namespace),
|
|
$stmts,
|
|
);
|
|
}
|
|
|
|
$code = "<?php\n\n/** @import-library */\n\n";
|
|
if ($namespaceNodes !== []) {
|
|
$code .= $this->printer->prettyPrint($namespaceNodes) . "\n";
|
|
}
|
|
return $code;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, array<Node\Stmt>> $namespaces
|
|
* @param array<Node\Stmt> $stmts
|
|
*/
|
|
private function appendDeclarations(array &$namespaces, string $namespace, array $stmts): void
|
|
{
|
|
foreach ($stmts as $stmt) {
|
|
if ($namespace === '' && $stmt instanceof Node\Stmt\Function_
|
|
&& strtolower($stmt->name->toString()) === 'main') {
|
|
continue;
|
|
}
|
|
$declaration = $this->makeImportDeclaration($stmt);
|
|
if ($declaration !== null) {
|
|
$namespaces[$namespace][] = $declaration;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function makeImportDeclaration(Node\Stmt $stmt): ?Node\Stmt
|
|
{
|
|
if ($this->hasNoExportAttribute($stmt)) {
|
|
return null;
|
|
}
|
|
if ($stmt instanceof Node\Stmt\Class_ && $this->hasNativeAttribute($stmt)) {
|
|
$name = isset($stmt->namespacedName)
|
|
? $stmt->namespacedName->toString()
|
|
: ($stmt->name?->toString() ?? '<anonymous>');
|
|
throw new SyntaxError(
|
|
"Native class `{$name}` cannot be exported through a library stub; mark it with #[NoExport]",
|
|
);
|
|
}
|
|
|
|
$comments = array_filter(
|
|
$stmt->getComments(),
|
|
static fn(\PhpParser\Comment $comment): bool => preg_match(
|
|
'/@import-library\b/',
|
|
$comment->getText(),
|
|
) !== 1,
|
|
);
|
|
$stmt->setAttribute('comments', array_values($comments));
|
|
$this->filterAttributesForLibraryStub($stmt);
|
|
|
|
if ($stmt instanceof Node\Stmt\Function_) {
|
|
foreach ($stmt->params as $param) {
|
|
$this->filterAttributesForLibraryStub($param);
|
|
}
|
|
$stmt->stmts = [];
|
|
return $stmt;
|
|
}
|
|
|
|
if ($stmt instanceof Node\Stmt\ClassLike) {
|
|
$members = [];
|
|
foreach ($stmt->stmts as $member) {
|
|
if ($member instanceof Node\Stmt\ClassMethod) {
|
|
if ($this->hasNoExportAttribute($member)) {
|
|
continue;
|
|
}
|
|
if (!($member->flags & Modifiers::ABSTRACT)
|
|
&& !($stmt instanceof Node\Stmt\Interface_)) {
|
|
$member->stmts = [];
|
|
}
|
|
$this->filterAttributesForLibraryStub($member);
|
|
foreach ($member->params as $param) {
|
|
$this->filterAttributesForLibraryStub($param);
|
|
}
|
|
$members[] = $member;
|
|
continue;
|
|
}
|
|
if ($member instanceof Node\Stmt\Property) {
|
|
$this->filterAttributesForLibraryStub($member);
|
|
foreach ($member->hooks as $hook) {
|
|
if ($hook->body !== null) {
|
|
$hook->body = [];
|
|
}
|
|
}
|
|
$members[] = $member;
|
|
continue;
|
|
}
|
|
if ($member instanceof Node\Stmt\ClassConst
|
|
|| $member instanceof Node\Stmt\TraitUse
|
|
|| $member instanceof Node\Stmt\EnumCase) {
|
|
$members[] = $member;
|
|
}
|
|
}
|
|
$stmt->stmts = $members;
|
|
return $stmt;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function hasNativeAttribute(Node\Stmt\Class_ $class): bool
|
|
{
|
|
foreach ($class->attrGroups as $group) {
|
|
foreach ($group->attrs as $attribute) {
|
|
if (CompileTimeAttribute::is($attribute, 'Native')) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function hasNoExportAttribute(Node $node): bool
|
|
{
|
|
if (!property_exists($node, 'attrGroups')) {
|
|
return false;
|
|
}
|
|
foreach ($node->attrGroups as $group) {
|
|
foreach ($group->attrs as $attribute) {
|
|
if (CompileTimeAttribute::is($attribute, 'NoExport')) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function filterAttributesForLibraryStub(Node $node): void
|
|
{
|
|
if (!property_exists($node, 'attrGroups')) {
|
|
return;
|
|
}
|
|
foreach ($node->attrGroups as $groupIndex => $group) {
|
|
foreach ($group->attrs as $attributeIndex => $attribute) {
|
|
$definition = CompileTimeAttributeRegistry::get(CompileTimeAttribute::resolvedName($attribute));
|
|
if ($definition !== null && !$definition['preserve_in_library_stub']) {
|
|
unset($group->attrs[$attributeIndex]);
|
|
}
|
|
}
|
|
$group->attrs = array_values($group->attrs);
|
|
if ($group->attrs === []) {
|
|
unset($node->attrGroups[$groupIndex]);
|
|
}
|
|
}
|
|
$node->attrGroups = array_values($node->attrGroups);
|
|
}
|
|
}
|
|
|