Mettle

Reading a diagnostic

Every error and warning has the same five parts.

── tests/diag_call_notes.mettle:6:20 ──────────────────────────────
error[E0003]: Function 'add' expects 2 arguments, got 3

  ────┬─────────────────────────────────────────────────────────────
    5 │  fn main() -> int64 {
    6 │      var r: int64 = add(1, 2, 3);
      ·                     ^^^ expected 2 arguments, got 3
    7 │      return r;
  ────┼─────────────────────────────────────────────────────────────
      │  note  function 'add' defined here
    1 │  fn add(a: int64, b: int64) -> int64 {
      ·     ^^^
  ────┴─────────────────────────────────────────────────────────────

  help: pass 2 arguments, or change 'add' to take 3

The rule

Carries the location and nothing else, as file:line:column, so a terminal or an editor can turn it into a jump. Every diagnostic opens with one, which is also what separates one from the next.

The headline

Severity, code, message. The bracketed code is stable across compiler versions, which is what makes it worth grepping for and what the reference is indexed by. This line is never wrapped, even on a narrow terminal.

The frame

The line the diagnostic is about, with one line of context either side. Tabs expand to four columns so the caret lands under the character you see rather than the byte offset.

The caret

^^^ marks the exact span, and the text after it says what is wrong there. The headline says what happened; the label says which part of the line caused it.

Notes and help

A note points at a second place that matters: the declaration a call disagrees with, the previous declaration a duplicate collides with, or the comptime iteration a generated declaration came from. Notes share the parent's frame, so one error reads as one object. help: is the suggested fix.

Several errors at once

The compiler reports every error it can reach, then summarizes:

──────────────────────────────────────────────────────────────────
  error: could not compile `bad.mettle` due to 2 previous errors
  help: for more about this error, run `mettle explain M0118`

The parser resynchronizes at block boundaries, so one missing brace reports once instead of cascading. Diagnostics that are only consequences of an earlier one are suppressed.

Severity

error fails the build, warning does not. Most memory diagnostics are warnings, because the code they describe is legal and wrong. The ones that cannot possibly be intended, such as returning the address of a local, are errors.

The codes

E0001 to E0007 are the compiler's categories: lexical, syntax, semantic, type, scope, I/O, internal. M0101 to M0119 come from the memory, borrow and range analyses, which stamp a finer code over the category they would otherwise carry.

mettle explain E0003
mettle explain list

The same text is on this site: every code the compiler can print. Both come from one table, so they cannot disagree.

JSON

--error-format=json writes one object per diagnostic to stderr, newline-delimited:

{"severity":"error","code":"M0118","message":"Integer 300 is out of range for 'int8'",
 "file":"bad.mettle","line":2,"column":17,"length":3,
 "label":"does not fit in 'int8' (-128..127)",
 "help":"'int8' holds -128..127. Widen the type, or cast to say the wrap is meant: (int8)value",
 "notes":[]}

length is how many columns the caret spans; notes carries the secondary locations.

Presentation

Redirected output gets no colour, no box characters, and no line wrapping, so a captured build log is stable regardless of anyone's terminal.

Next: reading the optimization report.