Status: Superseded by ADR-285 — its D1 is D22, D2 is D26 (as narrowed by ADR-249 D3), D3 is D28 there Extends: ADR-060 D2, which gave that to a literal and did not reach a sum Related: ADR-043 D5 (the constant fold this uses, and the two messages it already decides), ADR-028 (the emitter has no types, which is what shapes D3), ADR-048 (the numeric surface: two integer types a program may write)
ADR-060 D2 gives an unconstrained literal the first type that holds
it, so let big = 3000000000 compiles. Its D3 says why that needs no analysis,
and the argument is exact — and it is about a literal. A sum is not a literal,
and the rule did not reach it. Built and run:
let b = 3000000000 + 1 → 3000000001
let c = 2000000000 + 2000000000 → refused
println(f"{2000000000 + 2000000000}") → refused
let a = 2000000000 … a + a → refused
And what “refused” means is the point. Not a message of ours:
error: src/main.nika:2:5: this arithmetic operation will overflow
2 | let c = 2000000000 + 2000000000
That is the backend’s own lint, in Rust’s words, on the Nikaia line that wrote it
— Part III C.1’s class, which this compiler calls a bug of its own.
ADR-043 D5 built NK1116 and NK1118 for exactly that reason: what
it takes back is the message. The sum with nothing beside it was the case those
two did not cover, because neither a type beside it nor an operand’s declaration
said what it should fit.
Two passes already exist, and the fold already runs in the first one.
constant_of folds in an i128 with every step checked_, follows an immutable
let to its value, knows the five operators and the negation, and records the type
an operand’s declaration pinned. At a bare let it is called twice already —
once to ask whether the value fits, once to carry it to the next name. The folded
value is in hand at the place where the decision falls.
The compile-time interpreter is not what answers this. It is a 143-line stub
that prints Bind: x = <evaluated> and has no store for values, and what a
program may run at compile time is itself an open question. Hanging a language rule
on it would block a small answer on a large undecided one.
ADR-060’s rule, one step wider: the unit is the expression and
not the token. i32 if it holds the value, else i64; a value neither holds is
refused in this language’s words rather than widened, because there is no
third type to move to.
Decided once, on the outermost expression that folds, and reaching every literal
under it. Rust computes in the type of the operands, so one suffix on one half
would be two types meeting across a +. What is emitted is
2000000000i64 + 2000000000i64.
Not where the position decides the type. A sequence index and a repeat count
are usize, and both are deliberately left bare for Rust’s own inference —
index::at(0) and count::of(2) have nothing to infer from
(ADR-048, ADR-054). A suffix written into one would pin
the type the position is there to give, and what came back would be a message about
the generated file.
A value that fits is untouched, which is what keeps every program that compiles today compiling: a suffix pins what a use is supposed to answer (Part I 2.4).
let a = 2000000000 is an i32 — the first type that holds it — so a + a is an
i32 sum, and it does not fit. It is refused, and the refusal is ours:
“this comes to 4000000000, which does not fit in an i32“, with the way out in
one word, let a: i64 = 2000000000.
Why it is not widened. Widening a + a would mean widening a’s
declaration — a walk from a use back to a binding. That is exactly the analysis
ADR-060 D3 was cheap for not needing, and it is the expensive half in
every form it takes. It is also the answer every language with two integer widths
gives: arithmetic happens in the type of its operands.
So the rule reaches exactly as far as the expression does, and a name is a step outside it. That is predictable from the page: a number takes the first type that holds it, and a name is a number that already took one.
The arithmetic moves to crate::fold, and what a name is worth becomes a
parameter. The checker passes what it knows; the emitter, which has no scope
and no types (ADR-028), passes nothing_is_known — and thereby gets
exactly the subset of expressions that can be answered without looking anything up,
which is D1’s subset and no more.
It is a function and not an absent argument so the difference is visible at the call: the emitter is not skipping the lookup, it has none. Two constant folds in one compiler would be one fold and one liability.
The class is closed. Every constant integer expression now gets an answer from
this compiler: widened where an i64 holds it, refused in this language’s words
where no type does, and arithmetic in the pinned type where a declaration or a name
says so. No arrangement of them reaches rustc’s overflow lint any more.
What it costs is one sentence of ADR-060 D3, restated honestly. The emitter reads an expression where it read a token — but an expression containing nothing but tokens. It still has no types, consults no scope and asks no use site. “No analysis” becomes “no lookup”, which was always the expensive half and is the half that is still not there.
And the emitter already had this distinction. It asks “is this written only in literals?” for an index, for precisely the reason that the answer is then fixed without types. D1 is the second user of that idea rather than the first.
NK1116 and NK1118 were built
to close.3000000000 + 1, which compiles today. A refusal is free before programs
exist and breaking afterwards, and this one would break a program the page calls
correct.All three, and the four programs in §1 now answer this way:
let c = 2000000000 + 2000000000 → 4000000000
println(f"{2000000000 + 2000000000}") → 4000000000
let a = 2000000000 … a + a → NK1116, "this comes to 4000000000,
which does not fit in an `i32`"
9000000000000000000 + 9000000000000000000
→ NK1116, "does not fit in an `i64`"
crate::fold holds the arithmetic and wants_widening; check::Checker::constant_of
is the lookup over locals; the emitter folds at the outermost Expr::Binary, carries
the answer inward on Flow::widen, and refuses to start one under Flow::inferred —
an index and a count. let small = 2 + 3 and let small = 42 are written exactly as
before.