From e4d8cbb798f270a8477ac406ca634e12842bbf3a Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Thu, 3 Sep 2026 11:37:37 +0800 Subject: [PATCH] docs: define compatibility engineering policy --- docs/en/COMPATIBILITY_POLICY.md | 84 +++++++++++++++++++ docs/en/INCOMPATIBLE_PHP_FEATURES.md | 7 ++ docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md | 3 +- docs/en/README.md | 1 + docs/zh-cn/COMPATIBILITY_POLICY.md | 68 +++++++++++++++ docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md | 5 ++ docs/zh-cn/README.md | 1 + 7 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 docs/en/COMPATIBILITY_POLICY.md create mode 100644 docs/zh-cn/COMPATIBILITY_POLICY.md diff --git a/docs/en/COMPATIBILITY_POLICY.md b/docs/en/COMPATIBILITY_POLICY.md new file mode 100644 index 00000000..c6beafd5 --- /dev/null +++ b/docs/en/COMPATIBILITY_POLICY.md @@ -0,0 +1,84 @@ +# TypePHP Compatibility Engineering Policy + +TypePHP aims to make ordinary application code correct, type-safe, stable and +fast. It does not attempt to reproduce every observable ZendVM edge case. When +exact PHP compatibility conflicts with PHPX encapsulation, maintainability or +static compilation, the architecture takes precedence and the difference must +be documented. + +## Priority + +| Priority | Required work | Examples | +|---|---|---| +| P0 | Crashes, use-after-free, memory corruption, lifecycle bugs, invalid generated C++, and silent data corruption | Cache-domain confusion, invalid object lifetime, backend-specific crashes | +| P1 | Clearly wrong results in common language constructs, bypassed type checks, or repeated evaluation of ordinary side-effecting expressions | `ArrayAccess` assignment writing to an `offsetGet()` temporary; evaluating a receiver twice | +| P2 | Uncommon combinations whose exact behavior depends on dynamic aliases, callbacks, warnings, or deprecated conversions | A callback rebinding an array to an object during an operation; exact warning order | +| Non-goal | Mirroring ZendVM internals solely for obscure compatibility | Copying large parts of `zend_execute.c` or `zend_vm_def.h` into PHPX | + +P0 and P1 issues must be fixed. P2 behavior is fixed only when the solution is +small, general, and consistent with the existing architecture. Otherwise it is +classified as Partial, an Intentional Rule, or a Hard Limit. + +## Architectural Boundaries + +- Do not copy substantial ZendVM executor logic into PHPX. +- Do not add a public PHPX API for one compiler edge case. +- Do not expose raw `zval *`, HashTable slots, or other Zend storage details to + generated project code. +- A new PHPX API must describe a general operation, fit the existing naming and + ownership model, and remain useful outside one PHPT reproducer. +- Emit a native fast path only when the compiler can prove it is safe. +- Prefer a clear TypePHP-specific rule or diagnostic over a large dynamic + compatibility layer. +- A statically known invalid operation should fail during compilation. A + runtime-only invalid operation may use a stable PHPX error; its exact Zend + error level, wording and timing are not part of the compatibility promise. + +## Review Decision + +Before accepting a compatibility change, answer these questions: + +1. Does the issue crash, corrupt memory/data, generate invalid C++, bypass a + type rule, or break a common construct? +2. Can the compiler prove the required dispatch or evaluation order? +3. Can the fix use existing compiler and PHPX abstractions? +4. If a new API is proposed, is it independently useful and correctly placed? +5. Will the implementation remain valid across supported PHP versions without + tracking private ZendVM code? + +If the first answer is no and the remaining answers expose disproportionate +complexity, document the boundary instead of implementing it. + +## Dimension-Assignment Example + +For `$container[$key] ??= $value`, the important behavior is: + +- a real array writes its bucket; +- an `ArrayAccess` object uses `offsetExists()`, `offsetGet()` and + `offsetSet()` in the correct branch; +- ordinary receiver, key and RHS expressions are not evaluated more than + required; +- assigning to the temporary returned by `offsetGet()` is never treated as an + object-dimension write. + +These are common correctness requirements. By contrast, exact Zend behavior +when callbacks rebind the container or key between phases, deprecated +false-to-array conversion, every string-offset result detail, and exact warning +ordering are edge compatibility. They must not cause PHPX to duplicate the +ZendVM dimension executor. + +TypePHP intentionally treats a null array key as append. This established +language difference must be preserved unless a separate design decision changes +it. + +## Documentation and Tests + +- Every intentional or partial incompatibility must be recorded in + `INCOMPATIBLE_PHP_FEATURES.md` and classified in + `PHP_INCOMPATIBILITY_CLASSIFICATION.md` when appropriate. +- Regression tests should protect the supported boundary, not require behavior + that the project deliberately does not promise. +- Do not make exact diagnostic text part of a test unless the diagnostic is a + stable TypePHP contract. +- A test-only gap is normally completed after a sound implementation; it is not + a reason to accept an unsound architecture. diff --git a/docs/en/INCOMPATIBLE_PHP_FEATURES.md b/docs/en/INCOMPATIBLE_PHP_FEATURES.md index dbb7404e..39513c66 100644 --- a/docs/en/INCOMPATIBLE_PHP_FEATURES.md +++ b/docs/en/INCOMPATIBLE_PHP_FEATURES.md @@ -135,6 +135,13 @@ incompatible with or more restrictive than standard PHP. ## Expressions and control flow +- Dynamic dimension writes use PHPX's array/object/string abstractions. Exact + ZendVM behavior is not promised when a key expression, `ArrayAccess` callback, + or right-hand side rebinds the container or key between the read and write + phases. Unsupported scalar containers raise a stable PHPX error instead of + reproducing every Zend conversion, deprecation, and diagnostic detail. +- TypePHP intentionally treats a null array key as an append operation rather + than converting it to an empty-string key. - A `match` arm condition may not itself be a `match` expression. - The value target in a by-reference `foreach` may only be a variable. - `foreach` list destructuring does not support binding elements by reference. diff --git a/docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md b/docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md index 97c19173..34192c66 100644 --- a/docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md +++ b/docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md @@ -11,7 +11,7 @@ The goal is to distinguish: - Partial support where the current behavior is known to differ from PHP. The main compatibility checklist remains -[INCOMPATIBLE_PHP_FEATURES.md](en/INCOMPATIBLE_PHP_FEATURES.md). This document +[INCOMPATIBLE_PHP_FEATURES.md](INCOMPATIBLE_PHP_FEATURES.md). This document explains how those items should be interpreted. ## Categories @@ -111,6 +111,7 @@ These items should be documented with the exact boundary. | Dynamic properties and dynamic property chains | Partial | Dynamic property reads and writes use the runtime property API; native property optimization is not guaranteed. | | Native typed properties | Partial / Intentional Rule | Fast native paths may not preserve every PHP dynamic state transition. Unknown or incompatible values can fall back to `setProperty()`. | | Reflection metadata | Partial | Runtime declarations preserve constructor-promotion and asymmetric-visibility flags; other AOT-specific metadata may still be incomplete. | +| Dynamic dimension assignment | Partial / Intentional Rule | Ordinary array and `ArrayAccess` paths must remain correct, but callback-driven container/key rebinding, deprecated scalar conversions, exact string-offset results, and diagnostic ordering are not promised to mirror ZendVM. A null array key is intentionally treated as append. | ## Self-hosting Compatibility Notes diff --git a/docs/en/README.md b/docs/en/README.md index 1cdfb04d..040d5954 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -6,6 +6,7 @@ This directory contains compiler implementation, compatibility, build-mode, and - [AOT and PHP Incompatible Features Checklist](INCOMPATIBLE_PHP_FEATURES.md): a concise list of current limitations. - [Incompatibility Classification](PHP_INCOMPATIBILITY_CLASSIFICATION.md): distinguishes Hard Limit, Intentional Rule, Pending, and Partial. +- [Compatibility Engineering Policy](COMPATIBILITY_POLICY.md): prioritization, PHPX abstraction boundaries, and review rules for PHP compatibility work. - [Compiler CLI](COMPILER_CLI.md): current CLI arguments and project configuration. - [Compilation Modes](COMPILATION_MODES.md): binary, extension, library modes. - [Quick Start](QUICKSTART.md): the minimal compile flow. diff --git a/docs/zh-cn/COMPATIBILITY_POLICY.md b/docs/zh-cn/COMPATIBILITY_POLICY.md new file mode 100644 index 00000000..d766fc28 --- /dev/null +++ b/docs/zh-cn/COMPATIBILITY_POLICY.md @@ -0,0 +1,68 @@ +# TypePHP 兼容性工程准则 + +TypePHP 的目标是保证常规应用代码正确、类型安全、稳定且高效,而不是复刻 +ZendVM 每一个可观察的边缘行为。当 PHP 的精确兼容性与 PHPX 封装性、长期 +可维护性或静态编译模型冲突时,应优先保护架构,并明确记录行为差异。 + +## 优先级 + +| 优先级 | 必须处理的范围 | 示例 | +|---|---|---| +| P0 | 崩溃、UAF、内存破坏、生命周期错误、生成非法 C++、静默破坏数据 | Cache 域混淆、对象生命周期错误、特定后端崩溃 | +| P1 | 常用语法产生明显错误结果、类型检查被绕过、普通副作用表达式被重复求值 | `ArrayAccess` 赋值错误写入 `offsetGet()` 临时值;receiver 被执行两次 | +| P2 | 依赖动态引用、回调、警告或废弃转换的低频组合 | 操作期间由回调把 array 改成 object;警告产生顺序 | +| 非目标 | 只为低频兼容细节而镜像 ZendVM 内部实现 | 向 PHPX 复制大量 `zend_execute.c` 或 `zend_vm_def.h` 代码 | + +P0 和 P1 必须修复。P2 只有在方案足够小、通用且符合现有架构时才修复; +否则应归类为 Partial、Intentional Rule 或 Hard Limit。 + +## 架构边界 + +- 不向 PHPX 复制大段 ZendVM executor 逻辑。 +- 不为单个编译器边缘场景增加 PHPX 公共 API。 +- 不向生成的项目代码暴露 `zval *`、HashTable slot 等 Zend 存储细节。 +- 新 PHPX API 必须表达通用操作,符合现有命名和所有权模型,并且不只服务于 + 某一个 PHPT 复现代码。 +- 只有编译器能够证明安全时,才生成原生快速路径。 +- 与其引入庞大的动态兼容层,更应选择清晰的 TypePHP 专有规则或诊断。 +- 静态阶段能够确定的非法操作应在编译期失败。只能在运行期确定的非法操作 + 可以抛出稳定、统一的 PHPX 错误,不承诺错误级别、文本和时序与 Zend 完全一致。 + +## Code Review 决策流程 + +接受兼容性修改前,应依次回答: + +1. 问题是否会导致崩溃、内存或数据破坏、非法 C++、类型规则穿透,或者破坏 + 常用语法? +2. 编译器能否证明所需的派发方式和求值顺序? +3. 是否能够使用现有 TypePHP/PHPX 抽象完成修复? +4. 如果需要新增 API,它是否具有独立、通用的价值,并位于正确的层次? +5. 实现是否能够跨受支持的 PHP 版本长期工作,而不需要跟踪 ZendVM 私有代码? + +如果第一项是否定的,而后续问题显示实现复杂度与收益明显不成比例,应记录 +兼容边界,而不是继续实现。 + +## 数组维度赋值案例 + +对于 `$container[$key] ??= $value`,需要保证的主要行为是: + +- 确定为数组时写入真实 bucket; +- 确定为 `ArrayAccess` 对象时,在正确分支调用 `offsetExists()`、 + `offsetGet()` 和 `offsetSet()`; +- 普通 receiver、key 和 RHS 表达式不会被无故重复执行; +- 不能把 `offsetGet()` 返回的临时值当成对象维度写入目标。 + +这些属于常用路径的正确性。相比之下,回调在各阶段之间重新绑定 container/key、 +`false` 转数组的废弃警告、字符串 offset 的全部返回值细节,以及警告的精确顺序, +属于边缘兼容问题。不能因此让 PHPX 复制 ZendVM 的维度处置逻辑。 + +TypePHP 明确将 null 数组 key 视为追加。除非经过单独的设计决策,否则必须保留 +这一既有不兼容行为。 + +## 文档与测试 + +- 有意或部分不兼容行为必须写入 `INCOMPATIBLE_PHP_FEATURES.md`,必要时同时在 + `PHP_INCOMPATIBILITY_CLASSIFICATION.md` 中分类。 +- 回归测试应保护项目承诺支持的边界,不应强制尚未承诺的 Zend 边缘行为。 +- 除非诊断文本已经成为稳定的 TypePHP 契约,否则测试不应锁死完整报错文本。 +- 仅缺少测试通常可以在可靠实现合入后补齐,但不能为了满足测试接受错误的架构。 diff --git a/docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md b/docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md index 7f00ac71..04619f54 100644 --- a/docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md +++ b/docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md @@ -61,6 +61,11 @@ ## 表达式与控制流 +- 动态维度写入使用 PHPX 的 array/object/string 抽象。当 key 表达式、 + `ArrayAccess` 回调或右值在读写阶段之间重新绑定 container/key 时,不承诺 + 复刻 ZendVM 的全部行为。不支持的标量 container 会抛出稳定、统一的 PHPX + 错误,而不是复刻 Zend 的全部转换、废弃警告和诊断细节。 +- TypePHP 明确将 null 数组 key 视为追加操作,而不是转换为空字符串 key。 - `match` 的 arm condition 不能是 `match` 表达式。 - `foreach` by reference 的 value 只能是变量。 - `foreach` list destructuring 不支持按引用绑定元素。 diff --git a/docs/zh-cn/README.md b/docs/zh-cn/README.md index ad25ba86..4ae22267 100644 --- a/docs/zh-cn/README.md +++ b/docs/zh-cn/README.md @@ -6,6 +6,7 @@ - [AOT 与 PHP 不兼容特性清单](INCOMPATIBLE_PHP_FEATURES.md):当前限制的简明清单。 - [不兼容性分类](PHP_INCOMPATIBILITY_CLASSIFICATION.md):区分 Hard Limit、Intentional Rule、Pending 和 Partial。 +- [兼容性工程准则](COMPATIBILITY_POLICY.md):PHP 兼容性工作的优先级、PHPX 抽象边界与 Code Review 规则。 - [编译器命令行](COMPILER_CLI.md):当前 CLI 参数和项目配置。 - [编译模式](COMPILATION_MODES.md):binary、extension、library 模式。 - [快速入门](QUICKSTART.md):最小编译流程。