Every diagnostic on this page is real output, produced by running the compiler on the source shown next to it. The shape is always the same:
severity[CODE]: message at file:line:columnfollowed by a summary line — N error(s) found when the compilation failed, or
OK (N diagnostics) when only warnings and hints were emitted. Warnings and
hints never stop a build.
Two commands, two gates
atoll check resolves names, types, effects, and errors. atoll build also
lowers the program to WebAssembly. A program can pass check and fail
build, so treat check as a fast inner loop and build as the real
acceptance test:
fn banner(titles: []string): string {
return titles.join(", ")
}
fn main(): void {
println(banner(["ship", "review"]))
}$ atoll check src/main.at
OK (0 diagnostics)
$ atoll build src/main.at
error[ATOLL2004]: builtin method `join` has no lowering path — declared in the
prelude but never implemented (no body, host import, or intrinsic), so this
call cannot be compiled to wasm at src/main.at:2:12
1 error(s); build abortedATOLL2004 means the method exists in the prelude — so it type-checks — but
has no implementation behind it. Only reachable code is lowered, so an
unused function containing such a call will never report it. Write the loop
yourself until the method lands:
fn banner(titles: []string): string {
mut out := ""
for title in titles {
if !out.is_empty() { out += ", " }
out += title
}
return out
}
fn main(): void {
println(banner(["ship", "review"]))
}Feature Status lists which prelude methods are currently in this state.
Unresolved names — ATOLL1009
The resolver runs before typing, so a misspelled name is reported on its own and nothing downstream of it is trustworthy.
struct WorkItem {
title: string
done: bool
}
fn open_titles(items: []WorkItem): []string {
mut out: []string = []
for item in items {
if !item.done {
out.add(itm.title)
}
}
return out
}$ atoll check src/main.at
error[ATOLL1009]: unresolved name `itm` at src/main.at:10:21
1 error(s) foundThe range points at the use, not at a declaration, because the compiler has
no candidate to point to. The same code covers a name that exists but is not
in scope: a private declaration in another file, or a member missing from an
import list.
struct WorkItem {
title: string
done: bool
}
fn open_titles(items: []WorkItem): []string {
mut out: []string = []
for item in items {
if !item.done {
out.add(item.title)
}
}
return out
}
fn main(): void {
mut items: []WorkItem = []
items.add(WorkItem { title: "ship", done: false })
items.add(WorkItem { title: "review", done: true })
println("${open_titles(items).len()} open")
}A near miss on a method is a different code, because the receiver’s type is already known:
fn shout(word: string): string {
return word.uppercase()
}$ atoll check src/main.at
error[ATOLL2003]: no method `uppercase` on type `string` at src/main.at:2:12
1 error(s) foundType mismatches — ATOLL2002
A mismatch names both types and, where the checker recorded one, the boundary that established the expectation.
struct WorkItem {
title: string
weight: string
}
fn total_weight(items: []WorkItem): int {
mut total: int = 0
for item in items {
total = item.weight
}
return total
}$ atoll check src/main.at
error[ATOLL2002]: expected `int`, found `string` at src/main.at:9:17
1 error(s) foundThe parenthetical suffix is the blame: (from a type annotation),
(in argument position), (operands of a binary operator). It tells you
which side is the expectation, which is usually what you actually want to
change.
The mismatch you will hit first is Option. Indexing and get return T?,
never T:
fn first_plus_one(xs: []int): int {
return xs[0] + 1
}$ atoll check src/main.at
error[ATOLL2002]: type mismatch: expected `int?`, got `int` (operands of a
binary operator) at src/main.at:2:12
error[ATOLL2002]: expected `int`, found `int?` at src/main.at:2:12
2 error(s) foundTwo diagnostics, one cause — the second is the constraint the first one violated. Fix the earliest one and both go away:
fn first_plus_one(xs: []int): int {
return (xs[0] ?? 0) + 1
}
fn main(): void {
println("${first_plus_one([41])}")
}Related codes on the same axis: ATOLL3010 for an unknown field and
ATOLL3012 for a call arity error.
fn rescale(value: int, factor: int): int {
return value * factor
}
fn main(): void {
println("${rescale(3)}")
}$ atoll check src/main.at
error[ATOLL3012]: function `rescale` takes 2 arguments, but 1 was supplied
at src/main.at:6:16
1 error(s) foundNon-exhaustive match — ATOLL2010
The message names the type and lists the variants you left out.
enum Priority {
Low
Normal
Urgent
}
fn marker(p: Priority): string {
match p {
Low => "."
Normal => "-"
}
}$ atoll check src/main.at
error[ATOLL2010]: non-exhaustive `match` on `Priority` — missing: Urgent
at src/main.at:8:5
1 error(s) foundThis is the diagnostic that turns adding an enum variant into a checklist. Add
the arm rather than a _, unless treating the remainder uniformly is a real
decision:
enum Priority {
Low
Normal
Urgent
}
fn marker(p: Priority): string {
match p {
Low => "."
Normal => "-"
Urgent => "!"
}
}
fn main(): void {
println(marker(Priority.Urgent))
}The same code and the same rule govern catch
arms, which are match arms over the error channel.
? outside a fallible function — ATOLL3051
? returns through an error channel. A function without one cannot host it.
error WorkError { EmptyTitle }
fn normalize(title: string): string ! WorkError {
clean := title.trim()
if clean.is_empty() { error EmptyTitle }
return clean
}
fn main(): void {
println(normalize(" ship ")?)
}$ atoll check src/main.at
error[ATOLL3051]: function `main` uses `error`/`?` but its return type `()` is
not fallible — declare `() ! <Error>` or `Result[(), <Error>]`
at src/main.at:9:4
error[ATOLL2002]: ? on result requires an enclosing fallible function
at src/main.at:10:13
warning[ATOLL2032]: fn `main` inherits effect `error` from a
transitively-called callee but its declared `using [..]` row does not list
`error` at src/main.at:10:13
2 error(s) foundThree lines, one cause: ATOLL3051 at the declaration that lacks the error
channel, ATOLL2002 at the ? itself, and an effect warning riding along.
Read the declaration-level one first.
main is usually the place where propagation should stop and a decision
should be made, so handle the Result instead of widening main:
error WorkError { EmptyTitle }
fn normalize(title: string): string ! WorkError {
clean := title.trim()
if clean.is_empty() { error EmptyTitle }
return clean
}
fn main(): void {
match normalize(" ship ") {
Ok(clean) => println("accepted: ${clean}")
Err(EmptyTitle) => println("rejected: empty title")
}
}That program still emits one ATOLL2032 warning, because main calls a
fallible function and therefore inherits the error effect without declaring
it. It compiles — see below.
Effect rows — ATOLL2032 and ATOLL2031
The compiler infers what a body actually needs and compares it against the
declared using [...] row.
fn load_title(id: int): string {
return "item ${id}"
}
fn describe(id: int): string {
return load_title(id)
}
fn main(): void {
println(describe(7))
}$ atoll check src/main.at
hint[ATOLL2031]: fn `main` inherits effect `suspend` from a
transitively-called callee at src/main.at:10:13
warning[ATOLL2032]: fn `describe` inherits effect `suspend` from a
transitively-called callee but its declared `using [..]` row does not list
`suspend` at src/main.at:6:12
OK (2 diagnostics)Read the two apart:
ATOLL2032(warning) — the function’s declared effect row is narrower than the effects it actually inherits. A function with nousingclause declares nothing, so this fires for essentially every effectful function.ATOLL2031(hint) — informational. It fires even when the row is correct, recording where an inherited effect entered.
Neither stops the build, and for ordinary code the right response to both is
to ignore them. Effects are inferred; you are not expected to annotate. Real
Atoll code carries no using clauses at all — there is not one in the
compiler’s sample projects or prelude. Reach for the row only when you are
deliberately pinning a contract, and see
Effects for that case.
Treat ATOLL2032 as a signal worth reading only when you expected a function
to stay effect-free — then it tells you purity slipped and where:
fn load_title(id: int): string {
return "item ${id}"
}
fn describe(id: int): string {
return load_title(id)
}
fn main(): void {
println(describe(7))
}Widen a row when callers genuinely should permit the capability; narrow the
call when they should not. The effects are Suspend, Error, Alloc,
Spawn, and Cancel — see Effects.
Missing trait implementations — ATOLL3100
String interpolation goes through Display, so a bare ${item} on a struct
is a missing-impl error rather than a fallback to some debug format.
struct WorkItem {
title: string
done: bool
}
fn main(): void {
item := WorkItem { title: "ship", done: false }
println("${item}")
}$ atoll check src/main.at
error[ATOLL3100]: type `WorkItem` does not implement `Display` (required for
`${...}` segments in string templates) at src/main.at:8:13
1 error(s) foundEither interpolate a field, or implement the trait:
struct WorkItem {
title: string
done: bool
}
impl Display for WorkItem {
fn to_string(self): string {
mark := if self.done { "x" } else { " " }
return "[${mark}] ${self.title}"
}
}
fn main(): void {
item := WorkItem { title: "ship", done: false }
println("${item}")
}Syntax cascades — ATOLL1xxx
The parser inserts recovery nodes so one bad construct does not hide the rest of the file. The cost is that a single mistake can produce a spray of diagnostics, several of them nonsense.
struct WorkItem {
title: string
done: bool
}
fn main(): void {
item := WorkItem {
title: "Publish guide"
done: false
}
println(item.title)
}$ atoll check src/main.at
error[ATOLL1006]: Expected '}' at src/main.at:9:9
error[ATOLL1022]: Expected type expression at src/main.at:9:15
error[ATOLL1019]: Invalid statement at src/main.at:11:5
error[ATOLL2002]: type mismatch: expected `void`, got `bool` (in argument
position) at src/main.at:9:15
warning[ATOLL4100]: local binding `item` is declared but never used
at src/main.at:7:5
warning[ATOLL4100]: local binding `done` is declared but never used
at src/main.at:9:9
4 error(s) foundSix diagnostics from one missing comma after "Publish guide". Struct
declarations separate fields by newline; struct literals require commas.
The rule for any noisy run: fix the first syntax error in each file and rerun before reading anything else. Recovery is not acceptance — a placeholder node never becomes a runtime value.
Warnings and lints
Two warnings show up constantly and neither fails a build.
ATOLL4100 flags a binding you never read:
fn main(): void {
scratch := "ship".len()
println("done")
}$ atoll check src/main.at
warning[ATOLL4100]: local binding `scratch` is declared but never used
at src/main.at:2:5
OK (1 diagnostics)ATOLL7002 is the naming lint. It is opt-in — set ATOLL_LINT_NAMING=1:
struct workItem {
Title: string
}
fn MakeItem(name: string): workItem {
return workItem { Title: name }
}
fn main(): void {
println(MakeItem("ship").Title)
}$ ATOLL_LINT_NAMING=1 atoll check src/main.at
warning[ATOLL7002]: struct name `workItem` should be `WorkItem` (Atoll uses
PascalCase for structs) at src/main.at:1:8
warning[ATOLL7002]: function name `MakeItem` should be `make_item` (Atoll uses
snake_case for functions) at src/main.at:5:4
OK (2 diagnostics)@allow(naming) on a declaration suppresses it for that declaration:
@allow(naming)
struct workItem {
Title: string
}
fn main(): void {
println(workItem { Title: "ship" }.Title)
}Suppression is per-declaration and only applies to compiler-recognized lints.
Generic decorator syntax does not invent new warning categories, and nothing
suppresses a type error, an effect error, or ATOLL2004.
Code families
The leading digits narrow the stage, which narrows the fix:
| Range | Stage | Example |
|---|---|---|
1xxx |
Lexing, parsing, resolution | ATOLL1006 expected }, ATOLL1009 unresolved name |
2xxx |
Typing, exhaustiveness, effects, lowering availability | ATOLL2002 mismatch, ATOLL2010 non-exhaustive, ATOLL2004 no lowering path |
3xxx |
Members, calls, fallibility, trait obligations | ATOLL3010 unknown field, ATOLL3012 arity, ATOLL3051 ? in a non-fallible fn, ATOLL3100 missing impl |
4xxx |
Dead and unused code | ATOLL4100 unused local |
5xxx, 6xxx |
Backend and monomorphization | ATOLL6002 structural type-arity mismatch |
7xxx |
Style and advisory lints | ATOLL7002 naming |
A 5xxx or 6xxx code means checked source hit a representation edge, and it
only ever comes from atoll build. The common one is ATOLL6002, which
usually means you passed a builtin a closure of the wrong arity — check the
signature in crates/atoll-sema/src/stubs/. Anything else in that range is
worth reporting as a compiler bug.
Triage order
- Fix the first syntax error in each file. Rerun.
- Fix unresolved names — everything after them is a guess.
- Fix the first type error in each constraint chain, reading the blame suffix to decide which side is wrong.
- Satisfy trait and exhaustiveness obligations.
- Decide error and effect policy: who propagates, who handles, which rows widen.
- Only then run
atoll buildand address any lowering failure.
Working the list out of order wastes time on diagnostics that will disappear on their own.
Reporting a compiler bug
Include:
- the smallest source that reproduces it, in current syntax;
- whether
checkorbuildfailed, and the full diagnostic text with code and range; - the
atoll.tomlfields that matter (module, dependencies, host access, SQL datasources) when the failure is project-scoped; - compiler revision and target.
Preserve the first unexpected diagnostic and delete unrelated code around
it. A ATOLL2004 on a prelude method is already a complete report — the
method name is the bug.