- Rename stubLibrary to stubImportLibrary and add externalImportStubFiles property - Create LibraryImportStubGenerator to generate combined stub files with @import-library annotation - Update documentation to reflect new import library mechanism without @typephp-library annotation - Modify build pipeline to exclude generated stub from input files and generate library import stubs - Add TYPEPHP_LIBRARYNAME_IMPORT macros for importing functions from external libraries - Update function definition entities to track importLibrary instead of library ownership - Refactor preprocessing to detect @import-library annotation and manage external stub files - Implement proper handling of imported functions with different macro prefixes in declarationspull/34/head
parent
44ba3d4d1c
commit
3ac611220e
13 changed files with 405 additions and 52 deletions
@ -0,0 +1,13 @@ |
||||
<?php |
||||
|
||||
namespace LibraryApi; |
||||
|
||||
class NativeCounter |
||||
{ |
||||
public const int INITIAL = 3; |
||||
public int $value = self::INITIAL; |
||||
|
||||
public function bump(int $amount): int {} |
||||
} |
||||
|
||||
function native_value(string $name = 'typephp'): string {} |
||||
@ -0,0 +1,28 @@ |
||||
<?php |
||||
|
||||
namespace LibraryApi; |
||||
|
||||
class Counter |
||||
{ |
||||
public const int STEP = 2; |
||||
public int $value = 1; |
||||
public int $doubled { |
||||
get { |
||||
return $this->value * 2; |
||||
} |
||||
set(int $value) { |
||||
$this->value = intdiv($value, 2); |
||||
} |
||||
} |
||||
|
||||
public function add(int $amount = self::STEP): int |
||||
{ |
||||
$this->value += $amount; |
||||
return $this->value; |
||||
} |
||||
} |
||||
|
||||
function twice(int $value): int |
||||
{ |
||||
return $value * 2; |
||||
} |
||||
@ -0,0 +1,13 @@ |
||||
<?php |
||||
|
||||
/** @import-library */ |
||||
|
||||
function exported_defaults( |
||||
string $text = 'hello', |
||||
array $options = ['mode' => 'fast'], |
||||
mixed $value = null, |
||||
int $count = 0, |
||||
bool $enabled = false |
||||
): array {} |
||||
|
||||
function exported_variadic(string ...$values): array {} |
||||
@ -0,0 +1,147 @@ |
||||
<?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; |
||||
|
||||
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()); |
||||
$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 |
||||
{ |
||||
$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)); |
||||
|
||||
if ($stmt instanceof Node\Stmt\Function_) { |
||||
$stmt->stmts = []; |
||||
return $stmt; |
||||
} |
||||
|
||||
if ($stmt instanceof Node\Stmt\ClassLike) { |
||||
$members = []; |
||||
foreach ($stmt->stmts as $member) { |
||||
if ($member instanceof Node\Stmt\ClassMethod) { |
||||
if (!($member->flags & Modifiers::ABSTRACT) |
||||
&& !($stmt instanceof Node\Stmt\Interface_)) { |
||||
$member->stmts = []; |
||||
} |
||||
$members[] = $member; |
||||
continue; |
||||
} |
||||
if ($member instanceof Node\Stmt\Property) { |
||||
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; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue