Week in review: 27 Apr 2026
A recap of what shipped during the week of 27 April. Six items this week: a complete audit of the math module against every PHP version since 4, two more Pure Utilities packages converted at 100%, a deep analysis of string literal encodings, an experimental TypeScript output mode in the transpiler, and initial support for static variables inside functions.
Complete audit of the math module
The math module went through a full audit this week. Every function it exposes (arithmetic, exponentiation, trigonometric, hyperbolic, logarithmic, rounding, base conversion) was checked end-to-end against the PHP reference. PHP unit tests run the transpiled module from native PHP, JavaScript unit tests run directly against the runtime, and the two were brought into agreement function by function.
Edge cases were enumerated rather than sampled: NaN, positive and negative infinity, signed zero, denormals, the int/float boundary at PHP_INT_MAX, overflow into float, division by zero, modulo with negative operands, the fmod sign convention, intdiv truncation, and the rounding modes for round were each matched cell by cell. Compatibility was verified across PHP versions from 4 forward: every signature change, return-type change, and behavioural divergence introduced over twenty years of releases is now accounted for, with the runtime selecting the right behaviour based on the target version.
The result is that nothing in the math module is allowed to drift from PHP without tripping a test. Like the numeric coercion work the week prior, this moves the module out of the "things we keep finding bugs in" column and into "stable, tested, done".
composer/semver at 100%
composer/semver, the version-constraint library that powers Composer's dependency resolver, is fully converted and at 100% on its test suite. Pure parsing and comparison, no I/O, but with enough corner cases (pre-release identifiers, build metadata, branch aliases, caret and tilde operators, stability flags, the VersionParser normalisation rules) that a clean run is non-trivial. It's the second package cleared in Phase 2, Pure Utilities, after brick/math.
doctrine/inflector at 100%
doctrine/inflector, the singular/plural/camel/pascal/snake/table/classify utility used across Doctrine and Symfony, is also fully converted and at 100%. Like semver, it's pure string manipulation with no I/O, but the rules engine (irregular plurals, uncountables, locale-specific transformations) makes for a wide test surface. Phase 2 is now three for three; doctrine/lexer and dragonmantank/cron-expression are next on the roadmap.
String literal encodings
A major analysis and implementation pass landed on string literal encodings. PHP strings are byte sequences (the runtime treats them as opaque arrays of octets, with source files conventionally Latin-1 or UTF-8), while JavaScript strings are sequences of UTF-16 code units. The mismatch matters for indexing, strlen, byte-level comparison, PCRE (which operates on bytes), substr, ord/chr, and every function that reads or writes a single character.
The week was spent characterising the divergence in detail: which built-ins are byte-oriented, which are codepoint-oriented in PHP's mb_* family, and how each behaves once the underlying buffer is a UTF-16 string instead of a byte array. The decision is to preserve PHP's byte semantics at the runtime boundary: strings carry their original encoding, byte-oriented functions stay byte-oriented, and conversion happens only at the edges where a JavaScript API requires it. Implementation against that model is underway and will be the main string-handling change visible in transpiled codebases over the next few weeks.
Experimental TypeScript output
The transpiler now has an experimental TypeScript output mode. Until now Pext has emitted JavaScript only: the runtime is JavaScript, the transpiled code is JavaScript. The first cut of the new mode emits .ts files using TypeScript-style import and export syntax, with the build and execution pipeline wired up to compile and run them end-to-end.
No type annotations are derived from PHP yet. That's the eventual goal: pull types from PHP type declarations, typed properties, return types, and docblocks so transpiled code drops into a TypeScript codebase and can be indexed, navigated, and refactored like any other module. This week was about getting the plumbing right so the type-derivation work has somewhere to land.
Static variables inside functions
Initial support for static $foo = expr; inside function bodies is in. The construct keeps a variable across invocations of the function and initialises it once on the first call, a small feature on the surface that touches scoping, lifetime, and call-frame layout in the runtime. A few specific cases were handled as part of the doctrine/inflector work; this week's change generalises that into a first-pass implementation covering scalar initialisers, array initialisers, lazy first-call initialisation, and reset semantics.
The harder edge cases (initialisers that reference other locals, recursion through the same static, generators, closures that capture the static binding) are not all handled yet. "Initial support" is the honest framing: enough for the patterns we see in production PHP code, not enough to call the feature done.