in most languages the error messages are an afterthought—the thing you see when something else went wrong. in kanso they are the product. the founding principle is that anything a style guide, a linter, or a code review would enforce by convention is instead a compile error with a caret under it, so the compiler treats waste and disorder the way other compilers treat type mismatches. that principle is only as good as the diagnostics that carry it, and this appendix is the complete list: every error the compiler emits, grouped by kind, each with the smallest program that triggers it and the exact text you get back.
errors arrive under a tag in square brackets that names the family the rule belongs to—formatting, unused, name, dispatch, signature, syntax, and, at the far edge, endpoint. the first six are caught by kanso check before a single line runs. the last one names a failure that only a running program can produce. every sample below was fed to the real compiler, and every diagnostic on this page is copied from the machine, not typed from memory.
anatomy of a diagnostic
one specimen, read part by part. this program binds x and never reads it:
four parts, always in this shape. the first line is the tag—error[unused]—followed by the rule stated as a fact about the language, not a suggestion. the second line, after -->, is the location: file, line, column. then the offending source line is reprinted with its number. finally a caret points at the exact column named in the location. every diagnostic in this catalog is that same four-part object; once you can read one, you can read all of them.
error[formatting]—spacing and invisibles
the formatting family is the largest, because canonical form is not a convention layered over the grammar—it is the grammar, and a program has exactly one legal rendering. these first rules protect the layer you can barely see: spaces, and the characters that hide between tokens. start with the one that carries no visible mark at all. a space after the closing quote:
trailing_ws_check.kso
print"hi"
kanso check trailing_ws_check.kso
error[formatting]: trailing whitespace is not part of the canonical grammar
--> trailing_ws_check.kso:1:11
1 | print "hi"
^
spacing around = is fixed at exactly one space on each side. cram it and the compiler reports both sides—two carets, one per missing space:
cramped_binding_check.kso
answer=42print"{answer}"
kanso check cramped_binding_check.kso
error[formatting]: canonical form requires exactly one space here
--> cramped_binding_check.kso:1:7
1 | answer=42
^error[formatting]: canonical form requires exactly one space here
--> cramped_binding_check.kso:1:8
1 | answer=42
^
the reflex you brought from every c-descended language is parentheses hugging a call. a call is a name, one space, its arguments, and the compiler says so by name:
paren_call_check.kso
print("hi")
kanso check paren_call_check.kso
error[formatting]: application is a space, so `print x` calls `print` — `print(x)` reads as `print` applied to nothing, then a parenthesised `x`
--> paren_call_check.kso:1:6
1 | print("hi")
^
and the other reflex: a comma between things. there are no commas in kanso, anywhere—arguments, list elements, and record fields are all space-separated:
commas_check.kso
total =sum [1, 2]
print"{total}"
kanso check commas_check.kso
error[formatting]: kanso has no commas; enumerations are space-separated
--> commas_check.kso:1:15
1 | total = sum [1, 2]
^
a line has a hard ceiling of eighty characters. the diagnostic counts them for you and points at column eighty-one, the first one over the line:
line_length_check.kso
print"this string pads the line out well past the canonical width limit of eighty"
kanso check line_length_check.kso
error[formatting]: a line holds at most 80 characters — this one has 83
--> line_length_check.kso:1:81
1 | print "this string pads the line out well past the canonical width limit of eighty"
^
error[formatting]—one true name
every identifier is snake_case, all lowercase. this protects a single reading of every name: no debate over whether the constant is maxRetries, MaxRetries, or max_retries, because two of the three won't parse. a capital letter is flagged at its definition and again at every use, so you fix the name once and both carets disappear together:
snake_case_check.kso
myName =1print"{myName}"
kanso check snake_case_check.kso
error[formatting]: identifiers are snake_case, all lowercase, always
--> snake_case_check.kso:1:3
1 | myName = 1
^error[formatting]: identifiers are snake_case, all lowercase, always
--> snake_case_check.kso:2:11
2 | print "{myName}"
^
error[formatting]—order and separation
a file has a fixed shape: types come first, then functions and constants. within those groups the order is yours to choose, so a helper can sit beside the thing it helps and the entry point can stay where a reader will look for it. put a type after a function and the compiler tells you which way it must move:
type_order_check.kso
fnuse_it t
ttype thing
sizepubplay=print"x"
kanso check type_order_check.kso
error[formatting]: canonical order places type declarations before functions; move `thing` up
--> type_order_check.kso:4:6
4 | type thing
^
between two top-level declarations there is exactly one blank line—never zero, never two. run two declarations together and the second is flagged:
error[formatting]: exactly one blank line separates top-level declarations
--> blank_between_check.kso:3:1
3 | fn wave _
^
the mirror rule holds inside a body: a blank line separates declarations, so it may not appear within one. a body is a solid block of lines. break it with an empty line and the block ends where you didn't mean it to:
blank_in_body_check.kso
fngreet _
x =1print"{x}"pubplay=greet0
kanso check blank_in_body_check.kso
error[formatting]: blank lines may not appear inside a body
--> blank_in_body_check.kso:4:1
4 | print "{x}"
^
error[formatting]—chains and sequencing
a chain of steps has two legal shapes and nothing between them: it fits on one line, or every step gets its own continuation line. this protects the diff again—there is no "reflow" a reviewer has to squint past—and it keeps the eye from having to guess where a statement really ends. the simplest violation is wrapping a chain that would have fit. the compiler measures it and tells you it fits in forty-two characters:
seq_wrap_check.kso
print"steeping">>print"serving"
kanso check seq_wrap_check.kso
error[formatting]: needless continuation: this statement fits on one line (35 characters)
--> seq_wrap_check.kso:2:3
2 | >> print "serving"
^
the same rule governs method chains joined by .—wrapping is for statements too long to fit, not for taste:
needless_continuation_check.kso
total = [91827] .sort.sumprint"sum: {total}"
kanso check needless_continuation_check.kso
error[formatting]: needless continuation: this statement fits on one line (32 characters)
--> needless_continuation_check.kso:2:3
2 | . sum
^
the reverse is also a rule: once a chain earns a continuation line, every step gets one. no half-and-half, where the first two steps share a line and the third wraps:
partial_chain_check.kso
print"steeping the finest uji sencha">>print"pouring the hot water">>print"serving it in the warmed cups with seasonal wagashi"
kanso check partial_chain_check.kso
error[formatting]: no partial chaining: a chain fits on one line, or each step gets its own `>>` continuation line
--> partial_chain_check.kso:1:40
1 | print "steeping the finest uji sencha" >> print "pouring the hot water"
^
a lone >> is a wall that introduces a group of steps. when the group has only one step, the wall and the step belong on the same line, fused as >> step:
unfused_wall_check.kso
print"steeping"print"warming">>print"serving"
kanso check unfused_wall_check.kso
error[formatting]: a one-step stage fuses with its wall: write `>> step` on one line
--> unfused_wall_check.kso:3:1
3 | >>
^
and the fused form is a single step, sealed. a bare line beneath it cannot quietly become part of the stage; if you want a group, put the wall alone and list its members below:
bare_after_fused_check.kso
print"x">>print"c"print"d"
kanso check bare_after_fused_check.kso
error[formatting]: a fused `>> step` is a single sequential step — a line cannot silently join it. for a group, put the wall alone and list the members below it
--> bare_after_fused_check.kso:3:1
3 | print "d"
^
a body separates its two phases: bindings first, then the effects. a binding wedged between effect lines is flagged and told to move up, so a body always reads as "compute these values, then do these things":
binding_after_effect_check.kso
print"steeping"
cups =4>>print"serving {cups}"
kanso check binding_after_effect_check.kso
error[formatting]: bindings precede the effects in a body: every binding runs before every bare effect line, so move it above the chain
--> binding_after_effect_check.kso:2:8
2 | cups = 4
^
error[formatting]—signatures and reads
a single-type ascription is written tight, name:type, with no parentheses—those are reserved for the parenthesized guards that return typesets. reach for the familiar parenthesized form and the compiler names the tight one you wanted:
paren_ascription_check.kso
fnarea (w: int)
w * w
pubplay=print"{area 3}"
kanso check paren_ascription_check.kso
error[formatting]: a single-type ascription is written tight: `w:type` (parenthesized guards return with typesets)
--> paren_ascription_check.kso:1:11
1 | fn area (w: int)
^
a keyed read pulls named fields out of a value, and it lists them alphabetically—the same ordering law that governs the fields in the type declaration itself:
keyed_order_check.kso
type user
adminnamepubplay=
{ name admin } =user false "clay"print"{name}{admin}"
kanso check keyed_order_check.kso
error[formatting]: keyed reads list fields in alphabetical order: `admin` before `name`
--> keyed_order_check.kso:6:10
6 | { name admin } = user false "clay"
^
overloads of one name are ordered too, but by specificity rather than the alphabet: the most specific clause comes first—a literal, then a concrete type, then the generic catch-all. writing the recursive fact n before the base case fact 0 is the classic mistake, and it is a formatting error, not a logic one:
overload_order_check.kso
fnfact n
n * fact (n -1)
fnfact01pubplay=print"{fact 3}"
kanso check overload_order_check.kso
error[formatting]: overloads of `fact` appear most-specific first: literal, then concrete type, then generic
--> overload_order_check.kso:4:4
4 | fn fact 0
^
error[unused]—nothing computed for nothing
the unused family is the personality of the language in miniature: if a program computes something, it must use it. you have already met the plainest case in the anatomy section—a bound name nobody reads. the sibling is an expression that computes a value and drops it on the floor. every non-final line in a body must bind a name; a naked expression is either a mistake or an effect you meant to sequence with >>:
unused_expr_check.kso
1+1print"hi"
kanso check unused_expr_check.kso
error[unused]: this value is never used: a non-final line binds a name, or is an effect joining the group
--> unused_expr_check.kso:1:3
1 | 1 + 1
^
rebinding a name is allowed—values are immutable, but a name may point at a new value as it evolves. the condition is that each version must be read before the next replaces it. rebind over a value nobody used and the first line had no reason to exist:
rebind_check.kso
x =1
x =2print"{x}"
kanso check rebind_check.kso
error[unused]: unused binding: each version of `x` is used before the next
--> rebind_check.kso:1:1
1 | x = 1
^
error[name]—every name resolves
a name in a kanso program refers to something, and the compiler proves it before the program runs. an undefined name is caught at the point of reference, with the caret inside the interpolation where you wrote it:
the opposite hazard—a name that resolves to two things—is refused as well. a local binding may not reuse the name of a top-level declaration, because that would quietly shadow it. the fix is to rename the binding, not to reason about scope:
shadowed_check.kso
fngreet name
"hi, {name}"pubplay=
greet =1print"{greet}"
kanso check shadowed_check.kso
error[name]: `greet` is already a declaration; rename the binding
--> shadowed_check.kso:5:3
5 | greet = 1
^
one name is required rather than merely resolved. a program that the toolchain will run must define main—the constant the runtime asks for. this is the one diagnostic in the family that kanso check lets pass, because a file with no main is a perfectly good library; it surfaces only when you ask to run the file as a program:
no_main.kso
fngreet name
"hi, {name}"
kanso run no_main.kso
error: `no_main.kso` is a library — nothing to run. give the module a main.kso entry, or run its definitions beside their statements with `kanso play`
error[naming]—a question mark answers questions
a function that can only answer true or false is a predicate, and its name must say so with a trailing ?—the convention every verb in std/list follows (all?, any?). the rule cuts both ways: a ? name that returns anything besides a boolean is refused too, so the suffix is a promise the compiler keeps for you rather than a style you remember:
bare_predicate_check.kso
pubplay=print"{fresh 3}"fnfresh n
n <10
kanso check bare_predicate_check.kso
error[naming]: `fresh` answers only true or false: name it `fresh?`
--> bare_predicate_check.kso:3:4
3 | fn fresh n
^
error[dispatch]—one name, disjoint shapes
kanso dispatches a call by the shape of its arguments, so several functions can share a name as long as their signatures never collide. the dispatch family guards that arrangement. first, a constant is a function of no arguments, and a zero-argument name admits no overloads—there is nothing to dispatch on. declaring fn size x alongside the constant size is a contradiction the compiler names outright:
constant_overload_check.kso
pubplay=print"{size 1}"
size =9fnsize x
x
kanso check constant_overload_check.kso
error[dispatch]: `size` is a constant (arity 0); a constant admits no overloads
--> constant_overload_check.kso:5:4
5 | fn size x
^
second, even with the same arity, two overloads may not overlap—there must be no argument they both accept, or a call would have no single answer. two generic one-argument clauses of same overlap completely, and the second is rejected:
overlap_check.kso
pubplay=print"{same 1}"fnsame x
xfnsame y
y
kanso check overlap_check.kso
error[dispatch]: overlapping overloads of `same` are illegal
--> overlap_check.kso:6:4
6 | fn same y
^
error[arity]—every call names an arm that exists
overloads make the argument count part of a function's name: greet with two parameters and greet with three are different arms of one group. a call whose count matches no arm can never dispatch, and the world is closed, so the compiler says which counts the group actually offers:
wrong_arity_check.kso
fngreet name mood
"hello {name}, feeling {mood}"pubplay=print (greet"mai")
kanso check wrong_arity_check.kso
error[arity]: no 1-argument arm of `greet` (arms take 2)
--> wrong_arity_check.kso:4:19
4 | pub play = print (greet "mai")
^
error[type]—a written type disagreeing with the program
the type family fires where a type is written down and the program says otherwise, and its largest member is the one that fires where a type is written down at all. a record field's type is whatever the program puts in it, so writing it is a second copy of a fact the compiler already has, and the second copy is the one that can go stale:
field_type_check.kso
type track
artist:string
titlepubplay=
song =track"fishmans""long season"print"{song.title}"
kanso check field_type_check.kso
error[type]: a record field carries no type — write `artist` and let the compiler infer what it holds
--> field_type_check.kso:2:3
2 | artist:string
^
a parameter keeps its ascription, because there it does work no inference can do for you: n:int chooses an arm. the rule is about the field, where nothing is being chosen.
the family's other half is where two lines each look right on their own. a field filled with one kind of thing and read by a function that takes another is a disagreement between two places, so the diagnostic names both and takes no side—you are the one who knows which line you meant:
field_conflict_check.kso
type box
heldfndouble n:int
n *2pubplay=
b =box"words"print"{double b.held}"
kanso check field_conflict_check.kso
error[type]: `box`'s `held` is a string here, and `double` takes an int — these two cannot both hold
--> field_conflict_check.kso:8:11
8 | b = box "words"
^error[type]: ...and this is where it is read as an int
--> field_conflict_check.kso:9:19
9 | print "{double b.held}"
^
the last member is a name that used to exist. any was the way to say "whatever you like", and it never meant that—it matched every value except none, which is a real constraint wearing a word that denies being one. a bare parameter says the honest version:
retired_any_check.kso
fnlabel x:any
"a value: {x}"pubplay=print"{label 3}"
kanso check retired_any_check.kso
error[type]: there is no `any` type; leave the annotation off for a field or parameter that accepts anything
--> retired_any_check.kso:1:10
1 | fn label x:any
^
error[none]—absence lives in slots, never in collections
none answers a lookup that found nothing. a list or map that held one would break that answer—an element that is none and a miss would be indistinguishable—so absence is refused at the point of construction. a record field may hold none, because a field is a named slot with no lookup to confuse:
none_element_check.kso
pubplay=
xs = [1 none 3]
print"{xs}"
kanso check none_element_check.kso
error[none]: a list cannot hold a none: a lookup answers "not found" with one, so an element would be indistinguishable
--> none_element_check.kso:2:11
2 | xs = [1 none 3]
^
error[signature]—markers carry no fields
a type with no fields is a marker: its bare name is its only value, the way none is a value and not a constructor waiting for arguments. applying a marker to an argument treats a value as if it were a function, and the signature family catches the confusion:
marker_args_check.kso
type null
pubplay=
bad =null true
print"{bad}"
kanso check marker_args_check.kso
error[signature]: `null` takes no fields; its bare mention is its value
--> marker_args_check.kso:4:9
4 | bad = null true
^
error[build]—a field write belongs to a build block
values do not mutate. the one place a field assignment is legal is inside a build block, where a record is under construction and no one else can see it yet (chapter 03 introduces the block). a field write anywhere else would be mutation of a finished value, and the compiler stops it where it stands:
field_outside_build_check.kso
type cart
owneritemspubplay=
basket =cart"mai" []
basket.items = ["dango"]
print"{basket}"
kanso check field_outside_build_check.kso
error[build]: `basket.items = ...` writes a field, and only a `build` block may do that
--> field_outside_build_check.kso:7:10
7 | basket.items = ["dango"]
^
error[opacity]—only pub crosses an import
a module's unmarked names are its own business, and the opacity family is what makes the claim true rather than polite. reaching past an import for a name the module kept private is a compile error that names the module whose interior you touched:
shady/peek_check.kso
import"teahouse"print"the wholesale number is {teahouse/base_price}"
kanso check peek_check.kso
error[opacity]: `base_price` is private to module `teahouse` — only pub names cross an import
--> peek_check.kso:3:33
3 | print "the wholesale number is {teahouse/base_price}"
^
the fix is never on the caller's side of the boundary. either the module meant to export the name—one pub, where the thing is defined—or the caller is asking for something the module deliberately kept, and the error is doing its job.
error[ownership]—render what you define
interpolation renders every value the same way for everyone, which is what makes <none> and <io> dependable sentinels. an overload of to_string may therefore only match types this module defines—an arm on a primitive or a sentinel would quietly change what other people's output looks like. wrap the value in a type of your own and render that:
error[ownership]: an arm of `to_string` must match on a type this module defines — rendering of primitives and sentinels is fixed; wrap the value in your own type
--> foreign_render_check.kso:5:4
5 | fn to_string none
^
error[syntax]—shapes the grammar won't parse
a handful of constructs are simply not in the grammar, and the syntax family names them so you reach for the real spelling. the wildcard _, borrowed from languages with positional destructuring, is one: kanso omits fields by naming the ones you want in a keyed read, not by placeholder-ing the ones you don't:
underscore_check.kso
type user
adminnamepubplay=user _ name =user false "clay"print"{name}"
kanso check underscore_check.kso
error[syntax]: `_` does not appear in binding patterns; omit fields with a keyed read
--> underscore_check.kso:6:8
6 | user _ name = user false "clay"
^
the fix is the keyed read from the previous section: { name } = user false "clay" takes the field you want and leaves the rest, no wildcard required.
error[runtime]—the type mistake that outlives check
a handful of mistakes are visible only when a value arrives: a builtin handed the wrong shape entirely. where the wrong shape is written as a literal, check refuses it first—see error[type] above—so what reaches here is the case a checker cannot see, a value computed one way and used another. these end the run at the point of the call with a message naming the contract, the same words on every engine. they are bugs to fix, never values to handle—the failure that is a value rides the railway instead, and the endpoint family below reports it:
runtime_mismatch.kso
fnmeasured _
5print"{length (measured 0)}"
kanso run runtime_mismatch.kso
error[runtime]: length takes a list, string, or map, not 5
the same kind reports the one resource a recursive program can exhaust. recursion is kanso's loop, and a tail call costs no stack at all—but a call that still has work to do after the recursion returns keeps its frame, and a million kept frames is more than any stack holds. the simplest counting shapes never get there: when the leftover work is adding or multiplying an integer literal, the compiler rewrites the group to carry an accumulator and the recursion runs as a loop on every engine. an operand that varies per call, like the n below, keeps its frames, and the report is the same words on every engine. the fix is the rewrite the compiler does for the simple shapes: carry the running total down as an argument, so the call becomes a tail call:
deep_recursion.kso
fnweigh00fnweigh n
n + weigh (n -1)
print"sum {weigh 1000000}"
kanso run deep_recursion.kso
error[runtime]: the program ran out of stack: recursion went deeper than the stack holds
the same kind counts arguments across an import. a call to a name from another module is checked against that module's arms, so list/range 1 5—where range takes one—is refused before anything runs, with the count it wanted and the counts it has:
error[arity]: no 2-argument arm of `list/range` (arms take 1)
--> imported_arity.kso:3:23
3 | print "{list/to_list (list/range 1 5)}"
^
and an argument written as a literal is checked against the arms that could take it. this is the narrow, certain half of the type question—a literal's type is on the page, so no inference is needed to know that no arm could ever answer:
literal_arg_type.kso
fntwice n:int
n *2print"{twice "a string"}"
kanso play literal_arg_type.kso
error[type]: no arm of `twice` takes a string here (arms take int)
--> literal_arg_type.kso:4:15
4 | print "{twice "a string"}"
^
the builtins are checked the same way, against what each one accepts: length 5 is refused where it stands rather than at the call. a std module's function that is a rename over a builtin—text/bytes, math/sqrt—is checked as the builtin it stands for, so the report names that:
builtin_arg_type.kso
print"{length 5}"
kanso play builtin_arg_type.kso
error[type]: `length` takes a list, a map, or a string here, not an int
--> builtin_arg_type.kso:1:16
1 | print "{length 5}"
^
note that dispatch does not widen. arithmetic joins an int and a float64 happily—1 + 2.5 is 3.5—but an arm asking for float64 is asking for that type, so widen 3 is refused here rather than at runtime. a typeset admits whatever its members admit, and a wrapper admits whatever its parent does.
the six families above are all settled before the program runs. some failures cannot be: whether a divisor is zero, or a lookup finds nothing, is a fact about values that only exist at runtime. kanso represents those outcomes as ordinary values—err for a genuine failure, none for a legitimate absence—and lets them flow. chapter four is the full story; the catalog entry is what happens when such a value is never handled and reaches the edge of the program. a division by zero produces an err, and an unhandled err that reaches main ends the program with a report of where it was born:
divzero.kso
steeps = []
n =10/ length steeps
print"{n}"
kanso run divzero.kso
error[endpoint]: unhandled err reached the entry: "division by zero"
born in the entry at divzero.kso:2
none reaches this endpoint one way only: as the program's final value. reading past the end of a list answers none—a real answer, not a crash—and interpolated into a string it simply renders as <none>, loud in the output. but a program whose last expression is the none has produced absence where a result should be, and the endpoint reports it, without the "born" line, because an absence has no failure site to point at:
missing_index.kso
xs = [123]
xs[9]
kanso run missing_index.kso
error[endpoint]: unhandled none reached the entry
the lesson of the endpoint family is that it is short by design. everything a type system and a formatter can settle, kanso settles at check time; what remains at runtime is only the genuinely value-dependent, and even that arrives as a value you could have handled rather than an exception that unwinds your stack.
mugi
every entry on this page is a lesson i can teach in one run. pick the one you fear and type it on purpose—cram a space, miscase a name, drop a comma. the caret lands in milliseconds, and you never make that mistake blind again. the red text is the cheapest tutor in the language.
read the tags again as a set—formatting, naming, unused, name, dispatch, arity, type, none, signature, build, opacity, ownership, syntax, and then runtime and endpoint—and you have the whole surface on which a kanso program can be wrong. thirteen of the fifteen never survive check; the last two are the small, honest residue that only running can reveal. there is no separate linter, no formatter, no style argument to have, because every one of those disputes has already been answered by a diagnostic on this page. you can now recognize any error kanso will hand you, name the family it belongs to, and know before you run whether it will greet you at check or at the endpoint.
exercises
write the shortest program you can that produces two diagnostics from two different families in a single run. (a cramped binding whose name is also miscased is a place to start.)
take the overload_order_check.kso sample and, without changing what it computes, make it compile. then break it a second way so it reports an error[dispatch] instead of an error[formatting].
for each of the five samples in "chains and sequencing", predict the exact line and column the caret will land on before you run it, then check yourself against the compiler.
find a program that passes kanso check cleanly but fails at the endpoint. explain why no earlier family could have caught it.