Release: 0.41 - Fold and Inline
0.41.0 - Fold and Inline
Released 2026-06-01 Β· GitHub release
Opt-in compiler optimizations (inlining, tail-call rewrite, constant folding, call-site specialization) + Clojure-parity & lazy-seq fixes
π Added
- CI:
announce-release.yml+scripts/announce-release.mjsdraft a Twitter/X thread from the released tag's CHANGELOG (manual posting;workflow_dispatchback-fills) CompileOptions::setOptimizationLevel(int)(default0); reserved for auto-inline (1) and tail-call rewrite (2) (#2092, #2141)
Performance
Opt-in (setOptimizationLevel(2)):
TailCallRewriter: self-recursive tail calls βrecurloop (variadic / multi-arity skipped) (#2141)CallInliner: inline pure single-aritydefnbodies at the call site for folding; args evaluate once;^:pureopts in; multi-arity inlines the matching arity; variadic / recursive / memoised /^:asyncskipped (#2135, #2215, #2216, #2217, #2218) Compile-time folding:ConstantFolder: fold purephel.corecalls on literals + 3-argreducewith whitelisted reducers; aborts when runtime would throw/promote (#2088, #2140)DoSimplifier/LetSimplifier: drop pure non-tail exprs + unused bindings; inline(let [x <lit>] x)(#2089) Call-site lowering (CallSpecialization, tag-driven):- Persistent collection methods, fn-handle calls, nested
assoc/conjchains β native calls (#2090, #2142, #2143) - Variadic numeric ops on tagged locals β native chain (literal-int chains stay on dispatch for overflow) (#2090)
- Peepholes:
(not (= a b)),(apply f [β¦]),(get arr k)on^array(#2090, #2139) - Predicate specialisation: nil/some, bool, numeric, type,
struct?/set?/map?/vector?/seq?(#2157, #2160, #2162, #2168, #2177) - Direct calls on tagged values:
counton^array,name/namespace,empty?,contains?,deref/reset!(#2158, #2164, #2166, #2170, #2179) php/*scalar return-type inference + typed-arith propagation β nested native chains (#2175) Control-flow lowering:- Literal-key
ifchains βmatch;(or β¦)/(and β¦)β||/&&/ ternary;if/else returnβ ternary; dropelse nil(#2091, #2132, #2133, #2134, #2174) Emit shape: defstructemittedfinal(#2137);@varhints for taggedlet/loop(#2136); keywords viaKeyword::create(#2131); orphan-slot fix (#2144); specialised predicates marked bool-returning (#2172)
π Fixed
- Clojure-parity fixes surfaced by the clojure-test-suite CI job (#2223):
</<=/>/>=rejectnilinstead of coercing it to0(#2228)- Transients reject reuse after
persistent!, the bang ops and a secondpersistent!throw; persistentconj/assoc/dissocon transients stay lenient (#2229) - Clean errors instead of leaked PHP
TypeError/ deprecations:nan?onnil,takenon-int count,remove/select-keysnon-seqable (#2229);phel.stringfns on non-strings (blank?still treatsnilas blank) (#2226);int/long/float/doublenon-numeric objects andget/assoc/updatenon-int keys (#2224)
phel.cli: Symfony Console 8.0 compat (#2094)phel compile: dry-run no longer evaluates forms; newCompileOptionsemitOnlyflag (#2095)defonce: same-file redefinition is a silent no-op (#2096)- REPL restores
*ns*touserafter a require instead of leaving it in the last dependency namespace (#2125) phel run <file>: preloads only the bundledphel.*namespaces the script references by fully qualified form (#2127, #2182)^int/^float/^stringtags onlet/loopbindings propagate to the body, so typed-arith specialisation fires there too (#2146)phel.test/assert-exprfollows Clojure's[message form]signature;derive/some-fn/take-last/nthrestinvalid calls are catchable viathrown?(#2129)- Clojure-aligned laziness:
repeatedly/filter/remove/concat/map/distinctno longer pre-realize their head (#2186, #2187, #2188, #2190, #2191, #2210) mapovernil/ empty returns a lazy seq; over a map yields pair entries;LazySeqno longer dropsnilvalues (#2188, #2187)get/nthon a vector with a non-int key return the default / throwOutOfBoundsExceptioninstead of leaking a PHP warning (#2211)- Reader meta on vector / map / set literals survives compile/emit, fixing
group-byelement-meta loss (#2189) BigInt/BigDecimal::fromFloatrenderNaN/Infinityin rejection messages instead of coercing to string (PHP 8.5 warning)phel build: the compiled-code cache no longer evicts a(load ...)secondary the running build still needs, which shipped aphel/core.phpwith a sibling-less(load "core/meta")that failed away from the source tree (#2231)
π₯ Contributors
@Chemaclass @JesusValeraDev @SauronBot
Full Changelog: v0.40.0...v0.41.0
Downloads
v0.41.0
- phel.phar (1.54 MB)