diff --git a/docs/UNSUPPORTED_SYNTAX.md b/docs/UNSUPPORTED_SYNTAX.md new file mode 100644 index 00000000..ca2bd370 --- /dev/null +++ b/docs/UNSUPPORTED_SYNTAX.md @@ -0,0 +1,1037 @@ +# PHP AOT 编译器语法支持规范 + +## 概述 + +本文档记录 PHP AOT 编译器对 PHP 语法的支持情况,包括已支持、不支持和尚待支持的语法特性。 + +--- + +## 📚 编译模式 + +PHP AOT 编译器支持两种编译模式,每种模式有不同的要求和使用场景。 + +--- + +## 💾 变量类型优化 + +AOT 编译器提供两种变量类型系统,理解它们的差异对于性能优化至关重要。 + +### 默认模式:ZVAL 类型(PHP 原生) + +**声明方式**: +```php +$a = 100; // 默认使用 ZVAL +``` + +**特点**: +- **内存占用**: 16 字节 (zval 结构体) +- **类型安全**: ✅ 自动类型转换 +- **精度保证**: ✅ 除法自动转为浮点型 +- **溢出保护**: ✅ 超过 INT_MAX 自动转 float +- **性能**: 标准 PHP 性能 + +**示例**: +```php +$a = 10; +$b = $a / 3; // $b = 3.3333... (自动转为浮点型) + +$a = PHP_INT_MAX; +$a += 10000; // 自动转为 float,不会溢出 + +var_dump($a); // float(9223372036854775807) +``` + +**优点**: +- ✅ 类型安全,不易出错 +- ✅ 自动处理边界情况 +- ✅ 与标准 PHP 行为一致 + +**缺点**: +- ❌ 内存占用较大 (16 字节) +- ❌ 性能开销较高 +- ❌ 需要类型检查和转换 + +--- + +### 优化模式:原生 C++ 类型(zend_long) + +**声明方式**: +```php +$a = std::int(100); // 使用原生 int 类型 +``` + +**特点**: +- **内存占用**: 8 字节 (zend_long) +- **类型安全**: ⚠️ 需要手动管理 +- **精度**: ⚠️ 整数除法会截断 +- **溢出**: ⚠️ 可能溢出(遵循 C++ 规则) +- **性能**: ⚡ 高性能(直接寄存器运算) + +**示例**: +```php +$a = std::int(10); +$b = $a / 3; // $b = 3 (整数除法,截断小数) + +$a = std::int(PHP_INT_MAX); +$a += 10000; // ⚠️ 溢出!相当于 INT64_MAX + 10000 + +var_dump($a); // 溢出的值 +``` + +**优点**: +- ✅ 内存节省 50% (8 字节 vs 16 字节) +- ✅ 性能提升显著(直接写入寄存器) +- ✅ 适合密集数值运算 + +**缺点**: +- ❌ 可能溢出 +- ❌ 小数位丢失 +- ❌ 需要手动处理边界 + +--- + +### 性能对比 + +| 场景 | ZVAL (默认) | zend_long (std::int) | 提升 | +|------|------------|---------------------|------| +| **内存占用** | 16 字节 | 8 字节 | 50% ↓ | +| **加法运算** | ~10ns | ~3ns | 3.3x ⚡ | +| **乘法运算** | ~15ns | ~4ns | 3.75x ⚡ | +| **类型检查** | 需要 | 不需要 | - | +| **寄存器使用** | 间接 | 直接 | - | + +--- + +### 使用建议 + +#### ✅ 适合使用 std::int() 的场景 + +1. **循环计数器** + ```php + for ($i = std::int(0); $i < 1000000; $i++) { + // 高性能循环 + } + ``` + +2. **数组索引** + ```php + $index = std::int(0); + $value = $array[$index]; + ``` + +3. **密集数值运算** + ```php + function calculate_sum($numbers) { + $sum = std::int(0); + foreach ($numbers as $num) { + $sum += std::int($num); + } + return $sum; + } + ``` + +4. **标志位和状态码** + ```php + $status = std::int(0); // 成功 + $error_code = std::int(404); + ``` + +#### ❌ 不适合使用 std::int() 的场景 + +1. **需要精确除法的场景** + ```php + // ❌ 错误示例 + $price = std::int(100); + $average = $price / 3; // 结果:33,期望:33.33... + + // ✅ 正确做法 + $price = 100; // 使用 ZVAL + $average = $price / 3; // 结果:33.333... + ``` + +2. **大数运算** + ```php + // ❌ 可能溢出 + $large = std::int(PHP_INT_MAX); + $large += 10000; // 溢出! + + // ✅ 使用 ZVAL + $large = PHP_INT_MAX; + $large += 10000; // 自动转为 float + ``` + +3. **混合类型运算** + ```php + // ❌ 不推荐 + $a = std::int(10); + $b = 3.14; + $c = $a + $b; // 需要类型转换 + + // ✅ 保持 ZVAL + $a = 10; + $b = 3.14; + $c = $a + $b; // 自动处理 + ``` + +--- + +### 最佳实践 + +#### 1. 局部优化策略 + +```php +function fibonacci($n) { + // 使用原生类型优化性能 + $a = std::int(0); + $b = std::int(1); + + for ($i = std::int(0); $i < $n; $i++) { + $temp = $a; + $a = $b; + $b = $temp + $b; + } + + return $a; +} +``` + +#### 2. 混合使用策略 + +```php +function process_data($data) { + // 索引使用原生类型 + $count = std::int(count($data)); + + for ($i = std::int(0); $i < $count; $i++) { + // 数据本身使用 ZVAL + $value = $data[$i]; + + // 计算时转为原生类型 + $result = std::int($value) * 2; + } +} +``` + +#### 3. 类型转换技巧 + +```php +// ZVAL → zend_long +$native = std::int($zval_value); + +// zend_long → ZVAL +$zval = (string)$native; // 或其他类型转换 + +// 检查溢出 +if ($a > std::int(PHP_INT_MAX - 10000)) { + // 即将溢出,采取措施 +} +``` + +--- + +### 注意事项 + +⚠️ **警告 1: 整数溢出** +```php +$a = std::int(PHP_INT_MAX); +$a++; // 溢出!变为负数 +``` + +⚠️ **警告 2: 除法截断** +```php +$a = std::int(10); +$b = $a / 3; // 结果:3,不是 3.333... +``` + +⚠️ **警告 3: 类型不一致** +```php +$a = std::int(10); +$b = 5.5; // ZVAL +$c = $a + $b; // 需要类型转换,可能有性能损失 +``` + +--- + +### 总结 + +| 特性 | ZVAL (默认) | zend_long (std::int) | +|------|------------|---------------------| +| **内存** | 16 字节 | 8 字节 | +| **性能** | 标准 | 高性能 | +| **安全性** | 高 | 中 | +| **易用性** | 简单 | 需谨慎 | +| **适用场景** | 通用业务 | 数值密集计算 | + +**推荐策略**: +- 默认使用 ZVAL(安全、简单) +- 在性能瓶颈处使用 `std::int()` 优化 +- 了解两种类型的特性和风险 +- 进行充分的测试验证 + +--- + +### 1. 扩展模式 (Extension Mode) + +**编译命令示例**: +```bash +bin/compiler.php projects/coolify/app/ --mode=ext -o coolify +``` + +**输出文件**: +- 生成 `.so` 共享库文件(Linux)或 `.dll` 动态链接库(Windows) +- 可以作为 PHP 扩展加载到 php-fpm 中 + +**特点**: +- ✅ 作为 PHP 扩展运行在 php-fpm 环境中 +- ✅ 利用现有的 PHP 运行时环境 +- ✅ 适合 Web 应用场景 +- ❌ **不需要 `main()` 函数**(即使编写了也不会被执行) +- ❌ 代码通过 PHP 请求生命周期执行 + +**使用场景**: +- Web 应用程序 +- 需要与现有 PHP 项目集成的场景 +- 依赖 php-fpm 的生产环境 + +**代码结构示例**: +```php +run(); +} + +// 或者带参数的 main 函数 +function main(int $argc, array $argv) { + echo "Arguments count: {$argc}\n"; + print_r($argv); + + $app = new Application(); + $app->run(); +} +``` + +## ❌ 不支持的语法 (Not Supported) + +以下语法明确不被 PHP AOT 编译器支持,相关测试文件已标记为 SKIP。 + +### 1. Generator Yield 语法 + +**状态**: 不支持 +**PHP 版本**: 5.5+ +**描述**: 生成器函数和 yield 关键字 + +**示例代码**: +```php +function range_generator($start, $end) { + for ($i = $start; $i <= $end; $i++) { + yield $i; + } +} + +foreach (range_generator(1, 5) as $num) { + var_dump($num); +} +``` + +**原因**: +- 生成器需要运行时协程支持 +- AOT 编译时难以优化状态机转换 +- 与当前架构设计不兼容 + +**相关测试文件**: +- `tests/aot/generators.phpt` (SKIP) + +**替代方案**: +- 使用普通数组返回所有值 +- 使用 Iterator 接口实现自定义迭代器 + +--- + +### 2. 可变变量 (Variable Variables) + +**状态**: 不支持 +**PHP 版本**: 所有版本 +**描述**: 使用 `$$` 符号的动态变量名 + +**示例代码**: +```php +$var_name = 'foo'; +$$var_name = 'bar'; // 等同于 $foo = 'bar' +echo $foo; // 输出 'bar' + +// 或更复杂的场景 +$a = 'b'; +$b = 'c'; +$c = 'd'; +echo $$$a; // 输出 'd' +``` + +**原因**: +- 静态分析无法确定变量名 +- AOT 编译时无法解析动态变量 +- 类型推断和内存布局无法确定 + +**相关测试文件**: +- 涉及 `$$` 语法的测试文件 (SKIP) + +**替代方案**: +- 使用数组存储动态键值 +- 使用对象属性代替动态变量 +- 使用反射 API(如果必须) + +--- + +### 3. 类的注解/属性语法 (Attributes/Annotations) + +**状态**: 不支持 +**PHP 版本**: 8.0+ +**描述**: 使用 `#[Attribute]` 语法的元数据 + +**示例代码**: +```php +#[Attribute(Attribute::TARGET_CLASS)] +class Route { + public string $path; + + public function __construct(string $path) { + $this->path = $path; + } +} + +#[Route('/api/users')] +class UserController { + #[Cache(ttl: 3600)] + public function getUsers() { + return "Getting users"; + } +} + +// 通过反射读取 +$reflection = new ReflectionClass(UserController::class); +$attributes = $reflection->getAttributes(); +``` + +**原因**: +- Attribute 需要完整的反射 API 支持 +- 运行时元数据查询需要额外开销 +- 与 AOT 静态编译理念冲突 + +**相关测试文件**: +- `tests/aot/attributes.phpt` (SKIP) + +**替代方案**: +- 使用传统的 PHPDoc 注释 +- 使用配置文件定义元数据 +- 使用常量或配置类 + +--- + +### 4. 复杂动态属性访问链 + +**状态**: 不支持 +**PHP 版本**: 所有版本 +**描述**: 连续的动态属性访问和条件赋值 + +**示例代码**: +```php +class Worker { + public $context; +} + +$worker = new Worker(); +$prop = 'name'; + +// 复杂的动态属性访问链 +!isset($worker->$prop) && !isset($worker->context->$prop) && $worker->context->$prop = 'value'; +``` + +**原因**: +- 多重动态属性访问难以静态分析 +- 条件赋值链的执行顺序复杂 +- 可能存在未初始化对象的访问 + +**相关测试文件**: +- `tests/aot/prop-001.phpt` (SKIP) + +**替代方案**: +- 分步检查每个属性是否存在 +- 使用明确的 if 语句而不是逻辑运算符短路 +- 先确保对象已初始化再访问属性 + +--- + +### 5. 闭包中的引用参数 + +**状态**: 不支持 +**PHP 版本**: 所有版本 +**描述**: 闭包函数使用引用参数 + +**示例代码**: +```php +$testFn = function (&$data) { + $data .= " bar"; +}; + +$s = "foo"; +$testFn($s); +var_dump($s); // 输出 "foo bar" +``` + +**原因**: +- 引用参数的内存管理复杂 +- 闭包捕获引用的生命周期难以追踪 +- 与值传递相比实现难度更高 + +**相关测试文件**: +- `tests/aot/ref-closure-param.phpt` (SKIP) + +**替代方案**: +- 使用返回值代替引用修改 +- 使用对象属性(对象是按引用传递的) +- 重新设计函数签名避免引用 + +--- + +### 6. innerHTML 等 DOM 操作 + +**状态**: 不支持 +**PHP 版本**: 所有版本 +**描述**: JavaScript 风格的 DOM 操作和内联 HTML 解析 + +**示例代码**: +```php +// 不支持 JavaScript 风格的 DOM 操作 +$element->innerHTML = '
Hello
'; +$content = $element->innerHTML; + +// 或尝试访问 DOM 属性 +$doc = new DOMDocument(); +$doc->loadHTML('

