Release: 0.40 - Sharper Edges
0.40.0 - Sharper Edges
Released 2026-05-25 · GitHub release
EDN/Transit IO + defonce + ^:by-ref params + big compiler perf wins (call-site cache, type-driven specialisation, const folding)
🎉 Added
phel.edn: eval-free EDN read/write (#2008)phel.transit: Transit + JSON-Verbose read/write (#2009)phel compile: emit PHP from a snippet, file, or stdin without evaluating.--targetreserved for futureast/tokensdumps (#2043)defoncespecial form: bind a global only when not already defined; survives REPL file reloads (#2055)^:by-refparam hint compiles to PHP&$param, sophp/aset/php/array_pushon(php/array)buffers propagates to the caller.^:referencekept as alias (#2065)- Lexer: namespaced tagged literals (
#my.app/Person) - CI:
clojure-test-suiteworkflow runs against dev HEAD on every PR, non-blocking (#2036) CITATION.cfffor academic citation (#2016)- Tooling:
tools/upgrade-ecosystem.shbumps phel-lang across sibling repos;composer test-toolsruns bashunit overtools/*-test.sh
Performance
Dispatch / call sites:
- Cache global fn call sites in build mode via per-fn
static $__phel_call_Nslots; route knownAbstractFncallees through direct->__invoke(...)to skip magic dispatch (#2044) - Multi-arity fn dispatch via
match (\count($args)); variadic body folds intodefault(#2049) Compile-time folding / hoisting: - Constant-fold pure
phel.corearithmetic (+ - * inc dec) on literal args; short-circuitifwith a literal test (#2045) - Hoist keyword literals to per-fn
static $__phel_const_Nslots; one intern-pool lookup per call site (#2046) Type-driven call specialisation (sharedCallSpecializationkeeps the cache scanner aligned): - Surface analyser
:tagmeta viaLocalVarNode::getInferredType();IterableTargetconsumes it for^"array"params (#2052) - Drop
Seq::toIterable/Seq::toApplyArgumentswraps when the source is already iterable or a PHP array (#2047) - Auto-tag
^floaton params used underphel.corearithmetic / ordering wrappers (+ - * < <= > >= <=>) when a float literal sibling makes the float path inevitable. Int literals stay ambiguous to preserveBigInt/Ratiopolymorphism (#2078) - Specialise two-arg
phel.core+ - * < <= > >= =to native PHP ops when both args are int / float literals or^int|^floatlocals (=also accepts^bool|^string):(+ ^int a ^int b)→($a + $b)(#2079) - Specialise
(str ...)to PHP.concat for literal and^stringargs; skip the two-temp IIFE for(php/instanceof x c)between locals; specialise(:k m)to$m->find(...)whenmis^PersistentMapInterface(#2048) - Specialise
(get coll k)to$coll->get($k)/$coll->find($k)whencollis^PersistentVectorInterface/^PersistentMapInterface(#2067) Runtime data structures: - Persistent-collection
hash()memo uses anullsentinel so empty maps / sets andhash == 0stop recomputing (#2050) TransientVector::update()mutates the trie in place via a by-reference walk; PHP COW still detaches shared nodes from the persistent ancestor on first write (#2069) Emit size:- Collapse synthesised
defnlocation maps into one\Phel::location(...)call (#2053)
⚖️ Changed
- AI tooling: gitignore
.<tool>/dirs; regenerate viaagnostic-ai syncfrom.agnostic-ai/source (#2085) - BC: release tooling moved from
build/totools/;build/is now phar-only - CI: split
ci.ymlintoquality.yml/tests.yml/smoke.ymlwith a sharedsetup-phelcomposite action; concurrency cancellation + least-privilege perms (#2039) - Compiler: hoist pure vector / map / set literals to a per-fn
staticcache - Compiler: skip
Truthy::isTruthywrap inif/?:when the test is known-bool - Compiler:
recurskips the$__phel_Ntemp shuffle when no aliasing is possible phel agent-install: copy.agents/docs by default;--with-docsreplaced by--no-docsopt-outphel agent-install: skip.agents/examples/by default; opt in with--with-examples
🐛 Fixed
- Map destructure surfaces a "Did you mean" suggestion for the Clojure-style
{local :keyword}pair;:keys/:strs/:symswith a non-vector value report a shape error instead of being silently dropped (#2066) ConstantScopeusesSplObjectStorage::offsetExists()to silence a PHP 8.5 deprecation in emitted codephel.cli: register commands viaApplication::addCommand()for Symfony 8 compat (#2033)phel lint: cache invalidates whenphel-lint.phelchanges (#2027);unused-bindingno longer flags symbols used in laterletbindings (#2018)phel doctor: auto-bootstraps temp dir; passes on fresh installs (#2020)Phel::run(): backslash namespaces normalise to dot-form, matching CLI (#2021)phel format: appends trailing newline (POSIX / EditorConfig) (#2022)phel test: non-zero exit when--filtermatches zero tests (#2023)BackslashSeparatorDeprecator: stop false-warning on top-level PHP symbols with a leading-only\(e.g.\strlen,\DateTimeInterface) (#2038)- Dev: patch vendored Psalm 6.16.1 on install / update for PHP 8.5 NAN-coercion crash in
TLiteralFloat
👥 Contributors
Full Changelog: v0.39.0...v0.40.0
Downloads
v0.40.0
- phel.phar (1.49 MB)