docs(features): update incompatible PHP features documentation

- Add note about interface property hooks not supporting explicit set parameter
- Document that .stub.php files must not use #[Native] on classes
- Clarify typed property dynamic writes use strict type checks with runtime
  verification when type is unknown at compile time
- Explain Native Class object model restrictions and boundaries
- Add information about switch case termination requirements on non-int/bool
  lowering path
- Update class constant lookup support differences between Zend
master
韩天峰 4 hours ago
parent 3189d5bd7f
commit c8148018f8
  1. 78
      docs/en/INCOMPATIBLE_PHP_FEATURES.md
  2. 22
      docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md

@ -30,6 +30,8 @@ incompatible with or more restrictive than standard PHP.
corresponding Zend hook metadata; direct property reads/writes, Reflection,
and object iteration are all supported. Taking a reference to a hooked
property is currently not supported.
- Interface property hooks do not currently support an explicitly declared
`set` parameter; use the implicit setter value parameter.
- PHP 8.4 Reflection Lazy Objects cannot be used with TypePHP AOT classes. AOT
classes are registered as persistent internal classes, and Zend's
`zend_object_make_lazy()` explicitly rejects internal classes. Zend PHP user
@ -49,6 +51,9 @@ incompatible with or more restrictive than standard PHP.
- TypePHP forbids attributes on global or namespaced constant declarations; PHP
8.5 global constant attributes are out of scope. Class constant attributes are
not affected by this restriction.
- A `.stub.php` file only declares Zend ABI symbols supplied by external C++ and
must not use `#[Native]` on its classes. Native Class object layouts must be
generated and owned by the TypePHP compiler.
- Returning by reference from closures or arrow functions is not supported.
- PHP 8.5 `static function` expressions in global constants, class constants,
parameter defaults, or property defaults are not supported yet. Closures
@ -113,17 +118,24 @@ incompatible with or more restrictive than standard PHP.
property of the same name; `public` / `protected` declarations of the same name
are treated as the same inherited property slot and must still satisfy the
type, visibility, and `readonly` compatibility requirements.
- To avoid introducing extra dynamic checks in the typed-property write path, a
native typed property falls back to `setProperty()` when the right-hand side's
type is unknown or inconsistent with the property type; some scalar
assignments may then follow Zend's weak type conversion instead of the AOT
default strict semantics.
- Dynamic writes to typed properties still use strict type checks. When the
right-hand side cannot be determined at compile time, TypePHP preserves a
runtime check rather than falling back to Zend weak scalar conversion.
- Native Classes use a separate fixed-layout object model. They cannot be used
as ordinary Zend Objects, PHP array keys/values, or arbitrary `mixed` values,
and they restrict dynamic members, references, static/readonly members, and
several operators. See [Native Class Object Design](NATIVE_CLASS_OBJECT.md)
for the complete boundary.
## Expressions and control flow
- 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.
- On the non-`int/bool` lowering path, every non-empty `switch` case must end in
`return`, `break`, `continue`, `exit`, or `throw`; do not rely on implicit PHP
case fallthrough. The native `int/bool` switch path can currently retain C++
fallthrough, so project code should terminate every non-empty case explicitly.
- Appending, inserting, `unset()`, and wholesale replacement of `std::vector`,
`std::map`, and `std::ordered_map` are forbidden during a `foreach`;
non-structural updates of existing elements can still be done with assignment
@ -135,8 +147,9 @@ incompatible with or more restrictive than standard PHP.
## Runtime dynamic capabilities
- `ClassName::class` only supports string literals or statically resolvable
class names.
- Ordinary Zend objects and dynamic class expressions support runtime `::class`
and class-constant lookup. Native Classes still require the relevant class
target to be known at compile time.
- `static::class` is not supported in positions that require a compile-time
constant class name.
- `__CLASS__` may only be used within a `class` definition (PHP allows
@ -147,44 +160,17 @@ incompatible with or more restrictive than standard PHP.
dynamic callbacks all go through the Zend runtime fallback and are not
guaranteed to be natively optimized; reference parameters of dynamic calls
still require an explicit `refval()` or `toRef()`.
- When `Closure::bind()` binds a static closure that accesses private members,
the current behavior is not fully consistent with standard PHP.
- Storing a first-class callable in a nullable typed `Closure` property
currently has runtime stability limitations.
- `Closure::bind()`, `Closure::bindTo()`, and `Closure::call()` are not
supported. A closure cannot be rebound to an object or class scope in AOT
code.
- All source files must be encoded as `UTF-8`.
## Compiler bootstrapping and internal refactoring constraints
This section describes the constraints that apply when the compiler itself is
compiled with TypePHP. These are not additional PHP semantic differences for
user code.
- Before the refactoring, a statically resolvable `$this->method()` call within
the same core class generated a native C++ direct call. Reference parameters
were mapped directly to `php::Ref` or a C++ reference, and write-back
semantics worked normally.
- After splitting the caller and callee into different traits, compiling the
trait body on its own makes it impossible to determine the final host class
from the trait's `$this`. The current method resolver may lower a cross-trait
call to a Zend method call, for example generating
`this_.call(..., php::ArgList{value})`.
- The `ArgList` of a dynamic method call does not automatically promote ordinary
arguments to references based solely on the callee wrapper's arginfo. If the
callee method declares `&$value`, the wrapper fetches the argument through
`getCallArgByRef()`, while the caller passes an ordinary value; the result is a
`must be passed by reference` warning, and modifications made by the callee
cannot be written back to the caller.
- Therefore, cross-trait APIs inside the compiler must not use reference output
parameters or a protocol in which a passed scalar or array is modified and
then read by the caller. They should return a result value, a tuple array, or
a DTO — for example, use `[$type, $class] = resolveTypeDecl(...)` instead of
`parseTypeDecl(..., &$class)`.
- Internal helpers for string accumulation, array sorting, parse-result output,
and the like should preferably be designed around pure return values:
`$code .= format(...)`, `$files = sort(...)`. Only when the call is confirmed
to stay a native direct call may reference write-back be relied upon.
- Every time a method is moved to a trait, a parent class, or a separate
component, at least one test covering that call must be recompiled with the
bootstrapped artifact; running tests only with `bin/tpc.php` cannot reveal
problems where "the source compiler works but the bootstrapped compiler
regresses".
## Generators
TypePHP generators use `FiberGenerator` rather than Zend `Generator`. Code must
therefore not rely on `instanceof Generator`, `ReflectionGenerator`, Zend
Generator's internal object layout, or identical exception traces. Generators
do not support returning by reference, by-reference yield, by-reference
`foreach`, by-reference parameters, or variadic parameters. Fiber and Generator
are not currently supported by the WASI target. See
[Generators](YIELD_GENERATOR.md) for the complete boundary.

@ -18,10 +18,12 @@
- 支持 PHP 8.5 `(void)` 显式丢弃语句;操作数仍会求值并保留副作用,不能在赋值、返回、参数或条件等值上下文中使用。
- PHP 8.5 `clone()` / clone-with 依赖实际链接的 `libphp` 版本不低于 8.5。公开、动态、private/protected/readonly 和 property hook 属性,以及调用顺序、错误传播和 callable 路径均有 PHPT 覆盖。
- PHP 8.4 property hooks 会编译为 AOT getter/setter,并注册对应的 Zend hook 元数据;直接属性读写、Reflection 和对象遍历均受支持。当前不支持对 hook 属性取引用。
- Interface property hook 暂不支持为 `set` 显式声明参数;应使用隐式的 setter value 参数。
- PHP 8.4 Reflection Lazy Object 不能用于 TypePHP AOT 类。AOT 类以 persistent internal class 注册,而 Zend 的 `zend_object_make_lazy()` 明确拒绝 internal class;运行时动态加载的 ZendPHP user class 不受此限制。
- 支持 `private(set)``protected(set)` 非对称属性可见性,包括 constructor property promotion;Zend-backed 对象通过 PHP 8.4+ 类级 object handler 执行作用域检查,并保留 promoted/set visibility/implicit final 反射标志;Native 对象通过编译期访问检查执行同等作用域规则。
- 支持 final constructor property promotion,但 TypePHP 要求同时显式声明 `public`、`protected` 或 `private`;不接受 PHP 8.5 的 `final int $value` 隐式 public promotion 写法。该语法作为 TypePHP 扩展不受所链接 `libphp` 的源码语法版本限制,使用 PHP 8.4 `libphp.so` 时仍然可用。
- TypePHP 禁止在全局或命名空间常量声明上使用 attributes;PHP 8.5 global constant attributes 不在支持范围内。class constant attributes 不受此限制。
- `.stub.php` 只声明由外部 C++ 提供的 Zend ABI 符号,禁止对其中的类使用 `#[Native]`;Native Class 的对象布局必须由 TypePHP 编译器生成并持有。
- 不支持闭包或箭头函数按引用返回。
- 暂不支持 PHP 8.5 在全局常量、类常量、参数默认值或属性默认值中使用 `static function`;初始化表达式内嵌套的闭包同样会在编译期被拒绝。
- `__construct()` 不允许返回值。
@ -54,35 +56,29 @@
- `toAny()``toRef()` 是不可覆盖的 TypePHP 关键词方法,普通 class-like 声明不得定义同名方法(方法名按 PHP 规则大小写不敏感)。Native class 仅可显式定义返回 `mixed/any``toAny()` 转换方法,不提供隐式转换;Native class 不支持 `toRef()`
- 固定值类型属性未显式初始化时使用类型零值,不保留 ZendPHP 的完整 uninitialized 状态;因此 `??` 等依赖 uninitialized 状态的表达式可能不同。
- 禁止子类用同名 `private` 属性隐藏父类私有属性;`public` / `protected` 同名声明视为同一个继承 property slot,仍须满足类型、可见性和 `readonly` 兼容性要求。
- 为避免 typed property 写入路径引入额外动态检查,native typed property 在右值类型不确定或与属性类型不一致时会退化为 `setProperty()`;部分标量赋值可能遵循 Zend 弱类型转换,而不是 AOT 默认 strict 语义。
- typed property 的动态写入路径仍执行 strict type check;右值类型在编译期无法确定时会保留运行时检查,而不会退化为 Zend 弱类型标量转换。
- Native Class 使用独立的固定布局对象模型,不能被当作普通 Zend Object、PHP array key/value 或任意 `mixed` 值使用,并限制动态成员、引用、static/readonly 成员及部分操作符。完整边界参见 [Native Class Object 设计](NATIVE_CLASS_OBJECT.md)。
## 表达式与控制流
- `match` 的 arm condition 不能是 `match` 表达式。
- `foreach` by reference 的 value 只能是变量。
- `foreach` list destructuring 不支持按引用绑定元素。
- 非 `int/bool` lowering 路径中的非空 `switch` case 必须以 `return`、`break`、`continue`、`exit` 或 `throw` 结束;不要依赖 PHP 的隐式 case fallthrough。当前 `int/bool` native switch 路径仍可保留 C++ fallthrough,因此项目代码应统一显式终止每个非空 case。
- `std::vector`、`std::map`、`std::ordered_map` 在 `foreach` 期间禁止追加、插入、`unset()` 或整体替换;已有元素的非结构性更新仍可使用赋值运算符完成。
- 固定 native typed object property 不允许按 PHP 未初始化语义自由 `unset()`
- native 类型变量执行 `unset()` 不会产生标准 PHP 的变量删除语义。
## 运行时动态能力
- `ClassName::class` 只支持字符串字面量或可静态解析的类名
- 普通 Zend 对象和动态类表达式支持运行时 `::class` 与 class constant lookup;Native Class 仍要求相关类目标能够在编译期确定
- `static::class` 在需要编译期常量类名的位置不支持。
- `__CLASS__` 只允许在 `class` 定义的代码段中使用(`PHP`允许,返回空字符串)。
- `__TRAIT__` 只允许在 `trait` 定义的代码段中使用(`PHP`允许,返回空字符串)。
- 动态属性链、动态类名、动态函数名和动态回调会统一走 Zend runtime fallback,不保证 native 优化;动态调用的引用参数仍需显式使用 `refval()``toRef()`
- `Closure::bind()` 绑定静态闭包访问私有成员时,当前行为与标准 PHP 不完全一致。
- first-class callable 存入 typed nullable `Closure` 属性后,当前存在运行时稳定性限制。
- 不支持 `Closure::bind()`、`Closure::bindTo()` 和 `Closure::call()`;闭包不能在 AOT 代码中重新绑定对象或 class scope。
- 所有源文件必须是 `UTF-8` 编码。
## 编译器自举与内部重构约束
## Generator
本节描述编译器自身使用 TypePHP 编译时的约束,不是面向用户代码新增的 PHP 语义差异。
- 重构前,同一核心类内可静态解析的 `$this->method()` 会生成 native C++ 直调。引用参数会直接映射为 `php::Ref` 或 C++ 引用,写回语义正常。
- 将调用方和被调用方拆到不同 Trait 后,单独编译 Trait 本体时无法从 Trait 的 `$this` 确定最终宿主类。当前方法解析器可能将跨 Trait 调用降级为 Zend method call,例如生成 `this_.call(..., php::ArgList{value})`
- 动态 method call 的 `ArgList` 不会仅凭被调 wrapper 的 arginfo 自动把普通实参升级为引用。若被调方法声明 `&$value`,wrapper 会通过 `getCallArgByRef()` 取参;调用方传入的却是普通值,结果是 `must be passed by reference` 警告,并且被调方修改无法写回调用方。
- 因此,编译器内部跨 Trait API 禁止使用引用输出参数和“修改传入标量/数组后由调用方读取”的协议。应返回结果值、元组数组或 DTO,例如用 `[$type, $class] = resolveTypeDecl(...)` 代替 `parseTypeDecl(..., &$class)`
- 对字符串累加、数组排序、解析结果输出等内部 helper,优先设计为纯返回值:`$code .= format(...)`、`$files = sort(...)`。只有确认调用会保持 native 直调时,才允许依赖引用写回。
- 每次移动方法到 Trait、父类或独立组件后,必须使用自举产物重新编译至少一个覆盖该调用的测试;仅使用 `bin/tpc.php` 运行测试不能发现“源编译器正常、自举编译器退化”的问题。
TypePHP generator 使用 `FiberGenerator` 而不是 Zend `Generator`,因此不能依赖 `instanceof Generator`、`ReflectionGenerator`、Zend Generator 的内部对象布局或完全相同的异常栈。Generator 不支持按引用返回、by-reference yield、按引用 `foreach`、引用参数或 variadic 参数;WASI target 暂不支持 Fiber 和 Generator。完整边界参见 [Generator](YIELD_GENERATOR.md)。

Loading…
Cancel
Save