Skip to main content

Release: 0.46 - Native Path

Latest update

0.46.0 - Native Path

Released 2026-06-25 · GitHub release

Faster CLI startup + REPL boot (~34%), native PHP interop & core-call compilation, config validation in doctor, plus hash/equality and inliner fixes.

🐛 Fixed

  • Opt-level-≥2 call inlining (#2586) emitted undefined-variable PHP when inlining a ^:pure let-bodied defn whose body contains a nested call that lowers to an IIFE (or/and/cond): the renamed parameter/let shadow was not captured by the nested closure's use(...) clause, crashing at runtime. The inliner now threads introduced shadows onto the rebase environment so nested closures capture them (#2622)

🎉 Added

  • Config validation in phel config and phel doctor (relative-path requirements, missing/empty source-test dirs, bad optimization levels, wrong types); doctor fails on errors and shows warnings as tips (#2600, #2601, #2602, #2603)
  • phel build --timing: per-phase compile time (lex/parse/read/analyze/emit) aggregated across compiled namespaces, with shares and a total. Compile-only; pair with --no-cache, composes with --report (#2615)

⚖️ Changed

  • Breaking: removed the deprecated PhelConfig setX() setters and useLayout()/useNestedLayout()/useFlatLayout() shortcuts (since 0.37), plus the setX() shims on PhelBuildConfig/PhelExportConfig, use the with*() methods (#2609)
  • A broken phel-config.php now fails with a clear message naming the file and the expected shape (exit 1), instead of an uncaught-exception stack trace (#2604, #2606, #2607)
  • phel init scaffolds phel-config.php with declare(strict_types=1); (#2605)

Performance

  • Compile interop to native PHP: php/->, php/::, php/new, php/oset emit direct expressions instead of closure wrappers (#2524, #2525, #2526, #2532, #2536)
  • Compile type-tagged core calls natively instead of via runtime dispatch: push/dissoc, second/get-in, count/first on strings, inc/dec on ^int/^float locals, last on vectors (#2527, #2528, #2529, #2530, #2562, #2563)
  • Fold comparisons at compile time: (= x :keyword) to an identity check, =/not= over string literals to a boolean (#2531, #2561)
  • Inline let-bodied pure defns at opt level ≥ 2, including provably-pure unannotated ones (#2586)
  • Allocate less in the compiler pipeline: reuse parser/analyzer sub-components, skip no-op env clones, build let/loop indexes in O(N), walk typed defn bodies once less (#2548, #2552, #2553, #2554, #2555)
  • Speed up the lexer: fewer per-token deprecation checks, ASCII-only column tracking (#2546, #2547)
  • Scan multiline comments (#| ... |#) delimiter-to-delimiter with strpos instead of per byte, preserving nesting (#2613)
  • Short-circuit Symbol equality on identity, and skip the dynamic-scope check on global reads when no dynamic bindings are active (#2545, #2551)
  • Share one static slot for repeated literals, and splice captured emitter nodes with a prefix check instead of a per-call regex (#2564, #2565)
  • Speed up persistent data structures: chunk-aware vector equality with an identity short-circuit, no-op dissoc/remove skip the deep compare, hash-map iteration drops a redundant per-node copy (#2544, #2549, #2550)
  • Cut CLI startup: persist the compiled-code and namespace indexes (one write, not per file), boot the REPL with only phel.core and lazy-load the rest (~34% faster), lazy-load CLI commands, ship a phel.ini from phel doctor for a persistent OPcache file cache (#2556, #2557, #2558, #2559, #2560, #2599)
  • Opt-in intermediate compile cache (withEnableIntermediateCache(), off by default): persists each file's gzip-compressed read result and replays it on warm rebuilds, skipping the front half of the pipeline; keyed by source + optimization level + Phel version (#2611)

🐛 Fixed

  • Collection hash/equals correctness: hashing large persistent collections no longer throws past ~13 elements (the rolling hash wraps in a 32-bit range), collections with a NaN key/element equal themselves again, and a Cons/sorted set whose hash is 0 is cached (#2567, #2585, #2589)
  • The "not defined" error hint now shows even when a Did you mean ...? suggestion is appended (#2523)
  • phel build no longer reuses stale output when a required namespace changed but the dependent's source did not: the incremental cache cascades recompiles to dependents (#2612)
  • phel lsp stays alive when the editor is idle, the reader distinguishes a read-timeout from end-of-stream instead of exiting after ~200ms of silence
  • phel lsp textDocument/documentSymbol lists a file's definitions, extracted from the open buffer so they reflect unsaved edits
  • Keyword equality is now value-based (matching Symbol): a keyword rebuilt outside the intern pool, e.g. via unserialize, compares equal to its interned twin, so keyword-keyed map lookups against deserialized collections no longer miss
  • phel build exits non-zero when compilation aborts, instead of printing the error and exiting 0 with a partial/empty output tree

👥 Contributors

@Chemaclass @JesusValeraDev

Full Changelog: v0.45.1...v0.46.0

Downloads

v0.46.0


View release on GitHub