Skip to content

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:

wyn
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):

wyn
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):

wyn
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:

wyn
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:

bash
wyn fix src/main.wyn     # rewrites && -> and, || -> or, elseif -> else if

The 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

bash
wyn --version        # 1.18.0

No 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.

MIT License - v1.19.1