refactor(compiler): migrate global compile-time functions to std namespace

- Replace any() with std::any() in test files and compiler logic
- Replace refval() with std::ref() for reference wrapping operations
- Replace expected() with std::expected() for branch prediction hints
- Replace unexpected() with std::unexpected() for branch prediction hints
- Remove deprecated objval() function and use toObject() keyword method
- Update parser to recognize std:: class expressions with case insensitivity
- Add comprehensive changelog documenting breaking API changes
- Update documentation to reflect new std:: namespace conventions
- Add tests for case-insensitive std class and method names
- Verify global function names remain available for user-defined functions
master
韩天峰 20 hours ago
parent 681c7d42e7
commit b184e47e5e
  1. 80
      CHANGELOG.md
  2. 7
      README-CN.md
  3. 7
      README.md
  4. 48
      docs/en/COMPILE_TIME_FUNCTIONS.md
  5. 8
      docs/en/INCOMPATIBLE_PHP_FEATURES.md
  6. 2
      docs/en/NATIVE_CLASS_OBJECT.md
  7. 56
      docs/en/NATIVE_TYPES.md
  8. 4
      docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md
  9. 2
      docs/en/README.md
  10. 10
      docs/en/REFACTORING_PLAN.md
  11. 46
      docs/zh-cn/COMPILE_TIME_FUNCTIONS.md
  12. 8
      docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md
  13. 2
      docs/zh-cn/NATIVE_CLASS_OBJECT.md
  14. 56
      docs/zh-cn/NATIVE_TYPES.md
  15. 2
      docs/zh-cn/README.md
  16. 10
      docs/zh-cn/REFACTORING_PLAN.md
  17. 2
      examples/debug/prop.php
  18. 2
      examples/debug_trace.php
  19. 2
      examples/obj.php
  20. 2
      phpunit/code/base-class-method.php
  21. 4
      phpunit/code/branch-prediction.php
  22. 2
      phpunit/code/native-class-any-escape.php
  23. 2
      phpunit/code/native-class-reference-function.php
  24. 26
      phpunit/src/Analysis/SsaBuilderTest.php
  25. 25
      phpunit/src/AstNodeTypeTest.php
  26. 2
      phpunit/src/FuncCallOptimizerTest.php
  27. 2
      phpunit/src/NativeClass/NativeClassValidationTest.php
  28. 3
      phpunit/src/Python/PythonModuleTest.php
  29. 22
      phpunit/src/SsaAnalysisTest.php
  30. 6
      project.yml
  31. 12
      src/Analysis/SsaBuilder.php
  32. 19
      src/CompilerBase.php
  33. 16
      src/Generator/CallArgumentGenerator.php
  34. 8
      src/Optimizer/LoopVarOptimizer.php
  35. 4
      src/Optimizer/SsaPropOptimizer.php
  36. 15
      src/Parser/AssignOpTrait.php
  37. 15
      src/Parser/AstNodeType.php
  38. 56
      src/Parser/FunctionCallTrait.php
  39. 18
      src/Parser/MethodCallTrait.php
  40. 10
      src/Parser/StdContainerTrait.php
  41. 2
      src/Translator.php
  42. 4
      src/TypeSystem/NativeTypeCompatibilityTrait.php
  43. 16
      src/gen_stub.php
  44. 52
      src/polyfills.php
  45. 32
      tests/compiler/basic/any-expression-positions.phpt
  46. 2
      tests/compiler/basic/bool-001.phpt
  47. 32
      tests/compiler/basic/std-any-expression-positions.phpt
  48. 4
      tests/compiler/basic/std-any.phpt
  49. 2
      tests/compiler/basic/to-object.phpt
  50. 2
      tests/compiler/bigint/pow.phpt
  51. 2
      tests/compiler/class/interface-declared-object-assign.phpt
  52. 4
      tests/compiler/class/to-object-parent.phpt
  53. 4
      tests/compiler/class/to-object-self.phpt
  54. 4
      tests/compiler/class/to-object.phpt
  55. 4
      tests/compiler/class/typed-object-assign-any.phpt
  56. 12
      tests/compiler/closure/by-reference-parameters.phpt
  57. 8
      tests/compiler/control_flow/branch-prediction.phpt
  58. 2
      tests/compiler/dynamic_call/call-cache-arguments.phpt
  59. 6
      tests/compiler/dynamic_call/runtime-fallback-unified.phpt
  60. 44
      tests/compiler/functions/compile-time-global-name-collisions.phpt
  61. 2
      tests/compiler/generator/union-signatures.phpt
  62. 2
      tests/compiler/object_property/int-property-compound-assignment.phpt
  63. 8
      tests/compiler/object_property/native-int-property-assign-op-var.phpt
  64. 4
      tests/compiler/object_property/native-int-property-string-var.phpt
  65. 10
      tests/compiler/object_property/native-scalar-property-assign-op-var.phpt
  66. 10
      tests/compiler/object_property/native-scalar-property-assign-var.phpt
  67. 4
      tests/compiler/optimizations/objprop-hoist-nested-std-ref.phpt
  68. 15
      tests/compiler/ref/refval1.phpt
  69. 16
      tests/compiler/ref/refval2.phpt
  70. 4
      tests/compiler/ref/static-byref-call.phpt
  71. 37
      tests/compiler/ref/std-any-ref.phpt
  72. 4
      tests/compiler/ref/std-ref-array.phpt
  73. 4
      tests/compiler/ref/std-ref-object.phpt
  74. 16
      tests/compiler/ref/std-ref-static-method.phpt
  75. 15
      tests/compiler/ref/std-ref-variable.phpt
  76. 18
      tests/compiler/std-array/case-insensitive-class-and-method.phpt
  77. 2
      tests/compiler/stdlib/abs_edge.phpt
  78. 2
      tests/compiler/stdlib/array_merge.phpt
  79. 4
      tests/compiler/type_decl/011.phpt
  80. 2
      tests/compiler/type_decl/composite-static-edge-cases.phpt
  81. 4
      tests/compiler/type_decl/dnf-types.phpt
  82. 2
      tests/compiler/type_decl/intersection-param-check.phpt
  83. 8
      tests/compiler/type_decl/union-param-check.phpt
  84. 4
      tests/compiler/type_decl/variadic-union-param-check.phpt
  85. 2
      tests/compiler/type_hits/009.phpt
  86. 4
      tests/compiler/type_hits/010.phpt
  87. 8
      tests/compiler/variadic/by-reference-dynamic-explicit.phpt
  88. 2
      tests/compiler/variadic/by-reference-types.phpt
  89. 2
      tests/zend/closures/closure_002.phpt

