Deleting the List
The reserved-word list had been wrong six times and its own comment admitted it would be wrong again. The robust fix — prefix every user value and function name so nothing can ever collide with C — had been deferred for months as a large, risky change. Investigation dissolved the risk: the self-hosting fixpoint runs entirely on the WAT backend and never touches C naming, and the sanitizer was already the single chokepoint every name flows through. So the prefix went in and the list came out. The uniform prefix turned out to be self-verifying — any emission path that skips the sanitizer now fails for every function, not just unlucky names — and that property immediately flushed out three latent mismatches the list had been silently masking. One retreat: type names stay raw, because the recursive-variant machinery keys on them. Both halves of the reserved-name problem are now structurally closed.
The comment next to the reserved-word list had been honest for months: “this list is inherently incomplete — the robust fix is to namespace all user top-level fn names, deferred as a larger byte-stream change.” Six collisions later, the deferral was the only part of that sentence still doing any work. What kept it deferred was fear with a specific shape: the compiler self-hosts, the self-hosted output is verified byte-identical, and a change to how every name is spelled sounded like the kind of thing that breaks a fixpoint.
The risk that wasn’t there
So the first move was not code but investigation. It returned two facts that dissolved the
fear. First, the byte-identical fixpoint lives entirely on the WebAssembly path — the
Mere-written compiler emits WAT, names its functions by index, and has no C backend at all,
so the C name mangler and the fixpoint share nothing. Second, the C backend already had a
single chokepoint: one sanitizer function that every user name flows through on its way to
becoming a C identifier, called from about forty sites. The deferred-as-huge change was, in
its core, editing one function: instead of checking a list and appending an underscore to
the unlucky, prefix everything. A user’s foo becomes mu_foo, and no user name can
collide with a C keyword, a POSIX symbol, or a Bessel function, because no C name starts
with that prefix. The list — sixty-odd entries of accumulated caution — gets deleted.
The prefix that checks its own work
The property that made the change safe rather than merely correct is that a uniform prefix
is self-verifying. Under the old scheme, an emission path that forgot to consult the
sanitizer produced correct C for almost everyone and broke only the person who named a
parameter y0 — silent until unlucky. Under the new scheme, a path that skips the
sanitizer emits an unprefixed name whose every reference is prefixed, and that mismatch
fails to compile for every function it touches. The whole test suite becomes a detector.
And it detected: three latent def/use mismatches surfaced within minutes of the change —
pattern-match binders declared raw while their references were sanitized, capture arguments
passed raw at lifted-call sites, and one path that applied the prefix twice, producing
mu_mu_. Each was exactly the class of bug that had produced the six collisions, sitting
in the code all along, masked by the fact that the old sanitizer was a no-op for ordinary
names. The migration surfaced its own stragglers.
The retreat that was also a finding
One part of the plan didn’t survive contact. The first attempt prefixed type names too, and records took it cleanly — but variants broke, in a subtle way: the recursive-variant machinery keys its lookups on the raw type name, and those lookups decide, among other things, whether a value is accessed with a dot or through a pointer. Prefixing the name at some sites and not others sent a heap-allocated closure through a by-value code path. Rather than chase that machinery through every corner, the change retreated to a cleaner line: values and functions are prefixed; type names are a separate C namespace and stay raw, split into their own mangler. Type names have never collided with C in practice, and if one ever does, it is a contained, separate fix. Knowing precisely where the boundary of a change should sit is worth as much as the change itself.
The suite ended green — two thousand two hundred and two tests, the roughly forty assertions that check emitted C updated to the new spelling, everything byte-identical for programs that never shadowed anything. Two episodes ago, a guard made user definitions win over the language’s own builtins; this release makes user names unable to collide with C at all. The reserved-name problem, which had produced a bug roughly every other week of dogfooding, is now closed from both sides — not by a longer list or a more careful audit, but by making the collision impossible to express. The best fix for a recurring bug is a world where it has nothing to collide with.