Wyn v1.18: The Correctness Release
v1.17 shipped the ecosystem. The obvious next question was: does everything actually work? So the day after, I did the least glamorous thing possible: I took the entire sample-app corpus, all 48 programs, and built every single one. Then I fixed what broke until the answer was yes.
This release is the result. No new headline features, no breaking changes, a drop-in upgrade from v1.17. Just a stack of bugs that are now gone, each with a regression test so they stay gone.
The one that mattered: a use-after-free in codegen
The 2600-line JS-interpreter sample would sometimes fail to build with the C compiler complaining about invalid bytes in the generated source. Not consistently. Not on small programs. Classic.
The cause was a heap-use-after-free in codegen's string-variable scope tracking: on large programs with nested scopes plus string-into-array moves, a freed name pointer was re-exposed and its stale bytes were emitted straight into a wyn_rc_release(...) call, producing invalid (non-UTF-8) C. The fix is ASan-verified, and that sample now builds reliably every time.
If a compiler is going to have bugs, "emits garbage memory into the output" is about the worst kind. This is exactly why the sample corpus exists.
Compiler correctness, found by dogfooding
Every one of these came from a real program in the corpus:
.sort() and .reverse() now work as expressions and chain:
fn main() {
xs = [3, 1, 4, 1, 5, 9, 2, 6]
top = xs.sort().reverse()
print(top) // [9, 6, 5, 4, 3, 2, 1, 1]
}Iterating a 2D array literal binds each row as the sub-array (it used to silently give you 0 0):
fn main() {
grid = [[1, 2, 3], [4, 5, 6]]
for row in grid {
print(row[0], row[1], row[2])
}
}Indexing a struct array binds the struct type (it used to infer long long and die in the C compiler):
struct User { name: string, age: int }
fn main() {
users = [User{name: "ada", age: 36}, User{name: "bob", age: 41}]
var i = 0
while i < 2 {
var u = users[i]
print(u.name, u.age)
i = i + 1
}
}match on a Result/Option payload inside a loop compiles:
fn parse(s: string) -> Result<int, string> {
if s == "bad" { return Err("not a number: ${s}") }
return Ok(s.len())
}
fn main() {
inputs = ["one", "bad", "three"]
for s in inputs {
match parse(s) {
Ok(n) => print("ok", n),
Err(e) => print("err", e),
}
}
}Plus: m.get(k).unwrap_or(default) on int maps, len(s) on a string, namespaced stdlib calls returning strings typed correctly, bare return in an inferred-void main, and array-index typing no longer keying off variable names (yes, really; it's fixed).
The parser also no longer hangs on the removed &&/||; you get a clean "use and/or" error instead. And since mechanical migrations should be mechanical, there's a new tool for it:
wyn fix src/main.wyn # rewrites && -> and, || -> or, elseif -> else ifThe FFI meets real-world headers
v1.17's wyn bind worked on clean headers. Real headers are not clean; they are wallpapered with export macros. wyn bind now strips __attribute__((...)) decorations, which took lz4 from 0 to 44 generated bindings and zstd from 0 to 59. Bare unsigned returns map to int. Three new curated recipes ship: lz4, zstd, jsonc.
One more fix that anyone on macOS with Homebrew will appreciate: wyn add now records pkg-config's -L directories into [ffi].lib_dirs, so libraries living off the default linker path (hello, Homebrew Cellar) actually link.
Lambdas: honest about the edges
Lambda parameter types are now inferred for integer lambdas, and .map/.filter/.reduce work as before. String- and float-parameter lambdas do not work yet, and instead of a cryptic error or a silent miscompile, the compiler now says exactly that, with a workaround: use a named function. Sometimes the correct fix for a bug is a truthful error message until the real implementation lands. (Spoiler: it landed shortly after.)
Also in the surface-cleanup department: the File namespace now works uniformly via both File.x and File::x, one step toward making . the one canonical call syntax.
Upgrading
wyn --version # 1.18.0No breaking changes, no source edits. If v1.17's cuts are still in your codebase, wyn fix migrates them for you.
Browse the version history.