Release: 0.46 - Native Path
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
^:purelet-bodieddefnwhose 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'suse(...)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 configandphel doctor(relative-path requirements, missing/empty source-test dirs, bad optimization levels, wrong types);doctorfails 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
PhelConfigsetX()setters anduseLayout()/useNestedLayout()/useFlatLayout()shortcuts (since 0.37), plus thesetX()shims onPhelBuildConfig/PhelExportConfig, use thewith*()methods (#2609) - A broken
phel-config.phpnow 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 initscaffoldsphel-config.phpwithdeclare(strict_types=1);(#2605)
Performance
- Compile interop to native PHP:
php/->,php/::,php/new,php/osetemit 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/firston strings,inc/decon^int/^floatlocals,laston 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 puredefns 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/loopindexes in O(N), walk typeddefnbodies 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 withstrposinstead of per byte, preserving nesting (#2613) - Short-circuit
Symbolequality on identity, and skip the dynamic-scope check on global reads when no dynamic bindings are active (#2545, #2551) - Share one
staticslot 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/removeskip 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.coreand lazy-load the rest (~34% faster), lazy-load CLI commands, ship aphel.inifromphel doctorfor 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
NaNkey/element equal themselves again, and aCons/sorted set whose hash is0is cached (#2567, #2585, #2589) - The "not defined" error hint now shows even when a
Did you mean ...?suggestion is appended (#2523) phel buildno 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 lspstays alive when the editor is idle, the reader distinguishes a read-timeout from end-of-stream instead of exiting after ~200ms of silencephel lsptextDocument/documentSymbollists 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. viaunserialize, compares equal to its interned twin, so keyword-keyed map lookups against deserialized collections no longer miss phel buildexits non-zero when compilation aborts, instead of printing the error and exiting 0 with a partial/empty output tree
👥 Contributors
Full Changelog: v0.45.1...v0.46.0
Downloads
v0.46.0
- phel.phar (1.98 MB)