ADR-076: A Name the Language Below Reserves Is Escaped Where It Is Written, and Refused Only Where It Cannot Be

Status: Superseded by ADR-298 — its D1-D4 are D9-D12 there Date: September 14, 2026 Target Version: Nikaia 0.0.8 Related: ADR-011 D2 (the emitter writes a name for a name and resolves nothing — this is one of that decision’s bills), ADR-051 D1 (reserving a word costs nothing before programs exist — and why that rule does not apply here), ADR-056 (a rule and not a list, at the same boundary one position over)

1. Context

fn main() {
    let type = 3
    println(f"{type}")
}

lowered to let type = 3;, and rustc answered

error: expected identifier, found keyword `type`
help: escape `type` to use it as an identifier

about a file nobody wrote, with advice that means nothing in this language. Part III C.1 calls that a bug in this compiler, and it is one this compiler had in every position a name can stand in — a local, a parameter, a field, a function’s own name, a struct, an enum, a variant, a lambda’s argument, a for binding.

Not the instance but the class, and the class had to be measured twice. The first sweep lowered let W = 3 for every Rust keyword Nikaia does not reserve and read rustc’s “found keyword”: twenty-four words. It was wrong by three. crate, super and box fail at a let with a different message each — E0532 about a module, E0433 about leading super, “expected pattern” — so a sweep keyed on one message missed them. Twenty-seven words, found by compiling every word in every position instead, which is the shape §6’s test now keeps. gen and union are the only candidates Rust accepts as a name.

2. Decisions

D1 — The name is escaped where it is written. It is not reserved here.

type becomes r#type at every position the emitter writes a name, and stays type in the source, in every diagnostic, and in the ledger.

The obvious alternative is to reserve those twenty-seven words in Nikaia, and it is wrong. ADR-051 D1’s rule — reserving costs nothing before programs exist — is about words this language needs for its own constructs, and every word it has reserved is one Nikaia itself uses. These are not: type is the field name of every tagged record anybody has ever written, and move, ref, static and final are ordinary words. Reserving them would let the backend decide what this language’s vocabulary is, which is the same mistake ADR-054 D1 refused in the other direction when a cast could name any type the language below has.

The cause is ADR-011 D2 — the emitter writes a name for a name — so the fix belongs where the name is written. That is ADR-056’s a rule and not a list applied at the same boundary one position over: there it was a relayed message naming a type the program never wrote, here it is a relayed name.

D2 — The escape is at the emitter’s name position, and never at text.

One line in Emitter::text would have covered every position at once, and it would have been a silent defect. text is also what the checker’s recorded answers are keyed by — pausing_methods, fallible_methods, nullable_fields are all keyed by the name the source wrote — so escaping there would make every one of those lookups miss, on exactly the programs this exists for. The two uses of one name stay two: escaped(name) is written, name is looked up.

D3 — crate, super, self and Self are refused, because there is no escape to write.

Rust’s answer to r#crate is “crate cannot be a raw identifier”, and the same for the other three. So D1’s one rule has one forced exception, and it is the target’s exception rather than a choice made here: NK1128 refuses those names at every position that declares one. self keeps NK1119, which says more — it is a reserved word of this language, with a reason of its own.

Three words rather than twenty-seven is the whole point of D1. crate and super name a module tree this language does not have, and Self is already how it writes the type an impl is on, so the vocabulary this costs is one nobody reaches for.

D4 — The two questions asked of a declared name live in one function.

NK1119 and NK1128 are asked at the same positions, so they are asked by one call (Checker::nameable) rather than two. A position added later is added to both, which is the failure that was waiting: not_self never covered an item’s own name, because struct self cannot parse — and struct crate parses fine and went straight through.

3. Consequences

struct Event { type: String } compiles and runs, and prints what was put in it. That is the program this was worth doing for.

The generated file says r#type, which a reader of it sees and nobody else does: the source map is by line (ADR-044 D1), and a rustc diagnostic that survives to the user names the line rather than the identifier.

Three names are gone from the language, and Part I 2.1’s table does not list them, because they are not reserved words of this language — they are refused by a rule about the target. The diagnostic says exactly that rather than claiming they are keywords here.

4. What this does not decide

5. What this cost to find

The first measurement reported twenty-four words and was wrong by three, because it read one rustc message at one position. The sweep that replaced it compiles every candidate in every position and asks only whether the program builds, which is the property actually wanted. Two of its own first versions failed for reasons that were the test’s: a local shadowing the function of the same name, and a let where the program needed let mut. Both are written into the test, because a sweep that is the coverage proof has to be one a reader can trust.

6. What is built

emit::escaped and Emitter::name over a twenty-five-word list, applied at every position the emitter writes a name; Checker::nameable, which asks NK1119 and NK1128 together, called from the six declaring positions it already had and from the item names it did not. Exercised by crates/nikaia/tests/reserved_below.rs: the twenty-five words each written in every position and compiled, the two Rust accepts checked not to be escaped, the three it cannot escape checked to be refused at each of eight positions, and one program with a field called type run to see that it prints click 7.

This site is open source. Improve this page.