17.4 Error Messages as Compiler UX
For many programmers, diagnostics are the compiler’s most frequently viewed interface. A technically correct message can still be unusable if it points at the wrong token, describes an internal implementation detail, or causes twenty follow-on errors. Treat diagnostics as structured product data, not strings scattered through compiler passes.
A diagnostic record commonly contains a stable code, severity, primary source span, secondary labels, a concise headline, explanatory notes, and zero or more fix-its. The headline should state the problem and relevant names: “expected bool, found String” is more actionable than “type mismatch.” The primary label identifies where the user must look; secondary labels connect evidence such as where a value was declared or a borrow began. Notes explain why a rule applies, while help text suggests a next action without pretending certainty.
Source coordinates require care. Parsers often store byte offsets, editors speak UTF-16 positions, and humans see display columns affected by tabs, combining marks, and wide Unicode characters. Keep canonical byte ranges internally and convert at the presentation boundary using the exact source revision. A fix-it must target that revision, avoid overlapping edits, and represent a syntactically valid change. If several repairs are plausible, explain alternatives rather than silently choosing one.
Suppose total += name fails because total is an integer and name is a string. A strong diagnostic underlines name, labels total with its inferred type, points to the declaration that made name a string, and offers conversion only if the language has an unambiguous operation. Underlining the entire statement makes the message visually louder but less precise. Exposing an internal unification-variable ID explains the implementation, not the user’s model.
Recovery determines the next messages
After detecting an error, a compiler can stop, or it can recover and continue. Recovery helps interactive editing but risks cascades: one missing delimiter distorts the parse tree and produces unrelated name and type errors. Panic-mode recovery discards tokens until a synchronization point such as ;, }, or a declaration starter. It is simple and robust, but may lose useful nearby structure. Local repair inserts, deletes, or replaces a small number of tokens; it keeps more structure but must not invent implausible programs. Island parsing recognizes reliable “islands” such as declarations while treating uncertain regions as opaque water.
Represent failure explicitly in the tree and type system. An error expression can carry a span and an “already diagnosed” marker. A poison or error type should flow through dependent operations without issuing the same mismatch repeatedly. Later passes must tolerate these nodes or be skipped when their invariants are unavailable. Recovery is therefore a pipeline contract, not only a parser technique.
Test usefulness as well as text
Diagnostic golden tests should cover the code, severity, labels, source rendering, and fix-its. Structured assertions are less brittle than comparing terminal colors. Apply every machine-applicable fix in a temporary workspace, reparse it, and ensure it does not introduce a new error. Test incomplete files, multiple simultaneous errors, macros or generated code, Unicode, and stale editor revisions.
Stable diagnostic codes let documentation, IDEs, suppressions, and telemetry refer to an error even when wording improves. Machine-readable output should version its schema and separate localized prose from stable fields. Localization affects grammar and width, so assembling sentences from tiny translated fragments is risky; keep complete translatable messages with typed placeholders.
Metrics need interpretation. Fewer emitted errors may mean excellent cascade suppression or a parser that gave up. Useful measures include distance from the true fault to the primary span, valid-fix rate, repeated-code rate, time to first relevant message, and whether users can resolve a task without external help. Short usability studies with real broken programs reveal problems snapshots cannot: misleading emphasis, unfamiliar jargon, or advice that is technically possible but contextually wrong. A great diagnostic teaches the violated model while helping the programmer make the next safe edit.