- 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