Slide 70 of 74

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: 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. A keyword is reserved however it is spelled, so `Proc` is no more a name than `proc` is.
Where an error in a module lands
$ hive check main.hive
main.hive:2: cannot read `lib/missing.hive`: open lib/missing.hive: no such file or directory

# With the import fixed, the mistake inside the module is reported there,
# by the name the module gives it.
$ hive check main.hive
lib/text.hive:1: in `shout`: `+` wants two of one type, and these are Str and Int

$ hive build
hive: `hive build` needs an entrypoint: `hive build <entrypoint.hive>`.
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=%f:%l:\ %m,%-G%.%#

" :make fills the quickfix list, and :cn walks the errors. The %-G item
" drops every line that is not a diagnostic.

Every diagnostic opens with where it happened — file:line: message — which is the shape editors expect, so wiring one up takes a compile command and a pattern. A compile error has nothing in front of it. A usage error, like hive build with no entrypoint, starts with hive:, because it is about what was typed rather than about anybody's source. Both go to standard error and exit with 1, so hive emit main.hive > main.go never writes an error into the Go. A failing test is an answer rather than a malfunction, so its report goes to standard output.

What a message says is held to one standard:

  • the thing, by name: parts wants Str[3] and this is Str[dyn], never "type mismatch";
  • what to do: a bounds error says how to guard the index, and a sort that cannot sort in place names both fixes;
  • why, when the why is the point: hive.math.max(0, health) is refused because Hive never widens a number behind your back;
  • no guess: a miscased keyword is reported as a keyword written the wrong way, and as a name the language has taken.

Each pass reports everything it finds, but the passes run one after another and the first to find anything stops the rest. A program with a type error and an unguarded index hears about the type error, and about the index once it gets that far. The lexer, the parser and the loader point at the exact line, and an import naming no file is reported on the line of its import. The passes after them see one flat program, so what they find lands on a declaration — its line, and in `main`: — in the file the declaration came from, named the way that file spells it.