Atoll’s prelude declares more than the backend implements. That is not a
secret failure mode — the compiler reports it precisely — but it does mean
atoll check passing is not the same as your program compiling. This page
tells you where the line currently sits and how to test any API yourself.
The two gates
atoll check resolves names, types, effects, and errors. atoll build does
all of that and then lowers to WebAssembly. Only reachable code is lowered,
so the second gate is the one that finds a missing implementation:
fn shout(word: string): string {
return word.to_upper_case()
}
fn main(): void {
println(shout("ship"))
}$ atoll check src/main.at
OK (0 diagnostics)
$ atoll build src/main.at
error[ATOLL2004]: builtin method `to_upper_case` 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 abortedTwo consequences follow, and both matter in practice:
- An unimplemented method inside a function nobody calls will never be reported. Dead code is not lowered.
- A program that checks clean can still stop at
build. Runatoll buildbefore you believe an API works.
Test any API in six lines
The fastest way to settle a question about the prelude is to call the method
from main and build:
fn main(): void {
xs := ["ship", "review"]
println(xs.join(", "))
}$ 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:3:16
1 error(s); build abortedThree outcomes are possible, and each tells you something different:
| Result | Meaning |
|---|---|
ATOLL2003: no method X on type Y from check |
Not in the prelude at all — you have the wrong name or the wrong receiver |
ATOLL2004 from build |
Declared in the prelude, no implementation behind it |
ATOLL6002 from build |
Implemented, but you called it with the wrong argument or closure shape |
Wrote 1 unit(s) |
Implemented end to end |
The prelude stubs themselves are readable source:
crates/atoll-sema/src/stubs/*.at. A method declared there without a body
is a candidate for ATOLL2004 — but not a certainty, because many bodyless
declarations are backed by an intrinsic or a host import (Map.get,
List.to_string, string.split all work).
Declared but not implementable
The lists below were produced by calling each method from main and running
atoll build. Every entry type-checks; none of them lowers. Do not use them
in code you intend to compile.
string
The string surface is the largest gap. Not implemented:
to_upper_case, to_lower_case, capitalize, title_case, replace,
replace_first, reversed, pad_start, pad_end, take, take_last,
drop, drop_last, first, last, is_blank, count,
equals_ignore_case, compare_to_ignore_case, split_with_limit, lines,
words, chars, enumerate, map, filter, any, all, none, fold,
to_bool, to_list.
Implemented and safe to use: len, length, is_empty, is_not_empty,
trim, trim_start, trim_end, contains, starts_with, ends_with,
index_of, last_index_of, split, substring, repeat, char_at,
to_upper_ascii, to_lower_ascii, to_int, to_float, to_bytes,
byte_length, to_string.
Note that to_upper_ascii and to_lower_ascii are the working case
conversions. string has no size() — use len().
List[T]
Not implemented: join, join_of, chunked, windowed, group_by,
associate, zip, flatten, distinct_by, shuffled.
Everything else in the list surface lowers, including the parts people reach
for most: map, filter, fold, reduce, scan, flat_map, any, all,
none, find, find_index, count, sorted, sorted_by,
sorted_descending, sorted_by_descending, distinct, take, skip,
take_while, skip_while, reversed, partition, associate_by,
zip_with_index, plus, plus_all, union, intersect, subtract,
to_set, min, max, sum, average, min_of, max_of, sum_of,
min_by, max_by, slice, clone, capacity, to_string.
Map[K, V] and Set[T]
Both hash types are implemented broadly — lookup, mutation, iteration,
filter/map/fold families, and the set algebra all lower.
SortedMap[K, V] and SortedSet[T] are a different matter: they are declared
types whose entire method surface is unimplemented, and the ordered accessors
that would return them (Map.first_key, Map.floor_key, Map.head_map,
Set.first, Set.floor, Set.head_set, to_sorted_map, to_sorted_set)
are not reachable from Map/Set at all — those calls fail at check with
ATOLL2003. Treat ordered collections as absent.
The other build-only failure: ATOLL6002
A second class of error also survives check and dies at build: passing a
builtin a closure of the wrong shape. List.sorted_by takes a key function
fn(T) -> K, not a comparator, and inference does not catch the difference:
fn main(): void {
xs := [3, 1, 4]
println("${xs.sorted_by((a, b) => a - b).len()}")
}$ atoll check src/main.at
OK (0 diagnostics)
$ atoll build src/main.at
error[ATOLL6002]: monomorphization detected a structural type-arity mismatch
while specializing `sorted_by` — sema may have missed an inference shape error
at src/main.at:1:1
1 error(s); build abortedThe diagnostic names the method, which is the useful part; the source range is
the file, not the call. When you see ATOLL6002, read the stub signature for
the named method and check the arity of every closure and argument you passed.
The correct call is xs.sorted_by(v => v). List.reduce is
reduce(initial, fn(R, T) -> R), Map.map_values takes fn(K, V) -> V2, and
List.slice takes a range — xs.slice(0..2), not xs.slice(0, 2).
The message says “sema may have missed an inference shape error”, and that is
exactly what happened: this should be a check-time diagnostic and is not
yet.
Working around a gap
Every gap above has a loop-shaped workaround, and the loop compiles:
fn join_with(parts: []string, sep: string): string {
mut out := ""
for part in parts {
if !out.is_empty() { out += sep }
out += part
}
return out
}
fn chunk(xs: []int, size: int): [][]int {
mut out: [][]int = []
mut current: []int = []
for x in xs {
current.add(x)
if current.len() == size {
out.add(current)
current = []
}
}
if current.is_not_empty() { out.add(current) }
return out
}
fn main(): void {
println(join_with(["ship", "review"], " / "))
println("SHOUT".to_lower_ascii())
for group in chunk([1, 2, 3, 4, 5], 2) {
println("${group.len()}")
}
}When you hit a gap, prefer a local helper over restructuring your data. The missing methods are missing implementations, not missing designs — their signatures are stable.
Language constructs
The language itself, as distinct from the library, is in much better shape. Every construct the guide documents lowers and runs. A single program exercising declarations, generics, traits, errors, and collections:
enum Priority { Normal Urgent }
error WorkError { EmptyTitle }
struct WorkItem {
title: string
priority: Priority
done: bool
}
trait Summary {
fn summary(self): string
}
impl Summary for WorkItem {
fn summary(self): string {
marker := match self.priority {
Normal => "-"
Urgent => "!"
}
return "${marker} ${self.title}"
}
}
fn best[T: Comparable[T]](values: []T, fallback: T): T {
mut winner := fallback
for v in values {
if winner < v { winner = v }
}
return winner
}
fn normalize(title: string): string ! WorkError {
clean := title.trim()
if clean.is_empty() { error EmptyTitle }
return clean
}
fn main(): void {
match normalize(" Publish guide ") {
Ok(title) => {
item := WorkItem { title: title, priority: Urgent, done: false }
println(item.summary())
}
Err(EmptyTitle) => println("empty title")
}
println("${best([3, 9, 4], 0)}")
}Concurrency lowers too — spawn, Task.await, select, race, and
Stream[T] all reach WebAssembly:
fn produce(s: Stream[int]): void {
mut i := 0
for i < 3 {
s.send(i)
i += 1
}
s.close()
}
fn main(): void {
s := Stream.new(4)
worker := spawn { produce(s) }
mut total := 0
for v in s {
total += v
}
worker.await()
println("total ${total}")
}So does integrated SQL, given a schema and model in the same unit:
schema shop
model Item {
@id
id: int
name: string
price: float
}
fn cheap(limit: float): []Item ! QueryError {
return FROM Item WHERE price < limit?
}
fn main(): void {
match cheap(10.0) {
Ok(rows) => println("${rows.len()} rows")
Err(e) => println("query failed")
}
}Two constructs to know about, because they parse without doing what you might expect:
- Decorators. The syntax
@name(args)parses on any declaration, but only compiler-recognized names have meaning (@idon a model field,@allow(naming)on a declaration). An unrecognized decorator is not an extension point. - The naming lint.
ATOLL7002exists but is opt-in behindATOLL_LINT_NAMING=1. Style is not enforced by default.
Historical declarations
Several legacy design documents describe an application framework that the current declaration parser does not accept. If you find one of these in an old document or an LLM’s suggestion, it is not current syntax:
| Legacy concept | Current status |
|---|---|
actor, actor messages, supervision |
No actor declaration; use structs, tasks, and streams |
service declarations, implicit wiring |
No service declaration; use modules and functions |
topic / messages declarations |
Use Stream[T] or a host API |
job / workflow declarations |
Scheduling and orchestration belong to a host layer |
Implicit context parameters |
No implicit threading rule; pass a parameter |
| UI, auth, AI, MCP, route, event decorator catalogs | Generic decorator syntax parses; the behavior is not in the compiler |
| Model inheritance and behaviors | Superseded by the current SQL model declaration |
| Logger facade, automatic error logging | No logger prelude; println and host integrations are separate |
Likewise, several docs/builtin/ files describe library types that are not
installed: Any (runtime type erasure), Email, Phone, Url, Money,
Queue, Stack. Nothing prevents a library from defining these; their
absence from the prelude means their behavior is not a language contract.
What “verified” means on a page
Each page in this guide carries a status field:
- verified — every code block on the page passes
atoll check, and every block with an entry point also passesatoll build. The book’s example gate enforces this. - provisional — the core is implemented but the public contract is still moving; individual claims are backed by narrower evidence.
- historical — preserved for context; does not define current behavior.
A block marked ```atoll fail in the source is one the compiler must
reject; the gate fails if such a block starts compiling. That is how the
guide’s error examples stay honest as diagnostics change.
If you find a documented API that does not build, that is a book bug and a compiler gap at once — report both. See Diagnostics for what to include.