diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..6ffd834d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,80 @@ +# TypePHP change log + +## 0.8.0 + +### Breaking: compile-time API namespace cleanup + +TypePHP compile-time APIs now occupy only two global class symbols: + +- `Type::*` describes types used by extension-method metadata. It does not + execute conversions or other built-in operations. +- `std::*` contains TypePHP built-in compile-time functions. + +As with PHP class and method names, the `std` / `Type` class names and `std` +method names are case-insensitive. `Type::Int` and the other `Type` members are +class constants, so their constant names remain case-sensitive under PHP rules. + +The former global compile-time functions have changed as follows: + +| Before 0.8 | Since 0.8 | Notes | +| --- | --- | --- | +| `any($value)` | `std::any($value)` | Erases the static type to `mixed/any`. | +| `refval($target)` | `std::ref($target)` | Explicit reference wrapper for call arguments. | +| `expected($condition)` | `std::expected($condition)` | Emits the `EXPECTED(...)` branch hint. | +| `unexpected($condition)` | `std::unexpected($condition)` | Emits the `UNEXPECTED(...)` branch hint. | +| `objval($value, Foo::class)` | `$value->toObject(Foo::class)` | Replaced by the existing keyword method. | + +No compatibility functions are installed in the global namespace. Applications +may define and call their own `any()`, `refval()`, `expected()`, `unexpected()`, +and `objval()` functions without being intercepted by the compiler. + +`std::ref()` retains the previous reference-wrapper restrictions: its target +must be a variable, array element, or object property, and it is only valid as a +call argument wrapper. `toRef()` remains the equivalent keyword method. + +All projects using the old spellings must update their source and rebuild. + +### Compatibility policy before 1.0 + +TypePHP is still pre-1.0 software. Public compiler APIs, TypePHP-specific source +syntax, generated-code interfaces, configuration, and packaging may change +between minor releases. Breaking changes will be documented here, but users +should review the change log and run their full test suite before upgrading. + +--- + +## 0.8.0(中文) + +### 破坏性变更:整理编译期 API 命名空间 + +TypePHP 编译期 API 现在只占用两个全局类符号: + +- `Type::*` 仅描述扩展方法元数据等场景使用的类型,不执行类型转换或其他内置操作。 +- `std::*` 承载 TypePHP 内置编译期函数。 + +遵循 PHP 的类名与方法名规则,`std` / `Type` 类名以及 `std` 方法名均不区分 +大小写。`Type::Int` 等成员属于类常量,因此其常量名仍按 PHP 规则区分大小写。 + +原全局编译期函数迁移如下: + +| 0.8 之前 | 0.8 起 | 说明 | +| --- | --- | --- | +| `any($value)` | `std::any($value)` | 将静态类型降级为 `mixed/any`。 | +| `refval($target)` | `std::ref($target)` | 调用参数的显式引用包装器。 | +| `expected($condition)` | `std::expected($condition)` | 生成 `EXPECTED(...)` 分支提示。 | +| `unexpected($condition)` | `std::unexpected($condition)` | 生成 `UNEXPECTED(...)` 分支提示。 | +| `objval($value, Foo::class)` | `$value->toObject(Foo::class)` | 改用现有关键词方法。 | + +TypePHP 不在全局命名空间安装兼容函数。应用可以自行定义并正常调用 +`any()`、`refval()`、`expected()`、`unexpected()` 和 `objval()`,编译器不会拦截。 + +`std::ref()` 延续原引用包装限制:目标必须是变量、数组元素或对象属性,且只能作为 +调用参数包装器使用。`toRef()` 仍是等价的关键词方法。 + +使用旧写法的项目必须修改源码并重新编译。 + +### 1.0 之前的兼容性策略 + +TypePHP 目前仍处于 1.0 之前。公开编译器 API、TypePHP 专有源码接口、生成代码接口、 +配置和打包方式都可能在次版本中发生变化。破坏性变更会记录在本文件中;升级前请阅读 +变更记录,并运行项目的完整测试。 diff --git a/README-CN.md b/README-CN.md index dc6489f7..0f4595b2 100644 --- a/README-CN.md +++ b/README-CN.md @@ -78,8 +78,8 @@ AST,待全部项目符号就绪后再在 convert 阶段解析。这一两阶 (`$s->upper()`、`$arr->contains()`、`$big->mul(2)`);静态类型已知时在编译期 直接解析调用。 - **混合 C++ / PHP 编程** —— 在性能关键内核中直接调用 C++ 函数(反之亦然)。 -- **编译期函数与关键词** —— `any()`、`refval()`、`objval()`、`expected()`、 - `unexpected()`,以及 `toInt()`、`toString()`、`toArray()` 等。 +- **编译期函数与关键词** —— `std::any()`、`std::ref()`、`std::expected()`、 + `std::unexpected()`,以及 `toObject()`、`toInt()`、`toString()`、`toArray()` 等。 - **编译期安全检查** —— `#[Immutable]` 只读契约和 `#[ArrayDef]` 数组结构元数据, 在编译期检查,零运行时开销。 - **编译期代码生成** —— `#[Getter]`、`#[Setter]`、`#[With]`、`#[Constructor]`、 @@ -673,6 +673,7 @@ GitHub Actions 会在 PHP 8.4 和 8.5 上分别运行 PHPUnit 与自举 PHPT。 ## 文档 - [快速入门](docs/zh-cn/QUICKSTART.md) —— 最小编译流程 +- [变更记录](CHANGELOG.md) —— 破坏性变更与 1.0 前升级说明 - [编译模式](docs/zh-cn/COMPILATION_MODES.md) —— `bin`、`ext`、`lib` - [编译器命令行](docs/zh-cn/COMPILER_CLI.md) —— CLI 参数与项目配置 - [不兼容 PHP 特性清单](docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md) —— 当前限制 @@ -680,7 +681,7 @@ GitHub Actions 会在 PHP 8.4 和 8.5 上分别运行 PHPUnit 与自举 PHPT。 - [高精度类型](docs/zh-cn/HIGH_PRECISION_TYPES.md) —— BigInt / Decimal / BigFloat - [Std 容器](docs/zh-cn/STD_CONTAINERS.md) —— 强类型容器 - [通用方法](docs/zh-cn/UNIVERSAL_METHODS.md) —— 编译期方法解析 -- [编译期函数](docs/zh-cn/COMPILE_TIME_FUNCTIONS.md) —— `any()`、`refval()`、`objval()` 等 +- [编译期函数](docs/zh-cn/COMPILE_TIME_FUNCTIONS.md) —— `std::any()`、`std::ref()`、`std::expected()` 等 - [混合 C++/PHP](docs/zh-cn/MIXED_CPP_PHP.md) —— C++/PHP 互操作 - [`#[Immutable]`](docs/zh-cn/IMMUTABLE.md) —— 编译期只读契约 - [`#[ArrayDef]`](docs/zh-cn/ARRAY_DEF.md) —— 强类型数组属性契约 diff --git a/README.md b/README.md index c7d319c6..54b611ed 100644 --- a/README.md +++ b/README.md @@ -90,8 +90,8 @@ This two-phase design keeps multi-file and self-hosted builds deterministic. are resolved directly at compile time. - **Mixed C++ / PHP** — call C++ functions from PHP (and vice versa) for performance-critical kernels. -- **Compile-time functions & keywords** — `any()`, `refval()`, `objval()`, - `expected()`, `unexpected()`, plus `toInt()`, `toString()`, `toArray()` and +- **Compile-time functions & keywords** — `std::any()`, `std::ref()`, + `std::expected()`, `std::unexpected()`, plus `toObject()`, `toInt()`, `toString()`, `toArray()` and friends. - **Compile-time safety** — `#[Immutable]` read-only contracts and `#[ArrayDef]` array-shape metadata, checked at compile time with zero runtime cost. @@ -724,6 +724,7 @@ rules and a PHPT whenever runtime output or diagnostics are observable. ## Documentation - [Quick Start](docs/en/QUICKSTART.md) — minimal compilation flow +- [Change log](CHANGELOG.md) — breaking changes and pre-1.0 upgrade notes - [Compilation modes](docs/en/COMPILATION_MODES.md) — `bin`, `ext`, `lib` - [Compiler CLI](docs/en/COMPILER_CLI.md) — CLI arguments and project config - [Incompatible PHP features](docs/en/INCOMPATIBLE_PHP_FEATURES.md) — current limits @@ -731,7 +732,7 @@ rules and a PHPT whenever runtime output or diagnostics are observable. - [High-precision types](docs/en/HIGH_PRECISION_TYPES.md) — BigInt / Decimal / BigFloat - [Std containers](docs/en/STD_CONTAINERS.md) — strongly-typed containers - [Universal methods](docs/en/UNIVERSAL_METHODS.md) — compile-time method resolution -- [Compile-time functions](docs/en/COMPILE_TIME_FUNCTIONS.md) — `any()`, `refval()`, `objval()`, … +- [Compile-time functions](docs/en/COMPILE_TIME_FUNCTIONS.md) — `std::any()`, `std::ref()`, `std::expected()`, … - [Mixed C++/PHP](docs/en/MIXED_CPP_PHP.md) — C++/PHP interop - [`#[Immutable]`](docs/en/IMMUTABLE.md) — compile-time read-only contracts - [`#[ArrayDef]`](docs/en/ARRAY_DEF.md) — typed array-property contracts diff --git a/docs/en/COMPILE_TIME_FUNCTIONS.md b/docs/en/COMPILE_TIME_FUNCTIONS.md index a9707b21..8a661710 100644 --- a/docs/en/COMPILE_TIME_FUNCTIONS.md +++ b/docs/en/COMPILE_TIME_FUNCTIONS.md @@ -2,24 +2,16 @@ This document records the compile-time functions, keyword methods, and related construction entry points that are specific to the AOT compiler. They are not part of standard PHP syntax, and an ordinary PHP runtime can only rely on the compatibility stubs provided by `src/polyfills.php`. -## Core compile-time functions +## Global function names -There are currently 5 core global compile-time functions. +TypePHP does not reserve global function names for compiler directives. The +compile-time API occupies two global class symbols: `Type::*` only describes +types for extension-method metadata, while `std::*` contains TypePHP built-in +functions. Object type assertions use the `toObject()` keyword method. -| Name | Parameters | Purpose | Current primary handling location | -| --- | --- | --- | --- | -| `any($value)` | 1 | Degrades the expression to `mixed/any`, preventing further processing as a static native/object type. | General function-call expression entry. | -| `refval($target)` | 1 | Explicitly passes a variable, array element, or object property by reference to a dynamic call or a call whose reference parameter cannot be statically identified. | Argument parsing, dynamic calls, SSA/optimizer reference escape analysis. | -| `objval($value, ClassName::class or 'ClassName')` | 2 | Tells the compiler that `$value` is an object of the specified class, and generates the `php::toObject(..., target_ce)` runtime fallback check. | Function-call resolution, object type inference. | -| `expected($condition)` | 1 | Marks the condition as usually true, generating the Zend `EXPECTED(...)` branch prediction macro. | General function-call expression entry. | -| `unexpected($condition)` | 1 | Marks the condition as usually false, generating the Zend `UNEXPECTED(...)` branch prediction macro. | General function-call expression entry. | - -Constraints: - -- `refval()` only accepts variables, array elements, or object properties. -- The second parameter of `objval()` must be a compile-time-resolvable class-name string or `ClassName::class`. -- `any()` can be used in any expression position; it directly expands its single argument at compile time without generating a runtime function call. -- `expected()` / `unexpected()` accept exactly one non-expanded argument and return bool; they are usually used in `if`, `elseif`, and loop conditions, and do not change the argument's evaluation count or true/false semantics. +The `std` / `Type` class names and `std` method names are case-insensitive, as +PHP class and method names are. `Type::*` members are class constants, whose +names remain case-sensitive. ## Keyword methods @@ -27,8 +19,8 @@ There are currently 12 built-in keyword methods. | Name | Equivalent behavior | Description | | --- | --- | --- | -| `toAny()` | `any($receiver)` | Returns the receiver itself, but with the type degraded to `mixed/any`. | -| `toRef()` | `refval($receiver)` | Returns a reference to the receiver; parameter restrictions are the same as `refval()`. | +| `toAny()` | `std::any($receiver)` | Returns the receiver itself, but with the type degraded to `mixed/any`. | +| `toRef()` | `std::ref($receiver)` | Returns a reference to the receiver; parameter restrictions are the same as `std::ref()`. | | `toObject()` | `php::toObject($receiver)` | May take a target-class parameter, performing object conversion/checking. | | `toInt()` | `php::toInt($receiver)` | Converts to a native int expression. | | `toFloat()` | `php::toFloat($receiver)` | Converts to a native float expression. | @@ -46,9 +38,9 @@ Constraints: - `toRef()` only applies to receivers that can take references. - Keyword methods take precedence over ordinary methods and universal method dispatch. -## `std::` compile-time construction entry points +## `std::` compile-time entry points -There are currently 10 `std::` compile-time construction entry points. +There are currently 14 `std::` compile-time entry points. | Name | Purpose | Main limitation | | --- | --- | --- | @@ -58,6 +50,10 @@ There are currently 10 `std::` compile-time construction entry points. | `std::bigInt($value)` | Constructs a BigInt. | Implicit construction from a float variable is not allowed. | | `std::decimal($value)` | Constructs a Decimal. | A float variable must be converted via string or integer; float literals are handled per the original literal. | | `std::bigFloat($value)` | Constructs a BigFloat. | Requires 1 value parameter. | +| `std::any($value)` | Degrades the expression to `mixed/any`. | Native objects and native-object std containers cannot escape through it. | +| `std::ref($target)` | Explicitly passes a target by reference. | Only accepts variables, array elements, or object properties and is only valid as a call argument wrapper. | +| `std::expected($condition)` | Marks a condition as usually true. | Accepts exactly one non-unpacked argument and returns bool. | +| `std::unexpected($condition)` | Marks a condition as usually false. | Accepts exactly one non-unpacked argument and returns bool. | | `std::array($type, $size[, ...$sizes])` | Constructs a fixed-size std array. | Can only be used in the top-level scope of the variable's first assignment. | | `std::vector($type[, $size])` | Constructs a std vector. | Can only be used in the top-level scope of the variable's first assignment. | | `std::map($keyType, $valueType)` | Constructs a std map. | Can only be used in the top-level scope of the variable's first assignment. | @@ -76,7 +72,7 @@ There are currently 4 Std container conversion keyword methods. ## Mechanisms not counted in this list -- `$array->any()` is a universal method that maps to PHP `array_any()`, not the `any()` compile-time function. +- `$array->any()` is a universal method that maps to PHP `array_any()`, not the `std::any()` compile-time function. - `Type::*` are compile-time type-description constants, not functions. - keyword extension methods are a user-defined extension method mechanism and are not part of the fixed built-in compile-time function list. @@ -84,13 +80,13 @@ There are currently 4 Std container conversion keyword methods. Compile-time functions should be usable in any legal expression position and maintain consistent semantics across all paths: -- `any()` is already handled uniformly at the ordinary function-call expression entry; assignments, parameters, return values, array elements, and operator subexpressions share the same semantics. -- `refval()` / `toRef()` have many special cases in argument parsing and dynamic call paths and should later be unified into a single "reference-wrapping expression" resolution entry. -- `objval()` is currently recognized through the function-call resolution and type-inference paths and is relatively centralized. -- `expected()` / `unexpected()` generate `EXPECTED(...)` / `UNEXPECTED(...)` respectively at the ordinary function-call entry and produce no PHP runtime function call. +- `std::any()` is handled through one lowering entry; assignments, parameters, return values, array elements, and operator subexpressions share the same semantics. +- `std::ref()` / `toRef()` share one reference-wrapper recognizer across argument parsing, SSA, and optimizer paths. +- `toObject(ClassName::class)` replaces the removed global `objval()` helper and provides object type assertion through the existing keyword-method path. +- `std::expected()` / `std::unexpected()` generate `EXPECTED(...)` / `UNEXPECTED(...)` respectively and produce no PHP runtime function call. Future refactoring goals: - Establish a unified `CompileTimeFunctionResolver` or equivalent module. - Reuse the same compile-time function metadata in `parseExpr()` / `detectTypeOfExpr()` / `detectClassOfExpr()` / argument parsing paths. -- Continue unifying the behavior of `refval()` and `objval()` across different expression paths. +- Continue unifying reference-wrapper behavior across different expression paths. diff --git a/docs/en/INCOMPATIBLE_PHP_FEATURES.md b/docs/en/INCOMPATIBLE_PHP_FEATURES.md index 39513c66..c0996085 100644 --- a/docs/en/INCOMPATIBLE_PHP_FEATURES.md +++ b/docs/en/INCOMPATIBLE_PHP_FEATURES.md @@ -93,15 +93,15 @@ incompatible with or more restrictive than standard PHP. limitation as "TypePHP does not support reference parameters". - Closures and arrow functions support fixed by-reference parameters. Because a Closure invocation is dynamically dispatched, the caller must still mark - reference arguments explicitly with `refval()` / `toRef()`; Zend callbacks + reference arguments explicitly with `std::ref()` / `toRef()`; Zend callbacks use the generated Closure arginfo automatically. - Reference assignment cannot create a reference from a complex static-property expression. - Calls whose argument signature cannot be determined at compile time — dynamic calls, closure calls, and the like — cannot convert reference parameters - automatically; `refval()` or the equivalent keyword method `toRef()` must be + automatically; `std::ref()` or the equivalent keyword method `toRef()` must be used explicitly. -- `refval()` / `toRef()` only accept variables, array elements, or object +- `std::ref()` / `toRef()` only accept variables, array elements, or object properties. - A call that uses argument unpacking followed by named arguments falls back to dynamic dispatch and cannot use the native call path. @@ -172,7 +172,7 @@ incompatible with or more restrictive than standard PHP. - Dynamic property chains, dynamic class names, dynamic function names, and 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()`. + still require an explicit `std::ref()` or `toRef()`. - `Closure::bind()`, `Closure::bindTo()`, and `Closure::call()` are not supported. A closure cannot be rebound to an object or class scope in AOT code. diff --git a/docs/en/NATIVE_CLASS_OBJECT.md b/docs/en/NATIVE_CLASS_OBJECT.md index 495984ff..bccab245 100644 --- a/docs/en/NATIVE_CLASS_OBJECT.md +++ b/docs/en/NATIVE_CLASS_OBJECT.md @@ -769,7 +769,7 @@ PHP reference symbols are neither needed nor allowed here: ```php function replace(Point &$point): void; // FatalError $alias =& $point; // FatalError -refval($point); // FatalError +std::ref($point); // FatalError $point->toRef(); // FatalError ``` diff --git a/docs/en/NATIVE_TYPES.md b/docs/en/NATIVE_TYPES.md index 689b9eea..82231bda 100644 --- a/docs/en/NATIVE_TYPES.md +++ b/docs/en/NATIVE_TYPES.md @@ -52,21 +52,18 @@ Correct practices: --- -## 🎯 The objval Compile-Time Function +## 🎯 The `toObject()` Keyword Method ### Use Cases -When obtaining objects from sources such as arrays or function return values, variables lose their type context information. In such cases you need to use `objval()` to explicitly declare the class of the object. +When obtaining objects from sources such as arrays or function return values, variables lose their type context information. Use the `toObject()` keyword method to assert the object's class. ### Basic Syntax ```php toObject(ClassName::class); ``` ### Typical Scenarios @@ -83,9 +80,9 @@ $data = [ // ❌ Wrong: type is lost $user = $data['user']; // AOT cannot infer the type -// ✅ Correct: use objval to declare the type -$user = objval($data['user'], 'User'); -$product = objval($data['product'], 'Product'); +// ✅ Correct: use toObject() to declare the type +$user = $data['user']->toObject(User::class); +$product = $data['product']->toObject(Product::class); ``` #### Scenario 2: A Function Returns an Object @@ -99,8 +96,8 @@ function get_object() { // ❌ Type is lost $obj = get_object(); -// ✅ Use objval to declare -$obj = objval(get_object(), 'stdClass'); +// ✅ Use toObject() to declare +$obj = get_object()->toObject(stdClass::class); ``` #### Scenario 3: The Factory Pattern @@ -123,8 +120,8 @@ class Factory { $factory = new Factory(); // ✅ Explicitly specify the returned object type -$user = objval($factory->create('user'), 'User'); -$product = objval($factory->create('product'), 'Product'); +$user = $factory->create('user')->toObject(User::class); +$product = $factory->create('product')->toObject(Product::class); ``` ### Notes @@ -134,46 +131,45 @@ $product = objval($factory->create('product'), 'Product'); ```php toObject(MyClass::class); // ❌ Wrong: variable class name (cannot be analyzed at compile time) $className = 'MyClass'; -$obj = objval($value, $className); // Compile error +$obj = $value->toObject($className); // Compile error // ❌ Wrong: constant class name (may not be resolvable at compile time) const CLASS_NAME = 'MyClass'; -$obj = objval($value, CLASS_NAME); // May fail +$obj = $value->toObject(CLASS_NAME); // May fail ``` -⚠️ **The first argument must be a variable expression**: +The receiver may be any supported value expression: ```php property, 'MyClass'); -$obj = objval(get_object(), 'MyClass'); +$obj = $array['key']->toObject(MyClass::class); +$obj = $object->property->toObject(MyClass::class); +$obj = get_object()->toObject(MyClass::class); -// ❌ Wrong: non-variable expression -$obj = objval(new MyClass(), 'MyClass'); // Not needed +// ✅ Legal, but redundant because the expression already has the exact type +$obj = (new MyClass())->toObject(MyClass::class); // Not needed ``` ### Performance Impact -- ✅ `objval()` is a **compile-time function** -- ✅ It produces no runtime overhead -- ✅ It only performs type inference during the compilation stage -- ✅ The generated C++ code is identical to a normal variable assignment +- ✅ `toObject()` is a TypePHP keyword method +- ✅ It provides the compiler with the target class +- ✅ It emits a PHPX object conversion/type check for values whose runtime class is not statically proven ### Differences from std:: Types -| Feature | std::int/float/bool | objval | +| Feature | std::int/float/bool | toObject | |------|---------------------|--------| | **Purpose** | Numeric/boolean type optimization | Object type declaration | | **Performance** | ⚡ High performance (native type) | 🐢 Standard (ZVAL) | | **Memory** | 8B/1B | Pointer (16B+) | -| **Timing** | Runtime optimization | Compile-time inference | -| **Syntax** | `std::int(value)` | `objval(variable, 'ClassName')` | +| **Timing** | Runtime optimization | Compile-time lowering plus runtime check when needed | +| **Syntax** | `std::int(value)` | `$value->toObject(ClassName::class)` | --- diff --git a/docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md b/docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md index 34192c66..0f6145a2 100644 --- a/docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md +++ b/docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md @@ -77,7 +77,7 @@ These items should be documented with the exact boundary. | Reserved keyword methods such as `toArray()` | Intentional Rule | Conversion keywords are resolved before ordinary object methods to keep conversion lowering static and predictable. | | Zero-initialized fixed typed property slots | Intentional Rule / Partial | Native fixed-layout slots use their type's zero value instead of preserving every Zend uninitialized-property transition. | | Structural mutation of `std` containers during `foreach` | Intentional Rule | Native C++ iterators may be invalidated by append, insertion, erase or whole-container replacement. TypePHP rejects these operations inside the active loop while allowing non-structural element updates. | -| Automatic reference inference for dynamic calls | Intentional Rule | A runtime callable may resolve to a function, method, or Closure unknown to the compiler. TypePHP does not mirror callable signatures at runtime; callers must use `refval()` / `toRef()` explicitly. | +| Automatic reference inference for dynamic calls | Intentional Rule | A runtime callable may resolve to a function, method, or Closure unknown to the compiler. TypePHP does not mirror callable signatures at runtime; callers must use `std::ref()` / `toRef()` explicitly. | | By-reference variadic parameters on dynamic Closures | Intentional Rule | Supporting `&...` here would require signature-aware runtime argument packing. Statically resolved ordinary functions and methods support `&...`; dynamic Closures do not. | ## Implementable but Currently Unsupported @@ -107,7 +107,7 @@ These items should be documented with the exact boundary. | Feature | Classification | Boundary | |---|---|---| | `eval()` | Partial / Hard Limit | `eval()` can execute PHP code through Zend VM, but it cannot access compiled local variables. Use return values or `$GLOBALS` for data exchange. | -| Dynamic calls and callbacks | Partial | Zend runtime fallback handles dynamic calls and callbacks. By-reference arguments still need explicit `refval()` / `toRef()`, and native-call optimization is not guaranteed. | +| Dynamic calls and callbacks | Partial | Zend runtime fallback handles dynamic calls and callbacks. By-reference arguments still need explicit `std::ref()` / `toRef()`, and native-call optimization is not guaranteed. | | 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. | diff --git a/docs/en/README.md b/docs/en/README.md index 040d5954..e463f994 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -10,7 +10,7 @@ This directory contains compiler implementation, compatibility, build-mode, and - [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. -- [Compile-time Functions](COMPILE_TIME_FUNCTIONS.md): `any()`, `refval()`, `objval()`, `expected()`, `unexpected()`, and keyword methods. +- [Compile-time Functions](COMPILE_TIME_FUNCTIONS.md): `std::any()`, `std::ref()`, `std::expected()`, `std::unexpected()`, and keyword methods. - [Native Types](NATIVE_TYPES.md), [High-Precision Types](HIGH_PRECISION_TYPES.md), [Std Containers](STD_CONTAINERS.md). - [Three Object Storage and Passing Models](OBJECT_STORAGE_AND_PASSING_MODELS.md): the responsibilities, ABI, and non-substitutable boundaries of Zend Object, PHPX Box, and Native Class Object. - [Universal and Extension Methods](UNIVERSAL_METHODS.md), [Generator](YIELD_GENERATOR.md). diff --git a/docs/en/REFACTORING_PLAN.md b/docs/en/REFACTORING_PLAN.md index 8efaa367..3dcc35b5 100644 --- a/docs/en/REFACTORING_PLAN.md +++ b/docs/en/REFACTORING_PLAN.md @@ -7,7 +7,7 @@ The current core classes of the AOT compiler carry too many responsibilities. In particular, classes such as `CompilerBase` and `Translator` simultaneously contain AST dispatch, type inference, property access resolution, call resolution, code generation, diagnostics, and context state maintenance. As functionality continues to grow, this structure causes the following problems: - Insufficient encapsulation: modifying one semantic point easily affects multiple code paths. -- Insufficient code reuse: similar logic is repeatedly implemented across normal properties, static properties, nullsafe, assignment, isset/empty/refval, and other paths. +- Insufficient code reuse: similar logic is repeatedly implemented across normal properties, static properties, nullsafe, assignment, isset/empty/`std::ref()`, and other paths. - Compile-time checks are prone to bypass paths: for example, some dynamic fallbacks do not reuse the static resolver. - Individual classes are too large, and review, test localization, and long-term maintenance costs keep rising. - Design boundaries are unclear: the type system, symbol resolution, property access, and call generation are too deeply coupled. @@ -143,7 +143,7 @@ Paths that need unified coverage: - `static::$prop` - `isset($obj->prop)` - `empty($obj->prop)` -- `refval($obj->prop)` +- `std::ref($obj->prop)` - normal assignment, compound assignment, increment/decrement, unset. It is recommended to start from this module as the first priority, because recent problems are concentrated in property access and visibility bypass, and the test boundaries are relatively clear. @@ -169,7 +169,7 @@ Recommended interfaces: Design requirements: - When the parameter information of a static function or built-in function is clear, references can be automatically converted. -- For dynamic calls, closures, and cases where by-ref parameter information cannot be obtained at compile time, an explicit `refval()` must be required. +- For dynamic calls, closures, and cases where by-ref parameter information cannot be obtained at compile time, an explicit `std::ref()` must be required. - When using unpack with trailing named args appended, it should degrade to a dynamic call and must not go through a native call. ### 5. ExpressionEmitter @@ -255,7 +255,7 @@ Current progress: Status: - Phase 1 is essentially wrapped up. Unless property read resolver bypass or behavior regression is found later, the scope of Phase 1 will not be further expanded. -- Phase 2 has begun; assignment, compound assignment, inc/dec, unset, and refval paths related to property writes still need to be further unified. +- Phase 2 has begun; assignment, compound assignment, inc/dec, unset, and `std::ref()` paths related to property writes still need to be further unified. Verification: @@ -297,7 +297,7 @@ Current progress: - `getProperty()` / `setProperty()` generation for dynamic object properties has been converged into the `emitDynamicPropertyRead()` / `emitDynamicPropertyWrite()` helpers; normal dynamic property assignment, compound assignment, and increment/decrement now reuse this entry point. - The dynamic property path of compound assignment has been connected to `preparePropertyWriteTarget()`, uniformly completing property write target preparation and static checks first. - `PropertyWriteTarget` has begun carrying the object/property expressions of safe dynamic property write targets; normal dynamic property assignment, compound assignment, and increment/decrement now prefer emitting code through target-level read/write helpers. -- Dynamic property `unset`, property array dimension writes, and safe object property reference paths in reference arguments/refval/reference assignment have begun reusing target-level unset/ref helpers. +- Dynamic property `unset`, property array dimension writes, and safe object property reference paths in reference arguments/`std::ref()`/reference assignment have begun reusing target-level unset/ref helpers. - Target/ref generation for object property reference expressions has been converged into `emitDynamicPropertyFetchRef()`; the unused old static property assignment entry point has been deleted, and static property assignment continues through the unified assignment target path. - The dynamic object/property fields of `PropertyWriteTarget` have been encapsulated as getters; property array dimension writes have been connected to target-level append/update emitters. - Established the `emitDynamicPropertyFetchRead/Write/Unset/AppendArray/UpdateArray()` wrapper layer; callers only pass in the property access AST and an optional target, and `CompilerBase` uniformly selects the target path or the old fallback path. diff --git a/docs/zh-cn/COMPILE_TIME_FUNCTIONS.md b/docs/zh-cn/COMPILE_TIME_FUNCTIONS.md index 29fdf09a..c72c1020 100644 --- a/docs/zh-cn/COMPILE_TIME_FUNCTIONS.md +++ b/docs/zh-cn/COMPILE_TIME_FUNCTIONS.md @@ -2,24 +2,14 @@ 本文档记录 AOT 编译器专有的编译期函数、关键词方法和相关构造入口。它们不是标准 PHP 语法的一部分,普通 PHP 运行时只能依赖 `src/polyfills.php` 提供的兼容占位。 -## 核心编译期函数 +## 全局函数名 -当前核心全局编译期函数共 5 个。 +TypePHP 不再为编译器指令保留任何全局函数名。编译期 API 最终只占用两个全局类符号: +`Type::*` 仅用于扩展方法等元数据中的类型表示,`std::*` 承载 TypePHP 内置函数。 +对象类型断言使用 `toObject()` 关键词方法。 -| 名称 | 参数 | 作用 | 当前主要处理位置 | -| --- | --- | --- | --- | -| `any($value)` | 1 个 | 将表达式降级为 `mixed/any`,阻止继续按静态 native/object 类型处理。 | 通用函数调用表达式入口。 | -| `refval($target)` | 1 个 | 显式把变量、数组元素或对象属性作为引用传给动态调用或无法静态识别引用参数的调用。 | 参数解析、动态调用、SSA/优化器引用逃逸分析。 | -| `objval($value, ClassName::class 或 'ClassName')` | 2 个 | 告诉编译器 `$value` 是指定类对象,并生成 `php::toObject(..., target_ce)` 运行时兜底检查。 | 函数调用解析、对象类型推导。 | -| `expected($condition)` | 1 个 | 标记条件通常为真,生成 Zend `EXPECTED(...)` 分支预测宏。 | 通用函数调用表达式入口。 | -| `unexpected($condition)` | 1 个 | 标记条件通常为假,生成 Zend `UNEXPECTED(...)` 分支预测宏。 | 通用函数调用表达式入口。 | - -约束: - -- `refval()` 只接受变量、数组元素或对象属性。 -- `objval()` 第二个参数必须是编译期可解析的类名字符串或 `ClassName::class`。 -- `any()` 可在任意表达式位置使用,编译时直接展开其唯一参数,不生成运行时函数调用。 -- `expected()` / `unexpected()` 只接受一个非展开参数,返回 bool;通常用于 `if`、`elseif` 和循环条件,不改变参数的求值次数及真假语义。 +遵循 PHP 规则,`std` / `Type` 类名以及 `std` 方法名不区分大小写;`Type::*` +成员是类常量,其常量名仍区分大小写。 ## 关键词方法 @@ -27,8 +17,8 @@ | 名称 | 等价行为 | 说明 | | --- | --- | --- | -| `toAny()` | `any($receiver)` | 返回接收者本身,但类型降级为 `mixed/any`。 | -| `toRef()` | `refval($receiver)` | 返回接收者引用;参数限制与 `refval()` 一致。 | +| `toAny()` | `std::any($receiver)` | 返回接收者本身,但类型降级为 `mixed/any`。 | +| `toRef()` | `std::ref($receiver)` | 返回接收者引用;参数限制与 `std::ref()` 一致。 | | `toObject()` | `php::toObject($receiver)` | 可带目标类参数,执行对象转换/检查。 | | `toInt()` | `php::toInt($receiver)` | 转为 native int 表达式。 | | `toFloat()` | `php::toFloat($receiver)` | 转为 native float 表达式。 | @@ -46,9 +36,9 @@ - `toRef()` 只适用于可取引用的接收者。 - 关键词方法优先于普通方法和 universal method 分派。 -## `std::` 编译期构造入口 +## `std::` 编译期入口 -当前 `std::` 编译期构造入口共 10 个。 +当前 `std::` 编译期入口共 14 个。 | 名称 | 作用 | 主要限制 | | --- | --- | --- | @@ -58,6 +48,10 @@ | `std::bigInt($value)` | 构造 BigInt。 | 不允许从 float 变量隐式构造。 | | `std::decimal($value)` | 构造 Decimal。 | float 变量需改用字符串或整型;float 字面量会按原始字面量处理。 | | `std::bigFloat($value)` | 构造 BigFloat。 | 需要 1 个值参数。 | +| `std::any($value)` | 将表达式降级为 `mixed/any`。 | Native 对象及包含 Native 对象的 std 容器不能通过它逃逸。 | +| `std::ref($target)` | 显式以引用方式传递目标。 | 只接受变量、数组元素或对象属性,且仅可作为调用参数的引用包装器。 | +| `std::expected($condition)` | 标记条件通常为真。 | 只接受一个非展开参数并返回 bool。 | +| `std::unexpected($condition)` | 标记条件通常为假。 | 只接受一个非展开参数并返回 bool。 | | `std::array($type, $size[, ...$sizes])` | 构造固定大小 std array。 | 只能在变量首次赋值的顶层作用域使用。 | | `std::vector($type[, $size])` | 构造 std vector。 | 只能在变量首次赋值的顶层作用域使用。 | | `std::map($keyType, $valueType)` | 构造 std map。 | 只能在变量首次赋值的顶层作用域使用。 | @@ -76,7 +70,7 @@ ## 不计入本文清单的机制 -- `$array->any()` 是 universal method,映射到 PHP `array_any()`,不是 `any()` 编译期函数。 +- `$array->any()` 是 universal method,映射到 PHP `array_any()`,不是 `std::any()` 编译期函数。 - `Type::*` 是编译期类型描述常量,不是函数。 - keyword extension method 是用户自定义扩展方法机制,不属于固定内置编译期函数清单。 @@ -84,13 +78,13 @@ 编译期函数应当在任意合法表达式位置可用,并且在所有路径上保持一致语义: -- `any()` 已统一在普通函数调用表达式入口处理;赋值、参数、返回值、数组元素和运算子表达式共用相同语义。 -- `refval()` / `toRef()` 在参数解析和动态调用路径中特判较多,后续应统一为一个“引用包装表达式”解析入口。 -- `objval()` 当前通过函数调用解析和类型推导路径识别,整体较集中。 -- `expected()` / `unexpected()` 在普通函数调用入口分别生成 `EXPECTED(...)` / `UNEXPECTED(...)`,不产生 PHP 运行时函数调用。 +- `std::any()` 使用统一的降级入口;赋值、参数、返回值、数组元素和运算子表达式共用相同语义。 +- `std::ref()` / `toRef()` 在参数解析、SSA 和优化器路径中共用同一个引用包装识别入口。 +- 已移除的全局 `objval()` 由 `toObject(ClassName::class)` 取代,对象类型断言复用现有关键词方法路径。 +- `std::expected()` / `std::unexpected()` 分别生成 `EXPECTED(...)` / `UNEXPECTED(...)`,不产生 PHP 运行时函数调用。 后续重构目标: - 建立统一的 `CompileTimeFunctionResolver` 或等价模块。 - 在 `parseExpr()` / `detectTypeOfExpr()` / `detectClassOfExpr()` / 参数解析路径中复用同一份编译期函数元信息。 -- 继续统一 `refval()`、`objval()` 在不同表达式路径上的行为。 +- 继续统一引用包装器在不同表达式路径上的行为。 diff --git a/docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md b/docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md index 04619f54..42986a04 100644 --- a/docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md +++ b/docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md @@ -44,10 +44,10 @@ - `exit(message: $value)` 可作为 TypePHP named-argument 扩展使用;它与位置参数 `exit($value)` 进入同一退出路径。 - TypePHP 使用严格参数数量规则:非 variadic 函数不接受声明范围之外的额外参数;`func_get_args()` 不会隐式放宽签名。 - 已知签名的普通函数、普通方法和 native 直调支持引用参数及写回;不要把编译器内部跨 Trait 动态分派的限制误写成“TypePHP 不支持引用参数”。 -- 闭包和箭头函数支持固定引用参数。Closure 调用属于动态分派,调用方仍须通过 `refval()` / `toRef()` 显式标记引用参数;由 Zend 发起 callback 时则会自动使用编译器生成的 Closure arginfo。 +- 闭包和箭头函数支持固定引用参数。Closure 调用属于动态分派,调用方仍须通过 `std::ref()` / `toRef()` 显式标记引用参数;由 Zend 发起 callback 时则会自动使用编译器生成的 Closure arginfo。 - 引用赋值不支持从复杂静态属性表达式建立引用。 -- 动态调用、闭包调用等编译期无法确定参数签名的调用,不能自动转换引用参数;需要显式使用 `refval()` 或等价关键词方法 `toRef()`。 -- `refval()` / `toRef()` 只接受变量、数组元素或对象属性。 +- 动态调用、闭包调用等编译期无法确定参数签名的调用,不能自动转换引用参数;需要显式使用 `std::ref()` 或等价关键词方法 `toRef()`。 +- `std::ref()` / `toRef()` 只接受变量、数组元素或对象属性。 - 带 unpack 且尾部追加 named arguments 的调用会退化为动态调用,不能使用 native call。 ## 对象模型 @@ -80,7 +80,7 @@ - `static::class` 在需要编译期常量类名的位置不支持。 - `__CLASS__` 只允许在 `class` 定义的代码段中使用(`PHP`允许,返回空字符串)。 - `__TRAIT__` 只允许在 `trait` 定义的代码段中使用(`PHP`允许,返回空字符串)。 -- 动态属性链、动态类名、动态函数名和动态回调会统一走 Zend runtime fallback,不保证 native 优化;动态调用的引用参数仍需显式使用 `refval()` 或 `toRef()`。 +- 动态属性链、动态类名、动态函数名和动态回调会统一走 Zend runtime fallback,不保证 native 优化;动态调用的引用参数仍需显式使用 `std::ref()` 或 `toRef()`。 - 不支持 `Closure::bind()`、`Closure::bindTo()` 和 `Closure::call()`;闭包不能在 AOT 代码中重新绑定对象或 class scope。 - 所有源文件必须是 `UTF-8` 编码。 diff --git a/docs/zh-cn/NATIVE_CLASS_OBJECT.md b/docs/zh-cn/NATIVE_CLASS_OBJECT.md index 38c0c8fd..1f3c9f25 100644 --- a/docs/zh-cn/NATIVE_CLASS_OBJECT.md +++ b/docs/zh-cn/NATIVE_CLASS_OBJECT.md @@ -799,7 +799,7 @@ void php_move(php_app__point *point, php::Float x); ```php function replace(Point &$point): void; // FatalError $alias =& $point; // FatalError -refval($point); // FatalError +std::ref($point); // FatalError $point->toRef(); // FatalError ``` diff --git a/docs/zh-cn/NATIVE_TYPES.md b/docs/zh-cn/NATIVE_TYPES.md index b347a489..0252734b 100644 --- a/docs/zh-cn/NATIVE_TYPES.md +++ b/docs/zh-cn/NATIVE_TYPES.md @@ -52,21 +52,18 @@ $user->profile = null; // ✅ 对象属性可显式设置为 null --- -## 🎯 objval 编译期函数 +## 🎯 `toObject()` 关键词方法 ### 使用场景 -当从数组、函数返回值等来源获取对象时,变量会丢失类型上下文信息。此时需要使用 `objval()` 显式声明对象的类。 +当从数组、函数返回值等来源获取对象时,变量会丢失类型上下文信息。此时使用 `toObject()` 关键词方法断言对象类型。 ### 基本语法 ```php toObject(ClassName::class); ``` ### 典型场景 @@ -83,9 +80,9 @@ $data = [ // ❌ 错误:类型丢失 $user = $data['user']; // AOT 无法推断类型 -// ✅ 正确:使用 objval 声明类型 -$user = objval($data['user'], 'User'); -$product = objval($data['product'], 'Product'); +// ✅ 正确:使用 toObject() 声明类型 +$user = $data['user']->toObject(User::class); +$product = $data['product']->toObject(Product::class); ``` #### 场景二:函数返回对象 @@ -99,8 +96,8 @@ function get_object() { // ❌ 类型丢失 $obj = get_object(); -// ✅ 使用 objval 声明 -$obj = objval(get_object(), 'stdClass'); +// ✅ 使用 toObject() 声明 +$obj = get_object()->toObject(stdClass::class); ``` #### 场景三:工厂模式 @@ -123,8 +120,8 @@ class Factory { $factory = new Factory(); // ✅ 明确指定返回的对象类型 -$user = objval($factory->create('user'), 'User'); -$product = objval($factory->create('product'), 'Product'); +$user = $factory->create('user')->toObject(User::class); +$product = $factory->create('product')->toObject(Product::class); ``` ### 注意事项 @@ -134,46 +131,45 @@ $product = objval($factory->create('product'), 'Product'); ```php toObject(MyClass::class); // ❌ 错误:变量类名(编译期无法分析) $className = 'MyClass'; -$obj = objval($value, $className); // 编译错误 +$obj = $value->toObject($className); // 编译错误 // ❌ 错误:常量类名(编译期可能无法解析) const CLASS_NAME = 'MyClass'; -$obj = objval($value, CLASS_NAME); // 可能失败 +$obj = $value->toObject(CLASS_NAME); // 可能失败 ``` -⚠️ **第一个参数必须是 variable 表达式**: +接收者可以是受支持的任意值表达式: ```php property, 'MyClass'); -$obj = objval(get_object(), 'MyClass'); +$obj = $array['key']->toObject(MyClass::class); +$obj = $object->property->toObject(MyClass::class); +$obj = get_object()->toObject(MyClass::class); -// ❌ 错误:非 variable 表达式 -$obj = objval(new MyClass(), 'MyClass'); // 不需要 +// ✅ 合法,但表达式已经具备精确类型,因此没有必要 +$obj = (new MyClass())->toObject(MyClass::class); // 不需要 ``` ### 性能影响 -- ✅ `objval()` 是**编译期函数** -- ✅ 不会产生运行时开销 -- ✅ 仅在编译阶段进行类型推断 -- ✅ 生成的 C++ 代码与普通变量赋值相同 +- ✅ `toObject()` 是 TypePHP 关键词方法 +- ✅ 为编译器提供目标类信息 +- ✅ 当运行时类型不能被静态证明时,生成 PHPX 对象转换/类型检查 ### 与 std:: 类型的区别 -| 特性 | std::int/float/bool | objval | +| 特性 | std::int/float/bool | toObject | |------|---------------------|--------| | **用途** | 数值/布尔类型优化 | 对象类型声明 | | **性能** | ⚡ 高性能(原生类型) | 🐢 标准(ZVAL) | | **内存** | 8B/1B | 指针(16B+) | -| **时机** | 运行时优化 | 编译期推断 | -| **语法** | `std::int(值)` | `objval(变量,'类名')` | +| **时机** | 运行时优化 | 编译期降级,必要时运行时检查 | +| **语法** | `std::int(值)` | `$value->toObject(ClassName::class)` | --- diff --git a/docs/zh-cn/README.md b/docs/zh-cn/README.md index 4ae22267..a8ee60fc 100644 --- a/docs/zh-cn/README.md +++ b/docs/zh-cn/README.md @@ -10,7 +10,7 @@ - [编译器命令行](COMPILER_CLI.md):当前 CLI 参数和项目配置。 - [编译模式](COMPILATION_MODES.md):binary、extension、library 模式。 - [快速入门](QUICKSTART.md):最小编译流程。 -- [编译期函数](COMPILE_TIME_FUNCTIONS.md):`any()`、`refval()`、`objval()`、`expected()`、`unexpected()` 和关键词方法。 +- [编译期函数](COMPILE_TIME_FUNCTIONS.md):`std::any()`、`std::ref()`、`std::expected()`、`std::unexpected()` 和关键词方法。 - [原生类型](NATIVE_TYPES.md)、[高精度类型](HIGH_PRECISION_TYPES.md)、[Std 容器](STD_CONTAINERS.md)。 - [三套对象存储与传递模型](OBJECT_STORAGE_AND_PASSING_MODELS.md):Zend Object、PHPX Box 与 Native Class Object 的职责、ABI 和不可替代边界。 - [通用与扩展方法](UNIVERSAL_METHODS.md)、[Generator](YIELD_GENERATOR.md)。 diff --git a/docs/zh-cn/REFACTORING_PLAN.md b/docs/zh-cn/REFACTORING_PLAN.md index a880a34d..7c56dcd0 100644 --- a/docs/zh-cn/REFACTORING_PLAN.md +++ b/docs/zh-cn/REFACTORING_PLAN.md @@ -7,7 +7,7 @@ 当前 AOT 编译器核心类承担了过多职责,尤其是 `CompilerBase`、`Translator` 等类同时包含 AST 分发、类型推导、属性访问解析、调用解析、代码生成、诊断信息、上下文状态维护等逻辑。随着功能持续增加,这种结构会带来以下问题: - 封装性不足,修改一个语义点时容易影响多个代码路径。 -- 代码复用不足,同类逻辑在普通属性、静态属性、nullsafe、assignment、isset/empty/refval 等路径中重复实现。 +- 代码复用不足,同类逻辑在普通属性、静态属性、nullsafe、assignment、isset/empty/`std::ref()` 等路径中重复实现。 - 编译期检查容易出现绕过路径,例如某些动态 fallback 没有复用静态 resolver。 - 单个类代码量过大,review、测试定位和长期维护成本持续升高。 - 设计边界不清晰,类型系统、符号解析、属性访问、调用生成之间耦合过深。 @@ -143,7 +143,7 @@ - `static::$prop` - `isset($obj->prop)` - `empty($obj->prop)` -- `refval($obj->prop)` +- `std::ref($obj->prop)` - 普通赋值、复合赋值、自增自减、unset。 第一优先级建议从本模块开始,因为近期问题集中在属性访问和可见性绕过,测试边界相对清晰。 @@ -169,7 +169,7 @@ 设计要求: - 静态 function 和内置 function 参数信息明确时,可以自动转引用。 -- 动态调用、closure、编译期无法获取参数 by-ref 信息时,必须要求显式 `refval()`。 +- 动态调用、closure、编译期无法获取参数 by-ref 信息时,必须要求显式 `std::ref()`。 - 使用 unpack 并追加尾部 named args 时,应退化为 dynamic call,不能走 native call。 ### 5. ExpressionEmitter @@ -255,7 +255,7 @@ 状态: - 阶段 1 已基本收尾。后续除非发现属性读取 resolver 绕过或行为回归,否则不再继续扩大阶段 1 范围。 -- 阶段 2 已开始;属性写入相关的 assignment、compound assignment、inc/dec、unset、refval 路径仍需继续统一。 +- 阶段 2 已开始;属性写入相关的 assignment、compound assignment、inc/dec、unset、`std::ref()` 路径仍需继续统一。 验证: @@ -297,7 +297,7 @@ - dynamic object property 的 `getProperty()` / `setProperty()` 生成已收敛到 `emitDynamicPropertyRead()` / `emitDynamicPropertyWrite()` helper;普通动态属性赋值、复合赋值、自增自减已复用该入口。 - 复合赋值的动态属性路径已接入 `preparePropertyWriteTarget()`,先统一完成属性写入 target 准备和静态检查。 - `PropertyWriteTarget` 已开始携带安全动态属性写入目标的 object/property 表达式;普通动态属性赋值、复合赋值、自增自减已优先通过 target 级 read/write helper 发射代码。 -- 动态属性 `unset`、属性数组维度写入、引用参数/refval/引用赋值中的安全对象属性引用路径已开始复用 target 级 unset/ref helper。 +- 动态属性 `unset`、属性数组维度写入、引用参数/`std::ref()`/引用赋值中的安全对象属性引用路径已开始复用 target 级 unset/ref helper。 - 对象属性引用表达式的 target/ref 生成已收敛到 `emitDynamicPropertyFetchRef()`;未使用的旧静态属性赋值入口已删除,静态属性赋值继续走统一 assignment target 路径。 - `PropertyWriteTarget` 的动态 object/property 字段已封装为 getter;属性数组维度写入已接入 target 级 append/update emitter。 - 已建立 `emitDynamicPropertyFetchRead/Write/Unset/AppendArray/UpdateArray()` 包装层,调用方只传入属性访问 AST 与可选 target,由 `CompilerBase` 统一选择 target 路径或旧 fallback 路径。 diff --git a/examples/debug/prop.php b/examples/debug/prop.php index 88959ff8..ffc20ba1 100644 --- a/examples/debug/prop.php +++ b/examples/debug/prop.php @@ -8,7 +8,7 @@ class Data { function main() { $o = new Data; - $value = any('222'); + $value = std::any('222'); $o->value = $value; $o->value += '333'; var_dump($o->value); diff --git a/examples/debug_trace.php b/examples/debug_trace.php index b21ecce0..6c967d19 100644 --- a/examples/debug_trace.php +++ b/examples/debug_trace.php @@ -13,7 +13,7 @@ class Obj { function run() { - $a = any(199); + $a = std::any(199); var_dump($a[9]); } diff --git a/examples/obj.php b/examples/obj.php index 4db98ff9..a63167d3 100644 --- a/examples/obj.php +++ b/examples/obj.php @@ -30,7 +30,7 @@ function main() $arr['obj'] = $obj; var_dump($obj->test()); - $obj2 = objval($arr['obj'], 'Test'); + $obj2 = $arr['obj']->toObject(Test::class); var_dump($obj2); var_dump($obj2->test()); -} \ No newline at end of file +} diff --git a/phpunit/code/base-class-method.php b/phpunit/code/base-class-method.php index e5c17d7b..ec003265 100644 --- a/phpunit/code/base-class-method.php +++ b/phpunit/code/base-class-method.php @@ -24,6 +24,6 @@ function bar(FooBase $o) function main() { $o = new FooChild(); - $o2 = any($o); + $o2 = std::any($o); bar($o2); } diff --git a/phpunit/code/branch-prediction.php b/phpunit/code/branch-prediction.php index d78280c1..bd33264f 100644 --- a/phpunit/code/branch-prediction.php +++ b/phpunit/code/branch-prediction.php @@ -2,10 +2,10 @@ function phpunit_branch_prediction(bool $likely, mixed $unlikely): int { - if (expected($likely)) { + if (std::expected($likely)) { return 1; } - if (unexpected((bool) $unlikely)) { + if (std::unexpected((bool) $unlikely)) { return 2; } return 3; diff --git a/phpunit/code/native-class-any-escape.php b/phpunit/code/native-class-any-escape.php index 616e969b..038983ba 100644 --- a/phpunit/code/native-class-any-escape.php +++ b/phpunit/code/native-class-any-escape.php @@ -8,5 +8,5 @@ class NativeAnyEscape function main(): void { $value = new NativeAnyEscape(); - $mixed = any($value); + $mixed = std::any($value); } diff --git a/phpunit/code/native-class-reference-function.php b/phpunit/code/native-class-reference-function.php index 95312e4f..6323650e 100644 --- a/phpunit/code/native-class-reference-function.php +++ b/phpunit/code/native-class-reference-function.php @@ -8,5 +8,5 @@ function acceptsReference(&$value): void {} function invalidNativeReferenceFunction(): void { $value = new NativeReferenceFunctionValue(); - acceptsReference(refval($value)); + acceptsReference(std::ref($value)); } diff --git a/phpunit/src/Analysis/SsaBuilderTest.php b/phpunit/src/Analysis/SsaBuilderTest.php index 7e690c0e..1dacd32f 100644 --- a/phpunit/src/Analysis/SsaBuilderTest.php +++ b/phpunit/src/Analysis/SsaBuilderTest.php @@ -313,7 +313,7 @@ class SsaBuilderTest extends TestCase public function testNestedCallByRefCreatesEscapedVar(): void { $builder = $this->buildSsa(' - $y = some_func(refval($x)); + $y = some_func(std::ref($x)); '); $escapedVar = null; @@ -323,14 +323,14 @@ class SsaBuilderTest extends TestCase break; } } - $this->assertNotNull($escapedVar, 'Nested refval($x) should create ESCAPED SSA var'); + $this->assertNotNull($escapedVar, 'Nested std::ref($x) should create ESCAPED SSA var'); } public function testCallByRefInsideLoopCreatesEscapedVar(): void { $builder = $this->buildSsa(' while ($x) { - some_func(refval($x)); + some_func(std::ref($x)); } '); @@ -341,14 +341,14 @@ class SsaBuilderTest extends TestCase break; } } - $this->assertNotNull($escapedVar, 'refval($x) inside loop body should create ESCAPED SSA var'); + $this->assertNotNull($escapedVar, 'std::ref($x) inside loop body should create ESCAPED SSA var'); } - public function testRefvalCallByRefCreatesEscapedVar(): void + public function testStdRefCallByRefCreatesEscapedVar(): void { - // refval() is the AOT compiler's pseudo-function for dynamic call reference passing + // std::ref() is the AOT compiler's pseudo-function for dynamic call reference passing $builder = $this->buildSsa(' - some_func(refval($x)); + some_func(std::ref($x)); '); $escapedVar = null; @@ -358,13 +358,13 @@ class SsaBuilderTest extends TestCase break; } } - $this->assertNotNull($escapedVar, 'refval() call should create ESCAPED SSA var for its argument'); + $this->assertNotNull($escapedVar, 'std::ref() call should create ESCAPED SSA var for its argument'); } - public function testRefvalWithMultipleArgs(): void + public function testStdRefWithMultipleArgs(): void { $builder = $this->buildSsa(' - some_func($a, refval($b), refval($c)); + some_func($a, std::ref($b), std::ref($c)); '); $escapedB = false; @@ -377,8 +377,8 @@ class SsaBuilderTest extends TestCase $escapedC = true; } } - $this->assertTrue($escapedB, 'refval($b) should create ESCAPED SSA var'); - $this->assertTrue($escapedC, 'refval($c) should create ESCAPED SSA var'); + $this->assertTrue($escapedB, 'std::ref($b) should create ESCAPED SSA var'); + $this->assertTrue($escapedC, 'std::ref($c) should create ESCAPED SSA var'); // $a is NOT passed by ref — it should NOT be escaped $aEscaped = false; @@ -387,7 +387,7 @@ class SsaBuilderTest extends TestCase $aEscaped = true; } } - $this->assertFalse($aEscaped, '$a (not refval) should NOT be escaped'); + $this->assertFalse($aEscaped, '$a (not std::ref) should NOT be escaped'); } // ======================================================================== diff --git a/phpunit/src/AstNodeTypeTest.php b/phpunit/src/AstNodeTypeTest.php index 2c07ba4d..5ee00a99 100644 --- a/phpunit/src/AstNodeTypeTest.php +++ b/phpunit/src/AstNodeTypeTest.php @@ -178,18 +178,27 @@ class AstNodeTypeTest extends TestCase } // ======================================================================== - // isRefvalCall + // isStdRefCall // ======================================================================== - public function testIsRefvalCall(): void + public function testIsStdRefCall(): void { - $refvalCall = new Expr\FuncCall(new Node\Name('refval')); - $this->assertTrue($this->invoke('isRefvalCall', $refvalCall)); + $stdRefCall = new Expr\StaticCall(new Node\Name('std'), new Node\Identifier('ref')); + $this->assertTrue($this->invoke('isStdRefCall', $stdRefCall)); - $otherCall = new Expr\FuncCall(new Node\Name('other')); - $this->assertFalse($this->invoke('isRefvalCall', $otherCall)); + $fullyQualifiedStdRefCall = new Expr\StaticCall(new Node\Name\FullyQualified('std'), new Node\Identifier('ref')); + $this->assertTrue($this->invoke('isStdRefCall', $fullyQualifiedStdRefCall)); - $this->assertFalse($this->invoke('isRefvalCall', new Expr\Variable('a'))); + $relativeStdRefCall = new Expr\StaticCall(new Node\Name\Relative('std'), new Node\Identifier('ref')); + $this->assertFalse($this->invoke('isStdRefCall', $relativeStdRefCall)); + + $globalRefvalCall = new Expr\FuncCall(new Node\Name('refval')); + $this->assertFalse($this->invoke('isStdRefCall', $globalRefvalCall)); + + $otherCall = new Expr\StaticCall(new Node\Name('other'), new Node\Identifier('ref')); + $this->assertFalse($this->invoke('isStdRefCall', $otherCall)); + + $this->assertFalse($this->invoke('isStdRefCall', new Expr\Variable('a'))); } // ======================================================================== @@ -396,7 +405,7 @@ class AstNodeTypeTest extends TestCase $methods = [ 'isArrayDimFetch', 'isPropertyFetch', 'isStaticPropertyFetch', 'isClassConstFetch', 'isNewExpr', 'isNameExpr', 'isFullNameExpr', - 'isFuncCallExpr', 'isRefvalCall', 'isMethodCall', 'isStaticCall', + 'isFuncCallExpr', 'isStdRefCall', 'isMethodCall', 'isStaticCall', 'isMatchExpr', 'isAssignExpr', 'isCallExpr', 'isPlaceholderExpr', 'isReturnExpr', 'isBreakExpr', 'isThrowExpr', 'isExitExpr', 'isEmptyArray', 'isNull', diff --git a/phpunit/src/FuncCallOptimizerTest.php b/phpunit/src/FuncCallOptimizerTest.php index 3c264419..10de1f87 100644 --- a/phpunit/src/FuncCallOptimizerTest.php +++ b/phpunit/src/FuncCallOptimizerTest.php @@ -31,7 +31,7 @@ final class FuncCallOptimizerTest extends BaseTest self::assertMatchesRegularExpression('/php_optimizertypedint\(\);\s*php::call/', $code); self::assertMatchesRegularExpression('/php_optimizerdynamicbool\(\);\s*php::call/', $code); self::assertStringContainsString('php::fn::hypot(php::toFloat(', $code); - self::assertStringContainsString('php::ArgList{php::null}', $code); + self::assertStringContainsString('php::VarList{php::null}', $code); self::assertMatchesRegularExpression('/php::fn::json_decode\([^;]+php::null\);/', $code); self::assertStringContainsString('php::fn::floor(1.5)', $code); self::assertStringContainsString('php::fn::round(1.25)', $code); diff --git a/phpunit/src/NativeClass/NativeClassValidationTest.php b/phpunit/src/NativeClass/NativeClassValidationTest.php index 36d5d695..f4c20594 100644 --- a/phpunit/src/NativeClass/NativeClassValidationTest.php +++ b/phpunit/src/NativeClass/NativeClassValidationTest.php @@ -995,7 +995,7 @@ final class NativeClassValidationTest extends \BaseTest public function testRejectsErasingNativeObjectTypeWithAny(): void { $this->expectException(TestError::class); - $this->expectExceptionMessage('Native objects cannot be converted to mixed with any()'); + $this->expectExceptionMessage('Native objects cannot be converted to mixed with std::any()'); $this->compile('native-class-any-escape.php'); } diff --git a/phpunit/src/Python/PythonModuleTest.php b/phpunit/src/Python/PythonModuleTest.php index 5650d192..099160e5 100644 --- a/phpunit/src/Python/PythonModuleTest.php +++ b/phpunit/src/Python/PythonModuleTest.php @@ -36,7 +36,8 @@ final class PythonModuleTest extends TestCase $compiler->prepareFile($source); $cpp = file_get_contents($compiler->convertFile($source)); - self::assertStringContainsString('typephp_call_method_cached(', $cpp); + self::assertStringContainsString('object.call(method, php::VarList{1L})', $cpp); + self::assertStringNotContainsString('typephp_call_method_cached(', $cpp); self::assertStringNotContainsString('php::python::callMember(', $cpp); } diff --git a/phpunit/src/SsaAnalysisTest.php b/phpunit/src/SsaAnalysisTest.php index 3f7e2e82..6fd61b86 100644 --- a/phpunit/src/SsaAnalysisTest.php +++ b/phpunit/src/SsaAnalysisTest.php @@ -734,13 +734,13 @@ class SsaAnalysisTest extends TestCase $this->assertTrue($result, 'func(&$obj->prop) before a later access should be detected'); } - public function testHasDangerousPropOpsRefval(): void + public function testHasDangerousPropOpsStdRef(): void { $objVar = new Expr\Variable('obj'); $propFetch = new Expr\PropertyFetch($objVar, 'prop'); - $refvalArg = new Arg($propFetch); - $refvalCall = new Expr\FuncCall(new Node\Name('refval'), [$refvalArg]); - $arg = new Arg($refvalCall); + $stdRefArg = new Arg($propFetch); + $stdRefCall = new Expr\StaticCall(new Node\Name('std'), new Node\Identifier('ref'), [$stdRefArg]); + $arg = new Arg($stdRefCall); $funcCall = new Expr\FuncCall(new Node\Name('someFunc'), [$arg]); $stmt = new Stmt\Expression($funcCall); $read = new Stmt\Expression(new Expr\Assign( @@ -749,7 +749,7 @@ class SsaAnalysisTest extends TestCase )); $result = $this->invoke('hasDangerousPropOps', 'obj', [$stmt, $read]); - $this->assertTrue($result, 'func(refval($obj->prop)) before a later access should be detected'); + $this->assertTrue($result, 'func(std::ref($obj->prop)) before a later access should be detected'); } public function testHasDangerousPropOpsClean(): void @@ -782,11 +782,15 @@ class SsaAnalysisTest extends TestCase $this->assertTrue($result, '&$obj->prop inside if before a later access should be detected'); } - public function testHasDangerousPropOpsNestedRefvalInAssignment(): void + public function testHasDangerousPropOpsNestedStdRefInAssignment(): void { $propFetch = new Expr\PropertyFetch(new Expr\Variable('obj'), 'prop'); - $refvalCall = new Expr\FuncCall(new Node\Name('refval'), [new Arg($propFetch)]); - $funcCall = new Expr\FuncCall(new Node\Name('someFunc'), [new Arg($refvalCall)]); + $stdRefCall = new Expr\StaticCall( + new Node\Name('std'), + new Node\Identifier('ref'), + [new Arg($propFetch)], + ); + $funcCall = new Expr\FuncCall(new Node\Name('someFunc'), [new Arg($stdRefCall)]); $stmt = new Stmt\Expression(new Expr\Assign(new Expr\Variable('result'), $funcCall)); $read = new Stmt\Expression(new Expr\Assign( new Expr\Variable('value'), @@ -794,7 +798,7 @@ class SsaAnalysisTest extends TestCase )); $result = $this->invoke('hasDangerousPropOps', 'obj', [$stmt, $read]); - $this->assertTrue($result, 'refval($obj->prop) nested in an assignment RHS before a later access should be detected'); + $this->assertTrue($result, 'std::ref($obj->prop) nested in an assignment RHS before a later access should be detected'); } public function testHasDangerousPropOpsNestedByRefInReturn(): void diff --git a/project.yml b/project.yml index b7600bd7..8594c147 100644 --- a/project.yml +++ b/project.yml @@ -1,6 +1,6 @@ name: tpc build-mode: bin -version: 0.7.0 +version: 0.8.0 cxx-std: c++17 cxx-flags: - -Wall @@ -12,8 +12,8 @@ resource: icon: swoole-logo.ico # 版本信息 version-info: - file-version: 0.7.0.1112 - product-version: 0.7.0 + file-version: 0.8.0.1112 + product-version: 0.8.0 company-name: "上海识沃网络科技有限公司" file-description: "TypePHP Compiler" internal-name: "typephp" diff --git a/src/Analysis/SsaBuilder.php b/src/Analysis/SsaBuilder.php index a417c1ad..e1de3059 100644 --- a/src/Analysis/SsaBuilder.php +++ b/src/Analysis/SsaBuilder.php @@ -1373,9 +1373,9 @@ class SsaBuilder * * Two mechanisms in TypePHP: * 1. Explicit &$var at call site: func(&$x) — detected via $arg->byRef - * 2. refval() pseudo-function: func(refval($x)) — used for dynamic calls + * 2. std::ref() pseudo-function: func(std::ref($x)) — used for dynamic calls * where the compiler can't statically determine if the parameter is byRef. - * The compiler detects refval() via isRefvalCall() and unwraps it during codegen. + * The compiler detects std::ref() via isStdRefCall() and unwraps it during codegen. * * When a variable is passed by reference to a function call, the function * may modify it. We model this as: @@ -1453,7 +1453,7 @@ class SsaBuilder * * Handles both: * - Explicit &$var (arg->byRef === true) - * - refval($var) pseudo-function wrapping + * - std::ref($var) pseudo-function wrapping * * @param Node\Arg[] $args */ @@ -1470,10 +1470,8 @@ class SsaBuilder $varName = $arg->value->name; } - // Case 2: refval($var) — TypePHP convention for dynamic calls - if ($varName === null && $arg->value instanceof Expr\FuncCall - && $arg->value->name instanceof Node\Name - && $arg->value->name->toLowerString() === 'refval' + // Case 2: std::ref($var) — TypePHP convention for dynamic calls + if ($varName === null && $this->isStdRefCall($arg->value) && !empty($arg->value->args)) { $inner = $arg->value->args[0]->value; if ($inner instanceof Expr\Variable && is_string($inner->name)) { diff --git a/src/CompilerBase.php b/src/CompilerBase.php index ad87b290..2c4251b3 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -2140,9 +2140,6 @@ class CompilerBase implements PropertyAccessContext } if ($this->isFuncCallExpr($expr) and $this->isNameExpr($expr->name)) { $fn = $this->parseIdentifier($expr->name); - if (count($expr->args) === 2 and $fn === 'objval') { - return $this->resolveClassNameArg($expr->args[1]->value); - } if ($this->hasFunction($fn)) { return $this->getFunction($fn)->returnClass; } @@ -3156,9 +3153,6 @@ class CompilerBase implements PropertyAccessContext if (in_array($name, self::STREAM_FUNCTIONS)) { return Type::STREAM; } - if ($globalName === 'expected' || $globalName === 'unexpected') { - return Type::BOOL; - } if (count($expr->args) === 1 and $this->isPlaceholderExpr($expr->args[0])) { return Type::OBJECT; } @@ -3212,7 +3206,7 @@ class CompilerBase implements PropertyAccessContext return Type::OBJECT; } $className = $this->parseIdentifier($expr->class); - if (strtolower($className) === 'std') { + if (strtolower(ltrim($className, '\\')) === 'std') { $method = strtolower($this->parseIdentifier($expr->name)); return match ($method) { 'int' => Type::INT, @@ -3221,6 +3215,7 @@ class CompilerBase implements PropertyAccessContext 'bigint' => Type::BIGINT, 'decimal' => Type::DECIMAL, 'bigfloat' => Type::BIGFLOAT, + 'expected', 'unexpected' => Type::BOOL, default => Type::VAR, }; } @@ -4726,16 +4721,6 @@ class CompilerBase implements PropertyAccessContext return Type::VAR; } - protected function genObjvalCall(Expr\FuncCall $expr): string - { - if (count($expr->args) !== 2) { - $this->fatalError($expr, 'objval() requires exactly 2 arguments'); - } - $receiver = $this->parseExpr($expr->args[0]->value); - $className = $this->resolveClassNameArg($expr->args[1]->value); - return 'php::toObject(' . $receiver . ', ' . $this->getClassEntryPtr($className) . ')'; - } - protected function identifierToStr(NodeAbstract $node, bool $require = true, bool $literal = false): string { $id = $this->parseIdentifier($node); diff --git a/src/Generator/CallArgumentGenerator.php b/src/Generator/CallArgumentGenerator.php index a08ba673..f960e6f6 100644 --- a/src/Generator/CallArgumentGenerator.php +++ b/src/Generator/CallArgumentGenerator.php @@ -467,7 +467,7 @@ trait CallArgumentGenerator if ($byRef) { $this->assertReadonlyPropertyReferenceForbidden($arg->value, $arg, false); } - $value = ($byRef || $this->isRefvalCall($arg->value) || $this->isToRefCall($arg->value)) + $value = ($byRef || $this->isStdRefCall($arg->value) || $this->isToRefCall($arg->value)) ? $this->parseReferenceCallArgValue($arg) : $this->parseCallArgValue($arg); $value = $this->wrapScopedCallbackArg($arg, $value); @@ -557,12 +557,12 @@ trait CallArgumentGenerator $this->addPositionalCallArg($this->parseArgRefVar($arg, $name), $arrayArgsVar, $list_args, $forceArrayArgs); continue; } - $expr = $this->expandRefvalExpr($inner, $arg); + $expr = $this->expandReferenceWrapperExpr($inner, $arg); if ($expr !== null) { $this->addPositionalCallArg($expr, $arrayArgsVar, $list_args, $forceArrayArgs); continue; } - $this->fatalError($arg, 'The refval function only accepts a variable, array element, or object property'); + $this->fatalError($arg, 'The std::ref function only accepts a variable, array element, or object property'); } else { if ($byRef) { if ($this->isScalar($arg->value)) { @@ -784,14 +784,14 @@ trait CallArgumentGenerator protected function isReferenceWrapperCall(NodeAbstract $expr): bool { - return $this->isRefvalCall($expr) || $this->isToRefCall($expr); + return $this->isStdRefCall($expr) || $this->isToRefCall($expr); } protected function unwrapReferenceWrapperCall(NodeAbstract $expr, NodeAbstract $errorNode): NodeAbstract { - if ($this->isRefvalCall($expr)) { + if ($this->isStdRefCall($expr)) { if (count($expr->args) !== 1) { - $this->fatalError($errorNode, 'The refval function only accepts one parameter'); + $this->fatalError($errorNode, 'The std::ref function only accepts one parameter'); } return $expr->args[0]->value; } @@ -807,11 +807,11 @@ trait CallArgumentGenerator } /** - * Expand an array element or object property inside a refval() call into its + * Expand an array element or object property inside a std::ref() call into its * corresponding C++ reference expression. Returns null for a plain variable, * which the caller then handles itself. */ - protected function expandRefvalExpr(NodeAbstract $inner, Node\Arg $arg): ?string + protected function expandReferenceWrapperExpr(NodeAbstract $inner, Node\Arg $arg): ?string { if ($this->isPropertyFetch($inner)) { return $this->emitDynamicPropertyFetchRef($inner, $arg); diff --git a/src/Optimizer/LoopVarOptimizer.php b/src/Optimizer/LoopVarOptimizer.php index b0b1349c..7cd0e278 100644 --- a/src/Optimizer/LoopVarOptimizer.php +++ b/src/Optimizer/LoopVarOptimizer.php @@ -526,9 +526,7 @@ trait LoopVarOptimizer return true; } } - if ($node instanceof Expr\FuncCall - && $node->name instanceof Node\Name - && strtolower($node->name->toString()) === 'refval') { + if ($this->isStdRefCall($node)) { foreach ($node->args as $arg) { if ($arg instanceof Node\Arg && $this->loopExprUsesAny($arg->value, $vars)) { return true; @@ -739,9 +737,7 @@ trait LoopVarOptimizer return true; } } - if ($expr instanceof Expr\FuncCall - && $expr->name instanceof Node\Name - && strtolower($expr->name->toString()) === 'refval') { + if ($this->isStdRefCall($expr)) { foreach ($expr->args as $arg) { if ($arg instanceof Node\Arg && $this->exprUsesVar($arg->value, $varName)) { return true; diff --git a/src/Optimizer/SsaPropOptimizer.php b/src/Optimizer/SsaPropOptimizer.php index 6f09aa58..09338bd3 100644 --- a/src/Optimizer/SsaPropOptimizer.php +++ b/src/Optimizer/SsaPropOptimizer.php @@ -470,9 +470,7 @@ trait SsaPropOptimizer return; } - if ($node instanceof Expr\FuncCall - && $node->name instanceof Node\Name - && $node->name->toLowerString() === 'refval' + if ($this->isStdRefCall($node) && isset($node->args[0]) && $node->args[0] instanceof Node\Arg) { $propName = $this->getPropNameOfObj($node->args[0]->value, $objName); diff --git a/src/Parser/AssignOpTrait.php b/src/Parser/AssignOpTrait.php index 265d65f2..8c7510a1 100644 --- a/src/Parser/AssignOpTrait.php +++ b/src/Parser/AssignOpTrait.php @@ -590,8 +590,9 @@ trait AssignOpTrait $type = $type === Type::VOID ? Type::VAR : $type; } elseif ($this->isStaticCall($right) and $this->isNameExpr($right->class) and $this->isIdExpr($right->name)) { $class = $this->parseIdentifier($right->class); - if ($class === 'std') { - if (in_array($right->name->toString(), ['array', 'vector', 'map', 'ordered_map'], true)) { + if ($this->isStdClassExpr($right->class)) { + $stdMethod = strtolower($right->name->toString()); + if (in_array($stdMethod, ['array', 'vector', 'map', 'ordered_map'], true)) { if ($this->hasScopeGlobalVar($var) || $this->hasStaticVar($var)) { $this->assertNativeStdContainerFunctionLocal($right); } @@ -601,15 +602,15 @@ trait AssignOpTrait if ($this->context->scopeLevel > 1) { $this->fatalError($left, "Must create std::{$right->name->toString()} in the top-level scope of the function"); } - if ($right->name->toString() === 'array') { + if ($stdMethod === 'array') { $this->addLocalVar($var, Type::STD_ARRAY); return $this->parseStdArray($var, $right); } - if ($right->name->toString() === 'vector') { + if ($stdMethod === 'vector') { $this->addLocalVar($var, Type::STD_VECTOR); return $this->parseStdVector($var, $right); } - if ($right->name->toString() === 'map') { + if ($stdMethod === 'map') { $this->addLocalVar($var, Type::STD_MAP); return $this->parseStdMap($var, $right); } @@ -617,11 +618,13 @@ trait AssignOpTrait return $this->parseStdOrderedMap($var, $right); } else { $valueExpr = $this->parseStdCall($right); + $finalVarType = $right->getAttribute('nativeType') ?? Type::VAR; if (!$this->hasVar($var)) { - $finalVarType = $right->getAttribute('nativeType'); $this->addLocalVar($var, $finalVarType); } - $this->context->explicitNativeTypeVars[$var] = true; + if ($finalVarType !== Type::VAR) { + $this->context->explicitNativeTypeVars[$var] = true; + } return $var . ' = ' . $valueExpr; } } diff --git a/src/Parser/AstNodeType.php b/src/Parser/AstNodeType.php index 9a770519..c3869942 100644 --- a/src/Parser/AstNodeType.php +++ b/src/Parser/AstNodeType.php @@ -86,10 +86,19 @@ trait AstNodeType return $expr instanceof Expr\FuncCall; } - /** @phpstan-assert-if-true Expr\FuncCall $expr */ - protected function isRefvalCall(Node $expr): bool + protected function isStdRefCall(Node $expr): bool + { + return $this->isStaticCall($expr) + && $this->isStdClassExpr($expr->class) + && $this->isIdExpr($expr->name) + && strtolower($expr->name->toString()) === 'ref'; + } + + protected function isStdClassExpr(Node $expr): bool { - return $this->isFuncCallExpr($expr) and $this->isNameExpr($expr->name) and $expr->name->toString() === 'refval'; + return $this->isNameExpr($expr) + && !$expr instanceof Node\Name\Relative + && strtolower(ltrim($expr->toString(), '\\')) === 'std'; } /** @phpstan-assert-if-true Expr\MethodCall $expr */ diff --git a/src/Parser/FunctionCallTrait.php b/src/Parser/FunctionCallTrait.php index ad1f2f6c..772685a3 100644 --- a/src/Parser/FunctionCallTrait.php +++ b/src/Parser/FunctionCallTrait.php @@ -141,35 +141,6 @@ trait FunctionCallTrait if (in_array($globalName, Constants::UNSUPPORTED_FUNCTIONS, true)) { $this->fatalError($expr, 'Unsupported function: `' . $globalName . '`'); } - if ($name === 'any') { - if (count($expr->args) !== 1 || $expr->args[0]->unpack) { - $this->fatalError($expr, 'The any function expects exactly one non-unpacked argument'); - } - $value = $expr->args[0]->value; - if ($this->isNativeObjectClass($this->detectClassOfExpr($value))) { - $this->fatalError( - $value, - 'Native objects cannot be converted to mixed with any(); use an explicitly typed Native variable', - ); - } - if ($this->isVarExpr($value)) { - $this->assertStdContainerDoesNotEscapeNativeObjects( - $value, - $this->parseIdentifier($value), - ); - } - return $this->parseExprAsValue($value); - } - if ($globalName === 'expected' || $globalName === 'unexpected') { - if (count($expr->args) !== 1 || $expr->args[0]->unpack) { - $this->fatalError($expr, "The {$globalName} function expects exactly one non-unpacked argument"); - } - $condition = $this->parseExprAsValue($expr->args[0]->value); - return 'static_cast(' . strtoupper($globalName) . '((' . $condition . ')))'; - } - if ($name === 'objval') { - return $this->genObjvalCall($expr); - } $nativeFn = $this->findNativeFunction($name); if ($nativeFn) { $expr->setAttribute('nativeCall', $nativeFn); @@ -237,6 +208,33 @@ trait FunctionCallTrait } } + /** + * Erase the static type of a value through std::any(). + */ + protected function parseAnyCompileTimeCall(CallLike $expr): string + { + if (count($expr->args) !== 1 + || !$expr->args[0] instanceof Node\Arg + || $expr->args[0]->unpack + ) { + $this->fatalError($expr, 'The std::any function expects exactly one non-unpacked argument'); + } + $value = $expr->args[0]->value; + if ($this->isNativeObjectClass($this->detectClassOfExpr($value))) { + $this->fatalError( + $value, + 'Native objects cannot be converted to mixed with std::any(); use an explicitly typed Native variable', + ); + } + if ($this->isVarExpr($value)) { + $this->assertStdContainerDoesNotEscapeNativeObjects( + $value, + $this->parseIdentifier($value), + ); + } + return $this->parseExprAsValue($value); + } + private function parseNamedExitMessageCall(string $name, Expr\FuncCall $expr): ?string { if (!in_array(strtolower($name), ['exit', 'die'], true) diff --git a/src/Parser/MethodCallTrait.php b/src/Parser/MethodCallTrait.php index 31aadef2..20ce7951 100644 --- a/src/Parser/MethodCallTrait.php +++ b/src/Parser/MethodCallTrait.php @@ -381,6 +381,22 @@ trait MethodCallTrait protected function parseStdCall(Expr\StaticCall $expr): string { $func = strtolower($this->parseIdentifier($expr->name)); + if ($func === 'any') { + return $this->parseAnyCompileTimeCall($expr); + } + if ($func === 'expected' || $func === 'unexpected') { + if (count($expr->args) !== 1 + || !$expr->args[0] instanceof Node\Arg + || $expr->args[0]->unpack + ) { + $this->fatalError($expr, "The std::{$func} function expects exactly one non-unpacked argument"); + } + $condition = $this->parseExprAsValue($expr->args[0]->value); + return 'static_cast(' . strtoupper($func) . '((' . $condition . ')))'; + } + if ($func === 'ref') { + $this->fatalError($expr, 'The std::ref function may only be used as a call argument reference wrapper'); + } $type = match ($func) { 'int' => Type::INT, 'float' => Type::FLOAT, @@ -1152,7 +1168,7 @@ trait MethodCallTrait if ($class === 'self') { $class = $this->getFullClassName(); $self = true; - } elseif ($class === 'std') { + } elseif ($this->isStdClassExpr($expr->class)) { return $this->parseStdCall($expr); } else { $class = $this->getNamespacedClassName($class); diff --git a/src/Parser/StdContainerTrait.php b/src/Parser/StdContainerTrait.php index 68abab28..aefff434 100644 --- a/src/Parser/StdContainerTrait.php +++ b/src/Parser/StdContainerTrait.php @@ -31,12 +31,12 @@ trait StdContainerTrait if (!$expr instanceof StaticCall || !$this->isNameExpr($expr->class) || !$this->isIdExpr($expr->name) - || $this->parseIdentifier($expr->class) !== 'std' + || !$this->isStdClassExpr($expr->class) ) { return ''; } - $method = $expr->name->toString(); + $method = strtolower($expr->name->toString()); if (!in_array($method, ['array', 'vector', 'map', 'ordered_map'], true)) { return ''; } @@ -46,8 +46,8 @@ trait StdContainerTrait while ($factory instanceof StaticCall && $this->isNameExpr($factory->class) && $this->isIdExpr($factory->name) - && $this->parseIdentifier($factory->class) === 'std' - && $factory->name->toString() === 'array' + && $this->isStdClassExpr($factory->class) + && strtolower($factory->name->toString()) === 'array' ) { if (count($factory->args) !== 2) { return ''; @@ -899,7 +899,7 @@ trait StdContainerTrait } if ($this->isStaticCall($typeExpr)) { $tmp = $typeExpr; - if (!$this->isNameExpr($tmp->class) || !$this->isIdExpr($tmp->name) || $tmp->class->toString() !== 'std' || $tmp->name->toString() !== 'array') { + if (!$this->isStdClassExpr($tmp->class) || !$this->isIdExpr($tmp->name) || strtolower($tmp->name->toString()) !== 'array') { $this->fatalError($tmp, 'An incorrect `std::array` definition'); } } else { diff --git a/src/Translator.php b/src/Translator.php index 2baa9489..26e4f3c6 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -73,7 +73,7 @@ class Translator extends Preprocessor use ResourceCompilationTrait; use ClassConstantValueTrait; - public const string VERSION = '0.7.0'; + public const string VERSION = '0.8.0'; public const string APP_NAME = 'TypePHP Compiler (AOT)'; protected bool $hasExplicitOutput = false; diff --git a/src/TypeSystem/NativeTypeCompatibilityTrait.php b/src/TypeSystem/NativeTypeCompatibilityTrait.php index ece51939..2ad17387 100644 --- a/src/TypeSystem/NativeTypeCompatibilityTrait.php +++ b/src/TypeSystem/NativeTypeCompatibilityTrait.php @@ -239,11 +239,11 @@ trait NativeTypeCompatibilityTrait if ($this->isVarExpr($inner)) { $arg->value = $inner; } else { - $expr = $this->expandRefvalExpr($inner, $arg); + $expr = $this->expandReferenceWrapperExpr($inner, $arg); if ($expr !== null) { return $expr; } - $this->fatalError($arg, 'The refval function only accepts a variable, array element, or object property'); + $this->fatalError($arg, 'The std::ref function only accepts a variable, array element, or object property'); } } else { $this->assertNativeObjectReferenceForbidden($arg->value, $arg); diff --git a/src/gen_stub.php b/src/gen_stub.php index d2d43239..a6acd681 100755 --- a/src/gen_stub.php +++ b/src/gen_stub.php @@ -3975,7 +3975,7 @@ class EnumCaseInfo { "{$id}_{$key}", $allConstInfos, $phpVersionIdMinimumCompatibility, - refval($declaredStrings), + \std::ref($declaredStrings), ); } } @@ -4306,7 +4306,7 @@ class ClassInfo { $code .= $enumCase->getDeclaration( $allConstInfos, $this->phpVersionIdMinimumCompatibility, - refval($declaredStrings), + \std::ref($declaredStrings), ); } @@ -4344,7 +4344,7 @@ class ClassInfo { "class_{$escapedName}_$key", $allConstInfos, $this->phpVersionIdMinimumCompatibility, - refval($declaredStrings) + \std::ref($declaredStrings) ); } @@ -6204,7 +6204,7 @@ function generateFunctionAttributeInitialization(iterable $funcInfos, array $all "func_" . $funcInfo->name->getNameForAttributes() . "_$key", $allConstInfos, $phpVersionIdMinimumCompatibility, - refval($useDeclared) + \std::ref($useDeclared) ); } @@ -6216,7 +6216,7 @@ function generateFunctionAttributeInitialization(iterable $funcInfos, array $all "func_{$funcInfo->name->getNameForAttributes()}_arg{$index}_$key", $allConstInfos, $phpVersionIdMinimumCompatibility, - refval($useDeclared) + \std::ref($useDeclared) ); } } @@ -6272,7 +6272,7 @@ function generateGlobalConstantAttributeInitialization( $constVarName . "_$key", $allConstInfos, PHP_85_VERSION_ID, - refval($useDeclared) + \std::ref($useDeclared) ); } @@ -6320,7 +6320,7 @@ function generateConstantAttributeInitialization( "const_" . $constInfo->name->getDeclarationName() . "_$key", $allConstInfos, $phpVersionIdMinimumCompatibility, - refval($useDeclared) + \std::ref($useDeclared) ); } @@ -6350,7 +6350,7 @@ function generatePropertyAttributeInitialization( "property_" . $propertyInfo->name->getDeclarationName() . "_" . $key, $allConstInfos, $phpVersionIdMinimumCompatibility, - refval($declaredStrings) + \std::ref($declaredStrings) ); } } diff --git a/src/polyfills.php b/src/polyfills.php index 0fe1695b..1687f13e 100644 --- a/src/polyfills.php +++ b/src/polyfills.php @@ -180,6 +180,26 @@ class std return $value; } + public static function any(mixed $value): mixed + { + return $value; + } + + public static function &ref(mixed &$var): mixed + { + return $var; + } + + public static function expected(mixed $condition): bool + { + return (bool) $condition; + } + + public static function unexpected(mixed $condition): bool + { + return (bool) $condition; + } + public static function array(mixed $type, int $size): array { return []; @@ -200,35 +220,3 @@ class std return []; } } - - -function &refval(&$var) -{ - return $var; -} - -function any(mixed $var): mixed -{ - return $var; -} - -function expected(mixed $condition): bool -{ - return (bool) $condition; -} - -function unexpected(mixed $condition): bool -{ - return (bool) $condition; -} - -/** - * @throws Exception - */ -function objval(mixed $var, string $className): mixed -{ - if (!$var instanceof $className) { - throw new \Exception("Invalid object type: " . get_class($var) . " expected " . $className); - } - return $var; -} diff --git a/tests/compiler/basic/any-expression-positions.phpt b/tests/compiler/basic/any-expression-positions.phpt deleted file mode 100644 index d4351838..00000000 --- a/tests/compiler/basic/any-expression-positions.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -any() is available in arbitrary expression positions ---FILE-- - ---EXPECT-- -int(1) -int(2) -array(2) { - [0]=> - int(3) - [1]=> - string(4) "four" -} -int(5) -int(5) -int(7) diff --git a/tests/compiler/basic/bool-001.phpt b/tests/compiler/basic/bool-001.phpt index d5ce9574..5e7a1b43 100644 --- a/tests/compiler/basic/bool-001.phpt +++ b/tests/compiler/basic/bool-001.phpt @@ -5,7 +5,7 @@ bool 001 function main() { $offset = 1; - $maxCount = any(10); + $maxCount = std::any(10); var_dump($maxCount-- > 0 && $offset); } ?> diff --git a/tests/compiler/basic/std-any-expression-positions.phpt b/tests/compiler/basic/std-any-expression-positions.phpt new file mode 100644 index 00000000..805bd3b1 --- /dev/null +++ b/tests/compiler/basic/std-any-expression-positions.phpt @@ -0,0 +1,32 @@ +--TEST-- +std::any() is available in arbitrary expression positions +--FILE-- + +--EXPECT-- +int(1) +int(2) +array(2) { + [0]=> + int(3) + [1]=> + string(4) "four" +} +int(5) +int(5) +int(7) diff --git a/tests/compiler/basic/any.phpt b/tests/compiler/basic/std-any.phpt similarity index 68% rename from tests/compiler/basic/any.phpt rename to tests/compiler/basic/std-any.phpt index 76815d40..68ddc96c 100644 --- a/tests/compiler/basic/any.phpt +++ b/tests/compiler/basic/std-any.phpt @@ -4,8 +4,8 @@ any diff --git a/tests/compiler/basic/objval.phpt b/tests/compiler/basic/to-object.phpt similarity index 94% rename from tests/compiler/basic/objval.phpt rename to tests/compiler/basic/to-object.phpt index 6571dc60..7c58f382 100644 --- a/tests/compiler/basic/objval.phpt +++ b/tests/compiler/basic/to-object.phpt @@ -1,5 +1,5 @@ --TEST-- -objval +toObject keyword method --FILE-- --EXPECT-- int(3) -int(3) \ No newline at end of file +int(3) diff --git a/tests/compiler/bigint/pow.phpt b/tests/compiler/bigint/pow.phpt index ad30fd03..d4869c8b 100644 --- a/tests/compiler/bigint/pow.phpt +++ b/tests/compiler/bigint/pow.phpt @@ -11,7 +11,7 @@ function main() $b = $a->pow(3); Assert::eq($b, 27); - $d = any(5); + $d = std::any(5); $c = $a->pow($d); Assert::eq($c, 243); } diff --git a/tests/compiler/class/interface-declared-object-assign.phpt b/tests/compiler/class/interface-declared-object-assign.phpt index 82dedd88..04c70162 100644 --- a/tests/compiler/class/interface-declared-object-assign.phpt +++ b/tests/compiler/class/interface-declared-object-assign.phpt @@ -40,7 +40,7 @@ function testDeclaredObject(DeclaredObjectContract $object): void var_dump($object->name()); try { - $object = any(new DeclaredObjectOther()); + $object = std::any(new DeclaredObjectOther()); } catch (Throwable $e) { echo $e->getMessage(), "\n"; } diff --git a/tests/compiler/class/objval-parent.phpt b/tests/compiler/class/to-object-parent.phpt similarity index 88% rename from tests/compiler/class/objval-parent.phpt rename to tests/compiler/class/to-object-parent.phpt index f10104a1..b1faaa13 100644 --- a/tests/compiler/class/objval-parent.phpt +++ b/tests/compiler/class/to-object-parent.phpt @@ -1,5 +1,5 @@ --TEST-- -objval with parent::class +toObject keyword method with parent::class --FILE-- toObject(parent::class); } public function toParent($obj): Base { diff --git a/tests/compiler/class/objval-2.phpt b/tests/compiler/class/to-object-self.phpt similarity index 75% rename from tests/compiler/class/objval-2.phpt rename to tests/compiler/class/to-object-self.phpt index 8ff33f23..4aa1b640 100644 --- a/tests/compiler/class/objval-2.phpt +++ b/tests/compiler/class/to-object-self.phpt @@ -1,11 +1,11 @@ --TEST-- -objval +toObject keyword method with self::class --FILE-- toObject(self::class); $o->bar(); } diff --git a/tests/compiler/class/objval.phpt b/tests/compiler/class/to-object.phpt similarity index 89% rename from tests/compiler/class/objval.phpt rename to tests/compiler/class/to-object.phpt index 5d78ed4a..9fcf1f04 100644 --- a/tests/compiler/class/objval.phpt +++ b/tests/compiler/class/to-object.phpt @@ -1,5 +1,5 @@ --TEST-- -objval +toObject keyword method --FILE-- toObject(TestEvent::class); } function main() { diff --git a/tests/compiler/class/typed-object-assign-any.phpt b/tests/compiler/class/typed-object-assign-any.phpt index 2d4d02ea..67af4af1 100644 --- a/tests/compiler/class/typed-object-assign-any.phpt +++ b/tests/compiler/class/typed-object-assign-any.phpt @@ -57,12 +57,12 @@ class AssignAnyOther implements AssignAnyInterface function main(): void { $base = new AssignAnyBase(); - $base = any(new AssignAnyChild()); + $base = std::any(new AssignAnyChild()); var_dump($base->name()); $child = new AssignAnyChild(); try { - $child = any(new AssignAnyBase()); + $child = std::any(new AssignAnyBase()); } catch (Throwable $e) { echo $e->getMessage(), "\n"; } diff --git a/tests/compiler/closure/by-reference-parameters.phpt b/tests/compiler/closure/by-reference-parameters.phpt index a82a0c95..2f20b058 100644 --- a/tests/compiler/closure/by-reference-parameters.phpt +++ b/tests/compiler/closure/by-reference-parameters.phpt @@ -1,5 +1,5 @@ --TEST-- -Dynamic Closures accept positional arguments explicitly marked with refval +Dynamic Closures accept positional arguments explicitly marked with std::ref --FILE-- ++$value; $number = 40; - var_dump($arrow(refval($number)), $number); + var_dump($arrow(std::ref($number)), $number); $typed = static function (int &$value): void { $value++; }; - $typed(refval($number)); + $typed(std::ref($number)); var_dump($number); - $invalid = any('not-an-int'); + $invalid = std::any('not-an-int'); try { - $typed(refval($invalid)); + $typed(std::ref($invalid)); } catch (TypeError $error) { echo "typed reference rejected\n"; } diff --git a/tests/compiler/control_flow/branch-prediction.phpt b/tests/compiler/control_flow/branch-prediction.phpt index b8489474..2f6e6727 100644 --- a/tests/compiler/control_flow/branch-prediction.phpt +++ b/tests/compiler/control_flow/branch-prediction.phpt @@ -13,21 +13,21 @@ function main(): void { $calls = 0; - if (expected(predicted_condition($calls, true))) { + if (std::expected(predicted_condition($calls, true))) { echo "expected\n"; } - if (unexpected(predicted_condition($calls, false))) { + if (std::unexpected(predicted_condition($calls, false))) { echo "unexpected-true\n"; } else { echo "unexpected-false\n"; } - if (\expected(condition: predicted_condition($calls, true))) { + if (\std::expected(condition: predicted_condition($calls, true))) { echo "fully-qualified\n"; } - var_dump(expected(1), unexpected(0), $calls); + var_dump(std::expected(1), std::unexpected(0), $calls); } ?> --EXPECT-- diff --git a/tests/compiler/dynamic_call/call-cache-arguments.phpt b/tests/compiler/dynamic_call/call-cache-arguments.phpt index aa9bcba0..9db6dcb9 100644 --- a/tests/compiler/dynamic_call/call-cache-arguments.phpt +++ b/tests/compiler/dynamic_call/call-cache-arguments.phpt @@ -39,7 +39,7 @@ function main(): void $increment = 'cached_increment'; $value = 10; - var_dump($increment(refval($value))); + var_dump($increment(std::ref($value))); var_dump($value); $throw = 'cached_throw'; diff --git a/tests/compiler/dynamic_call/runtime-fallback-unified.phpt b/tests/compiler/dynamic_call/runtime-fallback-unified.phpt index f66e43f1..300f29ec 100644 --- a/tests/compiler/dynamic_call/runtime-fallback-unified.phpt +++ b/tests/compiler/dynamic_call/runtime-fallback-unified.phpt @@ -65,13 +65,13 @@ function main(): void $callback = [$box, $method]; var_dump($callback('ok')); - $staticClass = any(RuntimeFallbackStatic::class); - $staticProperty = any('value'); + $staticClass = std::any(RuntimeFallbackStatic::class); + $staticProperty = std::any('value'); var_dump($staticClass::$$staticProperty); $staticClass::$$staticProperty = 'after'; var_dump($staticClass::$$staticProperty); - $staticMethod = any('format'); + $staticMethod = std::any('format'); var_dump($staticClass::$staticMethod('ok')); } ?> diff --git a/tests/compiler/functions/compile-time-global-name-collisions.phpt b/tests/compiler/functions/compile-time-global-name-collisions.phpt new file mode 100644 index 00000000..4367e864 --- /dev/null +++ b/tests/compiler/functions/compile-time-global-name-collisions.phpt @@ -0,0 +1,44 @@ +--TEST-- +Former compile-time global names remain available to user functions +--FILE-- + +--EXPECT-- +user-any:value +user-refval:value +user-expected:value +user-unexpected:value +user-objval:value diff --git a/tests/compiler/generator/union-signatures.phpt b/tests/compiler/generator/union-signatures.phpt index d58f7ef8..945f220d 100644 --- a/tests/compiler/generator/union-signatures.phpt +++ b/tests/compiler/generator/union-signatures.phpt @@ -16,7 +16,7 @@ function main(): void var_dump($generator->getReturn()); try { - union_generator(any([])); + union_generator(std::any([])); } catch (Throwable $e) { echo get_class($e), "\n"; } diff --git a/tests/compiler/object_property/int-property-compound-assignment.phpt b/tests/compiler/object_property/int-property-compound-assignment.phpt index 5a84c88a..2631fd9b 100644 --- a/tests/compiler/object_property/int-property-compound-assignment.phpt +++ b/tests/compiler/object_property/int-property-compound-assignment.phpt @@ -70,7 +70,7 @@ function main(): void var_dump($box->value, $receiverCalls, $operandCalls); $box->value = 3; - $numericString = any('4'); + $numericString = std::any('4'); $box->value += $numericString; var_dump($box->value); diff --git a/tests/compiler/object_property/native-int-property-assign-op-var.phpt b/tests/compiler/object_property/native-int-property-assign-op-var.phpt index 9fa205e3..48f858e0 100644 --- a/tests/compiler/object_property/native-int-property-assign-op-var.phpt +++ b/tests/compiler/object_property/native-int-property-assign-op-var.phpt @@ -15,15 +15,15 @@ class NativeIntAssignOpBox function main(): void { $box = new NativeIntAssignOpBox(); - $delta = any(2); + $delta = std::any(2); $box->value += $delta; var_dump($box->value); - $text = any("3"); + $text = std::any("3"); $box->value += $text; var_dump($box->value); - $bad = any("abc"); + $bad = std::any("abc"); try { $box->value += $bad; } catch (TypeError $e) { @@ -31,7 +31,7 @@ function main(): void } $selfBox = new NativeIntAssignOpBox(); - $selfDelta = any(5); + $selfDelta = std::any(5); $selfBox->add($selfDelta); var_dump($selfBox->value); } diff --git a/tests/compiler/object_property/native-int-property-string-var.phpt b/tests/compiler/object_property/native-int-property-string-var.phpt index 169ac5f9..f6b0407a 100644 --- a/tests/compiler/object_property/native-int-property-string-var.phpt +++ b/tests/compiler/object_property/native-int-property-string-var.phpt @@ -11,14 +11,14 @@ function main(): void { $box = new NativeIntStringVarBox(); - $numeric = any("123"); + $numeric = std::any("123"); try { $box->value = $numeric; } catch (TypeError $e) { var_dump($e->getMessage()); } - $bad = any("abc"); + $bad = std::any("abc"); try { $box->value = $bad; } catch (TypeError $e) { diff --git a/tests/compiler/object_property/native-scalar-property-assign-op-var.phpt b/tests/compiler/object_property/native-scalar-property-assign-op-var.phpt index a7e53a71..9051ebed 100644 --- a/tests/compiler/object_property/native-scalar-property-assign-op-var.phpt +++ b/tests/compiler/object_property/native-scalar-property-assign-op-var.phpt @@ -20,23 +20,23 @@ function main(): void { $box = new NativeScalarAssignOpVarBox(); - $intDelta = any(2); + $intDelta = std::any(2); $box->intValue += $intDelta; - $floatDelta = any(2.25); + $floatDelta = std::any(2.25); $box->floatValue += $floatDelta; var_dump($box->intValue); var_dump($box->floatValue); - $methodIntDelta = any(3); - $methodFloatDelta = any(0.25); + $methodIntDelta = std::any(3); + $methodFloatDelta = std::any(0.25); $box->addInside($methodIntDelta, $methodFloatDelta); var_dump($box->intValue); var_dump($box->floatValue); try { - $badIntDelta = any("4"); + $badIntDelta = std::any("4"); $box->intValue += $badIntDelta; } catch (TypeError $e) { var_dump($e->getMessage()); diff --git a/tests/compiler/object_property/native-scalar-property-assign-var.phpt b/tests/compiler/object_property/native-scalar-property-assign-var.phpt index f19a147e..ef4a2a6f 100644 --- a/tests/compiler/object_property/native-scalar-property-assign-var.phpt +++ b/tests/compiler/object_property/native-scalar-property-assign-var.phpt @@ -16,16 +16,16 @@ function main(): void { $box = new NativeScalarAssignVarBox(); - $intValue = any(12); + $intValue = std::any(12); $box->intValue = $intValue; - $floatValue = any(3.5); + $floatValue = std::any(3.5); $box->floatValue = $floatValue; - $boolValue = any(false); + $boolValue = std::any(false); $box->boolValue = $boolValue; - $stringValue = any("123"); + $stringValue = std::any("123"); $box->stringValue = $stringValue; var_dump($box->intValue); @@ -34,7 +34,7 @@ function main(): void var_dump($box->stringValue); try { - $badIntValue = any("12"); + $badIntValue = std::any("12"); $box->intValue = $badIntValue; } catch (TypeError $e) { var_dump($e->getMessage()); diff --git a/tests/compiler/optimizations/objprop-hoist-nested-refval.phpt b/tests/compiler/optimizations/objprop-hoist-nested-std-ref.phpt similarity index 71% rename from tests/compiler/optimizations/objprop-hoist-nested-refval.phpt rename to tests/compiler/optimizations/objprop-hoist-nested-std-ref.phpt index 9136af26..ea3ea55b 100644 --- a/tests/compiler/optimizations/objprop-hoist-nested-refval.phpt +++ b/tests/compiler/optimizations/objprop-hoist-nested-std-ref.phpt @@ -1,5 +1,5 @@ --TEST-- -SSA object prop: nested refval property use prevents hoisting +SSA object prop: nested std::ref property use prevents hoisting --FILE-- a = 10; - $ignored = mutate(refval($o->a)); + $ignored = mutate(std::ref($o->a)); $o->a += 5; var_dump($o->a); diff --git a/tests/compiler/ref/refval1.phpt b/tests/compiler/ref/refval1.phpt deleted file mode 100644 index 1966fd0f..00000000 --- a/tests/compiler/ref/refval1.phpt +++ /dev/null @@ -1,15 +0,0 @@ ---TEST-- -refval ---FILE-- - ---EXPECT-- -php refval test \ No newline at end of file diff --git a/tests/compiler/ref/refval2.phpt b/tests/compiler/ref/refval2.phpt deleted file mode 100644 index 8e86c43c..00000000 --- a/tests/compiler/ref/refval2.phpt +++ /dev/null @@ -1,16 +0,0 @@ ---TEST-- -refval ---FILE-- - ---EXPECT-- -php refval test \ No newline at end of file diff --git a/tests/compiler/ref/static-byref-call.phpt b/tests/compiler/ref/static-byref-call.phpt index a0bfbc74..63c337d9 100644 --- a/tests/compiler/ref/static-byref-call.phpt +++ b/tests/compiler/ref/static-byref-call.phpt @@ -45,10 +45,10 @@ function main(): void mutate_named_arg(...['named-function'], value: $value); $target->mutateNamed(...['named-method'], value: $value); $fn = 'mutate_named_arg'; - $fn(...['dynamic-refval'], value: refval($value)); + $fn(...['dynamic-std-ref'], value: std::ref($value)); echo $value, PHP_EOL; } ?> --EXPECT-- -start:function:ctor:method:ctor:named-function:named-method:dynamic-refval +start:function:ctor:method:ctor:named-function:named-method:dynamic-std-ref diff --git a/tests/compiler/ref/std-any-ref.phpt b/tests/compiler/ref/std-any-ref.phpt new file mode 100644 index 00000000..f712e2fd --- /dev/null +++ b/tests/compiler/ref/std-any-ref.phpt @@ -0,0 +1,37 @@ +--TEST-- +std::any() and std::ref() compile-time functions +--FILE-- + 'array']; + $replace(std::ref($values['item'])); + echo $values['item'], "\n"; + + $box = new RefBox(); + $replace(\std::ref($box->value)); + echo $box->value, "\n"; +} +?> +--EXPECT-- +float(2.5) +changed +changed +changed diff --git a/tests/compiler/ref/refval-array.phpt b/tests/compiler/ref/std-ref-array.phpt similarity index 72% rename from tests/compiler/ref/refval-array.phpt rename to tests/compiler/ref/std-ref-array.phpt index ed4721f9..c2c34c39 100644 --- a/tests/compiler/ref/refval-array.phpt +++ b/tests/compiler/ref/std-ref-array.phpt @@ -1,5 +1,5 @@ --TEST-- -refval with array element +std::ref with an array element --FILE-- 'original']; - array_ref_test(refval($arr['key'])); + array_ref_test(std::ref($arr['key'])); echo $arr['key']; } ?> diff --git a/tests/compiler/ref/refval-object.phpt b/tests/compiler/ref/std-ref-object.phpt similarity index 74% rename from tests/compiler/ref/refval-object.phpt rename to tests/compiler/ref/std-ref-object.phpt index ef90633c..d35c558a 100644 --- a/tests/compiler/ref/refval-object.phpt +++ b/tests/compiler/ref/std-ref-object.phpt @@ -1,5 +1,5 @@ --TEST-- -refval with object property +std::ref with an object property --FILE-- prop = 'original'; - prop_ref_test(refval($obj->prop)); + prop_ref_test(std::ref($obj->prop)); echo $obj->prop; } ?> diff --git a/tests/compiler/ref/std-ref-static-method.phpt b/tests/compiler/ref/std-ref-static-method.phpt new file mode 100644 index 00000000..bf14a556 --- /dev/null +++ b/tests/compiler/ref/std-ref-static-method.phpt @@ -0,0 +1,16 @@ +--TEST-- +std::ref with a statically resolved function call +--FILE-- + +--EXPECT-- +php std::ref test diff --git a/tests/compiler/ref/std-ref-variable.phpt b/tests/compiler/ref/std-ref-variable.phpt new file mode 100644 index 00000000..7a69792a --- /dev/null +++ b/tests/compiler/ref/std-ref-variable.phpt @@ -0,0 +1,15 @@ +--TEST-- +std::ref with a variable +--FILE-- + +--EXPECT-- +php std::ref test diff --git a/tests/compiler/std-array/case-insensitive-class-and-method.phpt b/tests/compiler/std-array/case-insensitive-class-and-method.phpt new file mode 100644 index 00000000..32e60cbf --- /dev/null +++ b/tests/compiler/std-array/case-insensitive-class-and-method.phpt @@ -0,0 +1,18 @@ +--TEST-- +std class and method names, and the Type class name, are case-insensitive +--FILE-- + +--EXPECT-- +int(10) +int(20) +bool(true) diff --git a/tests/compiler/stdlib/abs_edge.phpt b/tests/compiler/stdlib/abs_edge.phpt index 326ae42a..ca51ed37 100644 --- a/tests/compiler/stdlib/abs_edge.phpt +++ b/tests/compiler/stdlib/abs_edge.phpt @@ -2,7 +2,7 @@ abs edge cases: PHP_INT_MIN and -0.0 --FILE-- arr1, $v, $o->arr2); var_dump($array); } diff --git a/tests/compiler/type_decl/011.phpt b/tests/compiler/type_decl/011.phpt index 03cb8d02..83d15026 100644 --- a/tests/compiler/type_decl/011.phpt +++ b/tests/compiler/type_decl/011.phpt @@ -17,9 +17,9 @@ class Foo2 { function main() { $rand = random_int(0, 10000); if ($rand % 2) { - $o = any(new Foo1()); + $o = std::any(new Foo1()); } else { - $o = any(new Foo2()); + $o = std::any(new Foo2()); } if (method_exists($o, 'run')) { $o->run(); diff --git a/tests/compiler/type_decl/composite-static-edge-cases.phpt b/tests/compiler/type_decl/composite-static-edge-cases.phpt index 723c1a3f..49742a30 100644 --- a/tests/compiler/type_decl/composite-static-edge-cases.phpt +++ b/tests/compiler/type_decl/composite-static-edge-cases.phpt @@ -27,7 +27,7 @@ class CompositeEdgeBox function main(): void { var_dump(float_or_string(1)); - var_dump(float_or_string(any(2))); + var_dump(float_or_string(std::any(2))); var_dump(variadic_float_or_string(3, "ok")); $closure = fn (float|string $value): float|string => $value; var_dump($closure(5)); diff --git a/tests/compiler/type_decl/dnf-types.phpt b/tests/compiler/type_decl/dnf-types.phpt index 76ef7d49..02b2443a 100644 --- a/tests/compiler/type_decl/dnf-types.phpt +++ b/tests/compiler/type_decl/dnf-types.phpt @@ -89,7 +89,7 @@ function main(): void $box->value = $both; var_dump($box->value instanceof DnfBoth); - $dynamicBox = any(new DnfBox($fallback)); + $dynamicBox = std::any(new DnfBox($fallback)); dnf_dynamic_property_write($dynamicBox, $both); var_dump($dynamicBox->value instanceof DnfBoth); echo (new ReflectionProperty(DnfBox::class, 'value'))->getType(), "\n"; @@ -106,7 +106,7 @@ function main(): void return $value; }; - $invalid = any(new DnfOnlyLeft()); + $invalid = std::any(new DnfOnlyLeft()); try { dnf_label($invalid); } catch (TypeError $error) { diff --git a/tests/compiler/type_decl/intersection-param-check.phpt b/tests/compiler/type_decl/intersection-param-check.phpt index 343330f8..eefac15c 100644 --- a/tests/compiler/type_decl/intersection-param-check.phpt +++ b/tests/compiler/type_decl/intersection-param-check.phpt @@ -19,7 +19,7 @@ function main() { $errors = []; try { - expect_both(any(new OnlyA())); + expect_both(std::any(new OnlyA())); } catch (\TypeError $e) { $errors[] = $e->getMessage(); } diff --git a/tests/compiler/type_decl/union-param-check.phpt b/tests/compiler/type_decl/union-param-check.phpt index 348242b4..0ef4a5f3 100644 --- a/tests/compiler/type_decl/union-param-check.phpt +++ b/tests/compiler/type_decl/union-param-check.phpt @@ -47,25 +47,25 @@ function main() { $errors = []; try { - expect_int_or_string(any(3.14)); + expect_int_or_string(std::any(3.14)); } catch (\TypeError $e) { $errors[] = $e->getMessage(); } try { - expect_int_or_string(any([])); + expect_int_or_string(std::any([])); } catch (\TypeError $e) { $errors[] = $e->getMessage(); } try { - expect_nullable_int(any("hello")); + expect_nullable_int(std::any("hello")); } catch (\TypeError $e) { $errors[] = $e->getMessage(); } try { - expect_bool_or_array(any(42)); + expect_bool_or_array(std::any(42)); } catch (\TypeError $e) { $errors[] = $e->getMessage(); } diff --git a/tests/compiler/type_decl/variadic-union-param-check.phpt b/tests/compiler/type_decl/variadic-union-param-check.phpt index 44b1ebb4..03742d28 100644 --- a/tests/compiler/type_decl/variadic-union-param-check.phpt +++ b/tests/compiler/type_decl/variadic-union-param-check.phpt @@ -19,12 +19,12 @@ function main(): void $errors = []; try { - collect_scalars(1, "two", any([])); + collect_scalars(1, "two", std::any([])); } catch (\TypeError $e) { $errors[] = $e->getMessage(); } try { - collect_nullable(ok: 1, bad: any("x")); + collect_nullable(ok: 1, bad: std::any("x")); } catch (\TypeError $e) { $errors[] = $e->getMessage(); } diff --git a/tests/compiler/type_hits/009.phpt b/tests/compiler/type_hits/009.phpt index 24b38f30..0ed273dc 100644 --- a/tests/compiler/type_hits/009.phpt +++ b/tests/compiler/type_hits/009.phpt @@ -10,7 +10,7 @@ class TypeHitPropertyMessage public function setInvalid(): void { try { - $this->union = any(null); + $this->union = std::any(null); } catch (TypeError $e) { var_dump($e->getMessage()); } diff --git a/tests/compiler/type_hits/010.phpt b/tests/compiler/type_hits/010.phpt index ca2cd360..cacea2df 100644 --- a/tests/compiler/type_hits/010.phpt +++ b/tests/compiler/type_hits/010.phpt @@ -12,13 +12,13 @@ class TypeHitCoalesceProperty public function run(): void { try { - $this->union ??= any(null); + $this->union ??= std::any(null); } catch (TypeError $e) { var_dump($e->getMessage()); } $this->union = "ok"; - $this->union ??= any(null); + $this->union ??= std::any(null); var_dump($this->union); } } diff --git a/tests/compiler/variadic/by-reference-dynamic-explicit.phpt b/tests/compiler/variadic/by-reference-dynamic-explicit.phpt index fcad5efb..3413de9a 100644 --- a/tests/compiler/variadic/by-reference-dynamic-explicit.phpt +++ b/tests/compiler/variadic/by-reference-dynamic-explicit.phpt @@ -1,5 +1,5 @@ --TEST-- -Dynamic calls require explicit refval for by-reference arguments +Dynamic calls require explicit std::ref for by-reference arguments --FILE-- diff --git a/tests/compiler/variadic/by-reference-types.phpt b/tests/compiler/variadic/by-reference-types.phpt index d3ed129d..a1e65e3c 100644 --- a/tests/compiler/variadic/by-reference-types.phpt +++ b/tests/compiler/variadic/by-reference-types.phpt @@ -59,7 +59,7 @@ function main(): void bump_objects($first, $second); var_dump($first->value, $second->value); - $invalid = any('not-an-int'); + $invalid = std::any('not-an-int'); try { require_ints($invalid); } catch (TypeError $error) { diff --git a/tests/zend/closures/closure_002.phpt b/tests/zend/closures/closure_002.phpt index 8cc1d681..e26e822d 100644 --- a/tests/zend/closures/closure_002.phpt +++ b/tests/zend/closures/closure_002.phpt @@ -3,7 +3,7 @@ Closure 002: Lambda with lexical variables (global scope) --FILE--