Slide 50 of 53

What an error looks like

Three mistakes, and three messages
proc main(): void {
	Str[3] parts = split("north,south", ",")
	echo parts[0]

	health := 3
	echo hive.math.max(0, health)
}
What hive check says about it
$ hive check colony.hive
colony.hive:1: in `main`: the declaration of `parts` wants Str[3] and this is Str[dyn]
colony.hive:1: in `main`: the first argument of `hive.math.max` wants Float and this is Int
colony.hive:1: in `main`: the second argument of `hive.math.max` wants Float and this is Int

# All three land on line 1 — the line `main` is declared on. That is the
# limit above: after the loader, a position is a declaration's.
A message that says what to do
$ hive check guard.hive
guard.hive:1: in `main`: cannot prove this index is in range: it is a computed expression, and there is nothing to hang a guard on. Bind it to a name first, then guard that

$ hive check shouted.hive
shouted.hive:5: `Proc` is a keyword written the wrong way: every keyword in Hive is lower case, and only lower case. Write `proc` — or, if a name was meant, one the language has not already taken.
Wiring up an editor
" In Vim or Neovim. hive check runs every compiler pass and stops before
" the Go toolchain, so it answers as fast as the front end does and writes
" nothing next to the file — which is what makes it usable on every save.
setlocal makeprg=hive\ check\ %:S
setlocal errorformat=%-G\ %.%#,%f:%l:\ %m,%-G%.%#

" :make fills the quickfix list, and :cn walks the errors. The two %-G
" items throw away what is not a diagnostic: the first drops the indented
" continuation of a message that runs on, the last drops everything else.
Every diagnostic opens with where it happened — file:line: message — because that is the shape editors expect, so wiring one up needs nothing more than a compile command and a pattern to read its output with. A message that runs on continues on indented lines, so one error stays one entry in a quickfix list rather than several. And a compile error is printed with nothing at all in front of it: prefixing it with a program name would put something before the file name and break the very pattern. Only a usage error — hive build with no entrypoint — says who is complaining, because that is not a diagnostic about anybody's source.Every pass reports as much as it can find rather than stopping at the first thing, so a program with three mistakes is a program whose author is told about three.What a message should say is not part of the grammar, but it is the standard the compiler's own messages are held to. Name the thing: `v` is declared Str[3], never "type mismatch". Say what to do: a bounds error that cannot prove an index says how to guard it, and a sort that cannot sort in place names both fixes. Say why, when the why is the point — hive.math.max(0, health) is rejected because Hive never widens a number behind your back, and a message that only said "expected Float, found Int" would leave the reader hunting for a conversion they were supposed to have known about. And never guess: a miscased keyword is reported as a keyword written the wrong way and as a name that was already taken, because both readings are live and the compiler cannot know which was meant.Two limits are worth knowing, and both are the price of one decision — that every pass after the loader sees one flat program. Errors from the passes after parsing land on a declaration rather than on a line inside it, so the declaration a mistake is in is as precise as they get: its line is what the message opens with, and its name is what the message says. And in a program with imports those same errors are reported against the entrypoint, even where the declaration at fault came from an imported module. Errors the lexer, the parser and the import resolver raise do name the right file and the right line, because those three run per file.One diagnostic is not an error at all. A test failure is an answer rather than a malfunction, so that report goes to stdout, and only a compiler or toolchain failure goes to stderr.