Release: 0.51 - Only Once
0.51.0 - Only Once
Released 2026-09-05 · GitHub release
Mutation testing + smarter test runner, 18-24% faster compiles, big runtime/stdlib speedups, 20% faster boot.
🎉 Added
phel mutate: mutation testing for Phel with 11 mutators, parallel and changed-file modes, coverage selection, and MSI gates. (#3208 #3209)phel testgains--changed[=<ref>],--reporter=github, per-test coverage JSON,^:skip/^:focus/skip!with--fail-on-focus, andphel.test/*event-hook*. (#3207 #3209 #3210 #3211 #3214)phel format --exclude=<glob>and the matchingformat-excludeconfig key. (#3233)cache-env-vars: environment values that take part in the compiled-code cache key. (#3236)- Clojure-style binding-first map destructuring, nested bindings included. (#3115)
^:redef, which keeps a call interceptable where-O2would inline it. (#3126)- CSS-style id and class shorthand in
phel.htmlelement tags. (#3240) phel.ai/*sleep-fn*, a bindable retry backoff seam. (#3204)- A bare local
defexceptionname now resolves incatch. (#3211) - Public API:
Shared\Process\PhelBinaryLocator,Shared\DeprecationResolver, per-test coverage, and immediate compiled-code cache flushing. (#3203 #3209 #3243)
⚖️ Changed
- Render the changelog's
### Performancesection in the GitHub release notes, and stop listing the same contributor twice when their commits carry different capitalisation. (#3258) tools/release.shaccepts a pre-release version (1.0.0-rc1): it leaves## Unreleasedin place and publishes the GitHub release as a pre-release. (#3253)- Document the two release-time documentation steps no tooling performs, and mark the paragraph in the stability policy that flips at a major. (#3254)
Registry::addDefinition()also accepts aClosurereturning the metadata map. Widening a parameter is not breaking, and the eager form still works, so an artifact compiled earlier stays loadable. (#3250)- Resolve bare all-caps PHP names by position instead of autoload state. See ADR 0016. (#3064)
- Expose compiler environment and deprecation controls through
CompilerFacadeInterface. (#3048)
⚡ Performance
Compiler:
- Evaluate the emission already produced instead of emitting every form a second time: 18% faster to compile and evaluate. (#3249 #3257)
- Emit each form once under
phel compile --emit-only: 24% faster, byte-identical output. (#3246) - Reuse the lists a macro expansion leaves unchanged: 5% faster analysis of macro-heavy source. (#3246)
- Resolve a call's
phel.coreidentity once per node rather than once per specialisation probe: 4% faster emission. (#3246) - Compile
isarms through one runtime helper, cutting generated size, compile time and memory. (#3212) - Inline
-O2calls in return position. (#3125) Runtime: - Answer
contains?, map lookups and realized lazy sequences without repeating work already done: up to 22% faster. (#3245) - Promote every map construction path at the same size, making grown map reads up to 5.5x faster. (#3172)
- Read non-dynamic globals straight from the registry: 1.9x. (#3179)
- Spread vectors into
applyin one pass: 2.1x. (#3181) - Give hot core functions fixed arities: up to 22x. (#2973) Standard library:
- Build
mapvandfiltervdirectly instead of through a lazy sequencevecthen tears down: 3.5x and 2.8x. (#3251) - Give
update-inthe fixed aritiesupdatehas: 32% faster, andassoc-in27%. (#3251) - Build the walked list and the
pprintjoins once instead of through a spread: 57% and 23% faster. (#3248) - Write
into's targets through transient methods, and reverse and escape natively inphel.string. (#2973) Test framework: - Record an assertion against one rebuild of the counts map instead of two nested walks, halving the per-assertion bookkeeping. (#3247) Boot:
- Carry a definition's metadata as a thunk forced on first read, so loading a namespace no longer builds a map per definition:
phel.coreloads 20% faster, peak memory 22MB to 20MB. (#3250) - Skip Gacela's event dispatch when no listener is registered; a host that wants the events sets
GacelaConfig::setEventDispatcher().
⚠️ Deprecated
to-php-array; use its Clojure-aligned aliasto-array. (#3076)- Key-first map destructuring (
{:key local}); use binding-first ({local :key}). (#3115)
🐛 Fixed
- Name the namespace and the fix when an exported wrapper cannot resolve its definition, instead of failing with
Value of type null is not callable. (#3244) - Iterate a hash map's
nilkey, whichkeys,vals,for ... :pairsandintodropped whilecountstill counted it. (#3172) - Treat associative PHP arrays as maps in
merge,merge-with, andconj. (#3114) - Preserve JSON objects as maps during
phel.json/decode, including empty objects. (#3089) - Preserve assertion source locations through macro expansion. (#3228)
- Preserve outer
--slowestand--last-failedstate across nested test runs. (#3206) - Avoid redundant project walks and cache races in
phel test --parallel. (#3203) - Invalidate the scan-index cache when a configured source or test directory appears. (#3205)
- Load dependencies for test and benchmark files outside configured source directories. (#3187)
- Skip the build output directory when indexing, so navigation prefers a source over its copy in
out/. (#3155) - Format collection literals in linear time with byte-identical output. (#3218)
- Replay deprecation warnings from warm compiled-code caches. (#3222)
- Reject blank AI provider API keys before making a request. (#3204)
👥 Contributors
@Chemaclass @JeroenvanDijk @JesusValeraDev
Full Changelog: v0.50.0...v0.51.0
Downloads
v0.51.0
- phel.phar (2.6 MB)