From 11e89428d029e2bba30551d43561edf7846d05de Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 21 Jul 2026 18:07:03 +0800 Subject: [PATCH] feat(compiler): add library-specific stub function handling - Add stubLibrary property to CompilerBase to track owning library - Implement @typephp-library annotation parsing in stub files - Generate library-specific API macros for imported functions - Add automatic linking of referenced libraries during compilation - Update function declaration prefixes based on import/export status - Create test case for cross-library stub function imports - Document new @typephp-library annotation usage in documentation - Modify DefaultArgumentGenerator to skip imported functions - Add library tracking to FunctionDef entity structure --- docs/MIXED_CPP_PHP.md | 13 +++ examples/prime/php-src/vector.stub.php | 4 +- ..._abi.php => default_argument_abi.stub.php} | 9 +-- phpunit/src/CompilerBaseApiTest.php | 42 ++++++++-- src/CompilerBase.php | 2 + src/Entity/FunctionDef.php | 2 + src/Generator/DefaultArgumentGenerator.php | 7 +- src/Preprocessor.php | 31 +++++++ src/Translator.php | 81 ++++++++++++++----- 9 files changed, 155 insertions(+), 36 deletions(-) rename phpunit/code/compiler_api/{default_argument_abi.php => default_argument_abi.stub.php} (66%) diff --git a/docs/MIXED_CPP_PHP.md b/docs/MIXED_CPP_PHP.md index 42154cc2..5e62344c 100644 --- a/docs/MIXED_CPP_PHP.md +++ b/docs/MIXED_CPP_PHP.md @@ -686,6 +686,19 @@ void php_print(php::Str msg); // void 不支持 ### 3. .stub.php 文件要求 +当 stub 中的函数由一个 TypePHP 库提供时,在文件顶部声明库名: + +```php +compiler; $this->setPropertyValue('buildMode', CompilerBase::BUILD_MODE_LIB); - $this->compiler->setTargetName('abi_defaults'); + $this->compiler->setTargetName('prime2'); - $testFile = ROOT_PATH . '/phpunit/code/compiler_api/default_argument_abi.php'; + $testFile = ROOT_PATH . '/phpunit/code/compiler_api/default_argument_abi.stub.php'; $this->compiler->addFiles([$testFile]); $this->compiler->prepareFile($testFile); $this->compiler->convertFile($testFile); @@ -848,10 +848,10 @@ YAML); $header = file_get_contents($headerFile); $this->assertStringContainsString('#pragma once', $header); - $this->assertStringContainsString('TYPEPHP_ABI_DEFAULTS_API __declspec(dllexport)', $header); - $this->assertStringContainsString('TYPEPHP_ABI_DEFAULTS_API __declspec(dllimport)', $header); + $this->assertStringContainsString('TYPEPHP_PRIME2_API __declspec(dllexport)', $header); + $this->assertStringContainsString('TYPEPHP_PRIME2_API __declspec(dllimport)', $header); $this->assertStringContainsString( - 'TYPEPHP_ABI_DEFAULTS_API php::Str php_exported_defaults_arg_0_default_value();', + 'TYPEPHP_PRIME2_API php::Str php_exported_defaults_arg_0_default_value();', $header ); $this->assertStringContainsString( @@ -859,7 +859,7 @@ YAML); $header ); $this->assertStringContainsString( - 'TYPEPHP_ABI_DEFAULTS_API php::Array php_exported_variadic_arg_0_default_value();', + 'TYPEPHP_PRIME2_API php::Array php_exported_variadic_arg_0_default_value();', $header ); $this->assertStringNotContainsString('_literal_strings', $header); @@ -883,6 +883,36 @@ YAML); $this->assertStringContainsString('php::Array php_exported_variadic_arg_0_default_value() {', $extension); } + public function testTypePhpLibraryStubFunctionsAreImportedOutsideOwningTarget(): void + { + global $translator; + $translator = $this->compiler; + $this->setPropertyValue('buildMode', CompilerBase::BUILD_MODE_LIB); + $this->compiler->setTargetName('consumer'); + + $testFile = ROOT_PATH . '/phpunit/code/compiler_api/default_argument_abi.stub.php'; + $this->compiler->addFiles([$testFile]); + $this->compiler->prepareFile($testFile); + $this->compiler->convertFile($testFile); + + $headerFile = $this->testDir . '/php_consumer_func_decl.h'; + $this->compiler->genFunctionDeclaration($headerFile); + $header = file_get_contents($headerFile); + + $this->assertStringContainsString('TYPEPHP_PRIME2_API __declspec(dllimport)', $header); + $this->assertStringContainsString('TYPEPHP_CONSUMER_API __declspec(dllexport)', $header); + $this->assertStringContainsString( + 'TYPEPHP_PRIME2_API php::Array php_exported_defaults(', + $header + ); + $this->assertSame(['prime2'], $this->getPropertyValue('linkLibs')); + $this->assertSame('', $this->invokeMethod('genDefaultArgumentHelperDefinitions')); + + $options = $this->invokeMethod('getCompileCommandOptions'); + $this->assertContains('TYPEPHP_CONSUMER_EXPORTS=1', $options['user_defines']); + $this->assertNotContains('TYPEPHP_PRIME2_EXPORTS=1', $options['user_defines']); + } + public function testLibraryCompileOptionsExportOnlyPublicApiByDefault(): void { $this->setPropertyValue('buildMode', CompilerBase::BUILD_MODE_LIB); diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 85ced177..ac200ce0 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -369,6 +369,7 @@ class CompilerBase implements PropertyAccessContext protected int $debugLine = 0; protected CLImate $climate; protected bool $stubFile = false; + protected string $stubLibrary = ''; protected bool $enableProfiler = false; protected bool $noProgress = false; protected bool $forTest = false; @@ -999,6 +1000,7 @@ class CompilerBase implements PropertyAccessContext $this->classesDefineInFile = []; $this->interfacesDefineInFile = []; $this->functionDefineInFile = []; + $this->stubLibrary = ''; } protected function resetNamespace(): void diff --git a/src/Entity/FunctionDef.php b/src/Entity/FunctionDef.php index 0f37718c..64698bd8 100644 --- a/src/Entity/FunctionDef.php +++ b/src/Entity/FunctionDef.php @@ -25,6 +25,8 @@ class FunctionDef public string $namespace; public bool $method = false; public bool $stub = false; + /** Library that owns this stub function, as declared by @typephp-library. */ + public string $library = ''; public bool $returnTypeUndeclared = false; public bool $returnsByRef = false; public bool $generator = false; diff --git a/src/Generator/DefaultArgumentGenerator.php b/src/Generator/DefaultArgumentGenerator.php index 3db867ef..891c4b77 100644 --- a/src/Generator/DefaultArgumentGenerator.php +++ b/src/Generator/DefaultArgumentGenerator.php @@ -43,7 +43,7 @@ trait DefaultArgumentGenerator return "do {\n" . $plan->init . $body . $plan->clean . "} while (0);\n"; } - protected function genDefaultArgumentHelperDeclarations(string $declarationPrefix): string + protected function genDefaultArgumentHelperDeclarations(): string { $code = ''; foreach ($this->symbols->functions() as $nativeName => $func) { @@ -54,7 +54,7 @@ trait DefaultArgumentGenerator $type = $this->getDefaultArgumentHelperType($argInfo); $helper = $this->getDefaultArgumentHelperName($nativeName, $argumentIndex); - $code .= $declarationPrefix . $type . ' ' . $helper . '();' . PHP_EOL; + $code .= $this->getFunctionDeclarationPrefix($func) . $type . ' ' . $helper . '();' . PHP_EOL; } } @@ -65,6 +65,9 @@ trait DefaultArgumentGenerator { $code = ''; foreach ($this->symbols->functions() as $nativeName => $func) { + if ($this->isImportedFunction($func)) { + continue; + } foreach ($func->argInfoList as $argumentIndex => $argInfo) { if (!$this->shouldGenerateDefaultArgumentHelper($argInfo)) { continue; diff --git a/src/Preprocessor.php b/src/Preprocessor.php index 1251309d..1288fbc9 100644 --- a/src/Preprocessor.php +++ b/src/Preprocessor.php @@ -125,6 +125,12 @@ class Preprocessor extends CompilerBase throw new SyntaxError($e->getMessage(), $e->getCode()); } + $this->stubLibrary = $this->stubFile ? $this->parseTypePhpLibrary($ast) : ''; + if ($this->stubLibrary !== '' && $this->stubLibrary !== $this->targetName + && !in_array($this->stubLibrary, $this->linkLibs, true)) { + $this->linkLibs[] = $this->stubLibrary; + } + $traverser = new NodeTraverser(); $traverser->addVisitor(new Visitor()); $stmts = $traverser->traverse($ast); @@ -192,6 +198,30 @@ class Preprocessor extends CompilerBase } } + /** @param array $stmts */ + private function parseTypePhpLibrary(array $stmts): string + { + $library = ''; + foreach ($stmts as $stmt) { + foreach ($stmt->getComments() as $comment) { + if (!preg_match('/@typephp-library\s+([^\s*]+)/', $comment->getText(), $matches)) { + continue; + } + + $candidate = str_replace('-', '_', trim($matches[1])); + if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $candidate)) { + $this->fatalError($stmt, 'Invalid @typephp-library name `' . $matches[1] . '`'); + } + if ($library !== '' && $library !== $candidate) { + $this->fatalError($stmt, 'A stub file cannot declare multiple @typephp-library values'); + } + $library = $candidate; + } + } + + return $library; + } + protected function findSymbolUsing(NodeAbstract $ast) { $nodeFinder = new NodeFinder(); @@ -468,6 +498,7 @@ class Preprocessor extends CompilerBase // the consuming class when a trait method is flattened into a class. $functionDef->returnTypeKeyword = $returnTypeKeyword; $functionDef->stub = $this->stubFile; + $functionDef->library = $this->stubLibrary; $functionDef->returnTypeUndeclared = $v->returnType === null; $functionDef->returnsByRef = $v->byRef; if ($this->containsYield($v)) { diff --git a/src/Translator.php b/src/Translator.php index e727c991..79a13400 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -1545,27 +1545,23 @@ CODE; $code .= '#include ' . PHP_EOL; $code .= '#include ' . PHP_EOL; - $declarationPrefix = 'extern '; + $apiLibraries = []; if ($this->isBuildModeLib()) { - $apiMacro = $this->getLibraryApiMacroName(); - $exportsMacro = $this->getLibraryExportsMacroName(); - $code .= "#if defined(_WIN32)\n"; - $code .= "# if defined({$exportsMacro})\n"; - $code .= "# define {$apiMacro} __declspec(dllexport)\n"; - $code .= "# else\n"; - $code .= "# define {$apiMacro} __declspec(dllimport)\n"; - $code .= "# endif\n"; - $code .= "#elif defined(__GNUC__) && __GNUC__ >= 4\n"; - $code .= "# define {$apiMacro} __attribute__((visibility(\"default\")))\n"; - $code .= "#else\n"; - $code .= "# define {$apiMacro}\n"; - $code .= "#endif\n\n"; - $declarationPrefix = $apiMacro . ' '; - } - - $code .= $this->genDefaultArgumentHelperDeclarations($declarationPrefix); + $apiLibraries[$this->targetName] = true; + } + foreach ($this->symbols->functions() as $function) { + if ($this->isImportedFunction($function)) { + $apiLibraries[$function->library] = true; + } + } + foreach (array_keys($apiLibraries) as $library) { + $code .= $this->genLibraryApiMacro($library); + } + + $code .= $this->genDefaultArgumentHelperDeclarations(); foreach ($this->symbols->functions() as $name => $func) { + $functionDeclarationPrefix = $this->getFunctionDeclarationPrefix($func); $list = []; if ($func->method) { $list[] = Type::OBJECT . ' &this_'; @@ -1586,10 +1582,10 @@ CODE; } } $params = implode(', ', $list); - $code .= $declarationPrefix . ($func->returnsByRef ? Type::REF : $func->returnType) . ' ' . self::PREFIX . $name . '(' . $params . ');' . PHP_EOL; + $code .= $functionDeclarationPrefix . ($func->returnsByRef ? Type::REF : $func->returnType) . ' ' . self::PREFIX . $name . '(' . $params . ');' . PHP_EOL; if ($func->hasMultiReturn()) { $code .= 'namespace ' . self::MULTI_RETURN_NAMESPACE . ' {' . PHP_EOL; - $code .= $declarationPrefix . $func->getMultiReturnCppType() . ' ' . self::PREFIX . $name . '(' . $params . ');' . PHP_EOL; + $code .= $functionDeclarationPrefix . $func->getMultiReturnCppType() . ' ' . self::PREFIX . $name . '(' . $params . ');' . PHP_EOL; $code .= '}' . PHP_EOL; } } @@ -1602,9 +1598,52 @@ CODE; return 'TYPEPHP_' . strtoupper($this->targetName) . '_API'; } + protected function genLibraryApiMacro(string $library): string + { + $apiMacro = $this->getNamedLibraryApiMacroName($library); + $exportsMacro = $this->getNamedLibraryExportsMacroName($library); + $code = "#if defined(_WIN32)\n"; + $code .= "# if defined({$exportsMacro})\n"; + $code .= "# define {$apiMacro} __declspec(dllexport)\n"; + $code .= "# else\n"; + $code .= "# define {$apiMacro} __declspec(dllimport)\n"; + $code .= "# endif\n"; + $code .= "#elif defined(__GNUC__) && __GNUC__ >= 4\n"; + $code .= "# define {$apiMacro} __attribute__((visibility(\"default\")))\n"; + $code .= "#else\n"; + $code .= "# define {$apiMacro}\n"; + return $code . "#endif\n\n"; + } + + protected function getFunctionDeclarationPrefix(FunctionDef $function): string + { + if ($this->isImportedFunction($function)) { + return $this->getNamedLibraryApiMacroName($function->library) . ' '; + } + if ($this->isBuildModeLib()) { + return $this->getLibraryApiMacroName() . ' '; + } + return 'extern '; + } + + protected function isImportedFunction(FunctionDef $function): bool + { + return $function->library !== '' && $function->library !== $this->targetName; + } + + protected function getNamedLibraryApiMacroName(string $library): string + { + return 'TYPEPHP_' . strtoupper($library) . '_API'; + } + + protected function getNamedLibraryExportsMacroName(string $library): string + { + return 'TYPEPHP_' . strtoupper($library) . '_EXPORTS'; + } + protected function getLibraryExportsMacroName(): string { - return 'TYPEPHP_' . strtoupper($this->targetName) . '_EXPORTS'; + return $this->getNamedLibraryExportsMacroName($this->targetName); } public function getBuildMode(): string