From 02a98ffc611afe8204d1b8c64ec94926b4c73cda Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 4 Sep 2026 19:38:54 +0800 Subject: [PATCH] fix(compiler): resolve generated C++ symbol ambiguities --- docs/en/CPP_SYMBOL_NAMING.md | 28 ++++++++++++++-------------- docs/zh-cn/CPP_SYMBOL_NAMING.md | 28 ++++++++++++++-------------- phpunit/src/CompilerBaseApiTest.php | 9 ++++++--- src/CompilerBase.php | 9 ++++++--- src/Metadata/Constants.php | 3 +++ src/Parser/AssignOpTrait.php | 2 +- src/Parser/ForeachTrait.php | 2 +- src/Translator.php | 2 +- 8 files changed, 46 insertions(+), 37 deletions(-) diff --git a/docs/en/CPP_SYMBOL_NAMING.md b/docs/en/CPP_SYMBOL_NAMING.md index 4114eafd..035009d2 100644 --- a/docs/en/CPP_SYMBOL_NAMING.md +++ b/docs/en/CPP_SYMBOL_NAMING.md @@ -13,7 +13,7 @@ This document is the internal C++ naming convention for TypePHP, PHPX, and TypeP | --- | --- | --- | --- | --- | | `typephp_` | TypePHP-specific runtime or compiled-artifact support logic | `typephp_call_parent_constructor()` | TypePHP/PHPX runtime | Internal or explicitly exported ABI | | `php::` | C++ wrappers for PHP runtime capabilities such as ZendAPI, zval, HashTable, and call frames | `php::deindirect()` | PHPX C++ API | PHPX API | -| `typephp_` | The private C++ namespace of a single compiled project | `namespace typephp_tpc` | Current generated project | Non-public ABI | +| `typephp_project_` | The private C++ namespace of a single compiled project | `namespace typephp_project_tpc` | Current generated project | Non-public ABI | | `php_` | C++ callable symbols mapped from user PHP functions and class methods | `php_app__user__save()` | Visible to the linker | TypePHP/stub callable ABI | Core constraints: @@ -21,7 +21,7 @@ Core constraints: 1. Do not add new global framework `php_*` helpers. 2. Capabilities that are unrelated to TypePHP and only wrap ZendAPI must be placed in `namespace php`. 3. Logic that is unique to TypePHP and needs to be called across generated files uses the `typephp_` prefix. -4. Data and functions that serve only one compiled project go into the `typephp_` namespace. +4. Data and functions that serve only one compiled project go into the `typephp_project_` namespace. 5. Global `php_*` callable names are reserved for the compiled ABI of user PHP declarations. ## 2. `typephp_`: TypePHP-specific Logic @@ -144,23 +144,23 @@ php::stdCreateObject(); Do not mechanically preserve Zend's snake_case names as global C++ names. Lower-level calls can continue to use the original Zend API, such as `zend_objects_new()`, but the wrapper layer exposed to generated code should use `php::`. -## 4. `typephp_`: Project-private Namespace +## 4. `typephp_project_`: Project-private Namespace Each TypePHP compiled project has an independent C++ namespace: ```text -typephp_ +typephp_project_ ``` For example, if the project name is `tpc`: ```cpp -namespace typephp_tpc { +namespace typephp_project_tpc { // Project-private generated state and helpers. } ``` -The `-` and `*` in the project name are converted to `_`, and the remaining characters must satisfy the compiler's target identifier validation. Because of the fixed `typephp_` prefix, the final C++ namespace is a valid identifier even if the project name starts with a digit. +The `-` and `*` in the project name are converted to `_`, and the remaining characters must satisfy the compiler's target identifier validation. The distinct `typephp_project_` prefix prevents generated namespaces from colliding with global `typephp_*` runtime helpers. It also keeps the final C++ namespace valid when the project name starts with a digit. ### 4.1 Content That Should Go into This Namespace @@ -175,7 +175,7 @@ The `-` and `*` in the project name are converted to `_`, and the remaining char Illustration: ```cpp -namespace typephp_demo { +namespace typephp_project_demo { static php::Str literal_strings[] = { php::Str{"hello"}, @@ -195,12 +195,12 @@ static void module_init() { // Initialize this project's generated state. } -} // namespace typephp_demo +} // namespace typephp_project_demo ``` ### 4.2 Visibility and ABI -- Names inside `typephp_` are implementation details, not library stub ABI. +- Names inside `typephp_project_` are implementation details, not library stub ABI. - Objects and functions that can be limited to `static` should continue to be marked `static`. - Generated headers may declare project-internal accessors that must be used across translation units, but must not expose underlying arrays or cache tables. - External handwritten C++ code must not depend on literal indexes, cache indexes, or project-internal storage names. @@ -211,10 +211,10 @@ static void module_init() { Historical generated names may still appear in the project namespace, for example: ```cpp -typephp_demo::php_class_entry_App_User +typephp_project_demo::php_class_entry_App_User ``` -Although the member name starts with `php_`, the full symbol resides in `typephp_demo`, so it is a project-private implementation rather than the global user callable ABI described in Section 5. New project-internal helpers should prefer short names without `php_`, such as `get_class()`, `get_func()`, and `get_str()`. +Although the member name starts with `php_`, the full symbol resides in `typephp_project_demo`, so it is a project-private implementation rather than the global user callable ABI described in Section 5. New project-internal helpers should prefer short names without `php_`, such as `get_class()`, `get_func()`, and `get_str()`. ## 5. `php_`: The C++ ABI of User PHP Callables @@ -315,7 +315,7 @@ When adding a C++ API, judge in the following order: 1. **Is it the compiled body of a user PHP function or class method?** - Yes: use the existing `php_` callable ABI generator; do not handwrite another mapping. 2. **Does it serve only one current TypePHP project?** - - Yes: place it in `typephp_`, and use `static` or private accessors where possible. + - Yes: place it in `typephp_project_`, and use `static` or private accessors where possible. 3. **Does it implement TypePHP-specific semantics?** - Yes: use the `typephp_` prefix. 4. **Is it only a C++ wrapper of Zend/PHP runtime capabilities?** @@ -330,7 +330,7 @@ When adding or modifying generated helpers, check: - [ ] No new global `php_*` helpers in `typephp_helper.h`; - [ ] ZendAPI wrappers are in `namespace php`; - [ ] TypePHP-specific logic uses `typephp_`; -- [ ] Project caches and storage are in `typephp_`; +- [ ] Project caches and storage are in `typephp_project_`; - [ ] Project-private tables are not exposed directly via `extern` through generated headers; - [ ] User callables still use the unified `php_` ABI generator; - [ ] New names do not collide with user-declarable PHP functions or methods; @@ -350,7 +350,7 @@ tests/compiler/basic/helper-symbol-collision.phpt | --- | --- | | `php_` callable prefix and combination separator | `src/CompilerBase.php` | | Callable combination collision detection | `src/Preprocessor.php` | -| `typephp_` generation and project-private tables | `src/Translator.php` | +| `typephp_project_` generation and project-private tables | `src/Translator.php` | | TypePHP extension prefix constants | `src/Metadata/Constants.php` | | PHPX/TypePHP helper classification | `vendor/swoole/phpx/include/typephp_helper.h` | | Embed module accessor concatenation | `vendor/swoole/phpx/src/misc/typephp_main.cc` | diff --git a/docs/zh-cn/CPP_SYMBOL_NAMING.md b/docs/zh-cn/CPP_SYMBOL_NAMING.md index 20d06e49..ea19792c 100644 --- a/docs/zh-cn/CPP_SYMBOL_NAMING.md +++ b/docs/zh-cn/CPP_SYMBOL_NAMING.md @@ -13,7 +13,7 @@ | --- | --- | --- | --- | --- | | `typephp_` | TypePHP 独有的运行时或编译产物支持逻辑 | `typephp_call_parent_constructor()` | TypePHP/PHPX 运行时 | 内部或显式导出 ABI | | `php::` | 对 ZendAPI、zval、HashTable、call frame 等 PHP 运行时能力的 C++ 封装 | `php::deindirect()` | PHPX C++ API | PHPX API | -| `typephp_` | 单个编译项目的私有 C++ 命名空间 | `namespace typephp_tpc` | 当前生成项目 | 非公共 ABI | +| `typephp_project_` | 单个编译项目的私有 C++ 命名空间 | `namespace typephp_project_tpc` | 当前生成项目 | 非公共 ABI | | `php_` | 用户 PHP 函数和类方法映射后的 C++ callable 符号 | `php_app__user__save()` | 链接器可见 | TypePHP/stub callable ABI | 核心约束: @@ -21,7 +21,7 @@ 1. 不得新增全局的框架 `php_*` helper。 2. 与 TypePHP 无关、只是包装 ZendAPI 的能力必须放入 `namespace php`。 3. TypePHP 独有且需要跨生成文件调用的逻辑使用 `typephp_` 前缀。 -4. 只服务于一个编译项目的数据和函数放入 `typephp_` 命名空间。 +4. 只服务于一个编译项目的数据和函数放入 `typephp_project_` 命名空间。 5. 全局 `php_*` callable 名称保留给用户 PHP 声明的编译 ABI。 ## 2. `typephp_`:TypePHP 独有逻辑 @@ -144,23 +144,23 @@ php::stdCreateObject(); 不要把 Zend 的 snake_case 名称机械地保留为全局 C++ 名称。底层调用可以继续使用 Zend 原始 API,例如 `zend_objects_new()`,但对生成代码暴露的包装层应使用 `php::`。 -## 4. `typephp_`:项目私有命名空间 +## 4. `typephp_project_`:项目私有命名空间 每个 TypePHP 编译项目拥有独立的 C++ 命名空间: ```text -typephp_ +typephp_project_ ``` 例如项目名为 `tpc`: ```cpp -namespace typephp_tpc { +namespace typephp_project_tpc { // Project-private generated state and helpers. } ``` -项目名中的 `-` 和 `*` 会转换为 `_`,其余字符必须满足编译器的 target identifier 校验。由于固定带有 `typephp_` 前缀,即使项目名以数字开头,最终 C++ namespace 仍是合法标识符。 +项目名中的 `-` 和 `*` 会转换为 `_`,其余字符必须满足编译器的 target identifier 校验。独立的 `typephp_project_` 前缀可防止生成 namespace 与全局 `typephp_*` 运行时 helper 冲突;即使项目名以数字开头,最终 C++ namespace 仍是合法标识符。 ### 4.1 应放入该命名空间的内容 @@ -175,7 +175,7 @@ namespace typephp_tpc { 示意: ```cpp -namespace typephp_demo { +namespace typephp_project_demo { static php::Str literal_strings[] = { php::Str{"hello"}, @@ -195,12 +195,12 @@ static void module_init() { // Initialize this project's generated state. } -} // namespace typephp_demo +} // namespace typephp_project_demo ``` ### 4.2 可见性与 ABI -- `typephp_` 内的名称是实现细节,不是 library stub ABI。 +- `typephp_project_` 内的名称是实现细节,不是 library stub ABI。 - 可限制为 `static` 的对象和函数应继续标记为 `static`。 - 生成头文件可以声明必须跨 translation unit 使用的项目内部 accessor,但不应暴露底层数组或缓存表。 - 外部手写 C++ 代码不得依赖 literal index、cache index 或项目内部 storage 名称。 @@ -211,10 +211,10 @@ static void module_init() { 项目 namespace 中仍可能出现历史生成名称,例如: ```cpp -typephp_demo::php_class_entry_App_User +typephp_project_demo::php_class_entry_App_User ``` -虽然成员名以 `php_` 开头,但完整符号位于 `typephp_demo` 中,因此它属于项目私有实现,而不是第 5 节所述的全局用户 callable ABI。新增项目内部 helper 应优先使用不带 `php_` 的短名称,例如 `get_class()`、`get_func()` 和 `get_str()`。 +虽然成员名以 `php_` 开头,但完整符号位于 `typephp_project_demo` 中,因此它属于项目私有实现,而不是第 5 节所述的全局用户 callable ABI。新增项目内部 helper 应优先使用不带 `php_` 的短名称,例如 `get_class()`、`get_func()` 和 `get_str()`。 ## 5. `php_`:用户 PHP callable 的 C++ ABI @@ -315,7 +315,7 @@ TypePHP 扩展不得分别编译或静态链接包含进程级 Zend 状态的 PH 1. **它是否是用户 PHP 函数或类方法的编译本体?** - 是:使用既有 `php_` callable ABI 生成器,禁止手写另一套映射。 2. **它是否只服务于当前一个 TypePHP 项目?** - - 是:放入 `typephp_`,并尽可能使用 `static` 或私有 accessor。 + - 是:放入 `typephp_project_`,并尽可能使用 `static` 或私有 accessor。 3. **它是否实现 TypePHP 独有语义?** - 是:使用 `typephp_` 前缀。 4. **它是否只是对 Zend/PHP 运行时能力的 C++ 封装?** @@ -330,7 +330,7 @@ TypePHP 扩展不得分别编译或静态链接包含进程级 Zend 状态的 PH - [ ] `typephp_helper.h` 中没有新增全局 `php_*` helper; - [ ] ZendAPI 包装位于 `namespace php`; - [ ] TypePHP 独有逻辑使用 `typephp_`; -- [ ] 项目缓存和 storage 位于 `typephp_`; +- [ ] 项目缓存和 storage 位于 `typephp_project_`; - [ ] 项目私有表没有通过生成头文件直接 `extern` 暴露; - [ ] 用户 callable 仍使用统一的 `php_` ABI 生成器; - [ ] 新名称不会与用户可声明的 PHP 函数或方法发生冲突; @@ -350,7 +350,7 @@ tests/compiler/basic/helper-symbol-collision.phpt | --- | --- | | `php_` callable 前缀与组合分隔符 | `src/CompilerBase.php` | | callable 组合冲突检测 | `src/Preprocessor.php` | -| `typephp_` 生成及项目私有表 | `src/Translator.php` | +| `typephp_project_` 生成及项目私有表 | `src/Translator.php` | | TypePHP extension 前缀常量 | `src/Metadata/Constants.php` | | PHPX/TypePHP helper 分类 | `vendor/swoole/phpx/include/typephp_helper.h` | | embed module accessor 拼接 | `vendor/swoole/phpx/src/misc/typephp_main.cc` | diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index 3f70a484..2b46ad67 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.php @@ -5,6 +5,7 @@ namespace TypePhp\Tests; use PHPUnit\Framework\TestCase; use TypePhp\CompilerTest; use TypePhp\CompilerBase; +use TypePhp\Metadata\Constants; use TypePhp\Type; use TypePhp\Exception\TestError; use TypePhp\Platform\Macos; @@ -733,13 +734,15 @@ YAML, 'myproject.yml', 'examples/tetris-sdl'); $this->assertSame('demo.so', $this->invokeMethod('getTargetFileName')); } - public function testGeneratedZendModuleAlwaysUsesTypePhpPrefix(): void + public function testGeneratedZendModuleAndProjectNamespaceUseDistinctPrefixes(): void { $this->compiler->setTargetName('demo'); $this->assertSame('typephp_demo', $this->compiler->getModuleName()); + $this->assertSame('typephp_project_demo', $this->compiler->getProjectNamespace()); $this->compiler->setTargetName('123'); $this->assertSame('typephp_123', $this->compiler->getModuleName()); + $this->assertSame('typephp_project_123', $this->compiler->getProjectNamespace()); } public function testParseProjectYamlResolvesRelativePathOptionsAgainstYamlDirectory(): void @@ -1270,7 +1273,7 @@ YAML); $compiler->genDataDeclarations($dataFile); $data = file_get_contents($dataFile); $extension = file_get_contents($compiler->genExtension()); - $namespace = 'typephp_' . $target; + $namespace = Constants::CPP_PROJECT_NAMESPACE_PREFIX . $target; $this->assertStringContainsString('namespace ' . $namespace . ' {', $data, $mode); $this->assertStringContainsString('using namespace ' . $namespace . ';', $data, $mode); @@ -1303,7 +1306,7 @@ YAML); $this->assertStringContainsString('#include ', $extension); $this->assertStringContainsString('TYPEPHP_EMBED_GET_MODULE_FUNCTION(' . $target . ')', $extension); $this->assertStringContainsString( - 'return &' . $namespace . '::' . $namespace . '_module_entry;', + 'return &' . $namespace . '::typephp_' . $target . '_module_entry;', $extension, ); } else { diff --git a/src/CompilerBase.php b/src/CompilerBase.php index b18aef37..a28abe74 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -1546,13 +1546,16 @@ class CompilerBase implements PropertyAccessContext return $list; } - protected function parseArrayKey(NodeAbstract $expr): string + protected function parseArrayKey(NodeAbstract $expr, bool $keepStringObject = false): string { $this->assertNotNativeObjectArrayKey($expr); $key = $this->parseIdentifier($expr); if (str_starts_with($key, self::LITERAL_STRING_GETTER . '(')) { - $key = "{$key}.str()"; - } elseif ($this->isZeroLiteral($expr)) { + // Array initializers and setters use zend_string* keys, while item() + // uses php::String to avoid an ambiguous conversion to Variant. + return $keepStringObject ? $key : "{$key}.str()"; + } + if ($this->isZeroLiteral($expr)) { $key = self::VALUE_ZERO; } return $key; diff --git a/src/Metadata/Constants.php b/src/Metadata/Constants.php index 31f5cbec..f337b486 100644 --- a/src/Metadata/Constants.php +++ b/src/Metadata/Constants.php @@ -14,6 +14,9 @@ class Constants { public const string EXTENSION_PREFIX = 'typephp_'; + /** Keep generated project namespaces disjoint from global typephp_* runtime helpers. */ + public const string CPP_PROJECT_NAMESPACE_PREFIX = 'typephp_project_'; + public const array CPP_RESERVED_NAMES = [ 'alignas', 'alignof', diff --git a/src/Parser/AssignOpTrait.php b/src/Parser/AssignOpTrait.php index 55f7f608..265d65f2 100644 --- a/src/Parser/AssignOpTrait.php +++ b/src/Parser/AssignOpTrait.php @@ -287,7 +287,7 @@ trait AssignOpTrait continue; } if ($item instanceof ArrayItem) { - $key = $item->key ? $this->parseArrayKey($item->key) : (string) $k; + $key = $item->key ? $this->parseArrayKey($item->key, true) : (string) $k; if ($item->value instanceof Expr\List_) { $nestedTmp = $this->genTmpVarName(); $this->addLocalVar($nestedTmp, Type::ARRAY); diff --git a/src/Parser/ForeachTrait.php b/src/Parser/ForeachTrait.php index 7fc63ec2..583515b9 100644 --- a/src/Parser/ForeachTrait.php +++ b/src/Parser/ForeachTrait.php @@ -26,7 +26,7 @@ trait ForeachTrait if ($item->byRef) { $this->fatalError($item, 'Foreach list destructuring cannot bind items by reference'); } - $key = $item->key ? $this->parseArrayKey($item->key) : (string) $k; + $key = $item->key ? $this->parseArrayKey($item->key, true) : (string) $k; if ($item->value instanceof Expr\List_) { $nestedTmpVar = $this->genTmpVarName(); $this->addLocalVar($nestedTmpVar, Type::VAR); diff --git a/src/Translator.php b/src/Translator.php index f6e70010..36b1b087 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -1592,7 +1592,7 @@ CODE; public function getProjectNamespace(): string { - return $this->getModuleName(); + return Constants::CPP_PROJECT_NAMESPACE_PREFIX . $this->targetName; } /**