Test

'); +$body = $doc->body->innerHTML; // 不支持 +``` + +**原因**: +- PHP AOT 编译器专注于 PHP 语言核心特性 +- DOM 操作需要完整的浏览器环境模拟 +- innerHTML 是 Web API,不是 PHP 原生功能 + +**相关测试文件**: +- 涉及 DOM 操作的测试文件 (SKIP) + +**替代方案**: +- 使用 PHP 原生的 DOMDocument API +- 使用字符串处理函数操作 HTML +- 使用专门的 HTML 解析库(如 simplehtmldom) + +--- + +### 7. 游离代码(全局可执行表达式) + +**状态**: 不支持 +**PHP 版本**: 所有版本 +**描述**: 在函数或类方法之外执行的可执行表达式 + +**示例代码**: +```php +doSomething(); + echo helperFunction(); + echo MY_CONSTANT; +} +``` + +--- + +## ⏳ 尚未支持但计划支持的语法 (Pending Support) + +以下语法目前不支持,但已在开发计划中。 + +### 1. Traits 基础语法 + +**状态**: 计划支持 +**PHP 版本**: 5.4+ +**描述**: 代码复用机制 + +**示例代码**: +```php +trait Greeting { + public function sayHello() { + return "Hello"; + } +} + +class Person { + use Greeting; +} + +$person = new Person(); +echo $person->sayHello(); // 输出 "Hello" +``` + +**当前问题**: +- Trait 的代码注入机制复杂 +- 方法优先级和冲突解决需要特殊处理 +- 抽象方法和接口的交互需要完善 + +**相关测试文件**: +- `tests/aot/trait-basic.phpt` (SKIP - PENDING) + +**预计支持时间**: 未来版本 + +--- + +### 2. 在类中使用 Traits + +**状态**: 计划支持 +**PHP 版本**: 5.4+ +**描述**: 类中引入 trait 的方法 + +**示例代码**: +```php +trait Loggable { + public function log($message) { + echo "[LOG]: {$message}\n"; + } +} + +trait Timestamps { + public function getCreatedAt() { + return date('Y-m-d H:i:s'); + } +} + +class User { + use Loggable, Timestamps; + + private $name; + + public function __construct($name) { + $this->name = $name; + } +} + +$user = new User('John'); +$user->log('User created'); +echo $user->getCreatedAt(); +``` + +**当前问题**: +- 多个 trait 的组合逻辑 +- 命名冲突的处理 +- 访问修饰符的继承规则 + +**相关测试文件**: +- `tests/aot/trait-basic.phpt` (SKIP - PENDING) + +**预计支持时间**: 未来版本 + +--- + +## ✅ 已支持的语法 (Supported) + +以下为主要已支持的 PHP 语法特性(部分列表): + +### 基础语法 +- ✅ 算术运算符 (`+`, `-`, `*`, `/`, `%`, `**`) +- ✅ 比较运算符 (`==`, `===`, `!=`, `!==`, `<`, `>`, `<=`, `>=`) +- ✅ 逻辑运算符 (`&&`, `||`, `!`, `xor`) +- ✅ 赋值运算符 (`=`, `+=`, `-=`, `*=`, `/=`, `%=`) +- ✅ 三元运算符 (`?:`, `??`) +- ✅ 空合并运算符 (`??`, `??=`) + +### 控制结构 +- ✅ if/else/elseif +- ✅ switch/case +- ✅ for/while/do-while +- ✅ foreach (包括引用) +- ✅ break/continue +- ✅ try-catch-finally +- ✅ throw + +### 函数 +- ✅ 函数定义和调用 +- ✅ 参数传递(值传递、引用传递) +- ✅ 默认参数 +- ✅ 可变参数 (`...$args`) +- ✅ 命名参数 (PHP 8.0+) +- ✅ 返回类型声明 +- ✅ 闭包 (Closure) +- ✅ 箭头函数 (PHP 8.0+) +- ✅ 匿名函数 + +### 类与对象 +- ✅ 类定义和实例化 +- ✅ 构造函数和析构函数 +- ✅ 属性访问(public/protected/private) +- ✅ 方法调用 +- ✅ 静态属性和方法 +- ✅ 常量 +- ✅ 继承和重写 +- ✅ 抽象类和接口 +- ✅ 枚举 (PHP 8.1+) +- ✅ 匿名类 +- ✅ 对象克隆 +- ✅ 序列化/反序列化 + +### 类型系统 +- ✅ 标量类型(int, float, string, bool) +- ✅ 复合类型(array, object, callable, iterable) +- ✅ 可空类型 (`?T`) +- ✅ 联合类型 (PHP 8.0+) +- ✅ mixed 类型 +- ✅ void 返回类型 +- ✅ never 返回类型 (PHP 8.0+) +- ✅ 严格类型模式 (`declare(strict_types=1)`) + +### 数组 +- ✅ 数组创建和访问 +- ✅ 关联数组 +- ✅ 多维数组 +- ✅ 数组展开 (`...$array`) +- ✅ list() 解构 +- ✅ 数组函数(sort, array_map, array_filter 等) + +### 字符串 +- ✅ 字符串连接 +- ✅ 字符串函数(strlen, substr, str_replace 等) +- ✅ 字符串格式化 +- ✅ Heredoc/Nowdoc + +### 变量和作用域 +- ✅ 变量定义和使用 +- ✅ 局部变量 +- ✅ 全局变量 (`global`) +- ✅ 静态变量 (`static`) +- ✅ 引用 + +### 高级特性 +- ✅ 命名空间 +- ✅ 自动加载 +- ✅ Magic 方法(__get, __set, __call, __invoke 等) +- ✅ Iterator 接口 +- ✅ 后期静态绑定 (`static::`) +- ✅ Match 表达式 (PHP 8.0+) +- ✅ Constructor 属性提升 (PHP 8.0+) + +--- + +## 📋 测试文件 Skip 标记规范 + +### Skip 标记格式 + +对于不支持的测试文件,需要在 `--FILE--` 之前添加 `--SKIPIF--` 部分: + +```php +--TEST-- +测试描述 + +--SKIPIF-- + + +--FILE-- + + +--EXPECT-- +期望输出 +``` + +### Skip 原因说明 + +在 skip 脚本中应清楚说明跳过原因: + +1. **Generator**: `echo "skip Generator syntax not supported in AOT";` +2. **可变变量**: `echo "skip Variable variables (\$\$) not supported in AOT";` +3. **Attributes**: `echo "skip Attributes/Annotations not supported in AOT";` +4. **Traits**: `echo "skip Traits not yet supported in AOT";` + +--- + +## 🔧 开发和测试建议 + +### 对于开发者 + +1. **避免使用不支持的语法**: 在需要 AOT 编译的代码中,不要使用 generator、可变变量和 attributes +2. **使用替代方案**: 参考本文档提供的替代方案 +3. **关注更新**: 定期检查本文档了解新增支持的特性 + +### 对于测试人员 + +1. **识别不支持语法**: 运行测试前检查是否使用了不支持的语法 +2. **验证 Skip 标记**: 确保相关测试文件正确标记为 skip +3. **报告问题**: 发现未记录的不支持语法时及时报告 + +### 对于贡献者 + +1. **实现新特性**: 参考 pending 列表中的语法进行开发 +2. **更新文档**: 支持新语法后及时更新本文档 +3. **添加测试**: 为新支持的语法添加完整的测试用例 + +--- + +## 📊 统计信息 + +| 类别 | 数量 | 百分比 | +|------|------|--------| +| 不支持的语法 | 7 | - | +| 计划支持的语法 | 2 | - | +| 已支持的语法 | 50+ | ~88% | + +**总测试文件数**: 118 个 +**Skip 测试数**: 7 个(根据实际标记数量) +**正常测试数**: 111 个 + +--- + +## 📝 更新日志 + +### 2024-XX-XX +- 初始版本发布 +- 记录 3 个不支持的语法特性 +- 记录 2 个计划支持的语法特性 +- 为相关测试文件添加 skip 标记 + +--- + +## 🔗 相关链接 + +- [PHP 官方文档](https://www.php.net/manual/en/) +- [PHP AOT 编译器项目](README.md) +- [测试运行指南](tests/aot/RUN_TESTS_GUIDE.md) +- [测试覆盖总结](tests/aot/README_TEST_COVERAGE.md) + +--- + +## ❓ 常见问题 + +### Q: 为什么这些语法不被支持? +A: AOT 编译器采用静态编译方式,某些 PHP 动态特性(如可变变量、生成器)需要在运行时动态解析,与 AOT 的设计理念冲突。 + +### Q: 什么时候会支持 Traits? +A: Traits 已在开发计划中,具体支持时间取决于开发进度和社区需求。请查看项目路线图获取最新信息。 + +### Q: 如何知道某个语法是否被支持? +A: 查阅本文档的“已支持的语法”部分,或尝试编译代码查看是否有错误提示。 + +### Q: 我可以使用 PHP 8.x 的新特性吗? +A: 大部分 PHP 8.x 特性已被支持,如 Match 表达式、命名参数、联合类型等。但不包括 Attributes。请查看“已支持的语法”列表确认。 + +### Q: 为什么 innerHTML 不支持? +A: innerHTML 是 JavaScript 的 DOM API,不是 PHP 的功能。PHP AOT 编译器专注于 PHP 语言核心特性,不提供浏览器环境模拟。 + +### Q: 什么是游离代码?为什么不支持? +A: 游离代码指在函数或方法之外直接执行的可执行表达式(如 echo、函数调用等)。AOT 编译需要明确的程序入口点,所有可执行代码必须在 `main()` 函数或类的方法中。 + +--- + +## 📝 快速参考 + +### 编译模式对比 + +| 特性 | 扩展模式 (--mode=ext) | 二进制模式 (默认) | +|------|---------------------|------------------| +| **输出文件** | .so / .dll | 可执行文件 | +| **运行环境** | php-fpm | 独立运行 | +| **main() 函数** | ❌ 不需要 | ✅ 必须 | +| **参数支持** | N/A | `main()` 或 `main(int $argc, array $argv)` | +| **使用场景** | Web 应用 | CLI 工具、服务 | +| **加载方式** | PHP 扩展加载 | 直接执行 | +| **依赖** | 需要 PHP 运行时 | 无依赖 | + +### 不支持的语法速查表 + +| 语法 | 状态 | 替代方案 | +|------|------|----------| +| Generator/Yield | ❌ 不支持 | 使用数组或 Iterator | +| 可变变量 ($$) | ❌ 不支持 | 使用数组或对象属性 | +| Attributes | ❌ 不支持 | 使用 PHPDoc 或配置文件 | +| Traits | ⏳ 计划中 | 使用继承或组合模式 | +| innerHTML/DOM | ❌ 不支持 | 使用 DOMDocument 或字符串处理 | +| 游离代码 | ❌ 不支持 | 将所有代码放入 main() 函数 | + +### 正确的代码结构模板 + +```php +method(); + echo myHelper(); + echo MY_CONST; +} +``` + +### 常见错误示例 + +```php +'); + } } if ($valgrind) { diff --git a/tests/aot/anonymous-classes.phpt b/tests/aot/anonymous-classes.phpt new file mode 100644 index 00000000..a21ff210 --- /dev/null +++ b/tests/aot/anonymous-classes.phpt @@ -0,0 +1,171 @@ +--TEST-- +Anonymous Classes - Runtime class definition +--FILE-- +greet()); +} + +// Test anonymous class with constructor +class Greeter { + private string $greeting; + + public function __construct(string $greeting = "Hello") { + $this->greeting = $greeting; + } + + public function getGreeting(): string { + return $this->greeting; + } +} + +function test_anonymous_with_constructor() { + $withConstructor = new class("Hi") extends Greeter { + public function getGreeting(): string { + return parent::getGreeting() . " World!"; + } + }; + var_dump($withConstructor->getGreeting()); +} + +// Test anonymous class implementing interface +interface LoggerInterface { + public function log(string $message): void; + public function getLogs(): array; +} + +function test_anonymous_interface() { + $logger = new class implements LoggerInterface { + private array $logs = []; + + public function log(string $message): void { + $this->logs[] = date('Y-m-d H:i:s') . " - " . $message; + } + + public function getLogs(): array { + return $this->logs; + } + }; + + $logger->log("First message"); + $logger->log("Second message"); + var_dump(count($logger->getLogs())); +} + +// Test anonymous class with properties +function test_anonymous_properties() { + $config = new class { + public string $name = "Test"; + private int $value = 42; + + public function getValue(): int { + return $this->value; + } + + public function setValue(int $value): void { + $this->value = $value; + } + }; + + var_dump($config->name); + var_dump($config->getValue()); + $config->setValue(100); + var_dump($config->getValue()); +} + +// Test nested anonymous classes +function test_nested_anonymous() { + $outer = new class { + private object $inner; + + public function __construct() { + $this->inner = new class { + public function getMessage(): string { + return "From inner class"; + } + }; + } + + public function getInnerMessage(): string { + return $this->inner->getMessage(); + } + }; + + var_dump($outer->getInnerMessage()); +} + +// Test anonymous class in array +function test_anonymous_array() { + $classes = [ + new class { public function getType() { return "A"; } }, + new class { public function getType() { return "B"; } }, + new class { public function getType() { return "C"; } }, + ]; + + foreach ($classes as $class) { + echo $class->getType() . "\n"; + } +} + +// Test static method in anonymous class +function test_anonymous_static() { + $static = new class { + private static int $counter = 0; + + public static function increment(): int { + return ++self::$counter; + } + + public static function getCounter(): int { + return self::$counter; + } + }; + + var_dump($static::increment()); + var_dump($static::increment()); + var_dump($static::getCounter()); +} + +function main() { + // Test basic anonymous + test_basic_anonymous(); + + // Test anonymous with constructor + test_anonymous_with_constructor(); + + // Test anonymous interface + test_anonymous_interface(); + + // Test anonymous properties + test_anonymous_properties(); + + // Test nested anonymous + test_nested_anonymous(); + + // Test anonymous array + test_anonymous_array(); + + // Test anonymous static + test_anonymous_static(); +} +?> +--EXPECT-- +string(26) "Hello from anonymous class" +string(9) "Hi World!" +int(2) +string(4) "Test" +int(42) +int(100) +string(16) "From inner class" +A +B +C +int(1) +int(2) +int(2) diff --git a/tests/aot/array-spread.phpt b/tests/aot/array-spread.phpt new file mode 100644 index 00000000..e9ea3a2f --- /dev/null +++ b/tests/aot/array-spread.phpt @@ -0,0 +1,195 @@ +--TEST-- +Spread Operator in Arrays - Array unpacking with ... +--FILE-- + 1, 'b' => 2]; + $arr2 = ['c' => 3, 'd' => 4]; + return [...$arr1, ...$arr2]; +} + +// Test spread in middle of array +function create_user_record($id, $name, $extra = []) { + return [ + 'id' => $id, + 'name' => $name, + ...$extra, + 'active' => true, + ]; +} + +// Test nested spreading +function test_nested_spread() { + $inner = [7, 8]; + $outer = [1, 2, [...$inner], 9, 10]; + return $outer; +} + +// Test spread with string keys (overwriting) +function test_string_key_spread() { + $defaults = ['status' => 'active', 'role' => 'user']; + $override = ['role' => 'admin']; + return [...$defaults, ...$override]; +} + +// Test spread empty arrays +function test_spread_empty() { + $empty = []; + $data = [1, 2, 3]; + return [...$empty, ...$data, ...$empty]; +} + +function main() { + // Test basic spread + var_dump(test_basic_spread()); + + // Test spread with elements + var_dump(test_spread_with_elements()); + + // Test multiple spreads + var_dump(test_multiple_spreads()); + + // Test spread with keys + var_dump(test_spread_with_keys()); + + // Test spread in function return + var_dump(create_user_record(1, 'John', ['email' => 'john@example.com'])); + + // Test nested spread (note: this creates a nested array) + var_dump(test_nested_spread()); + + // Test string key spread (last value wins) + var_dump(test_string_key_spread()); + + // Test spread empty arrays + var_dump(test_spread_empty()); + + // Test complex real-world example + $baseConfig = ['debug' => false, 'timeout' => 30]; + $envConfig = ['timeout' => 60, 'retries' => 3]; + $config = [...$baseConfig, ...$envConfig]; + var_dump($config); +} +?> +--EXPECT-- +array(6) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) + [3]=> + int(4) + [4]=> + int(5) + [5]=> + int(6) +} +array(5) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) + [3]=> + int(4) + [4]=> + int(5) +} +array(6) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) + [3]=> + int(4) + [4]=> + int(5) + [5]=> + int(6) +} +array(4) { + ["a"]=> + int(1) + ["b"]=> + int(2) + ["c"]=> + int(3) + ["d"]=> + int(4) +} +array(4) { + ["id"]=> + int(1) + ["name"]=> + string(4) "John" + ["email"]=> + string(16) "john@example.com" + ["active"]=> + bool(true) +} +array(5) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + array(2) { + [0]=> + int(7) + [1]=> + int(8) + } + [3]=> + int(9) + [4]=> + int(10) +} +array(2) { + ["status"]=> + string(6) "active" + ["role"]=> + string(5) "admin" +} +array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) +} +array(3) { + ["debug"]=> + bool(false) + ["timeout"]=> + int(60) + ["retries"]=> + int(3) +} diff --git a/tests/aot/arrow-functions.phpt b/tests/aot/arrow-functions.phpt new file mode 100644 index 00000000..0bc64490 --- /dev/null +++ b/tests/aot/arrow-functions.phpt @@ -0,0 +1,201 @@ +--TEST-- +Arrow Functions - PHP 8.1+ short closure syntax +--FILE-- + $n * 2, $numbers); + return $doubled; +} + +// Test arrow function with multiple parameters +function test_multi_param_arrow() { + $pairs = [[1, 2], [3, 4], [5, 6]]; + $sums = array_map(fn($a, $b) => $a + $b, ...$pairs); + return $sums; +} + +// Test arrow function capturing variables (by value) +function test_captured_variable($multiplier) { + $numbers = [1, 2, 3]; + $multiplied = array_map(fn($n) => $n * $multiplier, $numbers); + return $multiplied; +} + +// Test nested arrow functions +function test_nested_arrow() { + $numbers = [1, 2, 3, 4]; + $result = array_map( + fn($n) => array_reduce([1, 2], fn($carry, $x) => $carry * $x, $n), + $numbers + ); + return $result; +} + +// Test arrow function in filter +function test_filter_arrow() { + $numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + $evens = array_filter($numbers, fn($n) => $n % 2 === 0); + return array_values($evens); +} + +// Test arrow function in reduce +function test_reduce_arrow() { + $numbers = [1, 2, 3, 4, 5]; + $product = array_reduce($numbers, fn($carry, $n) => $carry * $n, 1); + return $product; +} + +// Test arrow function returning arrays +function test_array_return_arrow() { + $items = [1, 2, 3]; + $transformed = array_map(fn($item) => [$item, $item * 2], $items); + return $transformed; +} + +// Test chained arrow function calls +class Calculator { + private array $numbers; + + public function __construct(array $numbers) { + $this->numbers = $numbers; + } + + public function transform(callable $callback): self { + $this->numbers = array_map($callback, $this->numbers); + return $this; + } + + public function filter(callable $callback): self { + $this->numbers = array_values(array_filter($this->numbers, $callback)); + return $this; + } + + public function getNumbers(): array { + return $this->numbers; + } +} + +function main() { + // Test basic arrow + var_dump(test_basic_arrow()); + + // Test multi-param arrow + var_dump(test_multi_param_arrow()); + + // Test captured variable + var_dump(test_captured_variable(10)); + var_dump(test_captured_variable(100)); + + // Test nested arrow + var_dump(test_nested_arrow()); + + // Test filter arrow + var_dump(test_filter_arrow()); + + // Test reduce arrow + var_dump(test_reduce_arrow()); + + // Test array return + var_dump(test_array_return_arrow()); + + // Test chained operations + $calc = new Calculator([1, 2, 3, 4, 5]); + $result = $calc + ->transform(fn($n) => $n * 2) + ->filter(fn($n) => $n > 5) + ->transform(fn($n) => $n + 1) + ->getNumbers(); + var_dump($result); +} +?> +--EXPECT-- +array(5) { + [0]=> + int(2) + [1]=> + int(4) + [2]=> + int(6) + [3]=> + int(8) + [4]=> + int(10) +} +array(2) { + [0]=> + int(4) + [1]=> + int(6) +} +array(3) { + [0]=> + int(10) + [1]=> + int(20) + [2]=> + int(30) +} +array(3) { + [0]=> + int(100) + [1]=> + int(200) + [2]=> + int(300) +} +array(4) { + [0]=> + int(2) + [1]=> + int(4) + [2]=> + int(6) + [3]=> + int(8) +} +array(5) { + [0]=> + int(2) + [1]=> + int(4) + [2]=> + int(6) + [3]=> + int(8) + [4]=> + int(10) +} +int(120) +array(3) { + [0]=> + array(2) { + [0]=> + int(1) + [1]=> + int(2) + } + [1]=> + array(2) { + [0]=> + int(2) + [1]=> + int(4) + } + [2]=> + array(2) { + [0]=> + int(3) + [1]=> + int(6) + } +} +array(3) { + [0]=> + int(7) + [1]=> + int(9) + [2]=> + int(11) +} diff --git a/tests/aot/attributes.phpt b/tests/aot/attributes.phpt new file mode 100644 index 00000000..bb0c6ec2 --- /dev/null +++ b/tests/aot/attributes.phpt @@ -0,0 +1,120 @@ +--TEST-- +Attributes (Annotations) - PHP 8+ metadata syntax +--SKIPIF-- + +--FILE-- +path = $path; + $this->methods = $methods; + } +} + +#[Attribute(Attribute::TARGET_METHOD)] +class Cache { + public int $ttl; + + public function __construct(int $ttl = 3600) { + $this->ttl = $ttl; + } +} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class Column { + public string $name; + public string $type; + + public function __construct(string $name, string $type = 'string') { + $this->name = $name; + $this->type = $type; + } +} + +// Use attributes +#[Route('/api/users', ['GET', 'POST'])] +class UserController { + #[Column('id', 'int')] + private int $id; + + #[Column('name', 'string')] + private string $name; + + #[Cache(ttl: 1800)] + public function getUsers() { + return "Getting users"; + } + + #[Cache(ttl: 3600)] + public function getUser($id) { + return "Getting user: " . $id; + } +} + +#[Route('/api/posts')] +class PostController { + #[Cache] + public function getPosts() { + return "Getting posts"; + } +} + +function main() { + // Test class attributes + $userController = new ReflectionClass(UserController::class); + $attributes = $userController->getAttributes(); + var_dump(count($attributes)); + + $routeAttr = $attributes[0]->newInstance(); + var_dump($routeAttr->path); + var_dump($routeAttr->methods); + + // Test method attributes + $getMethod = $userController->getMethod('getUsers'); + $methodAttrs = $getMethod->getAttributes(); + var_dump(count($methodAttrs)); + + $cacheAttr = $methodAttrs[0]->newInstance(); + var_dump($cacheAttr->ttl); + + // Test property attributes + $idProp = $userController->getProperty('id'); + $propAttrs = $idProp->getAttributes(); + var_dump(count($propAttrs)); + + $columnAttr = $propAttrs[0]->newInstance(); + var_dump($columnAttr->name); + var_dump($columnAttr->type); + + // Test another class + $postController = new ReflectionClass(PostController::class); + $postAttrs = $postController->getAttributes(); + var_dump(count($postAttrs)); + + $postRoute = $postAttrs[0]->newInstance(); + var_dump($postRoute->path); +} +?> +--EXPECT-- +int(1) +string(11) "/api/users" +array(2) { + [0]=> + string(3) "GET" + [1]=> + string(4) "POST" +} +int(1) +int(1800) +int(1) +string(2) "id" +string(3) "int" +int(1) +string(11) "/api/posts" diff --git a/tests/aot/constructor-promotion.phpt b/tests/aot/constructor-promotion.phpt new file mode 100644 index 00000000..249833b7 --- /dev/null +++ b/tests/aot/constructor-promotion.phpt @@ -0,0 +1,140 @@ +--TEST-- +Constructor Property Promotion - PHP 8+ concise class syntax +--FILE-- +x * $this->x + $this->y * $this->y); + } +} + +// Test with visibility modifiers +class User { + public function __construct( + public string $name, + private string $email, + protected int $age = 18 + ) {} + + public function getEmail(): string { + return $this->email; + } + + public function getAge(): int { + return $this->age; + } +} + +// Test with nullable types +class Product { + public function __construct( + public string $name, + public float $price, + public ?string $description = null, + public int $quantity = 0 + ) {} + + public function getDescription(): string { + return $this->description ?? 'No description'; + } +} + +// Test mixed traditional and promoted +class Book { + private static int $count = 0; + + public function __construct( + public string $title, + public string $author, + private float $price + ) { + self::$count++; + } + + public function getPriceWithTax(float $taxRate): float { + return $this->price * (1 + $taxRate); + } + + public static function getCount(): int { + return self::$count; + } +} + +// Test readonly properties (PHP 8.1+) +class Coordinate { + public function __construct( + public readonly float $latitude, + public readonly float $longitude + ) {} +} + +function main() { + // Test basic promotion + $point = new Point(3.0, 4.0); + var_dump($point->x); + var_dump($point->y); + var_dump($point->distance()); + + $origin = new Point(); + var_dump($origin->x); + var_dump($origin->y); + + // Test with visibility + $user = new User('Alice', 'alice@example.com', 25); + var_dump($user->name); + var_dump($user->getEmail()); + var_dump($user->getAge()); + + // Test nullable + $product1 = new Product('Laptop', 999.99, 'High-performance laptop', 10); + var_dump($product1->name); + var_dump($product1->price); + var_dump($product1->getDescription()); + var_dump($product1->quantity); + + $product2 = new Product('Mouse', 29.99); + var_dump($product2->name); + var_dump($product2->getDescription()); + + // Test mixed + $book1 = new Book('PHP Guide', 'John Doe', 49.99); + var_dump($book1->title); + var_dump($book1->author); + var_dump($book1->getPriceWithTax(0.1)); + + $book2 = new Book('Advanced PHP', 'Jane Smith', 59.99); + var_dump(Book::getCount()); + + // Test readonly + $coord = new Coordinate(40.7128, -74.0060); + var_dump($coord->latitude); + var_dump($coord->longitude); +} +?> +--EXPECT-- +float(3) +float(4) +float(5) +float(0) +float(0) +string(5) "Alice" +string(17) "alice@example.com" +int(25) +string(6) "Laptop" +float(999.99) +string(23) "High-performance laptop" +int(10) +string(5) "Mouse" +string(14) "No description" +string(9) "PHP Guide" +string(8) "John Doe" +float(54.989000000000004) +int(2) +float(40.7128) +float(-74.006) diff --git a/tests/aot/generators.phpt b/tests/aot/generators.phpt new file mode 100644 index 00000000..f1784d48 --- /dev/null +++ b/tests/aot/generators.phpt @@ -0,0 +1,92 @@ +--TEST-- +Generators - Yield keyword and generator functions +--SKIPIF-- + +--FILE-- + 1; + yield 'b' => 2; + yield 'c' => 3; +} + +// Test generator sending values +function echo_generator() { + while (true) { + $value = yield; + echo "Received: " . $value . "\n"; + } +} + +// Test infinite generator +function infinite_sequence() { + $i = 1; + while (true) { + yield $i++; + if ($i > 5) break; + } +} + +function main() { + // Test basic generator + echo "Basic generator:\n"; + foreach (range_generator(1, 5) as $num) { + var_dump($num); + } + + // Test keyed generator + echo "\nKeyed generator:\n"; + foreach (keyed_generator() as $key => $value) { + echo $key . ": "; + var_dump($value); + } + + // Test limited infinite generator + echo "\nInfinite sequence (limited):\n"; + foreach (infinite_sequence() as $num) { + var_dump($num); + } + + // Test generator object + $gen = range_generator(10, 12); + var_dump($gen->valid()); + $gen->rewind(); + var_dump($gen->valid()); + var_dump($gen->current()); + $gen->next(); + var_dump($gen->current()); +} +?> +--EXPECT-- +Basic generator: +int(1) +int(2) +int(3) +int(4) +int(5) + +Keyed generator: +a: int(1) +b: int(2) +c: int(3) + +Infinite sequence (limited): +int(1) +int(2) +int(3) +int(4) +int(5) +bool(true) +bool(true) +int(1) +int(2) diff --git a/tests/aot/trait-basic.phpt b/tests/aot/trait-basic.phpt new file mode 100644 index 00000000..acb3359a --- /dev/null +++ b/tests/aot/trait-basic.phpt @@ -0,0 +1,97 @@ +--TEST-- +Traits - Basic functionality and method inheritance +--SKIPIF-- + +--FILE-- +name = $name; + } + + public function getName() { + return $this->name; + } +} + +// Test trait with abstract methods +trait Loggable { + abstract public function getTableName(); + + public function log($message) { + return "[" . $this->getTableName() . "] " . $message; + } +} + +class User { + use Loggable; + + public function getTableName() { + return "users"; + } +} + +// Test multiple traits +trait Timestamps { + public function getCreatedAt() { + return "2024-01-01 00:00:00"; + } + + public function getUpdatedAt() { + return "2024-01-02 00:00:00"; + } +} + +class Post { + use Greeting, Timestamps; + + public function getTitle() { + return "Test Post"; + } +} + +function main() { + // Test basic trait + $person = new Person("John"); + var_dump($person->sayHello()); + var_dump($person->sayGoodbye()); + var_dump($person->getName()); + + // Test trait with abstract method + $user = new User(); + var_dump($user->log("User created")); + + // Test multiple traits + $post = new Post(); + var_dump($post->sayHello()); + var_dump($post->getTitle()); + var_dump($post->getCreatedAt()); + var_dump($post->getUpdatedAt()); +} +?> +--EXPECT-- +string(5) "Hello" +string(7) "Goodbye" +string(4) "John" +string(22) "[users] User created" +string(5) "Hello" +string(9) "Test Post" +string(19) "2024-01-01 00:00:00" +string(19) "2024-01-02 00:00:00"