feat(wasm): add TypePHP WASM implementation plan and trait scope guard support

- Add fake_scope_guard keyword to reserved words list
- Implement trait property scope handling in PropertyAccessTrait
- Generate proper scope guards for trait methods in Translator
- Add WASM implementation documentation outlining technical approach
- Include trait-fake-scope-guard-param test case
- Link WASM plan in main documentation index
pull/45/head
韩天峰 3 weeks ago
parent 2310c574ab
commit db4b7f9e28
  1. 1
      docs/README.md
  2. 472
      docs/TYPEPHP_WASM_IMPLEMENTATION_PLAN.md
  3. 1
      src/Metadata/Constants.php
  4. 30
      src/Parser/PropertyAccessTrait.php
  5. 5
      src/Translator.php
  6. 20
      tests/compiler/trait/trait-fake-scope-guard-param.phpt

@ -17,6 +17,7 @@
## 架构与维护
- [后端中立 IR](BACKEND_NEUTRAL_IR.md)
- [TypePHP WASM 技术方案与实施计划](TYPEPHP_WASM_IMPLEMENTATION_PLAN.md)
- [核心重构计划](REFACTORING_PLAN.md)
- [构建速度研究](AOT_BUILD_SPEED_RESEARCH.md)
- [优化优先级](aot-optimization-priority.md)

