The Name That Was Taken

The Scheme interpreter named a function `run`, and on the C backend the call to it compiled to a shell command instead — because `run` is also a built-in, and the compiler matched the built-in before it considered the user's definition. The interpreter had always gotten this right, so only the compiled backend was wrong. It was the fifth name in a slow parade of the same bug — index, remove, acct, dup, and now run — and the pattern was clear enough to stop treating instances and end the category. A single helper now asks whether a name is bound by the user before any built-in claims it, guarding about thirty collision-prone built-ins at their call sites. The guard is provably safe: it changes behavior only when the user actually shadowed a built-in, so every program that did not is byte-for-byte unchanged. A user's definition should win over a built-in, and now it does.

merecompilername-resolutioncodegenlanguage-design

The Scheme interpreter had a function named run — the loop that reads and evaluates each top-level form. On the interpreter it worked. On the C backend the program did something bizarre: it tried to execute its own source text as a shell command. The cause was that run is also a built-in — a way to run an external command line — and the compiler’s code generator matched that built-in at the call site before it ever considered that the user had defined a function by the same name. The interpreter resolved names the ordinary way, where a later binding shadows an earlier one, so it had always been correct; only the compiled backend reached for the built-in first.

The fifth time is a pattern

This was not the first name to collide. It was the fifth: earlier probes had tripped over index and remove colliding with C library symbols, and a ledger had named a function acct, which is a POSIX call. Each had been fixed in place — this one sanitized, that one renamed — and each fix had quietly acknowledged that the list of dangerous names was inherently incomplete. Five instances of the same shape is the point where fixing instances stops being the right move. The bug was never really about run or index or acct; it was about a resolution order that let a built-in claim a name the user had already bound. The fix had to end the category, not add a sixth name to a list.

Making the user win

There was already a precedent buried in the codebase: a few built-ins with common names — a thread join, the character predicates — carried a guard that stepped aside if the name was bound by the user, added case by case as each one bit someone. The fix generalizes that precedent into a single helper that asks one question — is this name bound locally, captured from an enclosing scope, lifted from an inner function, or defined at the top level? — and puts that guard on about thirty built-ins whose names a user might plausibly choose: run, spawn, even, odd, show, abs, and the rest. When the answer is yes, the call site falls through to the ordinary user-call path; when no, the built-in is emitted exactly as before.

Provably safe, and half a problem closed

The reason the change could go in broadly, all at once, is that it is safe by construction. The guard fires only when the user actually bound the name — and a program that binds run as a function is a program the compiler was already miscompiling. Every program that does not shadow a built-in produces byte-for-byte identical output to before; the whole test suite confirmed it, passing unchanged. What this closes is the built-in half of the reserved-name problem — user definitions now win over Mere’s own built-ins at the call site. The other half, collisions with C keywords and POSIX symbols in the generated code, is still handled by a name sanitizer, and the fully general fix — namespacing every user name so nothing can ever collide — remains deferred, because with the built-in half closed it is a larger change for a smaller remaining gain. A category of bug that had recurred five times is now structurally gone, which is worth more than the fix to any one instance of it.

← Back to Mere: Building a Language