Skip to content

v0.16.0 — Mojo 1.1.0, and error messages that say what they mean

Choose a tag to compare

@codetalcott codetalcott released this 17 Sep 23:42
· 25 commits to main since this release
4064374

Adopts the Mojo 1.1.0 stable toolchain (max = "==26.6.0", from Mojo
1.0.0 / 26.5.0). This is a minor rather than a patch for that reason alone:
downstream addons compile against the published src/, so taking this upgrade
moves them onto that toolchain. No public Mojo signature changed, so they
compile unmodified once there. Full account in
docs/toolchain-migrations.md.

It also corrects every computed error message the framework produces. Those
trailed heap garbage in every release to date — see Fixed, below. Anything
asserting on a type-mismatch message with a substring match will keep passing;
anything comparing one whole will now see the message it expected.

Fixed

  • Every computed error message trailed heap garbage. The eight
    throw_js_*_dynamic helpers passed a Mojo String's buffer to
    napi_throw_*, which reads a NUL-terminated const char*; a Mojo String
    carries no terminator, so JS saw greet: expected string, got numberuffer
    (the tail is whatever the heap held next). Every generated type-mismatch
    error was affected. The message is now NUL-terminated with
    as_c_string_span() before the call, and the tests assert the whole
    message with toBe rather than toContain, which is how the garbage
    passed. Found by running examples/codegen/codegen.js in CI for the first
    time and reading its output. Mutation-checked: reverting all eight
    terminations fails 2 of the 6 exact assertions — the tail is whatever the
    allocator left adjacent, so a given message may come back clean, which is
    the other reason toContain could never have caught this.
  • Legacy closure parameters spelled bare capturing read a dead stack slot
    on 1.1.0, silently.
    with_handle_scope and parallelize_safe now declare
    capturing[_], which binds the captured values' origins. The addon's
    scopedCall was reading a garbage JsFunction from a reclaimed slot and
    failing with napi_invalid_arg; tests/host.test.js caught it. No warning
    or error is emitted for the bare form, so the only signal is behaviour.
  • parallelize_safe did not compile against MAX 26.6, which moved
    parallelize to a unified closure argument (parallelize(func, n)). It
    keeps its legacy public signature and wraps func in a unified closure.
    The breakage was invisible to build.sh and to the whole Jest suite —
    nothing instantiates parallelize_safe in the addon graph, so Mojo never
    elaborated its body — and was surfaced only by
    tests/compile/framework_coverage.mojo.

Added

  • docs/MOJO-RULES.md ships in the package — the "Mojo dialect and FFI
    rules" section of the repository's CLAUDE.md, extracted verbatim by
    scripts/generate-rules-extract.mjs and held in lockstep by CI. A downstream
    addon compiles against this framework's source and until now got none of the
    rules that keep that code from crashing: a bare capturing reads a dead
    stack slot, _ = x^ is not a keep-alive, the function-pointer bitcast is
    spelled in exactly one place. A hand-copied extract would drift silently,
    which is why it is generated.
  • NodeHost.console_error(msg) and the hostConsoleError export (#113) —
    writes one line to stderr through the host's console.error, the diagnostic
    counterpart to console_log. console_log was the only output method on
    NodeHost, so a host-mode program whose stdout is piped into something else
    had no way to say anything about itself without corrupting that stream.
    Routing through the host console (rather than writing to fd 2 directly)
    keeps output interleaved correctly with the host's own writes and honours
    whatever the embedder has done to console.
  • parallelSquares(n, scale) — returns a Float64Array where
    [i] = i * i * scale, computed through parallelize_safe. A diagnostic
    export in the spirit of asyncRuntimeInitOk(), covering the other silent
    failure mode: the work runs but the captures are wrong. Every element
    depends on both captured values and the buffer is pre-filled with a
    sentinel, so a broken capture or a skipped index cannot produce a correct
    array. It is also the only thing that instantiates parallelize_safe in the
    addon graph, so a break there can no longer be invisible to build.sh —
    which is exactly how this bump's breakage hid. Mutation-checked: reverting
    runtime.mojo to bare capturing makes it SIGSEGV (node exits 139), so the
    regression surfaces as a crashed Jest worker rather than an assertion diff.

Changed

  • @parameter → @__parameter on closures passed as parameters (1.1.0
    rename; the old spelling warns).
  • StringLiteral.unsafe_ptr() → ptr() at the 38 sites the compiler flagged.
    String.unsafe_ptr() and StaticString.unsafe_ptr() are unchanged.
  • The addon build and the compile-coverage gate are warning-clean again. Five
    "assignment never used" warnings that the dev2026080905 notes recorded as
    false positives on capturing closures are gone: they were a symptom of the
    untracked capture, not a compiler quirk.
  • CI now compiles every piece of the repo's own Mojo with --Werror
    (build.sh and tests/codegen/build.sh default to it; NAPI_MOJO_WERROR=0
    opts out for a toolchain bump), with scripts/check-werror.mjs proving on
    every run that the flag still fails on a warning inside an imported module.
    spike/ffi_probe.mojo's dead except branch was flattened to get there.
    The Mojo samples in README, TUTORIAL, CONTRIBUTING and CLAUDE.md are now
    checked verbatim against CI-compiled source (scripts/check-doc-samples.mjs);
    the README's struct example quoted a function that existed nowhere and now
    quotes the tutorial's. examples/codegen/ is built and run in CI for the
    first time. Decisions and the deliberately ungated remainder are recorded in
    docs/plan-lazily-checked-artifacts.md.
  • The changelog-diffing recipe in docs/toolchain-migrations.md now points at
    the modular monorepo's current layout (Mojo/docs/site/..., release tags
    mojo/v<X.Y.Z>). The old mojo/docs/... paths 404, which reads as "no such
    changelog" rather than "wrong path".