11. Errors

11.1 Format

A diagnostic has a stable code, a location and one sentence, plus an optional hint. It is written in two forms.

Short form. The form agents read, and what synod check prints when its output is not a terminal:

gossip/gossip.synod:14:3: error[unknown-phrase]: no phrase matches "net.Sned digest from n to peer"
  hint: did you mean "net.Send [m: msg] from [a: node] to [b: node]"?

A diagnostic with no position (a folder without a main file, a run that reaches the time limit) is path: error[code]: message, without :line:col.

synod check --json emits the same as JSON (implementation/04-formats.md). Messages must be short and specific, because they are fed back to agents verbatim. synod explain <code> explains every code with an example (decision 93).

Rich form. The form for people at a terminal (synod check --style rich): the source line with markers under the exact span, labelled secondary spans, notes, and the fix shown as a diff.

p/p.synod:13:5: error[unknown-phrase]: no phrase matches "if n.Role is "leader""
   |
13 |     if n.Role is "leader"
   |     ^^^^^^^^^^^^^^^^^^^^^ no phrase takes these items
   |        ------------------ 3 items here; "if" takes one condition
   |
help: parenthesize the condition
   |
13 |     if (n.Role is "leader")
   |        +                  +

The invariants of the rich form:

The hint drives the short form. Labels, help and notes drive the rich form. A diagnostic that has a hint and no help shows the hint as = help: in the rich form, so a producer can add structure one code at a time.

Text style of labels, help and notes: a label is at most about 50 characters, starts lower-case and has no final period; quoted words use double quotes ("if"), never backticks; help is an imperative (parenthesize the condition); a note is a statement. None repeats the message.

Run-time errors render the same way. synod run prints the failed at <time>: <pos>: error[<code>]: <message> line first, unchanged, and under it the source line of the failure.

11.2 Compile-time codes

