merge master

pull/3/head
NathanFreeman 2 months ago
commit a6ff37c769
  1. 16
      CLAUDE.md
  2. 13
      docs/README.md
  3. 399
      docs/REFACTORING_PLAN.md
  4. 15
      phpunit/code/abstract_method_signature_mismatch.php
  5. 11
      phpunit/code/abstract_parent_method_missing.php
  6. 18
      phpunit/code/accessibility/private-prop-in-parent.php
  7. 18
      phpunit/code/accessibility/private-prop-in-trait.php
  8. 32
      phpunit/code/class-method-override-namespace.php
  9. 13
      phpunit/code/clone-invalid-return-type.php
  10. 8
      phpunit/code/closure-ref-param.php
  11. 6
      phpunit/code/compiler_api/extension_clean_maps.php
  12. 1
      phpunit/code/compiler_api/hello_world.cc
  13. 2
      phpunit/code/compiler_api/ignored.php
  14. 2
      phpunit/code/compiler_api/kept.php
  15. 2
      phpunit/code/compiler_api/main.php
  16. 2
      phpunit/code/compiler_api/skipped_nested.php
  17. 13
      phpunit/code/constructor-return-type.php
  18. 14
      phpunit/code/constructor-return-value.php
  19. 12
      phpunit/code/destructor-return-type.php
  20. 11
      phpunit/code/dynamic-call-positional-after-unpack.php
  21. 11
      phpunit/code/inheritance_const_visibility_widen.php
  22. 15
      phpunit/code/inheritance_error_final_method.php
  23. 23
      phpunit/code/inheritance_error_param_covariant_class.php
  24. 25
      phpunit/code/inheritance_error_return_contravariant_class.php
  25. 15
      phpunit/code/inheritance_error_static.php
  26. 12
      phpunit/code/inheritance_error_visibility_narrow.php
  27. 12
      phpunit/code/inheritance_optional_param_allowed.php
  28. 23
      phpunit/code/inheritance_parameter_type_contravariant_class.php
  29. 11
      phpunit/code/inheritance_private_const_redeclare.php
  30. 11
      phpunit/code/inheritance_private_prop_redeclare.php
  31. 11
      phpunit/code/inheritance_prop_visibility_widen.php
  32. 14
      phpunit/code/inheritance_protected_child_const_access.php
  33. 16
      phpunit/code/inheritance_protected_child_method_access.php
  34. 25
      phpunit/code/inheritance_return_type_covariant_class.php
  35. 17
      phpunit/code/inheritance_return_type_covariant_from_none.php
  36. 19
      phpunit/code/interface_abstract_class_missing.php
  37. 12
      phpunit/code/interface_abstract_method_mismatch.php
  38. 20
      phpunit/code/interface_abstract_method_signature.php
  39. 15
      phpunit/code/interface_abstract_parent_missing.php
  40. 7
      phpunit/code/interface_array_constant.php
  41. 14
      phpunit/code/interface_array_constant_implements.php
  42. 20
      phpunit/code/interface_method_from_trait.php
  43. 11
      phpunit/code/interface_method_missing.php
  44. 15
      phpunit/code/interface_method_signature_mismatch.php
  45. 13
      phpunit/code/interface_method_static_mismatch.php
  46. 6
      phpunit/code/internal-call-missing-required-named-arg.php
  47. 6
      phpunit/code/internal-call-unknown-named-arg.php
  48. 6
      phpunit/code/internal-void-function-assignment.php
  49. 6
      phpunit/code/internal-void-function-binary-operand.php
  50. 16
      phpunit/code/intersection_type_parent_not_allowed.php
  51. 12
      phpunit/code/intersection_type_self_not_allowed.php
  52. 15
      phpunit/code/intersection_type_static_not_allowed.php
  53. 13
      phpunit/code/magic-call-static.php
  54. 13
      phpunit/code/magic-callstatic-nonstatic.php
  55. 12
      phpunit/code/magic-destruct-args.php
  56. 13
      phpunit/code/magic-get-param-type.php
  57. 13
      phpunit/code/magic-get-protected.php
  58. 13
      phpunit/code/magic-set-state-nonstatic.php
  59. 13
      phpunit/code/magic-tostring-args.php
  60. 12
      phpunit/code/method-optional-before-required-param.php
  61. 10
      phpunit/code/native-call-positional-after-unpack.php
  62. 13
      phpunit/code/new-positional-after-unpack.php
  63. 24
      phpunit/code/nullsafe-nested-private-property.php
  64. 14
      phpunit/code/nullsafe-private-property.php
  65. 12
      phpunit/code/nullsafe-write-assign-op.php
  66. 13
      phpunit/code/nullsafe-write-assign-ref-left.php
  67. 12
      phpunit/code/nullsafe-write-assign-ref-right.php
  68. 12
      phpunit/code/nullsafe-write-assign.php
  69. 12
      phpunit/code/nullsafe-write-inc.php
  70. 12
      phpunit/code/nullsafe-write-unset.php
  71. 9
      phpunit/code/optional-before-required-param.php
  72. 21
      phpunit/code/parent-constructor-used-as-argument.php
  73. 21
      phpunit/code/parent-constructor-used-as-value.php
  74. 4
      phpunit/code/preprocessor/abstract_method_signature.php
  75. 4
      phpunit/code/preprocessor/class_constants.php
  76. 2
      phpunit/code/preprocessor/deps_class_implements.php
  77. 4
      phpunit/code/preprocessor/deps_class_uses_trait.php
  78. 2
      phpunit/code/preprocessor/deps_interface.php
  79. 2
      phpunit/code/preprocessor/deps_trait.php
  80. 5
      phpunit/code/preprocessor/duplicate_abstract_method.php
  81. 5
      phpunit/code/preprocessor/duplicate_class_constant.php
  82. 4
      phpunit/code/preprocessor/duplicate_namespaced_class_interface.php
  83. 7
      phpunit/code/preprocessor/interface_members.php
  84. 3
      phpunit/code/preprocessor/namespaced_const.php
  85. 10
      phpunit/code/preprocessor/trait_alias_modifier.php
  86. 6
      phpunit/code/throw-scalar.php
  87. 14
      phpunit/code/trait_constant_conflict.php
  88. 16
      phpunit/code/trait_member_compatible.php
  89. 14
      phpunit/code/trait_property_conflict.php
  90. 12
      phpunit/code/union_type_parent_allowed.php
  91. 8
      phpunit/code/union_type_self_allowed.php
  92. 10
      phpunit/code/unpack-after-named-arg.php
  93. 9
      phpunit/code/variadic-ref-param.php
  94. 21
      phpunit/code/void-expression-array-value.php
  95. 21
      phpunit/code/void-expression-binary-operand.php
  96. 23
      phpunit/code/void-expression-condition.php
  97. 23
      phpunit/code/void-expression-match-arm.php
  98. 21
      phpunit/code/void-expression-ternary-branch.php
  99. 30
      phpunit/src/Backend/BackendOptionsTest.php
  100. 105
      phpunit/src/Backend/BackendTest.php
  101. Some files were not shown because too many files have changed in this diff Show More

