loop, and the Preamble Gets No allowStatus: 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)
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.
while whose condition is the literal true is emitted as loop { … }.Not while true. Everything else about the lowering is unchanged.
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.
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.
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.
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.
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:
while true lowers to loop and the words while true are not in the
output;while (D2’s second paragraph);5 then after, so
the break leaves the loop and the line after it is reached.