Skip to main content

Release: 0.41 - Fold and Inline

Latest update

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.mjs draft a Twitter/X thread from the released tag's CHANGELOG (manual posting; workflow_dispatch back-fills)
  • CompileOptions::setOptimizationLevel(int) (default 0); reserved for auto-inline (1) and tail-call rewrite (2) (#2092, #2141)

Performance

Opt-in (setOptimizationLevel(2)):

  • TailCallRewriter: self-recursive tail calls β†’ recur loop (variadic / multi-arity skipped) (#2141)
  • CallInliner: inline pure single-arity defn bodies at the call site for folding; args evaluate once; ^:pure opts in; multi-arity inlines the matching arity; variadic / recursive / memoised / ^:async skipped (#2135, #2215, #2216, #2217, #2218) Compile-time folding:
  • ConstantFolder: fold pure phel.core calls on literals + 3-arg reduce with 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 / conj chains β†’ 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: count on ^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 if chains β†’ match; (or …) / (and …) β†’ || / && / ternary; if/else return β†’ ternary; drop else nil (#2091, #2132, #2133, #2134, #2174) Emit shape:
  • defstruct emitted final (#2137); @var hints for tagged let / loop (#2136); keywords via Keyword::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):
    • < / <= / > / >= reject nil instead of coercing it to 0 (#2228)
    • Transients reject reuse after persistent!, the bang ops and a second persistent! throw; persistent conj / assoc / dissoc on transients stay lenient (#2229)
    • Clean errors instead of leaked PHP TypeError / deprecations: nan? on nil, take non-int count, remove / select-keys non-seqable (#2229); phel.string fns on non-strings (blank? still treats nil as blank) (#2226); int / long / float / double non-numeric objects and get / assoc / update non-int keys (#2224)
  • phel.cli: Symfony Console 8.0 compat (#2094)
  • phel compile: dry-run no longer evaluates forms; new CompileOptions emitOnly flag (#2095)
  • defonce: same-file redefinition is a silent no-op (#2096)
  • REPL restores *ns* to user after a require instead of leaving it in the last dependency namespace (#2125)
  • phel run <file>: preloads only the bundled phel.* namespaces the script references by fully qualified form (#2127, #2182)
  • ^int / ^float / ^string tags on let / loop bindings propagate to the body, so typed-arith specialisation fires there too (#2146)
  • phel.test/assert-expr follows Clojure's [message form] signature; derive / some-fn / take-last / nthrest invalid calls are catchable via thrown? (#2129)
  • Clojure-aligned laziness: repeatedly / filter / remove / concat / map / distinct no longer pre-realize their head (#2186, #2187, #2188, #2190, #2191, #2210)
  • map over nil / empty returns a lazy seq; over a map yields pair entries; LazySeq no longer drops nil values (#2188, #2187)
  • get / nth on a vector with a non-int key return the default / throw OutOfBoundsException instead of leaking a PHP warning (#2211)
  • Reader meta on vector / map / set literals survives compile/emit, fixing group-by element-meta loss (#2189)
  • BigInt / BigDecimal::fromFloat render NaN / Infinity in 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 a phel/core.php with 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


View release on GitHub