The compiler reports every error it can find in one pass, not the first. Each has a stable code, the place, the reason, and what to write instead where that is known. Failures while running have the same shape, so nothing that reads diagnostics needs to know which kind it has.
error[K0201]: expected end of line, found `{`
--> typo.kest:16:15
|
16 | let p = P { x: 1, y: 2 }
| ^ a struct is built by position, in the order its fields are declared: `P(...)`
A diagnostic about more than one place shows each: a broken promise shows the line that breaks it, the promise, and every call in between; a failure while running shows the calls that led to it, outermost last. A diagnostic shows at most eight places and says how many it left out. A warning starts with warning and does not stop the build.
A code's meaning does not change between versions; its words may be improved. The first two digits say where it comes from:
| Codes | From |
|---|---|
K01xx |
reading the characters of a file: an unterminated string, a byte that begins no character, an escape that is not one |
K02xx |
the shape of the program: a missing bracket, a statement that cannot start there |
K03xx |
types and bodies: an unknown name, a type mismatch, a missing case, an argument count |
K04xx |
promises: no.alloc, no.host and deterministic |
K05xx |
what cannot be compiled: a limit reached, a constant that cannot be worked out, an unused declaration |
K06xx |
running: division by zero, an index past the end, a ceiling, a budget, a failed door, a host mistake caught at the boundary |
K07xx |
files and modules: an import that is not found, a module in the wrong place, a file that cannot be read |
These are what people and models new to Kest write most often, and what the compiler says.
| Written | Said | Instead |
|---|---|---|
max(1, 2) without importing |
K0306 unknown name max |
import std.math and write math.max(1, 2) |
P { x: 1, y: 2 } or P(x: 1) |
K0201 a struct is built by position |
P(1, 2) |
io.print(count) |
K0310 value expects text, found i32 |
io.print("{count}") |
"{f(\"x\")}" |
K0102 a hole holds code, so a string inside one needs no escape |
"{f("x")}" |
"{n:4}" |
K0201 expected the end of the hole, found : |
"{text.right("{n}", 4)}" with import std.text |
fn hurt(n: Npc) { n.hp -= 1 } |
warning K0346: the parameter is a value, so the write is discarded |
fn hurt(n: Npc) -> Npc and give the changed one back |
store<Node>() |
K0211 the type is not given where it is called |
let nodes: store<Node> = store() |
if (x < 3) { } |
K0213 a condition is written without brackets round the whole of it |
if x < 3 { } |
match over a number or text |
match chooses between the cases of an enum |
if |
Three mistakes the compiler cannot refuse: a field of a parameter written and not given back (it warns), remove(xs, i) inside for i in 0..len(xs), which reads past the end once something is removed (walk a store instead, or gather what to remove and remove it after), and treating a struct as shared when it is a copy.
| Code | Says | Why it is there |
|---|---|---|
K0346 |
a write to a parameter's field is discarded | the most common bug for someone coming from a language where objects are references |
K0347 |
an array has no room, so every fit into it writes nothing |
array() makes no room; give it some with room or array(n, v) |
K0506 |
nothing calls an extern, so no host is asked for it |
a host author reading the file would bind it for nothing |
K0508, K0509 |
nothing reads a constant, nothing names a type | dead declarations |
K0511 |
nothing writes through an import | an import is read and compiled whether used or not |
K0512 |
nothing reads a local | a value worked out for nobody, or an if let whose name could be != none |
These are said about the file that was named, not the library it imports.
--json emits the same set, notes and suggestions included, for tools and for models repairing their own output:
{
"schema": 4,
"diagnostics": [
{
"severity": "error",
"code": "K0309",
"file": "bad.kest",
"line": 8,
"column": 12,
"offset": 109,
"length": 7,
"message": "`doc.hurt` takes 2 arguments, found 1",
"notes": [
{"file": "bad.kest", "line": 3, "column": 19,
"message": "this one was not written"}
]
}
],
"errors": 1
}
A diagnostic about a whole file has file and no line; one about the whole program has neither. suggestion is the line shown under the caret, and leftOut counts the places that did not fit. schema changes when a field changes meaning or goes away, not when one is added; see Versions and editions.
A few numbers are limits because an instruction holds them in two bytes or a frame counts them in one. Each is a diagnostic with the number in it (K0502 for how many, K0503 for how far, K0630 for more than len can count), never a wrap or a silent cut:
| At most | What |
|---|---|
| 256 | names in a function, counting its parameters |
| 16 | loops one inside another |
| 128 | expressions one inside another |
| 32 | breaks in one loop, and 32 continues |
| 32 | defers in a function |
| 65535 | bytes of code a jump reaches, or a loop reaches back |
| 8 | scratch { } blocks one inside another in one body |
| 64 | scratch { } blocks one machine holds open at once |
| 8 | things one match chooses between at once |
| 256 | combinations one match answers before it needs an else |
| 65535 | elements in a [T; N], and at least one |
| 65535 | bytes, and slots, in one value: a struct, an enum, a [T; N], or an optional, which is what it holds and one byte more (K0327) |
| 65535 | slots a function's names hold at once, and slots of values it works on at once, such as two values of 32768 slots side by side in an array being made |
| 65536 | constant values in one function, counting every element of a table it reads where it is written |
| 65536 | doors a program asks the host for |
| 2147483647 | elements in an array or a store, and bytes in text |
| 16777215 | places in one store |
| 1099511627775 | places a process hands out, counting the ones reused |
All but the last three are found while compiling. The rest are met while running.