Heroes / Guide / chapter 2

Errors

Every message below is copied from the compiler’s output. The examples show how to locate an error and use the suggested fixes.

To fix a program, a model needs to know what is wrong and where. A precise message can save it from rereading entire files. Heroes errors therefore identify the affected code, any conflicting declarations and, when possible, a fix.

Swapped arguments

When two parameters have the same type, type checking alone cannot tell one argument from the other. Heroes therefore requires labels, the argument names written in the call:

error[needs_label]: two of `copy_text`'s parameters are `str`, so every one of them is named at the call site — this one is `from`
  at tests/golden/check/swapped-arguments.hero:16:21
     |
  16 |     print(copy_text("a", "b"))  #~ needs_label needs_label
     |                     ^^^
  fix (certain): write `from: `

error[wrong_label]: `copy_text` has nothing called `to` at this position — it is `from`
  at tests/golden/check/swapped-arguments.hero:18:21
     |
  18 |     print(copy_text(to: "b", from: "a"))  #~ wrong_label wrong_label
     |                     ^^
  fix (certain): write `from:`

The #~ needs_label annotations let the tests verify the underline’s position. The ^ characters mark the exact portion of code involved, so you can find the argument even in a long call.

Automatic fixes and suggestions

A fix marked certain uses information checked by the compiler, such as an inferred type or the only similar name available. heroes check --apply applies these fixes. Other errors may still need correction before the program is complete. Fixes marked guess are suggestions to review; the command does not apply them automatically.

The same messages also come out as JSON, schema 2, written to stderr, the format programs read easily, for when the reader is not a person but another program. Each fix says where it applies, and heroes check --apply is the command that writes the certain ones into the program. A program that still holds holes, the ??? placeholders, finds them there too, each with what belongs in it, and the answer says whether that list is the whole of them.

The loop, as commands:

$ heroes check program.hero           # diagnostics, on stderr
$ heroes check --apply program.hero   # certain fixes applied
$ heroes check --apply --in-place program.hero
$ heroes run program.hero

heroes there is the binary you built: ./heroes, built with clang from the downloaded source. How to build it →

Some errors in C function declarations are caught by heroes build. This command runs clang, the C compiler, which checks the declarations against the library’s header, the file that describes its functions and types. A misspelled C function name can therefore pass heroes check and be reported during the build. Checks on C libraries →

Keywords from other languages

A model can accidentally use a keyword from another language. Heroes recognises some of these words and suggests the correct form:

tests/golden/check/reserved-words.hero:2:1: error[reserved_word]: `struct` is not a word in this language — use `record`: `record Point`
tests/golden/check/reserved-words.hero:3:1: error[reserved_word]: `let` is not a word in this language — bind with `=`: `x = 5`
tests/golden/check/reserved-words.hero:4:1: error[reserved_word]: `const` is not a word in this language — use `constant`: `constant MAX: i64`
tests/golden/check/deleted-int.hero:23:8: error[reserved_word]: `int` is not a word in this language — an integer says its width: `i64`, and also `i8` `i16` `i32` `u8` `u16` `u32` `u64`

Here each diagnostic, or compiler message, occupies one line, which is what heroes check --brief prints; the earlier examples show the full form the plain command gives. Three of the four errors have a certain fix, which --apply applies. It leaves let unchanged: that fix requires choosing a declaration form, not just replacing a word.

Completing a program with the compiler’s help

You can write ??? where an expression is missing. The compiler checks the surrounding code and reports the expected type, the available names and functions that return that type. If there are no other errors, the check exits with status zero. No executable is produced while expressions remain unfinished:

hole at examples/gallery/09-holes.hero:24:5
  position: statement — any type, or none
  this function returns: Entry
  in scope:
    entries: [Entry]
hole at examples/gallery/09-holes.hero:28:74
  expected type: str
  in scope:
    entries: [Entry]
  functions that return it:
    summary_of(entries: [Entry])
      # The hole can be one argument of an otherwise finished line.

You can sketch the program, then complete it using the compiler’s guidance. In this example, the first ??? replaces a whole statement, so the compiler reports what the function must return. The second sits inside a statement and requires a specific type.

How it is measured

heroes mutate introduces one plausible mistake at a time into working programs and counts how many the compiler catches. It repeats the checks on the same programs with the Heroes-specific rules disabled. The difference measures the effect of these rules.

The table records a complete run on the 120 programs examples/ holds today, using every kind of modification the tool supports. It includes cases where the Heroes checks do not improve the result or fail to catch the mistake. Repeat it yourself with the command below: the corpus grows, so your counts will be higher and the percentages about the same.

$ heroes mutate examples

mistake injected                       mutants  caught  without the rules
two arguments swapped in a call           1910     80%                58%
a positional argument needing a label     1335     66%                 0%
a shadowed name                            945     95%                 0%
a lazy catch-all on a variant              457    100%                47%
a local that takes a module's name        1700    100%                96%
a dropped ?                                236    100%                82%
a forgotten @= on a declaration            762     99%                99%
a dropped variant case                     133    100%               100%
a bitwise & where && was meant             227    100%               100%
a name that was never declared             824    100%               100%
a typo in a name                         10463    100%               100%
mixed integer and float arithmetic        1495    100%               100%
a typo in an error code                     59      0%                 0%
a typo in a digit                           68      3%                 3%

The percentage includes only mutants, the modified programs, that still have valid syntax. If a modification makes the text invalid, it is caught while parsing, the step that reads the code’s structure. These cases are excluded to measure the later checks separately.

The tool does not summarise the results in one average. The first rows show the effect of Heroes-specific rules, such as mandatory labels and the ban on hiding a previously declared name. The middle rows involve errors detectable through ordinary name and type checks. A forgotten @ is excluded from summaries because the way the tool modifies the code makes almost all these errors detectable by construction.

The table also shows the limits of the checks. Some argument swaps go undetected. A typo in a string used as an error code is still a valid string. The same applies to a number: 10 and 100 are both allowed. Heroes’ checks cannot determine which value you meant to write.

This table measures the compiler, not a model. Whether a model makes these mistakes, and whether refusing them gets it to a working program sooner, is a test of its own, with its rules written down before it runs. What is measured and what is not yet →

Next: Records and functions, where a program starts having shapes of its own. ← Previous: A first program