Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
ccd1e83
fix(security): bound every write into the 64 KiB concat scratch
nahime0 Aug 4, 2026
c3ebdb6
fix(security): rebuild sprintf's formatter without unbounded stack wr…
nahime0 Aug 4, 2026
068b418
fix(security): reject allocation sizes that overflow a machine word
nahime0 Aug 4, 2026
ec2d5bb
fix(security): make composite symbols and internal labels injective
nahime0 Aug 4, 2026
7ce47fa
fix(security): escape assembler operands and harden ELF output
nahime0 Aug 4, 2026
0a9d03d
fix: render objects and enum cases in var_dump, print_r and var_export
nahime0 Aug 4, 2026
2321f1d
fix: match PHP for division by zero, shifts and float-to-int conversion
nahime0 Aug 4, 2026
7fffb62
fix: accept singletons, isset on undefined vars, callback element typ…
nahime0 Aug 4, 2026
5d48467
fix: make constant folding match PHP semantics
nahime0 Aug 4, 2026
f26400c
docs: record the 2026-08-04 security and PHP-parity fixes in the chan…
nahime0 Aug 4, 2026
b561f87
fix: implement loose equality for objects and arrays, and unset on a …
nahime0 Aug 4, 2026
0eeaed3
fix: match PHP's float printing, numeric strings, fmod and int expone…
nahime0 Aug 4, 2026
d316baa
fix: raise PHP's catchable ValueError instead of corrupting memory or…
nahime0 Aug 4, 2026
c0c62c1
fix: complete the integer-exponentiation lowering
nahime0 Aug 4, 2026
5b24335
fix(security): report a controlled fatal on stack exhaustion instead …
nahime0 Aug 4, 2026
6d13e02
fix(security): stop array_slice and array_splice trusting a negative …
nahime0 Aug 4, 2026
02c9c57
fix: continue generator key numbering, and reject unpacking after nam…
nahime0 Aug 4, 2026
d6925a9
fix(security): reject array_pad lengths whose magnitude is not repres…
nahime0 Aug 4, 2026
ef820dd
fix: complete the array_pad length guard
nahime0 Aug 4, 2026
3a1fac6
fix: accept <>, $this->n++, foreach destructuring and single-array mi…
nahime0 Aug 4, 2026
682e996
docs: describe generator key numbering rules
nahime0 Aug 4, 2026
bb8a82e
docs: state the string-increment limitation accurately
nahime0 Aug 4, 2026
89d9b05
feat: support PHP's alternative control-flow syntax
nahime0 Aug 4, 2026
7cc97fe
feat: add join() and substr_count(), and let min()/max() report an em…
nahime0 Aug 4, 2026
ec6e5da
feat: define the PHP_ROUND_HALF_* and COUNT_RECURSIVE constants
nahime0 Aug 4, 2026
52b3039
docs: record the remaining 2026-08-04 fixes in the changelog
nahime0 Aug 4, 2026
854766c
feat: add string builtins, base conversion, and min()/max() over any …
nahime0 Aug 4, 2026
734abc2
test: update the count() arity expectation for its new $mode argument
nahime0 Aug 4, 2026
3936d6e
fix: restore eval/AOT builtin parity for array_count_values, constant…
nahime0 Aug 4, 2026
bcb6adb
feat: increment and decrement strings the way PHP does
nahime0 Aug 4, 2026
d76a9bf
test: re-enable the eval parity gate for array_count_values and constant
nahime0 Aug 4, 2026
f572d8b
docs: record string increment and the new builtins in the changelog
nahime0 Aug 4, 2026
09aacf5
fix: stop dynamic new from silently dropping spread arguments
nahime0 Aug 4, 2026
8cabebb
feat: add the array internal pointer family and three more string bui…
nahime0 Aug 4, 2026
45d94d2
docs: record the array pointer family and dynamic-new spread fix
nahime0 Aug 4, 2026
fd27945
feat: support func_num_args, func_get_args and func_get_arg
nahime0 Aug 4, 2026
450dbae
feat: add strtr(), count_chars() and str_word_count()
nahime0 Aug 4, 2026
de5d4a8
docs: record func_get_args and the final builtins in the changelog
nahime0 Aug 4, 2026
bbfd761
fix: sort string arrays with usort and its callback family
nahime0 Aug 4, 2026
6fa3040
fix: unset() dynamic object properties, and stop two unsound property…
nahime0 Aug 4, 2026
a907fc4
fix: pass the caller's storage to array builtins that mutate by refer…
nahime0 Aug 4, 2026
8530201
docs: record the by-ref receiver and dynamic-property fixes in the ch…
nahime0 Aug 4, 2026
058e00f
fix: accept a named argument for by-reference array-builtin receivers
nahime0 Aug 5, 2026
b9db43e
feat: accept callable strings and PHP's scalar parameter coercion
nahime0 Aug 5, 2026
873c172
feat: widen builtin signatures to PHP's real parameter lists
nahime0 Aug 5, 2026
d272416
docs: record parameter coercion and the widened signatures in the cha…
nahime0 Aug 5, 2026
7fe72ad
fix: decode base64 the way PHP does, and add stripos, strripos and qu…
nahime0 Aug 5, 2026
5789c93
feat: add the key-preserving array slice and chunk forms, and file() …
nahime0 Aug 5, 2026
e910e84
docs: record the base64 fix and the last widened signatures in the ch…
nahime0 Aug 5, 2026
4927a1c
fix: store a nullable-int property default in its own slot shape
nahime0 Aug 5, 2026
0bb1594
fix: sort associative arrays by key and by value
nahime0 Aug 5, 2026
a6a9664
fix: re-derive an array_slice result layout EIR can actually build
nahime0 Aug 5, 2026
20bfde1
fix: infer a closure's array-literal return type from its signature
nahime0 Aug 5, 2026
4a8c9eb
feat: make declare(strict_types=1) take effect
nahime0 Aug 5, 2026
bad051e
fix: generators from methods, keyed yield from, and the foreach objec…
nahime0 Aug 5, 2026
c235f32
fix(builtins): raise PHP's ValueError for oversized array_fill/range
nahime0 Aug 5, 2026
ec85760
feat(builtins): array_splice() $replacement, file_get_contents() $off…
nahime0 Aug 5, 2026
7e3286f
fix(arrays): string-array splice corruption, splice type promotion, b…
nahime0 Aug 5, 2026
caa0c24
docs: regenerate builtin pages after the semantics.rs enum addition
nahime0 Aug 5, 2026
c525e92
fix(codegen): render .comm's alignment operand per object format
nahime0 Aug 6, 2026
4308a2e
test: float division by zero throws, it does not yield INF
nahime0 Aug 6, 2026
b81695d
fix(eir): don't type a callable-dispatched builtin from the call site…
nahime0 Aug 6, 2026
dcffd35
fix(runtime): correct three x86_64 register-ABI mismatches
nahime0 Aug 6, 2026
80cacd7
test: read array_map's element type through a strict-mode probe
nahime0 Aug 6, 2026
8241ed0
Merge origin/main into fix/security-round-0804
nahime0 Aug 7, 2026
3fcebff
Merge origin/main into fix/security-round-0804
nahime0 Aug 7, 2026
a0bd03b
Merge origin/main into fix/security-round-0804
nahime0 Aug 8, 2026
2088632
fix(tests): repair two merge breakages in the codegen test target
nahime0 Aug 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,37 @@ Releases are listed newest first.
- Added support for `static $x;` function-static declarations without an initializer, in both the native parser and the Magician `eval()` parser: the missing initializer desugars to `= null`, matching PHP, where `static $x;` and `static $x = null;` are identical (including `isset()` behavior).
- Fixed untyped properties (instance and static) initializing to their inferred type's zero value instead of PHP's implicit `null`: `public $x;` and `public $x = null;` now read as `NULL` before the first write, `is_null()` / `=== null` observe it, and later scalar assignments keep nullable storage (the same slot layout as a typed `?T` property) instead of failing to compile (`prop_set assigning PHP type Void ...`) or crashing `var_dump()` on null array slots. Heterogeneous assignments widen the slot to `mixed`; assignments inside the class's own constructor keep the historical precise inferred type, and untyped properties with concrete defaults are unchanged. `ReflectionClass::getDefaultProperties()` and `ReflectionProperty::getDefaultValue()` now see the implicit `null` default through the same schema.
- Added the `--strict-php` flag: the compiler accepts only PHP-compatible constructs. Extension syntax (`ifdef`, `packed class`, `extern`, `ptr_cast<T>`, `buffer_new<T>`, typed local declarations, `ptr`/`buffer<T>` annotations) is rejected at compile time with per-violation diagnostics across the main file, includes, and autoloaded files, while extension builtins (`ptr_*`, `zval_*`, `buffer_*`, `class_attribute_*`) behave exactly as under the PHP interpreter — `function_exists()` reports `false`, calling one is an undefined function with a hint naming the disabled extension, and user code may declare its own functions with those names. Strict mode also reaches `eval()` with PHP's execute-time semantics: extension builtins do not exist inside eval'd fragments (runtime fatal on call, coherent `function_exists`/`is_callable`), extension syntax in a fragment is a runtime parse error, and user functions shadowing extension names stay callable. Programs using compiler preludes (PDO, timezone, image, web) keep compiling; `--define` cannot be combined with the flag.
- Fixed every common symbol being declared with an alignment operand that only one object format reads correctly: `.comm`'s third operand is a power-of-two exponent on Mach-O and a byte count on ELF, and elephc emitted the Mach-O spelling everywhere. Linux builds therefore declared 3-byte alignment for 8-byte slots, which assembles cleanly and then fails to link with `relocation truncated to fit: R_AARCH64_LDST64_ABS_LO12_NC` as soon as any of them is reached by a 64-bit load — the stack-exhaustion guard's `_stack_limit` is the first symbol to do so, which took out linux-aarch64 entirely. All 172 emission sites now render the directive through one target-aware helper.
- Fixed `base64_decode()` returning wrong bytes with no diagnostic: embedded whitespace produced garbage, unpadded input was truncated, and an invalid character was folded into the output. It now follows php-src's decoder and gains the `$strict` parameter.
- Added `stripos()`, `strripos()`, `quoted_printable_encode()`, the key-preserving forms of `array_slice()` and `array_chunk()`, and `file($filename, $flags)`.
- Fixed a segfault when a shape-changing array builtin was called through a callable string (`$f = 'array_reverse'; $f([1,2,3,4]);`): the result was typed from the builtin's broad declared return instead of its real layout.
- Added PHP's scalar parameter coercion for the conversions that can be reproduced exactly (int/float/bool into a `string` parameter, int/float/string into a `bool` parameter, constant floats and numeric strings into `int`/`float`), and accepted callable strings such as `apply("strtoupper", $s)` at a `callable` parameter. The conversions PHP signals at runtime — a lossy float to int, a non-numeric string to int — are still rejected, with the PHP behavior named in the message. Corrects the documentation, which claimed "always strict typing" while `declare(strict_types=1)` was in fact parsed and discarded.
- Widened builtin signatures to PHP's real parameter lists: `implode($array)`, `array_unshift()` with several values, `array_search(..., $strict)`, `array_reverse(..., $preserve_keys)`, `range(..., $step)`, `strpos()`/`strrpos()` with `$offset`, `intval($value, $base)` and `ucwords($string, $separators)`. Also fixes `ucwords()` omitting carriage return, form feed and vertical tab from PHP's default separator set.
- Fixed array builtins that mutate by reference silently doing nothing when the receiver was an object property, a static property or an array element: `usort($obj->items, ...)` returned the array unsorted with no diagnostic, because the by-reference parameter got a value temporary instead of the caller's storage.
- Fixed reading a dynamic object property dropping a reference it never took, which freed the live value after a few reads and answered `null` from then on.
- Added `usort()` over string arrays, `array_reduce()` over string arrays with an integer or boolean accumulator, and `unset()` of dynamic object properties (`stdClass`, `#[AllowDynamicProperties]`). `unset()` of an untyped declared property, `uasort()`/`uksort()`/`array_walk()` over associative arrays, and `unset()` on a by-reference property are reported with diagnostics naming the shape instead of producing a wrong value.
- Added `func_num_args()`, `func_get_args()` and `func_get_arg()`, together with the surplus positional arguments PHP allows past a function's declared parameter list. Shapes that cannot be represented — a defaulted parameter, an existing variadic, an overridden method signature, a dynamic call, top-level use — get a targeted compile error rather than a wrong answer.
- Added `strtr()`, `count_chars()` and `str_word_count()`, completing the audit's missing-builtin list.
- Fixed `new $class(...$args)` silently dropping its spread arguments: the object was constructed with no arguments at all, or with only the named ones, and no diagnostic was reported. Dynamic `new` now shares the same call-argument planning as every other call surface.
- Added the internal array pointer family (`key()`, `current()`, `next()`, `prev()`, `reset()`, `end()`), plus `quotemeta()`, `chunk_split()` and `base_convert()`.
- Added `++`/`--` on strings with PHP's perl-style alphanumeric carry (`"az"` becomes `"ba"`, `"Zz"` becomes `"AAa"`), including the numeric-string cases where the value changes type (`"9"++` is `int(10)`). By-reference parameters and static locals are rejected with a diagnostic explaining why.
- Added `substr_count()`, `strncmp()`/`strncasecmp()`, `dechex`/`hexdec`/`decbin`/`bindec`/`decoct`/`octdec`, `array_count_values()`, `constant()`, `join()`, and the `PHP_ROUND_HALF_*`/`COUNT_*` constants. `round()` gains its `$mode` argument, implemented as php-src's algorithm — which also corrects the two-argument form, where `round(1.005, 2)` returned `1` instead of `1.01`. `count()` gains its `$mode` argument. Single-array `min()`/`max()` accept strings, associative arrays and heterogeneous elements.
- Fixed unbounded recursion crashing with a raw SIGSEGV: every function prologue now checks the stack pointer against a limit derived from `RLIMIT_STACK`, and reports PHP's "Maximum call stack size reached" instead. Fibers and generators, which run on their own stacks, carry their own floor.
- Fixed `array_slice()`, `array_splice()` and `array_pad()` trusting a negative length: the first two used `-1` as their "no length given" sentinel, which is exactly PHP's "stop one element before the end", and neither clamped other negative lengths, so results carried negative element counts and out-of-bounds contents; `array_pad()` negated its length unchecked, so `PHP_INT_MIN` walked off the allocation. All three now match PHP, and `array_pad()` raises PHP's `ValueError` beyond the maximum length.
- Fixed builtins skipping the argument validation PHP performs, where the failure mode was memory corruption or an infinite loop rather than an error: `str_pad()` with an empty pad string read uninitialized memory, `str_split()` with a chunk length of 0 exhausted the heap, `explode("")` and `array_chunk($a, 0)` looped forever, `number_format()` overran its formatting buffer for large magnitudes, `random_int()` with inverted bounds returned garbage, and `array_fill()` with a negative count produced an array whose `count()` was negative. All now raise PHP's catchable `ValueError`. `explode()` gains its `$limit` parameter and the `STR_PAD_*` constants now exist.
- Fixed `==` between two objects and between an array and a scalar or null failing to compile, and implemented `unset()` on a declared property. Cyclic graphs compared with `==` stop at a depth limit rather than overflowing the stack.
- Fixed float printing, numeric strings, `fmod()` and `**`: `echo` and `var_dump` follow PHP's two distinct precision rules (so `1.0E+300` and `float(1000000000000000)` instead of `1E+300` and `float(1E+15)`), string-to-number conversion follows PHP's grammar rather than accepting `INF`/`nan`/hex, `fmod()` keeps the sign of a negative zero, and `2 ** 3` stays an integer.
- Fixed generator keys restarting from zero after an explicit key, and made argument unpacking after a named argument the compile-time error PHP raises.
- Added PHP's alternative control-flow syntax (`if:`/`endif;`, `foreach:`, `while:`, `for:`, `switch:`), `<>`, `$this->n++`, destructuring in `foreach`, increment and decrement of floats, single-array `min()`/`max()`, `join()`, `substr_count()`, and the `PHP_ROUND_HALF_*` and `COUNT_RECURSIVE` constants. `goto` and reference elements in array literals now report clearly that they are unsupported instead of producing confusing syntax errors.
- Fixed memory corruption when a string or stream builtin produced a result larger than the shared 64 KiB concat scratch buffer: the `.` operator itself (which crashed outright past 64 KiB, and whose `.=` accumulation now allocates and frees exactly one block per append), `bin2hex()`, `base64_encode()`/`base64_decode()`, `urlencode()`/`rawurlencode()`/`urldecode()`, `hex2bin()`, `str_pad()`, `str_repeat()`, `addslashes()`/`stripslashes()`, `htmlspecialchars()`/`html_entity_decode()`, `nl2br()`, `wordwrap()`, `str_replace()`/`str_ireplace()`, `substr_replace()`, `number_format()`, `fread()`, `fgets()`, `stream_get_contents()` and `stream_get_line()` now size their result up front and fall back to owned heap storage, so large results are returned correctly instead of overwriting the stream-handle table, exception state and heap globals that follow the buffer in memory. `str_repeat()` with a `$length * $times` product that overflows a machine word now reports PHP's "Possible integer overflow in memory allocation" fatal error instead of crashing.
- Fixed `sprintf()`/`printf()` writing outside their buffers: a long conversion specifier overran a 32-byte stack buffer into the saved frame pointer and return address, and a conversion wider than 128 bytes copied live stack memory into the returned string, leaking pointers into program output. The formatter now parses each specifier into numeric state instead of copying program bytes into the C format string, which also closes a reachable `%n` (an unrecognized conversion used to be handed to libc, giving PHP source an arbitrary-write primitive). Adds the missing conversions `%b`, `%F`, `%E`, positional `%1$s` and custom padding `%'x`, and fixes `%s` truncating at 127 bytes, PHP's non-zero-padded `%e` exponent, and `%f` of `-0.0`.
- Fixed allocation sizes that overflow a machine word being handed to the allocator: `buffer_new<T>()` stored the pre-overflow length in the header, so bounds checks passed for indexes far outside the real allocation, and `array_fill()`, `range()`, `array_pad()` and `SplFixedArray` could crash the same way. Sizes are now checked before allocation on every supported target, and `buffer_new()` rejects negative lengths.
- Fixed unrelated PHP declarations sharing one symbol: composite symbol names joined mangled fragments with an underscore, so `class a { static $u_b; }` and `class a_u { static $b; }` shared a single storage cell, and the equivalent method and enum-case collisions failed to assemble. A static local named `$x_init` also aliased the initialization flag of a static `$x`, and internal labels could collide across functions whose names differ only in non-ASCII characters.
- Fixed `--debug-info` splicing the source path into assembler directives with incomplete escaping, where a crafted path could close the directive string and have the remainder assembled as directives. Linux executables and shared libraries are now linked with `-z noexecstack`, `-z relro` and `-z now`.
- Fixed `var_dump()` printing an enum case as an ordinary object instead of `enum(Status::Active)`, `print_r()` of an object failing to compile, and `var_export()` of an object returning the empty string. All three now match PHP, including nesting, visibility annotations, `*RECURSION*`, and `\Cls::__set_state(...)`.
- Fixed `%` and `/` by zero returning `0`/`INF` instead of raising the catchable `DivisionByZeroError` PHP 8 defines, shift amounts of 64 or more wrapping to hardware behavior instead of saturating like PHP (and negative shifts not raising `ArithmeticError`), `abs(PHP_INT_MIN)` wrapping negative instead of promoting to float, and float-to-int conversion of `NAN`/`INF`/out-of-range values disagreeing both with PHP and between targets. `PHP_INT_MIN % -1` no longer raises SIGFPE on x86_64.
- Fixed four idiomatic PHP shapes being rejected at compile time: the classic singleton (assigning a nullable static property inside its own null check now narrows it), `isset()`/`empty()`/`??` on a never-declared variable, untyped closure parameters passed to `usort()`/`array_filter()`/`array_map()`/`array_walk()`/`array_reduce()` (which now inherit the array's element and key types), and namespace aliases in qualified names such as `use App\Math as M; M\double(5)`.
- Fixed constant folding computing different answers than PHP: integer and numeric-string comparisons past 2^53 went through `f64`, `switch (2) { case true: }` took the wrong branch, `PHP_INT_MIN % -1` panicked the compiler, array-literal key normalization was ignored, `0.0` and `-0.0` were merged despite the sign being observable, and `(float)"INF"`/`"nan"` folded to `INF`/`NAN`.

## [0.26.3] - 2026-08-05
- Added tagless `.lfc` source files with per-file PHP/LFC classification across entry points, includes, and autoload; LFC always enables elephc extensions, while `--strict-php` remains PHP-only and now composes with `--define`, callable dispatch, and `eval()`.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ The full list of supported constructs, operators, and control structures is in t
- **Generators**: generator functions and closures, `yield`, key/value yields, `yield from`, `Generator::send()`, `throw()`, `getReturn()`, and `foreach` over `Iterator` / `IteratorAggregate`
- **Fibers**: `Fiber`, `FiberError`, `Fiber::suspend()`, `Fiber::getCurrent()`, `start()`, `resume()`, `throw()`, `getReturn()`, state predicates, closure captures, guarded native stacks, and target-aware context switching on macOS ARM64, Linux ARM64, and Linux x86_64
- **Control flow**: if/elseif/else, while, do-while, for, foreach, switch, match, break/continue including multi-level depths, try/catch/finally/throw
- **Statements and literals**: `const` / `define()` constants, `global` declarations, `static` locals (with or without an initializer), `print` expressions, list unpacking, PHP numeric literal forms, heredoc / nowdoc strings, `declare(strict_types=1)` and `declare(ticks=...)` directives (validated syntactically and treated as no-ops — elephc compiles an always-strict subset)
- **Statements and literals**: `const` / `define()` constants, `global` declarations, `static` locals (with or without an initializer), `print` expressions, list unpacking, PHP numeric literal forms, heredoc / nowdoc strings, `declare(strict_types=1)` (per-file strict parameter binding, exactly as in PHP) and `declare(ticks=...)` directives
- **Operators**: arithmetic, comparison, `instanceof`, logical, bitwise, ternary, null coalescing (`??`), PHP 8.5 pipe (`|>`), assignment expressions for local and stabilized non-local targets, null coalescing assignment (`??=`), error control (`@`), and compound assignments
- **Types**: union types (`int|string`), nullable (`?int`), `never` return type, `iterable` pseudo-type, inferred `resource|false` values for `fopen()` and `resource` values for standard streams, type casting, typed properties, typed function, method, closure, and arrow parameters and returns
- **Modules**: namespaces, use imports, include/require/include_once/require_once, compile-time Composer PSR-4/PSR-0/classmap/files autoloading, `spl_autoload_register()` rule extraction, PHP magic constants
Expand Down
3 changes: 3 additions & 0 deletions crates/elephc-magician/src/context/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub struct ElephcEvalContext {
pub(super) dynamic_destructed_objects: HashSet<u64>,
pub(super) dynamic_property_aliases: HashMap<(u64, String), EvalReferenceTarget>,
pub(super) array_element_aliases: HashMap<(usize, EvalArrayReferenceKey), EvalReferenceTarget>,
pub(super) array_cursors: HashMap<usize, EvalArrayCursor>,
pub(super) dynamic_initialized_properties: HashSet<(u64, String)>,
pub(super) eval_reflection_attributes: HashMap<u64, EvalReflectionAttributeMetadata>,
pub(super) eval_reflection_classes: HashMap<u64, String>,
Expand Down Expand Up @@ -128,6 +129,7 @@ impl ElephcEvalContext {
dynamic_destructed_objects: HashSet::new(),
dynamic_property_aliases: HashMap::new(),
array_element_aliases: HashMap::new(),
array_cursors: HashMap::new(),
dynamic_initialized_properties: HashSet::new(),
eval_reflection_attributes: HashMap::new(),
eval_reflection_classes: HashMap::new(),
Expand Down Expand Up @@ -202,6 +204,7 @@ impl ElephcEvalContext {
dynamic_destructed_objects: HashSet::new(),
dynamic_property_aliases: HashMap::new(),
array_element_aliases: HashMap::new(),
array_cursors: HashMap::new(),
dynamic_initialized_properties: HashSet::new(),
eval_reflection_attributes: HashMap::new(),
eval_reflection_classes: HashMap::new(),
Expand Down
17 changes: 16 additions & 1 deletion crates/elephc-magician/src/context/reference_metadata.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Purpose:
//! Defines callable ABI aliases, execution-scope snapshots, and reference target shapes.
//! Defines callable ABI aliases, execution-scope snapshots, reference target
//! shapes, and PHP internal array pointer state.
//!
//! Called from:
//! - Argument binding, reference writeback, object properties, and native invokers.
Expand Down Expand Up @@ -72,6 +73,20 @@ pub enum EvalArrayReferenceKey {
String(Vec<u8>),
}

/// PHP internal array pointer state tracked per runtime array cell.
///
/// Runtime cells do not carry PHP's `zend_array` internal position, so eval
/// models it as a cursor over the array's iteration order. PHP has exactly one
/// invalid state: once the cursor runs off either end, only `reset()`/`end()`
/// bring it back.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EvalArrayCursor {
/// The pointer addresses one zero-based iteration position.
Position(usize),
/// The pointer ran off an end and no longer addresses an element.
Invalid,
}

/// Late-static dispatch metadata attached to eval-created static callable arrays.
#[derive(Clone)]
pub(super) struct EvalStaticCallableMetadata {
Expand Down
Loading
Loading