@ -0,0 +1,472 @@
# TypePHP WASM 技术方案与实施计划
> 状态:技术预研结论,供后续实现使用
> 调研日期:2026-08-07
> 首期目标:浏览器中的 Emscripten/WASM,NTS,单线程
## 1. 文档目的
本文记录 TypePHP 支持 WebAssembly 的技术决策、功能边界、运行时架构、主要风险、验证方法和分阶段实施计划。
本文不是当前功能说明。TypePHP 尚未承诺已经支持 WASM,后续实现应以本文作为设计起点,并根据原型验证结果更新决策。
## 2. 核心结论
首个 TypePHP WASM 版本采用以下路线:
```text
PHP 源码
-> TypePHP 编译器
-> TypePHP 生成的 C++
-> Emscripten 编译和静态链接
+ PHP NTS
+ PHPX
+ TypePHP runtime
+ GMP / MPFR / mpdecimal
+ 最小 TypePHP WASM SAPI
-> typephp.wasm + 薄 JavaScript loader
```
具体决策如下:
1. 第一版复用当前 C++/Zend 后端,不直接生成 WAT/WASM,也不重新实现 PHP 运行时。
2. 使用 Emscripten,而不是把 WASI 作为首期目标。
3. PHP、PHPX、TypePHP 生成代码和高精度库全部静态链接到一个 `.wasm` 模块。
4. 浏览器侧保留一个很薄的 JavaScript loader,用于实例化模块和提供必要的宿主能力。
5. 仅支持 PHP NTS,不支持线程。
6. 禁用 Fiber 和 TypePHP Generator。
7. 必须支持 C++ 异常以及 Zend bailout 所需的 `setjmp/longjmp`
8. 保留 PHP stream 框架和本地 stream,禁用网络 transport 和依赖操作系统进程能力的功能。
9. WordPress Playground 和其他 PHP-WASM 项目只作为补丁与移植经验来源,不作为 TypePHP 的依赖或代码基础。
本文描述的是最短可落地路径。长期的后端中立方案参见 [BACKEND_NEUTRAL_IR.md](BACKEND_NEUTRAL_IR.md)。在首期原型成功前,不应为了 WASM 重写 TypePHP 前端和语义层。
## 3. 为什么不采用 WordPress Playground
WordPress Playground 是一个成熟的浏览器 WordPress 产品,但不是小型 PHP-WASM 移植层。其仓库和构建体系同时服务于:
- 多个 PHP 版本和扩展组合;
- WordPress 发行版及其资源;
- 浏览器、Web Worker 和 Node.js 运行时;
- 虚拟文件系统、挂载和持久化;
- 网络代理和浏览器 HTTP 适配;
- NPM 包、网站、开发工具及集成测试;
- WordPress 特有的 API 和产品功能。
TypePHP 无法直接复用 Playground 发布的 PHP-WASM 二进制,因为 TypePHP 需要把 PHPX、编译生成的 C++ 和高精度库一起静态链接。若 fork Playground,TypePHP 还会被其 monorepo、Node/NPM 构建、版本矩阵和产品发布周期绑定。
因此采用以下原则:
- 不 fork WordPress Playground;
- 不把 `@php-wasm/*` 作为 TypePHP 的运行时依赖;
- 不复制其 WordPress、网络代理、文件同步和 UI 层;
- 仅研究 PHP configure 参数、php-src 补丁、Emscripten 兼容处理和最小 C API;
- 所有借用补丁必须拆分、注明来源,并验证是否仍适用于 TypePHP 固定的 PHP/Emscripten 版本。
`seanmorris/php-wasm`、`soyuka/php-wasm` 等项目也遵循相同原则:可用作构建参考和问题索引,但不成为 TypePHP 的基础仓库。
## 4. 目标与非目标
### 4.1 首期目标
- 在主流浏览器中加载 TypePHP 编译产物。
- 执行静态编译的 TypePHP 应用入口。
- 保持 TypePHP 当前基于 Zend 和 PHPX 的主要语言语义。
- 正确处理 PHP request 生命周期、C++ 异常和 Zend bailout。
- 支持 GMP、MPFR 和 mpdecimal 高精度类型。
- 支持内存文件系统和必要的本地 PHP stream。
- 对不支持的功能给出确定、可测试的错误,而不是链接失败或运行时崩溃。
- 构建过程可复现,php-src 和 Emscripten 版本固定。
### 4.2 首期非目标
- WASI 独立运行时。
- pthread、Web Worker 并行 PHP 或共享内存。
- Fiber 和 TypePHP Generator。
- 动态扩展加载。
- PHP 源码的运行时编译或通用 `eval()`
- TCP、UDP、Unix socket 和监听端口。
- MySQL、PostgreSQL、Redis 等网络客户端。
- `curl`、FTP、SMTP 等网络协议实现。
- `fork`、`exec`、`system`、`shell_exec`、`proc_open` 和信号处理。
- FFI、JIT、opcache 和调试器。
- 完整 WordPress 兼容性。
- 在第一阶段实现异步宿主调用。
## 5. 目标平台选择
### 5.1 首期使用 Emscripten
首期目标是浏览器,且 PHP 依赖 `setjmp/longjmp`,PHPX 和 TypePHP 依赖 C++ 异常。Emscripten 已经提供:
- C/C++ 到 WebAssembly 的完整工具链;
- C++ exception handling;
- `setjmp/longjmp` 支持;
- 浏览器文件系统;
- JavaScript import/export 和模块加载;
- libc、时间、随机数和常见 POSIX 接口的兼容层。
这些能力可以显著减少 PHP 移植工作。
### 5.2 暂不以 WASI 为目标
WASI 更适合非浏览器运行时和能力安全模型,但首期采用 WASI 会同时引入以下变量:
- PHP 和 Zend bailout 的兼容性;
- C++ 异常实现;
- 浏览器侧 WASI shim;
- 文件系统和异步 I/O;
- GMP、MPFR、mpdecimal 的 WASI 构建;
- 不同 WASI runtime 的实现差异。
在 Emscripten 原型稳定前,不同时维护第二套平台。后续如需支持 Wasmtime、Wasmer 或边缘运行时,应建立独立的 WASI 可行性项目,而不是把兼容层混入首期实现。
## 6. 产物和运行模型
### 6.1 发布产物
建议最小发布物为:
```text
dist/
├── typephp.wasm
└── typephp-wasm.mjs
```
所有 C/C++ 代码进入 `typephp.wasm`。`typephp-wasm.mjs` 只负责:
- 获取和实例化 `.wasm`
- 提供 stdout/stderr;
- 初始化内存文件系统;
- 注入时间、随机数等宿主能力;
- 调用导出的 TypePHP 生命周期接口;
- 把状态码和错误信息转换为 JavaScript 结果。
不应把 PHP 语义、Zend 对象操作或 TypePHP 业务逻辑放入 JavaScript loader。
### 6.2 生命周期
建议采用“模块启动一次、请求可重复执行”的模型:
```text
instantiate wasm
-> typephp_wasm_module_startup()
-> typephp_wasm_request_startup()
-> TypePHP AOT entry
-> typephp_wasm_request_shutdown()
-> 可再次执行 request
-> typephp_wasm_module_shutdown()
```
每次请求必须有独立的 PHP request 内存池。执行成功、PHP 异常、C++ 异常和 Zend bailout 都必须进入统一的清理路径。
模块导出 API 可从以下最小集合开始,名称以实际实现为准:
```c
int typephp_wasm_module_startup(void);
int typephp_wasm_run(int argc, const char **argv);
const char *typephp_wasm_last_error(void);
void typephp_wasm_module_shutdown(void);
```
`typephp_wasm_run()` 执行已经静态链接的 AOT 入口,不负责在运行时解析和编译任意 PHP 源码。
## 7. PHP 构建策略
### 7.1 基础配置
- 固定一个明确的 php-src commit,而不是只固定分支名。
- NTS 构建。
- 禁用 CLI、CGI、FPM、Apache 等现有 SAPI。
- 新增最小 `typephp_wasm` SAPI,或先用极小的 embed 原型验证生命周期,再收敛为专用 SAPI。
- 禁用 opcache/JIT。
- 所有扩展静态链接。
- 关闭不需要的扩展和自动探测,避免宿主机环境改变构建结果。
- 用 `config.site` 和独立 patch 目录记录交叉编译结论。
首期不要直接复制其他项目的完整 configure 参数。应从最小 PHP core 启动,根据 TypePHP PHPT 和运行时依赖逐项增加扩展。
### 7.2 扩展分层
建议把扩展分为三组:
1. **必须启用**:TypePHP 和 Zend 基本运行所需的 core、standard、SPL、date、pcre、hash、json 等,最终以实际链接和测试结果为准。
2. **可选本地扩展**:ctype、filter、mbstring、tokenizer、fileinfo、zlib 等,无操作系统网络依赖,但会增加体积。
3. **首期禁用**:sockets、curl、mysqli、PDO 网络驱动、pcntl、posix、FFI、shm、sysv、readline、opcache/JIT 等。
GMP、MPFR 和 mpdecimal 首先作为 PHPX/TypePHP 高精度实现的静态依赖处理,不要求启用 PHP `ext/gmp`
## 8. PHP stream 和操作系统能力
### 8.1 不关闭整个 stream 子系统
PHP 标准库大量依赖 stream。完全关闭 stream 会破坏文件读写、`php://`、include 路径处理以及部分标准扩展,收益小而兼容成本高。
首期保留:
- 普通文件 stream,底层使用 Emscripten MEMFS;
- `php://memory`
- `php://temp`
- `php://stdin`、`php://stdout`、`php://stderr` 的宿主映射;
- `data://` 是否启用由体积和安全评估决定;
- 纯内存 stream filter 可按需启用。
### 8.2 禁用网络 stream
应在 PHP 构建和运行时注册阶段禁用或不注册:
- TCP、UDP 和 Unix socket transport;
- socket 扩展;
- `http://`、`https://`、`ftp://` 等依赖网络的 wrapper;
- `fsockopen()`、`pfsockopen()`、`stream_socket_*()`;
- 网络数据库和网络客户端扩展。
首期不应通过同步 XHR 或隐式 JavaScript fetch 模拟 PHP socket。若未来需要 HTTP,应设计显式、可授权的异步宿主 API,而不是伪造 POSIX socket。
### 8.3 其他 OS 相关功能
以下能力需要禁用、降级或由宿主注入:
| 能力 | 首期策略 |
|---|---|
| 文件系统 | MEMFS;可选只读预加载文件 |
| 当前目录和路径 | 虚拟根目录,禁止泄漏宿主路径 |
| 环境变量 | loader 注入白名单 |
| 时间 | Emscripten/JavaScript host clock |
| 随机数 | 浏览器安全随机源,不使用弱伪随机替代 |
| DNS、socket | 不支持 |
| 进程、shell | 不支持 |
| 信号 | 不支持 |
| 用户、组、权限 | 固定值或明确报错 |
| 文件锁 | 首期不支持跨实例锁;单实例内按需降级 |
| 持久化 | 首期不提供;后续可选 IDBFS/OPFS |
编译器应逐步增加 WASM target capability 检查:静态可识别的不支持函数在编译期报错;动态调用无法静态判断时,由运行时返回确定错误。禁止让这些调用表现为链接期缺失符号、空函数或未定义行为。
## 9. 异常、bailout 和清理
这是项目的首要技术风险,必须早于完整 PHP 功能移植进行验证。
### 9.1 编译选项
使用原生 WebAssembly exceptions 时,C 和 C++ 对 `setjmp/longjmp` 的模式必须一致。原型建议验证以下组合:
```text
C 编译:
-sSUPPORT_LONGJMP=wasm
C++ 编译:
-fwasm-exceptions
-sSUPPORT_LONGJMP=wasm
最终链接:
-fwasm-exceptions
-sSUPPORT_LONGJMP=wasm
```
所有 PHP、PHPX、TypePHP 和第三方 C/C++ 对象必须使用同一套 ABI 和异常配置。不能只在最终链接阶段补开 C++ exception catching。
如果目标浏览器兼容性不允许原生 Wasm EH,可研究 Emscripten JavaScript exception 模式作为备选,但不得在同一发布物中混用两套模型。
### 9.2 边界规则
- C++ 异常不得未经处理地穿过导出函数进入 JavaScript。
- Zend bailout 必须被 request 顶层捕获,并进入 request shutdown。
- bailout 后不能继续析构依赖已销毁 request 内存池的悬空 PHPX 对象。
- 栈上的 PHPX `Variant`、`Object`、`Array` 和高精度对象必须在内存池仍有效时完成析构,或由专门的 bailout 安全边界接管。
- 一个请求失败后,下一次请求必须仍可执行;否则运行时只能定义为一次性实例,并在 API 中明确。
### 9.3 必测场景
- PHP 正常返回。
- PHP `throw` 被 TypePHP 代码捕获。
- PHP 未捕获异常到达请求顶层。
- `fatalError`/Zend bailout。
- C++ `throw``catch`
- PHP 调用 C++、C++ 再调用 PHP 时抛出异常。
- bailout 发生时栈上存在 PHPX 对象和高精度对象。
- 连续执行成功、失败、成功三个请求。
- 内存增长后再次执行请求。
## 10. 内存和高精度库
### 10.1 WASM 内存
首期使用单一线性内存,并验证 `-sALLOW_MEMORY_GROWTH`。需要记录:
- 初始内存;
- 最大内存;
- PHP memory_limit;
- request 结束后的 Zend 内存回收;
- Emscripten allocator 的实际峰值;
- 多次 request 后是否持续增长。
不要在没有基准测试前选择 `emmalloc`。PHP、GMP、MPFR 和 mpdecimal 都是分配密集型组件,应在 `dlmalloc`、`emmalloc` 等候选之间测试体积与运行时间。
### 10.2 GMP、MPFR 和 mpdecimal
- 全部使用 Emscripten 工具链静态编译。
- 禁用汇编和宿主 CPU 专用优化。
- 固定 limb、整数宽度和 ABI 检测结果。
- 不依赖运行时动态库搜索。
- 运行现有 BigInt、BigFloat、Decimal PHPT,并增加最大内存、除零、精度、舍入和异常路径测试。
- 验证库异常或分配失败不会绕过 PHP request 清理。
## 11. 建议的仓库结构
建议在实现阶段增加独立目录,不把 Emscripten 条件散落到现有构建代码中:
```text
wasm/
├── README.md
├── build.sh
├── versions.env
├── config.site
├── cmake/
│ └── TypePhpWasmToolchain.cmake
├── patches/
│ ├── php-src/
│ ├── gmp/
│ ├── mpfr/
│ └── mpdecimal/
├── sapi/
│ └── typephp_wasm/
├── runtime/
│ └── typephp-wasm.mjs
└── tests/
```
维护原则:
- patch 应小而独立,一项兼容问题一个 patch;
- 每个 patch 记录上游版本、来源、原因和可删除条件;
- 下载缓存不提交到 Git;
- php-src、Emscripten 和第三方库使用校验和锁定;
- 构建产物不进入源码仓库;
- CI 至少保留 debug 和 release 两种构建。
## 12. 分阶段实施计划
### 阶段 0:工具链风险验证
目标:不接入完整 TypePHP,先证明关键底层机制可行。
- 固定 Emscripten 版本。
- 编译最小 C/C++ 混合程序。
- 验证 C++ exception。
- 验证 `setjmp/longjmp`
- 验证两者嵌套和重复调用。
- 验证主流浏览器支持情况。
退出条件:异常和 longjmp 行为稳定,没有不可接受的浏览器缺口。
### 阶段 1:最小 PHP NTS
目标:PHP core 在浏览器中完成模块和请求生命周期。
- 交叉编译最小 php-src。
- 实现最小 WASM SAPI 或 embed 验证层。
- 支持 stdout/stderr 和 MEMFS。
- 执行固定入口。
- 验证 fatal error、异常和 request shutdown。
退出条件:连续执行“成功、失败、成功”请求无崩溃、无持续内存增长。
### 阶段 2:接入 PHPX 和 TypePHP
目标:现有 TypePHP C++ 后端可以由 `em++` 编译并静态链接。
- 为编译器增加 WASM platform/backend 配置。
- 统一 PHPX、TypePHP 和第三方库编译 flags。
- 链接一个最小 TypePHP `main()`
- 建立 WASM smoke PHPT 子集。
- 为不支持的系统 API 增加 capability diagnostics。
退出条件:基础类型、函数、类、异常、数组和对象测试通过。
### 阶段 3:高精度与本地 stream
目标:支持 TypePHP 关键运行时能力。
- 静态链接 GMP、MPFR、mpdecimal。
- 运行高精度完整运算符和边界测试。
- 支持必要的 `file://``php://` stream。
- 增加预加载只读资源机制。
- 明确所有被禁用的 wrapper、transport 和扩展。
退出条件:高精度测试通过,本地文件行为确定,网络 API 全部可预测地失败。
### 阶段 4:体积、性能和发布
目标:形成可分发的 TypePHP WASM SDK。
- release 优化和 dead-code elimination。
- 检查导出符号白名单。
- 比较 allocator 和内存增长配置。
- 建立下载体积、启动时间和峰值内存基准。
- 生成 `typephp.wasm` 和薄 `.mjs` loader。
- 编写用户侧功能与限制文档。
退出条件:产物可复现,兼容性清单完整,性能达到预设基线。
### 阶段 5:可选宿主能力
后续按真实需求选择,不作为基础运行时默认能力:
- IDBFS 或 OPFS 持久化;
- 显式 HTTP host API;
- Node.js 宿主;
- WASI 原型;
- 多实例隔离;
- Web Worker 并行实例。
每项能力都必须通过显式 capability 开启,不能让 PHP 代码默认获得宿主全部权限。
## 13. 测试策略
### 13.1 测试层次
1. **工具链测试**:exception、longjmp、静态库、链接和导出符号。
2. **PHP 生命周期测试**:module/request startup、shutdown、bailout 和重复请求。
3. **PHPX 测试**:Variant、Object、Array、引用、异常和资源析构。
4. **TypePHP PHPT**:选择不依赖 OS 的现有测试,并维护 WASM 跳过原因。
5. **高精度测试**:完整运算符、边界、错误和内存压力。
6. **能力限制测试**:网络、进程、线程和动态扩展必须稳定拒绝。
7. **浏览器测试**:Chrome、Firefox、Safari 的最低支持版本。
### 13.2 关键指标
- `.wasm` 原始大小和压缩大小;
- 首次实例化时间;
- module startup 和 request startup 时间;
- 简单 TypePHP 程序执行时间;
- 初始、峰值和多请求后的线性内存;
- 异常和 bailout 后的可恢复性;
- JavaScript loader 大小;
- 相同输入的可复现构建校验和。
## 14. Go/No-Go 条件
出现以下任一情况,应暂停完整移植并重新评估架构:
- Zend bailout 与 C++ 栈析构无法建立安全边界;
- 请求失败会稳定破坏后续请求,且不能接受一次性实例模型;
- GMP、MPFR 或 mpdecimal 需要大规模侵入式 fork;
- `.wasm` 体积或浏览器峰值内存明显超出目标场景可接受范围;
- Safari、Firefox、Chrome 需要互不兼容的异常 ABI;
- PHPX 中依赖原生线程、动态链接或 OS 资源的假设无法隔离。
如果最短路径不可行,再评估 [BACKEND_NEUTRAL_IR.md](BACKEND_NEUTRAL_IR.md) 所述的独立 WASM runtime/backend,不应在没有原型数据时提前启动该重写。
## 15. 外部参考
- [PHP 源码仓库](https://github.com/php/php-src)
- [Emscripten:C setjmp/longjmp 支持](https://emscripten.org/docs/porting/setjmp-longjmp.html)
- [Emscripten:C/C++ 可移植性说明](https://emscripten.org/docs/porting/guidelines/portability_guidelines.html)
- [Emscripten:代码与内存优化](https://emscripten.org/docs/optimizing/Optimizing-Code.html)
- [WordPress Playground:编译 PHP 到 WebAssembly](https://developer.wordpress.org/playground/developers/architecture/wasm-php-compiling/)
- [WordPress Playground 架构](https://wordpress.github.io/wordpress-playground/developers/architecture/)
- [seanmorris/php-wasm](https://github.com/seanmorris/php-wasm)
- [soyuka/php-wasm](https://github.com/soyuka/php-wasm)
这些链接用于追踪上游行为和已知移植问题。TypePHP 的最终实现和兼容性必须由自己的构建、测试及基准验证,不能直接继承其他项目的结论。

@ -117,6 +117,7 @@ class Constants
'pipe',
'errno', // Linux error code
'this_', // phpx keywords
'fake_scope_guard',
];
public const array UNSUPPORTED_FUNCTIONS = [

@ -23,14 +23,25 @@ use TypePhp\Generator\Symbol;
trait PropertyAccessTrait
{
protected function usesTraitPropertyScope(string $object): bool
{
return $this->classDef?->trait && $object === 'this_';
}
protected function emitDynamicPropertyRead(string $object, string $property): string
{
if ($this->usesTraitPropertyScope($object)) {
return 'typephp_read_property_scoped('
. $object . ', ' . $property . ', php::FakeScopeGuard::current(), php::AttrMode::Get)';
}
return "{$object}.getProperty({$property})";
}
protected function emitDynamicPropertyWrite(string $object, string $property, string $value): string
{
$scope = $this->class ? $this->getClassEntryPtr($this->getFullClassName()) : 'nullptr';
$scope = $this->usesTraitPropertyScope($object)
? 'php::FakeScopeGuard::current()'
: ($this->class ? $this->getClassEntryPtr($this->getFullClassName()) : 'nullptr');
return 'typephp_write_property_scoped('
. $object . ', ' . $property . ', ' . $value . ', ' . $scope . ')';
}
@ -163,11 +174,21 @@ trait PropertyAccessTrait
protected function emitDynamicPropertyAppendArray(string $object, string $property, string $value): string
{
if ($this->usesTraitPropertyScope($object)) {
return 'typephp_read_property_scoped('
. $object . ', ' . $property . ', php::FakeScopeGuard::current(), php::AttrMode::Update)'
. ".newItem() = {$value}";
}
return "{$object}.attr({$property}, php::AttrMode::Update).newItem() = {$value}";
}
protected function emitDynamicPropertyUpdateArray(string $object, string $property, string $dim, string $value): string
{
if ($this->usesTraitPropertyScope($object)) {
return 'typephp_read_property_scoped('
. $object . ', ' . $property . ', php::FakeScopeGuard::current(), php::AttrMode::Update)'
. ".item({$dim}, true) = {$value}";
}
return "{$object}.attr({$property}, php::AttrMode::Update).item({$dim}, true) = {$value}";
}
@ -839,7 +860,12 @@ trait PropertyAccessTrait
$this->errorUndefinedVariable($object);
}
$objectVar = $objectName;
$getProperty = $objectVar . '.attr(' . $id . ', ' . $this->escapeAttrMode($update) . ')';
if ($this->usesTraitPropertyScope($objectVar)) {
$getProperty = 'typephp_read_property_scoped('
. $objectVar . ', ' . $id . ', php::FakeScopeGuard::current(), ' . $this->escapeAttrMode($update) . ')';
} else {
$getProperty = $objectVar . '.attr(' . $id . ', ' . $this->escapeAttrMode($update) . ')';
}
$def = $this->getNativePropertyDef($expr);
if ($def and $this->nativeTypes) {
$propName = $this->parseIdentifier($property);

@ -4588,6 +4588,11 @@ CODE;
$code .= $methodDef->functionDef->params . ')';
$code .= '{' . PHP_EOL;
$this->indentLevel++;
// The trait body is compiled once, but its private/protected property
// scope is the class that composes it. Keep that lexical scope fixed
// when this wrapper is inherited by a child class.
$scope = $this->getClassEntryPtr($classDef->getNamespacedName(false));
$code .= $this->getIndent() . 'php::FakeScopeGuard fake_scope_guard{' . $scope . '};' . PHP_EOL;
$methodCall = self::PREFIX . $traitMethodNativeName . '(' . $argv . ')';
if ($cppReturnType !== Type::VOID) {
$methodCall = 'return ' . $methodCall;

@ -0,0 +1,20 @@
--TEST--
Trait wrapper scope guard does not collide with a PHP parameter
--FILE--
<?php
trait GuardParameterTrait {
public function passThrough(string $fake_scope_guard): string {
return $fake_scope_guard;
}
}
class GuardParameterUser {
use GuardParameterTrait;
}
function main() {
var_dump((new GuardParameterUser())->passThrough('ok'));
}
?>
--EXPECT--
string(2) "ok"
Loading…
Cancel
Save