ADR-085: The Unconditional Loop Is Lowered to loop, and the Preamble Gets No allow

Status: Superseded by ADR-276 — its D1-D2 are D3, its D3-D4 are D4-D5 there Date: September 14, 2026 Target Version: Nikaia 0.0.8 Supersedes: ADR-084 §3’s paragraph “while true becomes writable, and the generated file says something about it”, which recorded the warning as an open defect and named a preamble allow as its fix. The defect is the same; the fix is not Related: ADR-070 D1 (while true { … } is the unconditional loop, and the absence of a second spelling is a decision), ADR-084 (which made the shape usable and so made the warning common), ADR-011 D2 (name for name), Part III C.1 (a message about the generated file is a bug in this compiler), [#227](https://github.com/Nikaia-Language/Nikaia/issues/227) ADR-093 (the unreachable return, which this unblocked and which is now built)


1. Context

ADR-084 gave the language a break, and with it while true { … } became a shape somebody would write — until then it could only be left by a return, which is why no .nika file in the tree contained one. What the emitted Rust then says is:

warning: denote infinite loops with `loop { ... }`
  --> countdown.rs:22:5
   |
22 |     while true {
   |     ^^^^^^^^^^ help: use `loop`
   = note: `#[warn(while_true)]` on by default

A warning naming a line of a file nobody wrote, with a help naming a keyword this language does not have. Part III C.1 calls an error of that shape a bug in this compiler; a warning is the same thing one severity down, and “a Rust warning about the generated file” is already on [#137](https://github.com/Nikaia-Language/Nikaia/issues/137) §1’s list of things that were defects here and were fixed.

ADR-084 §3 recorded it and named the obvious fix — one #![allow(while_true)] beside the #[allow(unused_imports)] the preamble already carries — and left it, on the grounds that what goes in the preamble is a decision about every file this compiler writes. That reasoning was right and the conclusion it was used for was wrong: the preamble is not where this is answered.

2. Decisions

D1 — A while whose condition is the literal true is emitted as loop { … }.

Not while true. Everything else about the lowering is unchanged.

D2 — This is name for name, not an exception to it.

ADR-011 D2’s rule is that the emitter transcribes rather than invents, and the question it answers is what is the language below’s name for this thing. ADR-070 D1 already decided what the thing is:

while true { … } is the unconditional loop and there is no second spelling for it

Rust’s name for the unconditional loop is loop. So loop is the translation of the program and while true is a transcription of its letters — and the letters are what the language below objects to. The rule is unchanged; this is the one construct where the two readings of it come apart, and D1 picks the one the rule is for.

The literal only, never a name that happens to be true. The equivalence is ADR-070 D1’s and that decision is about the written form; while running { … } stays a while even where running is a let bound to true. The polarity is the usual one — claim it where it is certain and nowhere else.

D3 — The preamble gets no allow, and the reason generalises.

An #![allow(while_true)] would have silenced this in one line, and it is the worse answer for two reasons that are worth separating.

It is a blanket, and blankets accumulate. The preamble is emitted into every file this compiler will ever write. A suppression there is not “we looked at this and it is fine”; it is “nobody will be shown this again”, including in the cases nobody has looked at yet. The next lint would arrive to a precedent, and the file would grow a list that no longer says anything about what was checked.

And it treats the symptom. The warning was not noise — it was rustc reading the generated code correctly and saying that it does not mean what its shape suggests. The emitted program was worse than it needed to be, which D4 is about. A fix that hides a true statement about the output leaves the output as it was.

The general rule this records: where the language below complains about emitted code, the first question is whether it is right. An allow is the answer only once that question has been asked and answered no — and then it belongs as near the emitted construct as it can be put, not in the preamble.

D4 — And it is not cosmetic: the two forms have different types below.

This is why D3’s “treats the symptom” is a statement about the program and not about tidiness. In Rust:

fn a() -> i32 { loop { } }        // compiles: `loop` diverges, so it is `!`
fn a() -> i32 { while true { } }  // error[E0308]: mismatched types

while true { } is (); loop { } is !.

ADR-093 wants a function that genuinely never returns — an accept loop, an event loop, a supervisor — to stop needing a return that cannot be reached. No change to the checker can deliver that while the lowering emits the form the language below refuses: the checker would stop refusing, and rustc would refuse instead, about a file nobody wrote. D1 is therefore the prerequisite for that entry rather than a nicety beside it, and this record is what puts it in place.

3. Consequences

The warning is gone from the corpus, measured rather than assumed: every .nika file in examples/ and tests/samples/ lowered and compiled, and while_true appears nowhere.

ADR-093 loses its blocker — and took it, one record later. What is left of it is the checker half — recording, per loop, whether a break reached it — and ADR-084 D4’s loop count is what answers that. The entry stays open; it is one step shorter.

What the same survey found and this does not fix, because it is a different defect with a different owner: catch { … } whose handler never mentions the error emits Err(error) => …, and rustc says “unused variable: error” about a name the emitter wrote — the user wrote catch { 1000 }. That is C.1’s class exactly and it has nothing to do with loops; it is on [#221](https://github.com/Nikaia-Language/Nikaia/issues/221) with its reproduction. The same survey also found unused variable warnings for names the user did write, a let and a for binding, and those are deliberately left alone here: the message is actionable and only its file is wrong, which is a smaller and different problem from a message about something nobody wrote.

No program changes and nothing runs differently. loop { … } and while true { … } execute identically; the test that says so runs the program rather than reading the emitted text.

4. What is built

D1, in the Stmt::While arm of the emitter — a match on the condition being Expr::LitBool(true), and the comment there carries D2’s reasoning and D4’s, because an emitter special case with no reason attached is one the next person removes.

Three tests in crates/nikaia/tests/jumps.rs:

This site is open source. Improve this page.