@ -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 专有源码接口、生成代码接口、
配置和打包方式都可能在次版本中发生变化。破坏性变更会记录在本文件中;升级前请阅读
变更记录,并运行项目的完整测试。

@ -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) —— 强类型数组属性契约

@ -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

@ -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.

@ -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.

@ -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
```

@ -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
<?php
// objval takes two arguments:
// 1. The object variable (must be a PHP variable expression)
// 2. The class name (must be a literal string)
$obj = objval($array['object'], 'ClassName');
// The receiver is the value to check. The argument is a compile-time class name.
$obj = $array['object']->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
<?php
// ✅ Correct: literal class name
$obj = objval($value, 'MyClass');
$obj = $value->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
<?php
// ✅ Correct: variable expression
$obj = objval($array['key'], 'MyClass');
$obj = objval($object->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)` |
---

@ -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. |

@ -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).

@ -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.

@ -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()` 在不同表达式路径上的行为。
- 继续统一引用包装器在不同表达式路径上的行为。

@ -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` 编码。

@ -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
```

@ -52,21 +52,18 @@ $user->profile = null; // ✅ 对象属性可显式设置为 null
---
## 🎯 objval 编译期函数
## 🎯 `toObject()` 关键词方法
### 使用场景
当从数组、函数返回值等来源获取对象时,变量会丢失类型上下文信息。此时需要使用 `objval()` 显式声明对象的类
当从数组、函数返回值等来源获取对象时,变量会丢失类型上下文信息。此时使用 `toObject()` 关键词方法断言对象类型
### 基本语法
```php
<?php
// objval 接收两个参数:
// 1. 对象变量(必须是 PHP variable 表达式)
// 2. 类名(必须是字面量字符串)
$obj = objval($array['object'], 'ClassName');
// 接收者是待检查值,参数是编译期可确定的类名
$obj = $array['object']->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
<?php
// ✅ 正确:字面量类名
$obj = objval($value, 'MyClass');
$obj = $value->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
<?php
// ✅ 正确:variable 表达式
$obj = objval($array['key'], 'MyClass');
$obj = objval($object->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)` |
---

@ -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)。

@ -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 路径。

@ -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);

@ -13,7 +13,7 @@ class Obj
{
function run()
{
$a = any(199);
$a = std::any(199);
var_dump($a[9]);
}

@ -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());
}

@ -24,6 +24,6 @@ function bar(FooBase $o)
function main() {
$o = new FooChild();
$o2 = any($o);
$o2 = std::any($o);
bar($o2);
}

@ -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;

@ -8,5 +8,5 @@ class NativeAnyEscape
function main(): void
{
$value = new NativeAnyEscape();
$mixed = any($value);
$mixed = std::any($value);
}

@ -8,5 +8,5 @@ function acceptsReference(&$value): void {}
function invalidNativeReferenceFunction(): void
{
$value = new NativeReferenceFunctionValue();
acceptsReference(refval($value));
acceptsReference(std::ref($value));
}

@ -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');
}
// ========================================================================

@ -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',

@ -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);

@ -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');
}

@ -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);
}

@ -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

@ -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"

@ -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)) {

@ -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);

@ -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);

@ -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;

@ -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);

@ -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);
}
if ($finalVarType !== Type::VAR) {
$this->context->explicitNativeTypeVars[$var] = true;
}
return $var . ' = ' . $valueExpr;
}
}

@ -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 */

@ -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<bool>(' . 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)

@ -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<bool>(' . 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);

@ -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 {

@ -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;

@ -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);

@ -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)
);
}
}

@ -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;
}

@ -1,32 +0,0 @@
--TEST--
any() is available in arbitrary expression positions
--FILE--
<?php
function return_any(int $value): mixed
{
return any($value);
}
function main(): void
{
var_dump(any(1));
var_dump(return_any(2));
var_dump([any(3), any("four")]);
var_dump(any(4) + 1);
var_dump(true ? any(5) : any(6));
var_dump(any(any(7)));
}
?>
--EXPECT--
int(1)
int(2)
array(2) {
[0]=>
int(3)
[1]=>
string(4) "four"
}
int(5)
int(5)
int(7)

@ -5,7 +5,7 @@ bool 001
function main()
{
$offset = 1;
$maxCount = any(10);
$maxCount = std::any(10);
var_dump($maxCount-- > 0 && $offset);
}
?>

@ -0,0 +1,32 @@
--TEST--
std::any() is available in arbitrary expression positions
--FILE--
<?php
function return_any(int $value): mixed
{
return std::any($value);
}
function main(): void
{
var_dump(std::any(1));
var_dump(return_any(2));
var_dump([std::any(3), std::any("four")]);
var_dump(std::any(4) + 1);
var_dump(true ? std::any(5) : std::any(6));
var_dump(std::any(std::any(7)));
}
?>
--EXPECT--
int(1)
int(2)
array(2) {
[0]=>
int(3)
[1]=>
string(4) "four"
}
int(5)
int(5)
int(7)

@ -4,8 +4,8 @@ any
<?php
function main()
{
$a = any(10);
$b = any(4);
$a = std::any(10);
$b = std::any(4);
echo var_dump($a/$b);
}
?>

@ -1,5 +1,5 @@
--TEST--
objval
toObject keyword method
--FILE--
<?php

@ -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);
}

@ -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";
}

@ -1,5 +1,5 @@
--TEST--
objval with parent::class
toObject keyword method with parent::class
--FILE--
<?php
@ -15,7 +15,7 @@ class Child extends Base {
}
public function castToParent($obj): Base {
return objval($obj, parent::class);
return $obj->toObject(parent::class);
}
public function toParent($obj): Base {

@ -1,11 +1,11 @@
--TEST--
objval
toObject keyword method with self::class
--FILE--
<?php
class Foo {
public function run($obj) {
$o = objval($obj, self::class);
$o = $obj->toObject(self::class);
$o->bar();
}

@ -1,5 +1,5 @@
--TEST--
objval
toObject keyword method
--FILE--
<?php
@ -23,7 +23,7 @@ class TestEvent
function wrapObjval($ev): TestEvent
{
return objval($ev, TestEvent::class);
return $ev->toObject(TestEvent::class);
}
function main() {

@ -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";
}

@ -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--
<?php
@ -9,7 +9,7 @@ function main(): void
$value .= '!';
};
$text = 'fixed';
$fixed(refval($text));
$fixed(std::ref($text));
var_dump($text);
$optional = static function (&$value = null): void {
@ -20,17 +20,17 @@ function main(): void
$arrow = static fn (&$value): int => ++$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";
}

@ -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--

@ -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';

@ -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'));
}
?>

@ -0,0 +1,44 @@
--TEST--
Former compile-time global names remain available to user functions
--FILE--
<?php
function any(string $value): string
{
return 'user-any:' . $value;
}
function refval(string $value): string
{
return 'user-refval:' . $value;
}
function expected(string $value): string
{
return 'user-expected:' . $value;
}
function unexpected(string $value): string
{
return 'user-unexpected:' . $value;
}
function objval(string $value): string
{
return 'user-objval:' . $value;
}
function main(): void
{
echo any('value'), "\n";
echo refval('value'), "\n";
echo expected('value'), "\n";
echo unexpected('value'), "\n";
echo objval('value'), "\n";
}
?>
--EXPECT--
user-any:value
user-refval:value
user-expected:value
user-unexpected:value
user-objval:value

@ -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";
}

@ -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);

@ -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);
}

@ -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) {

@ -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());

@ -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());

@ -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--
<?php
use native_types;
@ -17,7 +17,7 @@ function main(): void {
$o = new Foo();
$o->a = 10;
$ignored = mutate(refval($o->a));
$ignored = mutate(std::ref($o->a));
$o->a += 5;
var_dump($o->a);

@ -1,15 +0,0 @@
--TEST--
refval
--FILE--
<?php
function main()
{
eval('function retval_test(&$name) { $name .= "refval test"; }');
$name = 'php ';
retval_test(refval($name));
echo $name;
}
?>
--EXPECT--
php refval test

@ -1,16 +0,0 @@
--TEST--
refval
--FILE--
<?php
function retval_test(&$name) {
$name .= "refval test";
}
function main()
{
$name = 'php ';
retval_test(refval($name));
echo $name;
}
?>
--EXPECT--
php refval test

@ -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

@ -0,0 +1,37 @@
--TEST--
std::any() and std::ref() compile-time functions
--FILE--
<?php
class RefBox
{
public string $value = 'object';
}
function main(): void
{
$native = std::int(10);
$mixed = \STD::AnY($native);
var_dump($mixed / 4);
$replace = static function (mixed &$value): void {
$value = 'changed';
};
$value = 'variable';
$replace(StD::ReF($value));
echo $value, "\n";
$values = ['item' => '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

@ -1,5 +1,5 @@
--TEST--
refval with array element
std::ref with an array element
--FILE--
<?php
function main()
@ -7,7 +7,7 @@ function main()
eval('function array_ref_test(&$val) { $val = "modified"; }');
$arr = ['key' => 'original'];
array_ref_test(refval($arr['key']));
array_ref_test(std::ref($arr['key']));
echo $arr['key'];
}
?>

@ -1,5 +1,5 @@
--TEST--
refval with object property
std::ref with an object property
--FILE--
<?php
function main()
@ -8,7 +8,7 @@ function main()
$obj = new stdClass();
$obj->prop = 'original';
prop_ref_test(refval($obj->prop));
prop_ref_test(std::ref($obj->prop));
echo $obj->prop;
}
?>

@ -0,0 +1,16 @@
--TEST--
std::ref with a statically resolved function call
--FILE--
<?php
function reference_test(&$name) {
$name .= "std::ref test";
}
function main()
{
$name = 'php ';
reference_test(std::ref($name));
echo $name;
}
?>
--EXPECT--
php std::ref test

@ -0,0 +1,15 @@
--TEST--
std::ref with a variable
--FILE--
<?php
function main()
{
eval('function reference_test(&$name) { $name .= "std::ref test"; }');
$name = 'php ';
reference_test(std::ref($name));
echo $name;
}
?>
--EXPECT--
php std::ref test

@ -0,0 +1,18 @@
--TEST--
std class and method names, and the Type class name, are case-insensitive
--FILE--
<?php
function main(): void
{
$values = StD::ArRaY(tYpE::Int, 2);
$values[0] = 10;
$values[1] = 20;
var_dump($values[0], $values[1]);
var_dump(\STD::BoOl(1));
}
?>
--EXPECT--
int(10)
int(20)
bool(true)

@ -2,7 +2,7 @@
abs edge cases: PHP_INT_MIN and -0.0
--FILE--
<?php
$value = any(PHP_INT_MIN);
$value = std::any(PHP_INT_MIN);
var_dump(abs($value));
var_dump(abs(-0.0));
var_dump(abs(0));

@ -8,7 +8,7 @@ class TestArrayMerge {
}
function main() {
$o = new TestArrayMerge;
$v = any(['bar']);
$v = std::any(['bar']);
$array = array_merge($o->arr1, $v, $o->arr2);
var_dump($array);
}

@ -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();

@ -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));

@ -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) {

@ -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();
}

@ -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();
}

@ -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();
}

@ -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());
}

@ -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);
}
}

@ -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--
<?php
@ -26,20 +26,20 @@ function main(): void
{
$function = 'dynamic_increment';
$number = 40;
$function(refval($number));
$function(std::ref($number));
var_dump($number);
$mutator = new DynamicReferenceMutator();
$method = [$mutator, 'suffix'];
$first = 'one';
$second = 'two';
$method('!', refval($first), refval($second));
$method('!', std::ref($first), std::ref($second));
var_dump($first, $second);
$closure = static function (&$value): void {
$value .= '?';
};
$closure(refval($second));
$closure(std::ref($second));
var_dump($second);
}
?>

@ -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) {

@ -3,7 +3,7 @@ Closure 002: Lambda with lexical variables (global scope)
--FILE--
<?php
$x = any(4);
$x = std::any(4);
$lambda1 = function () use ($x) {
echo "$x\n";

Loading…
Cancel
Save