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