@ -8,6 +8,22 @@ Swoole-Compiler is an AOT (Ahead-of-Time) compiler that translates PHP source co
**Prerequisites**: PHP 8.2+, GCC 9+ (C++17), CMake 3.24+. The `swoole/phpx` extension must be compiled (see README.md).
## AOT Language Design Principles
The AOT compiler should not blindly mirror every PHP language behavior. Most PHP syntax and semantics should remain compatible with ZendPHP, but some legal PHP constructs are historical baggage or language-design mistakes that conflict with static compilation, clear semantics, or robust generated C++ code.
When reviewing or changing compiler behavior:
- Prefer PHP compatibility for common, well-defined syntax that does not weaken the AOT static model.
- Reject PHP historical baggage when the syntax is ambiguous, surprising, or only preserved for legacy compatibility.
- Diagnose such cases as early as possible during preprocessing/static compilation, instead of deferring to runtime TypeCheck or ZendVM errors.
- Provide precise errors that include the relevant function/method name, parameter/property name, and type information where applicable.
- Compare with other statically compiled languages such as C/C++, Java, C#, Go, Rust, Kotlin, and TypeScript before deciding whether AOT should preserve or reject a PHP behavior.
Example: `function test($a = 1, $b, $c) {}` is legal in PHP, but the default value for `$a` is effectively ignored and all parameters become required. This is a PHP historical compatibility artifact. AOT should reject it during preprocessing instead of preserving the behavior.
Example: PHP permits `return $value;` inside `__construct()` and lets callers consume `parent::__construct()` as a value, even though constructors cannot declare a return type. AOT treats constructors consistently with C++/Java-style semantics: constructors initialize objects and must not return values. `return;` is allowed, but `return $value;` or using a constructor call as a value must be rejected during static compilation.
## Build & Test Commands
```bash

@ -119,6 +119,19 @@
---
### 9. [核心重构计划](REFACTORING_PLAN.md)
**必读指数**: ⭐⭐⭐⭐
内容概要:
- 核心类职责拆分方向
- TypeSystem、SymbolResolver、PropertyAccessResolver、CallResolver 等模块规划
- 渐进式重构阶段计划
- 测试门禁和风险控制要求
**适合人群**: 核心开发者、架构重构参与者
---
## 🎯 快速导航
### 按使用场景

@ -0,0 +1,399 @@
# AOT 编译器核心重构计划
## 背景
当前 AOT 编译器核心类承担了过多职责,尤其是 `CompilerBase`、`Translator` 等类同时包含 AST 分发、类型推导、属性访问解析、调用解析、代码生成、诊断信息、上下文状态维护等逻辑。随着功能持续增加,这种结构会带来以下问题:
- 封装性不足,修改一个语义点时容易影响多个代码路径。
- 代码复用不足,同类逻辑在普通属性、静态属性、nullsafe、assignment、isset/empty/refval 等路径中重复实现。
- 编译期检查容易出现绕过路径,例如某些动态 fallback 没有复用静态 resolver。
- 单个类代码量过大,review、测试定位和长期维护成本持续升高。
- 设计边界不清晰,类型系统、符号解析、属性访问、调用生成之间耦合过深。
本计划用于指导后续逐步重构,目标是改善架构质量,同时避免一次性大规模重写带来的行为回归风险。
## 核心原则
1. 渐进式重构,禁止一次性重写核心编译流程。
2. 优先抽离无状态或低状态依赖的纯逻辑,再抽离依赖编译上下文的逻辑。
3. 每一步重构应尽量保持行为不变,行为变更必须单独说明并补测试。
4. 新模块应围绕稳定领域建模,而不是简单按 AST 节点机械拆分。
5. 优先使用小型 service/helper、明确 DTO、resolver、emitter;仅在边界稳定后再引入 Visitor、Strategy 等模式。
6. 所有错误信息保持 PHP 风格,不暴露实现细节,不使用 “AOT 禁止” 这类表述。
7. 每个阶段必须有 phpunit 或 phpt 回归验证,尤其是属性、类型、调用、继承、异常等高风险路径。
## 目标架构方向
### CompilerBase
最终应收敛为编译上下文、公共工具、AST 调度入口和跨模块协作层,不再直接承载大量领域逻辑。
保留职责:
- 当前文件、函数、类、方法上下文管理。
- 临时变量、局部变量、作用域状态维护。
- AST 顶层分发入口。
- fatal/warning 的统一入口。
- 与下层 resolver/emitter 的协作。
逐步移出职责:
- 类型声明解析和类型兼容判断。
- 属性可见性、属性 offset、typed property 检查。
- 函数和方法调用解析。
- 复杂表达式代码生成。
- union/intersection/nullable runtime typecheck 生成。
### Translator
保留文件级、类级、函数级编译流程控制,逐步减少具体语义检查和表达式生成逻辑。
保留职责:
- 文件扫描和转换入口。
- 类、函数、方法代码块生成流程。
- class/function/constant/property metadata 注册。
- 编译产物组织。
逐步移出职责:
- 继承兼容检查的细节逻辑。
- trait 属性冲突检查细节。
- 参数、返回值、属性类型兼容判断。
## 模块拆分计划
### 1. TypeSystem
职责:
- 类型声明解析。
- PHP 类型到 AOT 内部类型映射。
- nullable、union、intersection 类型展开。
- 参数、返回值、属性、常量的类型兼容判断。
- 静态类型与 runtime typecheck 的边界定义。
- 类型字符串格式化。
建议子模块:
- `TypeResolver`
- `TypeCompatibility`
- `TypeCheckEmitter`
- `TypeStringFormatter`
设计要求:
- union、intersection、nullable 在静态阶段仍可按 mixed/any 处理,但必须保留 runtime typecheck 信息。
- `self`、`parent`、`static` 等特殊类型名的解析规则必须集中,避免多个路径实现不一致。
- 类型错误信息必须包含函数、方法、参数、属性等必要上下文。
### 2. SymbolResolver
职责:
- 命名空间解析。
- use alias 解析。
- `self`、`parent`、`static` 解析。
- 类、接口、trait、函数、常量名称规范化。
- 动态符号和静态可解析符号的边界判断。
建议接口:
- `resolveClassName(NodeAbstract $node): SymbolResolution`
- `resolveFunctionName(NodeAbstract $node): SymbolResolution`
- `resolveMethodScope(NodeAbstract $node): SymbolResolution`
- `resolveClassConstScope(NodeAbstract $node): SymbolResolution`
设计要求:
- 不同调用路径不能重复实现 `self/parent/static` 规则。
- 对无法静态确定的符号,应明确返回 dynamic 状态,而不是默默退化为字符串拼接。
### 3. PropertyAccessResolver
职责:
- 对象属性访问解析。
- 静态属性访问解析。
- nullsafe 属性访问静态检查。
- private/protected/public 可见性检查。
- native property offset 查找。
- typed property 写入 typecheck 信息生成入口。
- static-vs-instance 属性误用检查。
建议接口:
- `resolveInstancePropertyAccess(PropertyAccessRequest $request): PropertyAccessResult`
- `resolveStaticPropertyAccess(StaticPropertyAccessRequest $request): PropertyAccessResult`
- `assertReadable(PropertyAccessResult $result): void`
- `assertWritable(PropertyAccessResult $result): void`
- `emitRead(PropertyAccessResult $result): string`
- `emitWrite(PropertyAccessResult $result, string $value): string`
需要统一覆盖的路径:
- `$obj->prop`
- `$obj?->prop`
- `Class::$prop`
- `self::$prop`
- `parent::$prop`
- `static::$prop`
- `isset($obj->prop)`
- `empty($obj->prop)`
- `refval($obj->prop)`
- 普通赋值、复合赋值、自增自减、unset。
第一优先级建议从本模块开始,因为近期问题集中在属性访问和可见性绕过,测试边界相对清晰。
### 4. CallResolver
职责:
- 函数调用解析。
- 对象方法调用解析。
- 静态方法调用解析。
- native call 与 dynamic call 选择。
- named args、unpack、by-ref 参数处理。
- closure 和动态 callable 退化规则。
建议接口:
- `resolveFunctionCall(CallRequest $request): CallResolution`
- `resolveMethodCall(MethodCallRequest $request): CallResolution`
- `resolveStaticCall(StaticCallRequest $request): CallResolution`
- `emitCall(CallResolution $resolution): string`
设计要求:
- 静态 function 和内置 function 参数信息明确时,可以自动转引用。
- 动态调用、closure、编译期无法获取参数 by-ref 信息时,必须要求显式 `refval()`
- 使用 unpack 并追加尾部 named args 时,应退化为 dynamic call,不能走 native call。
### 5. ExpressionEmitter
职责:
- 表达式级代码生成。
- 将大型 `parseExpr()` 分发逻辑逐步拆分。
- 复用 resolver 结果生成 C++ 代码。
建议按领域拆分,而不是一开始拆成大量 AST visitor:
- `AssignmentEmitter`
- `PropertyEmitter`
- `CallEmitter`
- `ArrayEmitter`
- `ControlExprEmitter`
- `ObjectEmitter`
设计要求:
- 先保持现有 `parseExpr()` 作为调度入口。
- 每次只迁移一组表达式,迁移后跑对应测试组。
- 对表达式副作用、求值顺序、临时变量生成必须保守处理。
### 6. Diagnostic
职责:
- 统一 fatal/warning 构造。
- 提供上下文增强能力。
- 保证错误消息风格接近 PHP。
建议能力:
- 当前函数/方法名。
- 参数名。
- 属性名和类名。
- 源码位置。
- 声明位置与使用位置。
设计要求:
- 错误信息不出现 “AOT” 作为行为主体。
- 对用户可修复的问题,应包含准确符号名。
- 编译期能发现的问题优先编译期 fatal,不应依赖运行时 typecheck 异常兜底。
## 阶段计划
### 阶段 1:属性访问解析模块化
目标:
- 抽离 `findNativeProperty()`、`findNativeStaticProperty()`、`canAccessProtectedProperty()` 等属性访问解析逻辑。
- 保持生成代码基本不变。
- 建立统一的 `PropertyAccessResult`,承载属性声明、声明类、访问类、是否 native、offset、是否 dynamic 等信息。
范围:
- 普通对象属性读取。
- 静态属性读取。
- nullsafe 属性静态检查。
- 属性可见性检查。
当前进展:
- 已建立 `PropertyAccessResolver``PropertyAccessResult` 作为属性访问解析的第一层抽象。
- 实例属性读取已通过 resolver 的 `resolveNativeInstanceProperty()` 显式接口完成 native property 查找、static-vs-instance 检查和可见性检查。
- `findNativeStaticProperty()` 已通过 resolver 的 `resolveNativeStaticProperty()` 显式接口完成静态属性检查。
- nullsafe 属性链检查已通过 resolver 的 `resolveNullsafePropertyChain()` 显式接口完成类名推进和可见性检查。
- 旧的 `CompilerBase::findNativeProperty()` 泛型入口已移除,避免后续继续扩散带 `$static` 布尔参数的访问模式。
- `CompilerBase::isSameClassName()`、`isSameOrSubclassOf()`、`canAccessProtectedProperty()` 已委托 resolver,避免规则继续扩散。
- 已添加 `prepare/convert/idle` 编译阶段状态;`PropertyAccessResolver` 只能在 convert 阶段创建和使用,避免预处理阶段误用不完整的类表状态。
- 当前迁移保持生成代码不变,后续阶段再统一 read/write emitter。
验证:
- `phpunit/src/NativePropertyTest.php`
- `phpunit/src/InheritanceErrorTest.php`
- object property 相关 phpt。
- static property 相关 phpt。
- nullsafe 相关 phpt。
### 阶段 2:属性写入路径统一
目标:
- 所有属性写入路径先 resolve,再 emit。
- typed property runtime typecheck 从散落逻辑收敛到属性写入模块。
- 消除 assignment、compound assignment、inc/dec、unset 各自实现属性访问规则的问题。
范围:
- `$obj->prop = $value`
- `$obj->prop += $value`
- `$obj->prop++`
- `unset($obj->prop)`
- `Class::$prop = $value`
- `??=` 相关属性路径。
验证:
- typed property 相关 phpunit/phpt。
- object property optimization 相关 phpt。
- nullsafe 写上下文错误测试。
- private/protected/static 属性错误测试。
### 阶段 3:类型系统模块化
目标:
- 抽离类型声明解析、类型兼容、runtime typecheck 生成。
- 明确静态类型推导和运行时 typecheck 的职责边界。
- 减少参数、返回值、属性、常量各自重复处理类型规则。
范围:
- `parseTypeDecl()`
- `buildTypeCheckFromNode()`
- 参数 typecheck。
- 返回值 typecheck。
- 属性 typecheck。
- 常量类型处理。
验证:
- union、intersection、nullable 类型测试。
- 参数、返回值、属性 typecheck 测试。
- namespace constant 测试。
- constructor、void expression 相关测试。
### 阶段 4:调用解析模块化
目标:
- 统一函数、方法、静态方法调用解析。
- 明确 native call、dynamic call、closure call 的退化规则。
- 集中处理 named args、unpack、by-ref 参数。
范围:
- `parseFuncCall()`
- `parseMethodCall()`
- `parseStaticCall()`
- call args 解析。
- native/internal/user function 调用路径。
验证:
- named args、unpack 相关 phpt。
- by-ref 参数相关 phpt。
- closure 相关 phpt。
- parent/self/static call 相关 phpt。
### 阶段 5:表达式生成器拆分
目标:
- 将 `parseExpr()` 背后的大量表达式生成逻辑迁移到领域 emitter。
- `CompilerBase` 保持调度和共享上下文能力。
- 降低单文件和单类代码量。
范围:
- AssignmentEmitter。
- PropertyEmitter。
- CallEmitter。
- ArrayEmitter。
- ControlExprEmitter。
验证:
- 每迁移一个 emitter,跑对应测试组。
- 最后跑核心 phpunit 和选定 phpt 回归集。
### 阶段 6:Translator 收敛
目标:
- 将继承兼容、trait 合并、类成员校验等逻辑抽离成专门 checker。
- `Translator` 聚焦编译流程组织。
建议子模块:
- `InheritanceChecker`
- `TraitCompositionChecker`
- `ClassMemberValidator`
- `FunctionSignatureChecker`
验证:
- 继承错误 phpunit。
- trait 相关 phpt。
- interface/abstract/final/readonly 相关 phpt。
## 测试门禁
每个重构 PR 或阶段至少满足:
- 相关 phpunit 必须通过。
- 相关 phpt 必须通过。
- 如果修改 C++/phpx,需要补 gtest 并通过对应测试。
- 如果引入新编译期错误,需要新增固定 fixture 到 `phpunit/code` 或新增 phpt。
- 不使用 `file_put_contents()` 临时生成源码作为新测试方式。
建议按模块维护最小回归集:
- 属性模块:`NativePropertyTest`、`InheritanceErrorTest`、object property、static property、nullsafe。
- 类型模块:type_decl、type_hits、typed property、union/intersection/nullable。
- 调用模块:function call、method call、parent_call、closure、named args、unpack、by-ref。
- 控制流和表达式:ternary、match、goto、loop、array、coalesce、void expression。
## 风险控制
- 不在同一个变更中同时做大规模文件移动和行为变更。
- 每次迁移前先补当前行为的测试,尤其是历史 bug 对应路径。
- 保留旧入口一段时间,通过 adapter 调用新模块,降低切换风险。
- 对动态 PHP 语义保持保守,无法静态确定时不要过度优化。
- 对 AOT 明确不兼容 PHP 历史包袱的地方,应在文档和错误信息中表达为语言规则,而不是实现限制。
## 推荐下一步
`PropertyAccessResolver` 开始。
理由:
- 最近 bug 多集中在属性访问、可见性、typed property、nullsafe、static-vs-instance 路径。
- 现有测试较容易扩展。
- 属性访问是类型系统、优化器、调用生成之外相对独立的领域,适合先建立 resolver/result 模式。
- 完成后可直接减少 `CompilerBase` 中的复杂分支,并为后续 ExpressionEmitter 拆分打基础。

@ -0,0 +1,15 @@
<?php
abstract class AbstractSignatureBase
{
abstract public function run(int $id): string;
}
class AbstractSignatureChild extends AbstractSignatureBase
{
public function run(string $id): string
{
return $id;
}
}
function main() {}

@ -0,0 +1,11 @@
<?php
abstract class AbstractParentMissing
{
abstract public function run(): void;
}
class ConcreteParentMissing extends AbstractParentMissing
{
}
function main() {}

@ -0,0 +1,18 @@
<?php
class Base {
private $prop = 999;
public function dump()
{
var_dump($this->prop);
}
}
class User extends Base {
public $prop;
}
function main() {
$u = new User();
$u->prop = 12;
var_dump($u);
$u->dump();
}

@ -0,0 +1,18 @@
<?php
trait TestTraitProp {
private $prop = 999;
}
class Base {
use TestTraitProp;
}
class User extends Base {
public $prop;
}
function main() {
$u = new User();
$u->prop = 12;
var_dump($u);
}

@ -0,0 +1,32 @@
<?php
namespace Demo\Dispatch {
class ChildOverrideNs extends ParentBaseNs
{
public function bar(): void
{
echo "Child\n";
}
}
class ParentBaseNs
{
public function run(): void
{
$this->bar();
}
public function bar(): void
{
echo "Parent\n";
}
}
}
namespace {
function main(): void
{
$o = new Demo\Dispatch\ChildOverrideNs();
$o->run();
}
}

@ -0,0 +1,13 @@
<?php
class CloneInvalidReturnType
{
public function __clone(): int
{
return 1;
}
}
function main(): void
{
}

@ -0,0 +1,8 @@
<?php
function main(): void
{
$fn = function (&$value): void {
$value = 1;
};
}

@ -0,0 +1,6 @@
<?php
function main(): void
{
echo "ok\n";
}

@ -0,0 +1 @@
int main() { return 0; }

@ -0,0 +1,2 @@
<?php
function ignored() {}

@ -0,0 +1,2 @@
<?php
function kept() {}

@ -0,0 +1,2 @@
<?php
function main() {}

@ -0,0 +1,2 @@
<?php
function skipped() {}

@ -0,0 +1,13 @@
<?php
class ConstructorReturnType
{
public function __construct(): int
{
return 1;
}
}
function main(): void
{
}

@ -0,0 +1,14 @@
<?php
class ConstructorReturnValue
{
public function __construct()
{
return 123;
}
}
function main(): void
{
new ConstructorReturnValue();
}

@ -0,0 +1,12 @@
<?php
class DestructorReturnType
{
public function __destruct(): void
{
}
}
function main(): void
{
}

@ -0,0 +1,11 @@
<?php
function target(int $a, int $b): void
{
}
function main(): void
{
$fn = 'target';
$fn(...[1], 2);
}

@ -0,0 +1,11 @@
<?php
class InheritanceConstVisibilityWidenParent
{
protected const VALUE = 1;
}
class InheritanceConstVisibilityWidenChild extends InheritanceConstVisibilityWidenParent
{
public const VALUE = 2;
}

@ -0,0 +1,15 @@
<?php
class InheritanceFinalMethodParent
{
final public function run(): void
{
}
}
class InheritanceFinalMethodChild extends InheritanceFinalMethodParent
{
public function run(): void
{
}
}

@ -0,0 +1,23 @@
<?php
class InheritanceParamCovariantParentType
{
}
class InheritanceParamCovariantChildType extends InheritanceParamCovariantParentType
{
}
class InheritanceParamCovariantParent
{
public function handle(InheritanceParamCovariantParentType $value): void
{
}
}
class InheritanceParamCovariantChild extends InheritanceParamCovariantParent
{
public function handle(InheritanceParamCovariantChildType $value): void
{
}
}

@ -0,0 +1,25 @@
<?php
class InheritanceReturnContravariantParentType
{
}
class InheritanceReturnContravariantChildType extends InheritanceReturnContravariantParentType
{
}
class InheritanceReturnContravariantParent
{
public function make(): InheritanceReturnContravariantChildType
{
return new InheritanceReturnContravariantChildType();
}
}
class InheritanceReturnContravariantChild extends InheritanceReturnContravariantParent
{
public function make(): InheritanceReturnContravariantParentType
{
return new InheritanceReturnContravariantParentType();
}
}

@ -0,0 +1,15 @@
<?php
class InheritanceStaticParent
{
public static function run(): void
{
}
}
class InheritanceStaticChild extends InheritanceStaticParent
{
public function run(): void
{
}
}

@ -0,0 +1,12 @@
<?php
class A
{
public function f() {}
}
class B extends A
{
protected function f() {}
}
function main() {}

@ -0,0 +1,12 @@
<?php
class A
{
public function f($x) {}
}
class B extends A
{
public function f($x, $y = 1) {}
}
function main() {}

@ -0,0 +1,23 @@
<?php
class InheritanceParamContravariantParentType
{
}
class InheritanceParamContravariantChildType extends InheritanceParamContravariantParentType
{
}
class InheritanceParamContravariantParent
{
public function handle(InheritanceParamContravariantChildType $value): void
{
}
}
class InheritanceParamContravariantChild extends InheritanceParamContravariantParent
{
public function handle(InheritanceParamContravariantParentType $value): void
{
}
}

@ -0,0 +1,11 @@
<?php
class InheritancePrivateConstRedeclareParent
{
private const int VALUE = 1;
}
class InheritancePrivateConstRedeclareChild extends InheritancePrivateConstRedeclareParent
{
public const string VALUE = 'child';
}

@ -0,0 +1,11 @@
<?php
class InheritancePrivatePropRedeclareParent
{
private int $value = 1;
}
class InheritancePrivatePropRedeclareChild extends InheritancePrivatePropRedeclareParent
{
private string $value = 'child';
}

@ -0,0 +1,11 @@
<?php
class InheritancePropVisibilityWidenParent
{
protected int $value = 1;
}
class InheritancePropVisibilityWidenChild extends InheritancePropVisibilityWidenParent
{
public int $value = 2;
}

@ -0,0 +1,14 @@
<?php
class InheritanceProtectedChildConstAccessParent
{
public function readChild(): int
{
return InheritanceProtectedChildConstAccessChild::VALUE;
}
}
class InheritanceProtectedChildConstAccessChild extends InheritanceProtectedChildConstAccessParent
{
protected const VALUE = 1;
}

@ -0,0 +1,16 @@
<?php
class InheritanceProtectedChildMethodAccessParent
{
public function callChild(): void
{
InheritanceProtectedChildMethodAccessChild::run();
}
}
class InheritanceProtectedChildMethodAccessChild extends InheritanceProtectedChildMethodAccessParent
{
protected static function run(): void
{
}
}

@ -0,0 +1,25 @@
<?php
class InheritanceReturnCovariantParentType
{
}
class InheritanceReturnCovariantChildType extends InheritanceReturnCovariantParentType
{
}
class InheritanceReturnCovariantParent
{
public function make(): InheritanceReturnCovariantParentType
{
return new InheritanceReturnCovariantParentType();
}
}
class InheritanceReturnCovariantChild extends InheritanceReturnCovariantParent
{
public function make(): InheritanceReturnCovariantChildType
{
return new InheritanceReturnCovariantChildType();
}
}

@ -0,0 +1,17 @@
<?php
class InheritanceReturnNoneParent
{
public function run()
{
return 1;
}
}
class InheritanceReturnNoneChild extends InheritanceReturnNoneParent
{
public function run(): int
{
return 1;
}
}

@ -0,0 +1,19 @@
<?php
interface ContractAbstractMissing
{
public function run(int $id): string;
}
abstract class AbstractImplMissing implements ContractAbstractMissing
{
}
class ConcreteImplMissing extends AbstractImplMissing
{
public function run(int $id): string
{
return (string) $id;
}
}
function main() {}

@ -0,0 +1,12 @@
<?php
interface ContractAbstractMismatch
{
public function run(int $id): string;
}
abstract class AbstractImplMismatch implements ContractAbstractMismatch
{
abstract public function run(string $id): string;
}
function main() {}

@ -0,0 +1,20 @@
<?php
interface ContractAbstractMethod
{
public function run(int $id): string;
}
abstract class AbstractImplMethod implements ContractAbstractMethod
{
abstract public function run(int $id): string;
}
class ConcreteImplMethod extends AbstractImplMethod
{
public function run(int $id): string
{
return (string) $id;
}
}
function main() {}

@ -0,0 +1,15 @@
<?php
interface ContractAbstractParentMissing
{
public function run(): void;
}
abstract class AbstractContractParentMissing implements ContractAbstractParentMissing
{
}
class ConcreteContractParentMissing extends AbstractContractParentMissing
{
}
function main() {}

@ -0,0 +1,7 @@
<?php
interface InterfaceArrayConstant
{
public const ITEMS = [1, 2, 3];
}
function main() {}

@ -0,0 +1,14 @@
<?php
interface InterfaceArrayConstantContract
{
public const ITEMS = [1, 2, 3];
}
class InterfaceArrayConstantImpl implements InterfaceArrayConstantContract
{
}
function main()
{
var_dump(InterfaceArrayConstantImpl::ITEMS);
}

@ -0,0 +1,20 @@
<?php
interface ContractTrait
{
public function run(int $id): string;
}
trait ContractTraitImpl
{
public function run(int $id): string
{
return (string) $id;
}
}
class ImplTrait implements ContractTrait
{
use ContractTraitImpl;
}
function main() {}

@ -0,0 +1,11 @@
<?php
interface ContractMissing
{
public function run(int $id): void;
}
class ImplMissing implements ContractMissing
{
}
function main() {}

@ -0,0 +1,15 @@
<?php
interface ContractMismatch
{
public function run(int $id): string;
}
class ImplMismatch implements ContractMismatch
{
public function run(string $id): string
{
return $id;
}
}
function main() {}

@ -0,0 +1,13 @@
<?php
interface InterfaceStaticContract
{
public static function run(): void;
}
class InterfaceStaticImpl implements InterfaceStaticContract
{
public function run(): void
{
}
}

@ -0,0 +1,6 @@
<?php
function main(): void
{
str_replace(search: "a", subject: "b");
}

@ -0,0 +1,6 @@
<?php
function main(): void
{
strlen(foo: "abc");
}

@ -0,0 +1,6 @@
<?php
function main(): void
{
$value = var_dump('hello');
}

@ -0,0 +1,6 @@
<?php
function main(): void
{
$value = var_dump('hello') + 1;
}

@ -0,0 +1,16 @@
<?php
interface ITypeParent
{
}
class BaseIntersectionParent implements ITypeParent
{
}
class DemoParentIntersection extends BaseIntersectionParent
{
public function run(parent&ITypeParent $value): void {}
}
function main() {}

@ -0,0 +1,12 @@
<?php
interface ITypeSelf
{
}
class DemoSelfIntersection implements ITypeSelf
{
public function run(self&ITypeSelf $value): void {}
}
function main() {}

@ -0,0 +1,15 @@
<?php
interface ITypeStatic
{
}
class DemoStaticIntersection implements ITypeStatic
{
public function run(): static&ITypeStatic
{
return $this;
}
}
function main() {}

@ -0,0 +1,13 @@
<?php
class MagicCallStaticInvalid
{
public static function __call(string $name, array $arguments): mixed
{
return null;
}
}
function main(): void
{
}

@ -0,0 +1,13 @@
<?php
class MagicCallStaticNonStaticInvalid
{
public function __callStatic(string $name, array $arguments): mixed
{
return null;
}
}
function main(): void
{
}

@ -0,0 +1,12 @@
<?php
class MagicDestructArgsInvalid
{
public function __destruct($extra)
{
}
}
function main(): void
{
}

@ -0,0 +1,13 @@
<?php
class MagicGetParamTypeInvalid
{
public function __get(int $name): mixed
{
return null;
}
}
function main(): void
{
}

@ -0,0 +1,13 @@
<?php
class MagicGetProtectedInvalid
{
protected function __get(string $name): mixed
{
return null;
}
}
function main(): void
{
}

@ -0,0 +1,13 @@
<?php
class MagicSetStateNonStaticInvalid
{
public function __set_state(array $properties): object
{
return new self();
}
}
function main(): void
{
}

@ -0,0 +1,13 @@
<?php
class MagicToStringArgsInvalid
{
public function __toString($extra): string
{
return '';
}
}
function main(): void
{
}

@ -0,0 +1,12 @@
<?php
class OptionalBeforeRequired
{
public function method($first = 1, $second): void
{
}
}
function main(): void
{
}

@ -0,0 +1,10 @@
<?php
function target(int $a, int $b): void
{
}
function main(): void
{
target(...[1], 2);
}

@ -0,0 +1,13 @@
<?php
class Target
{
public function __construct(int $a, int $b)
{
}
}
function main(): void
{
new Target(...[1], 2);
}

@ -0,0 +1,24 @@
<?php
use native_types;
class NullsafeNestedOwner
{
public NullsafeNestedChild $child;
public function __construct()
{
$this->child = new NullsafeNestedChild();
}
}
class NullsafeNestedChild
{
private int $value = 1;
}
function main(): void
{
$owner = new NullsafeNestedOwner();
var_dump($owner?->child?->value);
}

@ -0,0 +1,14 @@
<?php
use native_types;
class NullsafePrivateOwner
{
private int $value = 1;
}
function main(): void
{
$owner = new NullsafePrivateOwner();
var_dump($owner?->value);
}

@ -0,0 +1,12 @@
<?php
class NullsafeWriteAssignOp
{
public int $value = 1;
}
function main(): void
{
$object = new NullsafeWriteAssignOp();
$object?->value += 2;
}

@ -0,0 +1,13 @@
<?php
class NullsafeWriteAssignRefLeft
{
public int $value = 1;
}
function main(): void
{
$object = new NullsafeWriteAssignRefLeft();
$value = 2;
$object?->value =& $value;
}

@ -0,0 +1,12 @@
<?php
class NullsafeWriteAssignRefRight
{
public int $value = 1;
}
function main(): void
{
$object = new NullsafeWriteAssignRefRight();
$value =& $object?->value;
}

@ -0,0 +1,12 @@
<?php
class NullsafeWriteAssign
{
public int $value = 1;
}
function main(): void
{
$object = new NullsafeWriteAssign();
$object?->value = 2;
}

@ -0,0 +1,12 @@
<?php
class NullsafeWriteInc
{
public int $value = 1;
}
function main(): void
{
$object = new NullsafeWriteInc();
$object?->value++;
}

@ -0,0 +1,12 @@
<?php
class NullsafeWriteUnset
{
public int $value = 1;
}
function main(): void
{
$object = new NullsafeWriteUnset();
unset($object?->value);
}

@ -0,0 +1,9 @@
<?php
function test($a = 1, $b, $c): void
{
}
function main(): void
{
}

@ -0,0 +1,21 @@
<?php
class ParentConstructorArgument
{
public function __construct()
{
}
}
class ChildConstructorArgument extends ParentConstructorArgument
{
public function __construct()
{
var_dump(parent::__construct());
}
}
function main(): void
{
new ChildConstructorArgument();
}

@ -0,0 +1,21 @@
<?php
class ParentConstructorValue
{
public function __construct()
{
}
}
class ChildConstructorValue extends ParentConstructorValue
{
public function __construct()
{
$ret = parent::__construct();
}
}
function main(): void
{
new ChildConstructorValue();
}

@ -0,0 +1,4 @@
<?php
abstract class PreprocessorAbstractSignature {
abstract public function load(int $id, ?string $name = null): self;
}

@ -0,0 +1,4 @@
<?php
class PreprocessorClassConstants {
public const TEXT = 'value', ITEMS = [];
}

@ -0,0 +1,2 @@
<?php
class SortDependencyClass implements SortDependencyInterface {}

@ -0,0 +1,4 @@
<?php
class SortDependencyTraitUser {
use SortDependencyTrait;
}

@ -0,0 +1,2 @@
<?php
interface SortDependencyInterface {}

@ -0,0 +1,2 @@
<?php
trait SortDependencyTrait {}

@ -0,0 +1,5 @@
<?php
abstract class PreprocessorDuplicateAbstractMethod {
abstract public function load(): void;
abstract public function load(): void;
}

@ -0,0 +1,5 @@
<?php
class PreprocessorDuplicateClassConstant {
public const VALUE = 1;
public const VALUE = 2;
}

@ -0,0 +1,4 @@
<?php
namespace App;
interface Demo {}
class Demo {}

@ -0,0 +1,7 @@
<?php
interface ParentA {}
interface ParentB {}
interface Demo extends ParentA, ParentB {
public const VERSION = '1.0';
public function run(int $id, ?string $name = null): ParentA|ParentB;
}

@ -0,0 +1,3 @@
<?php
namespace App;
const VERSION = 100;

@ -0,0 +1,10 @@
<?php
trait AliasModifierTrait {
public function hello() {}
}
class AliasModifierUser {
use AliasModifierTrait {
hello as private;
}
}

@ -0,0 +1,6 @@
<?php
function test()
{
throw 1;
}

@ -0,0 +1,14 @@
<?php
trait TraitConstantConflict
{
public const VALUE = 1;
}
class TraitConstantConflictUser
{
use TraitConstantConflict;
public const VALUE = 2;
}
function main() {}

@ -0,0 +1,16 @@
<?php
trait TraitMemberCompatible
{
public const VALUE = 1;
public int $count = 1;
}
class TraitMemberCompatibleUser
{
use TraitMemberCompatible;
public const VALUE = 1;
public int $count = 1;
}
function main() {}

@ -0,0 +1,14 @@
<?php
trait TraitPropertyConflict
{
public int $count = 1;
}
class TraitPropertyConflictUser
{
use TraitPropertyConflict;
public int $count = 2;
}
function main() {}

@ -0,0 +1,12 @@
<?php
class BaseParentUnion
{
}
class DemoParentUnion extends BaseParentUnion
{
public function run(parent|string $value): void {}
}
function main() {}

@ -0,0 +1,8 @@
<?php
class DemoSelfUnion
{
public function run(self|string $value): void {}
}
function main() {}

@ -0,0 +1,10 @@
<?php
function target($a, $b): void
{
}
function main(): void
{
target(a: 1, ...[2]);
}

@ -0,0 +1,9 @@
<?php
function collect(&...$args): void
{
}
function main(): void
{
}

@ -0,0 +1,21 @@
<?php
class VoidExpressionArrayParent
{
public function __construct()
{
}
}
class VoidExpressionArrayChild extends VoidExpressionArrayParent
{
public function __construct()
{
$value = [parent::__construct()];
}
}
function main(): void
{
new VoidExpressionArrayChild();
}

@ -0,0 +1,21 @@
<?php
class VoidExpressionBinaryParent
{
public function __construct()
{
}
}
class VoidExpressionBinaryChild extends VoidExpressionBinaryParent
{
public function __construct()
{
$value = parent::__construct() + 1;
}
}
function main(): void
{
new VoidExpressionBinaryChild();
}

@ -0,0 +1,23 @@
<?php
class VoidExpressionConditionParent
{
public function __construct()
{
}
}
class VoidExpressionConditionChild extends VoidExpressionConditionParent
{
public function __construct()
{
if (parent::__construct()) {
echo "unreachable\n";
}
}
}
function main(): void
{
new VoidExpressionConditionChild();
}

@ -0,0 +1,23 @@
<?php
class VoidExpressionMatchParent
{
public function __construct()
{
}
}
class VoidExpressionMatchChild extends VoidExpressionMatchParent
{
public function __construct()
{
$value = match (1) {
1 => parent::__construct(),
};
}
}
function main(): void
{
new VoidExpressionMatchChild();
}

@ -0,0 +1,21 @@
<?php
class VoidExpressionTernaryParent
{
public function __construct()
{
}
}
class VoidExpressionTernaryChild extends VoidExpressionTernaryParent
{
public function __construct()
{
$value = true ? parent::__construct() : 1;
}
}
function main(): void
{
new VoidExpressionTernaryChild();
}

@ -121,6 +121,21 @@ class BackendOptionsTest extends TestCase
$this->assertStringContainsString('/DPPROF_ON=1', $options);
}
public function testMsvcCompileOptionsEscapesUnsafeDefines(): void
{
$platform = new Windows();
$compiler = new Msvc($platform);
$options = $compiler->buildCompileOptions([
'enable_profiler' => true,
'prof_output' => 'profile output.log',
'user_defines' => ['APP_NAME="hello world"'],
]);
$this->assertStringContainsString('/D' . escapeshellarg('APP_NAME="hello world"'), $options);
$this->assertStringContainsString('/D' . escapeshellarg('PROF_OUTPUT_FILE="profile output.log"'), $options);
}
/**
* 测试 MSVC 编译选项 - 自定义标志
*/
@ -276,6 +291,21 @@ class BackendOptionsTest extends TestCase
$this->assertStringContainsString('-fPIC', $options);
}
public function testGccCompileOptionsEscapesUnsafeDefines(): void
{
$platform = new Linux();
$compiler = new Gcc($platform);
$options = $compiler->buildCompileOptions([
'enable_profiler' => true,
'prof_output' => 'profile output.log',
'user_defines' => ['APP_NAME="hello world"'],
]);
$this->assertStringContainsString('-D' . escapeshellarg('APP_NAME="hello world"'), $options);
$this->assertStringContainsString('-D' . escapeshellarg('PROF_OUTPUT_FILE="profile output.log"'), $options);
}
/**
* 测试 GCC 链接选项 - 基本配置
*/

@ -13,6 +13,39 @@ use PhpAot\Php\Backend\CompilerFactory;
class BackendTest extends TestCase
{
private array $temporaryDirectories = [];
protected function tearDown(): void
{
parent::tearDown();
foreach ($this->temporaryDirectories as $dir) {
$this->removeDirectory($dir);
}
$this->temporaryDirectories = [];
}
private function createTemporaryDirectory(string $prefix): string
{
$dir = sys_get_temp_dir() . '/' . $prefix . '_' . uniqid();
mkdir($dir, 0777, true);
$this->temporaryDirectories[] = $dir;
return $dir;
}
private function removeDirectory(string $dir): void
{
if (!is_dir($dir)) {
return;
}
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . DIRECTORY_SEPARATOR . $file;
is_dir($path) ? $this->removeDirectory($path) : unlink($path);
}
rmdir($dir);
}
/**
* 测试 MSVC 编译器基本信息
*/
@ -103,6 +136,31 @@ class BackendTest extends TestCase
$this->assertStringContainsString('/nologo', $cmd);
}
public function testMsvcBuildCCompileCommandKeepsSharedCompilerOptions(): void
{
$platform = new Windows([], true);
$compiler = new Msvc($platform);
$cmd = $compiler->buildCCompileCommand('misc.c', 'misc.obj', [
'sanitize' => 'address',
'enable_profiler' => true,
'prof_output' => 'app.prof',
'user_defines' => ['FEATURE_X=1'],
'lto' => true,
'is_zts' => true,
]);
$this->assertStringContainsString('/TC', $cmd);
$this->assertStringContainsString('/fsanitize=address', $cmd);
$this->assertStringContainsString('/DPPROF_ON=1', $cmd);
$this->assertStringContainsString('/D' . escapeshellarg('PROF_OUTPUT_FILE="app.prof"'), $cmd);
$this->assertStringContainsString('/DFEATURE_X=1', $cmd);
$this->assertStringContainsString('/GL', $cmd);
$this->assertStringContainsString('/DZTS', $cmd);
$this->assertStringNotContainsString('/EHsc', $cmd);
$this->assertStringNotContainsString('/std:', $cmd);
}
/**
* 测试 MSVC 完整链接命令
*/
@ -250,6 +308,32 @@ class BackendTest extends TestCase
$this->assertStringContainsString('-fno-rtti', $cmd);
}
public function testGccBuildCCompileCommandKeepsSharedCompilerOptions(): void
{
$platform = new Linux();
$compiler = new Gcc($platform);
$cmd = $compiler->buildCCompileCommand('misc.c', 'misc.o', [
'sanitize' => 'address',
'enable_profiler' => true,
'prof_output' => 'app.prof',
'user_defines' => ['FEATURE_X=1'],
'lto' => true,
'march' => 'native',
'target_platform' => 'aarch64-linux-gnu',
'build_mode' => 'ext',
]);
$this->assertStringContainsString('-fsanitize=address', $cmd);
$this->assertStringContainsString('-DPPROF_ON=1', $cmd);
$this->assertStringContainsString('-D' . escapeshellarg('PROF_OUTPUT_FILE="app.prof"'), $cmd);
$this->assertStringContainsString('-DFEATURE_X=1', $cmd);
$this->assertStringContainsString('-flto', $cmd);
$this->assertStringContainsString('-march=native', $cmd);
$this->assertStringContainsString('--target=aarch64-linux-gnu', $cmd);
$this->assertStringContainsString('-fPIC', $cmd);
}
public function testGccBuildLinkCommandIncludesPlatformPathsOptionsAndLibraries(): void
{
$platform = new Linux();
@ -270,6 +354,27 @@ class BackendTest extends TestCase
$this->assertStringContainsString('-lphp', $cmd);
}
public function testResponseFileArgumentIsEscapedForPathsWithSpaces(): void
{
$platform = new Linux();
$compiler = new Gcc($platform, 'g++');
$dir = $this->createTemporaryDirectory('backend link path');
$target = $dir . '/my app';
$rspFile = $target . '.rsp';
$objectWithSpace = $dir . '/object one.o';
$objectWithoutSpace = $dir . '/object_two.o';
$cmd = $compiler->buildLinkCommand([$objectWithSpace, $objectWithoutSpace], $target);
$this->assertStringContainsString(escapeshellarg('@' . $rspFile), $cmd);
$this->assertStringContainsString('-o ' . escapeshellarg($target), $cmd);
$this->assertFileExists($rspFile);
$lines = file($rspFile, FILE_IGNORE_NEW_LINES);
$this->assertSame('"' . $objectWithSpace . '"', $lines[0]);
$this->assertSame('"' . $objectWithoutSpace . '"', $lines[1]);
}
/**
* 测试 GCC 完整编译选项
*/

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save