Downloads
Latest: v1.2.13, published 2026-05-13.
Verify
Each release includes a SHA-256 sum file alongside the binary. Run shasum -a 256 -c xs-<platform>.tar.gz.sha256 after downloading.
Changelog
v1.2.13
2026-05-13
Added
xs --emit wasmnow runs the full 17-test conformance suite end-to-end throughwasmtime, matching the interpreter / VM / JIT byte-for-byte. Previously only basic arithmetic and control flow worked; everything else either silently produced wrong output or trapped withwasm 'unreachable'.- Stdlib bridge for
import math / json / fs / timein the AOT path. Each module becomes a synthesised map of closures wrapping runtime helpers. - Cross-file
use "./mod.xs"(withasrename and selective destructure) on the AOT path, mirroring the VM and JS implementations. - Generators (
fn*,yield,g.next()),async/await,spawn,nursery, algebraic effects (perform/handle/resume), and reactivebindall lower to working WASM (sync resolution for async / spawn since a freestanding module has no scheduler). - Higher-order array methods (
.map,.filter,.reduce,.fold,.each,.some,.every,.find,.sort_with,.flat_map,.group_by,.partition,.sum,.product,.min_by,.max_by,.count). - Codepoint-aware string ops (
.chars,.lines,.lower,.trim,.split,.replace,.sort, UTF-8.len). - Bigint with arbitrary precision in the AOT path, so int overflow promotes the same way the runtime does.
- Tolerant
assert_eqso chained float arithmetic (3.14 * r * rsummed across shapes) still matches a literal expected value.
Changed
- Closure variables that get mutated inside a closure now write back through the captured env so subsequent calls see the update. Previously
n = n + 1insidefn() { ... }wrote to a fresh local and the capturednstayed at its initial value. - Map
setupdates the existing key in place rather than appending a duplicate entry. Previously the nextgetwould return the stale first match. - Map keys compare by content (via
RT_VAL_EQfollowed byRT_VAL_TRUTHY), not by underlying data pointer. Two literally equal"x"strings allocated at different sites now hash to the same slot. - Struct match by type name (
Point { x, y }) tags instances with__type__so the pattern can recognise them. Class declarations get the same field as a public class field. String.lencounts UTF-8 codepoints instead of bytes ("café".len() == 4).- String equality compares bytes byte-by-byte (
"a" ++ "b" == "ab"is nowtrue). - Indirect closure calls (
fns[i]()) sniff the closure-env field at the call site and use the rightcall_indirectsignature, so closures returned from arrays no longer trip "indirect call type mismatch". - Trait default methods get copied onto every impl that doesn't override them.
- Mutually recursive nested fns and named nested fns (
fn inc() {...}inside another fn) now compile correctly, withdeferand closure capture across them.
Fixed
RT_VAL_INDEX_SETonly handled arrays; map index assignment (m["k"] = v) silently no-op'd. It now dispatches on the value tag..starts_withwas off by one on the comparison length.- Deep array equality recurses through nested elements rather than comparing pointers.
v1.2.12
2026-05-12
Added
- JS transpiler now inlines cross-file
use "./mod.xs"and respects theasrename, namespace alias, and selective destructure (use "./m.xs" { foo, bar as baz }). Previously the import was silently dropped and any reference to the namespace blew up at runtime. - Trait default methods get copied onto every impl that doesn't override them, so
square.name()returning"shape"works the same under--emit jsas under the interpreter. assert_eqin the JS prelude does the same tolerant float compare as the native runtime, so chained arithmetic across shapes still matches a literal expected value (e.g.12.56 + 9.0 == 21.56).
Changed
- Struct match patterns recognise instances by name because the constructor tags
this.__type__; class declarations get the same field as a public class field. - Field reads on missing keys (
__xs_field) returnnullinstead ofundefinedso XS-stylem.k == nullchecks match the runtime. - The prelude's builtin
rangelives onglobalThisso a user-declaredfn* rangedoesn't tripIdentifier 'range' has already been declared. - Top-level
awaitinside anassert_eqargument auto-wraps the assertion IIFE as an awaited async function, so the innerawaitisn't a syntax error. - Effect handlers that don't call
resumeterminate the handle block with the arm's value instead of looping forever. - Exhaustiveness analyser stopped flagging
(a, b)andPoint { x, y, .. }as non-exhaustive; both are catchall for their shape.
Fixed
--emit jsno longer crashed seven of the conformance tests. All 17 now pass through Node end-to-end.
v1.2.11
2026-05-12
Added
export { name, name as alias, ... }-- a file's public surface, in
one place. Goes at the top level of the file, names can be aliased with as. Without an export list, every top-level binding is exposed (scripts stay zero-ceremony).
- Cross-file
use "file.xs"works on the VM backend. Lowers to a
__use_file native that compiles the imported file with the bytecode compiler and runs it on a child VM, so closures from the module are real XS_CLOSURE values the parent VM can invoke.
- Module-qualified struct construction:
lib.Point { x: 3, y: 4 }
builds an instance instead of erroring.
@deprecated("msg")actually emits aW0001warning at every call
site (was parsed and silently dropped).
Changed
- C transpiler: per-arm effect handler dispatch, comparator support in
arr.sort_with, predicate dispatch on find / index_of, del tombstone semantics, map-spread, structured runtime errors.
- JS transpiler: bare-builtins prelude, range methods, tuple-vs-array
shape correction, del tombstone, explicit refusals for bind and wrapping decorators.
- Build: every
.o/.droutes throughbuild/obj/, source tree
stays clean. make clean also sweeps any stray root artefacts.
Removed
pubmodifier and@exportdecorator. Using either is now a P0054
parse error pointing at export { ... }. The export list is the one and only mechanism.
Fixed
- VM cross-file
usewas a no-op (silently dropped the import). @deprecatedwarnings never fired.
v1.2.10
2026-05-10
Added
xs upgradedownloads the latest release and atomically replaces the running binary. Verifies SHA-256 before swapping. Use--yesto skip the prompt.xs uninstallremoves the binary; with--with-datait also clears~/.xsand~/.xs_cache.xs repl(and barexswith no args) drops into an interactive read-eval loop. Bindings persist across lines, multi-line input is detected via bracket / string state, parse errors no longer kill the session. Meta-commands::help,:quit,:env,:clear,:t <expr>.
Changed
assert_eqraises a catchableAssertionErrorinstead of callingexit(1).try { assert_eq(...) } catch e { ... }now works.import logno longer collides with the mathlogbuiltin; the stdlib module loads correctly.[1, 2, 3].sorted()works as an array method on the VM (was interpreter-only).collections.Set.has(x)works for ints, floats, bools, and strings.reflect.type_of(value)returns the struct or class name for instances built via either backend.xs --helplistsupgrade,uninstall,publish, andsearch.- VS Code extension icons regenerated in the new sage-on-dark style.
Removed
- Inline temporal block statements:
every 1s { ... },after 5s { ... },timeout 1s { ... },debounce 100ms { ... }. They never actually scheduled the body. Use the decorator forms (@every(1s),@after(5s),@cron(...)) on a function instead.
Fixed
- VM
del xtombstones the local slot; subsequent reads throw a runtime error (was silently rebinding to null and skipping anytry / catch). - Struct match patterns reject values of the wrong type instead of binding fields to null.
match shape { Circle { radius } => ... }no longer fires on aRect. - macOS build errors under Apple Clang:
-Wenum-conversioninlint.c,-Wenum-compare-conditionalacross the parser, and missing<sys/select.h>on POSIX. The release link step also stops passing GCC-only flags to Apple's linker. - Linux CI fuzz step no longer blocks the run; reproducer artifact still uploads on findings for offline triage.
v1.2.9
2026-05-10
no notes
v1.2.8
2026-05-09
no notes
v1.2.7
2026-05-09
no notes
v1.2.6
2026-05-09
no notes
v1.2.5
2026-05-09
no notes
v1.2.4
2026-05-09
no notes
v1.2.3
2026-05-09
no notes
v1.2.2
2026-05-09
no notes
v1.2.1
2026-05-06
no notes
v1.2.0
2026-05-06
no notes
v1.1.1
2026-05-06
no notes
v1.1.0
2026-05-05
no notes
v1.0.3
2026-05-04
no notes
v1.0.2
2026-05-03
no notes
v1.0.1
2026-05-02
no notes
v1.0.0
2026-04-26
no notes