Code Meaning
syntax the structure grammar cannot parse the line; a comma outside list items, message … with settings and each names; a continuation line that follows no block; a number, text, comment or use written wrongly (.5, +1, x-1, a control character in text, #todo, a nested use, ()); a duration that is not a whole number of microseconds, or a number out of 64-bit float range; a bad synod.mod line (an unknown directive, a repeated module or synod, a synod version newer than the compiler's)
indent bad indentation (tabs, odd widths, unexpected indent)
non-canonical parseable but not canonical (including CR LF line endings, a byte order mark, unsorted require lines, settings out of field order, a name that must start lower-case, and a package's own qualifier used inside it); synod fmt fixes it, when the package loads for the rows that need declarations (settings order, names, own qualifier)
bad-header missing # package header, or a name that is neither the folder name nor main, starts upper-case or is a reserved word
unknown-phrase no phrase matches the sentence by shape (types never decide, spec/03 §3.3); no event phrase matches after when, or an event phrase stands elsewhere; a record has no such field; a message … with setting of more than two items
ambiguous-phrase more than one phrase matches by shape
duplicate-name a name defined twice in one package namespace (spec/03 §3.2), including a second start, a record type named like a built-in type, two imports of one path or with one qualifier, a repeated hole name, and a field set twice in message … with
shadowing a name hides another name in scope, including a record type of the package, and a node field or phrase head spelled like an import qualifier of the file
reserved-word a reserved word used as a name or head
bad-pattern a define or when pattern breaks the rules in spec/03 §3.1, including a head directly followed by a comparison operator, a word after the head that starts upper-case, and an unknown type ([x: Foo], p.Node naming no imported library package)
bad-receiver a receiver whose type is not Node or p.Node; a start or when receiver that is not Node; a node-event when without a receiver, or a trace-event when with one; a receiver, start, node-event when or Node has in a main file
bad-place set targets something other than a local, a node field, or an element of one (hole parameters, when bindings, each names and record fields cannot be set); a local from outside an after block or block argument set inside it
type-mismatch an argument, operand, element or node type is not accepted (spec/04 §4.7)
ambiguous-empty cannot tell the type of {}: list or map, and its element types
missing-result a value phrase has a path without result
value-as-statement / action-as-value phrase kind used in the wrong place
unexported using a lower-case name from another package
unused-name / unused-import a local or each name is never read (setting it again is not a use); an import is never referred to and its package has no observers or invariants
unneeded-alias use … as without a collision of last segments (two imports, or an import and the importing package's own name)
unknown-import / import-cycle an import path names no library package (a missing folder, or one holding only a main file, an unknown std/ package, a module that is not required or not in vendor/, a version that differs from the selected one, github.com/synodlang/std/…, a last segment that is not a word); imports form a cycle
missing-main / extra-main a main file without define main, or synod run on a folder without a main file; a second main file in a folder, a second define main, or define main in a library file
context a statement not allowed in its context (spec/05 §5.4): e.g. set in an observer, a receiver phrase called from define main, a std phrase outside its catalog contexts, reading a lower-case field in look context, emit … by a node in main, an attachment outside look context, result outside a phrase definition or inside an after block or block argument, a non-declaration at file root, a has block line that is not set <field> to <constant>
foreign-state node-context code reaches a node field or receiver phrase through anything but its receiver, or names a node other than the receiver after from, on or by
color-literal a color literal outside a naming phrase
native-outside-std native used outside std/

11.3 Run-time errors

Recorded as sim.Failed (see spec/07) with the code, a message and the source position (none for time-limit). The position in the trace stays the start only (synod-trace/1); the span a failure underlines in the rich form is not part of the trace. A diagnostic's position is a span (start and end) internally, and check.json carries both.

A run-time error raised while std code runs (a failed argument check inside a std phrase, say) is reported at the position in user code of the call that entered std: the caller of the outermost std frame on the call stack (decision 89). That position is the pos of sim.Failed and of the failed at line; the std position is kept as a note in the rich and --json forms. The rule holds in every context, so a look fault is placed the same way.

Code Meaning
missing-key item k of m with a key not in the map
index-range a list index that is not a whole number in 0 ≤ i < size, in item or set item; random.Pick of an empty list
disk-type a disk.Read value whose type is not accepted as the type of its default (spec/04 §4.7)
division-by-zero divided by 0 or 0s; math.Remainder by 0
nobody sim.Nobody where a node is needed: net.Send to it, deliver to it, sim.Crash, sim.Restart, sim.Remove, net.Partition or net.DropNext naming it, a node field of it
time-limit the next event is later than 10 minutes of simulated time
bad-argument an argument outside a phrase's domain: a negative after delay, sim.Seed not a whole number in 0..2^53, a negative sim.StopAt, net.Latency unless 0 ≤ a ≤ b, net.Loss outside 0..1, a negative or fractional count, random.Between or random.TimeBetween with b below a, lists.Take or lists.Range with a non-whole argument
number-range a non-finite number, or a time too large for 64 bits
run-limit more than 100000 steps at one instant, call depth over 500, or a list or text over 1000000 items

Look faults

A run-time error in look context (an observer, a timer of an observer, or a reactive block) is a look fault. It uses the codes above but is never recorded in the trace and is not sim.Failed (decision 68): the scene stops at the fact being drawn, the partial scene is written with a failed marker (implementation/04-formats.md), and synod run reports look failed at <time>: <pos>: error[<code>]: <message> and exits 1 (implementation/05-cli.md). The cases:

Code Look cases
missing-key card of n or tag of k with no shape attached under that name to that key: no shape is attached to <key> under "<name>"; item k of m with a missing key
bad-argument Tween with a property other than the eight, or a value of the wrong kind or range ("opacity" outside 0..1, negative "scale"); a negative duration or after delay; look.Flight on a message not bound by a net event (look.Flight needs a message bound by a net event); look.Layout with another kind; look.Effect with another name; a second group for a shape; a Place cycle; a negative radius or size
run-limit more than 100000 timer firings at one instant; call depth over 500; more than 1000000 shapes; more than 10000000 keys
index-range, division-by-zero, number-range as in the table above