Skip to main content

Release: 0.51 - Only Once

Latest update

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 test gains --changed[=<ref>], --reporter=github, per-test coverage JSON, ^:skip / ^:focus / skip! with --fail-on-focus, and phel.test/*event-hook*. (#3207 #3209 #3210 #3211 #3214)
  • phel format --exclude=<glob> and the matching format-exclude config 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 -O2 would inline it. (#3126)
  • CSS-style id and class shorthand in phel.html element tags. (#3240)
  • phel.ai/*sleep-fn*, a bindable retry backoff seam. (#3204)
  • A bare local defexception name now resolves in catch. (#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 ### Performance section in the GitHub release notes, and stop listing the same contributor twice when their commits carry different capitalisation. (#3258)
  • tools/release.sh accepts a pre-release version (1.0.0-rc1): it leaves ## Unreleased in 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 a Closure returning 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.core identity once per node rather than once per specialisation probe: 4% faster emission. (#3246)
  • Compile is arms through one runtime helper, cutting generated size, compile time and memory. (#3212)
  • Inline -O2 calls 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 apply in one pass: 2.1x. (#3181)
  • Give hot core functions fixed arities: up to 22x. (#2973) Standard library:
  • Build mapv and filterv directly instead of through a lazy sequence vec then tears down: 3.5x and 2.8x. (#3251)
  • Give update-in the fixed arities update has: 32% faster, and assoc-in 27%. (#3251)
  • Build the walked list and the pprint joins once instead of through a spread: 57% and 23% faster. (#3248)
  • Write into's targets through transient methods, and reverse and escape natively in phel.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.core loads 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 alias to-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 nil key, which keys, vals, for ... :pairs and into dropped while count still counted it. (#3172)
  • Treat associative PHP arrays as maps in merge, merge-with, and conj. (#3114)
  • Preserve JSON objects as maps during phel.json/decode, including empty objects. (#3089)
  • Preserve assertion source locations through macro expansion. (#3228)
  • Preserve outer --slowest and --last-failed state 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


View release on GitHub