8.5 High-Quality Semantic Diagnostics
By chapter 8, the compiler frontend already knows names, scopes, declarations, and types. That makes semantic diagnostics one of the most important parts of the whole system. A type checker that rejects bad programs but produces vague messages is technically functional and pedagogically weak.
High-quality diagnostics should answer four questions quickly:
1. What went wrong?
2. Where is the narrowest relevant range?
3. Which facts prove the message?
4. What should the user try next?
Good Messages Are Structured Facts
A message such as type error is almost useless by itself. A better diagnostic might say:
argument 2 of takesInt expects int, found stringThat sentence already depends on several facts:
| needed fact | likely owner |
|---|---|
| source span of the argument | parser |
| callee declaration site | resolver |
| expected parameter type | resolver + type checker |
| actual argument type | type checker |
This is why semantic diagnostics should stay structured until the final rendering layer. CLI output, editor hovers, JSON diagnostics, and golden tests all need the same underlying fact set.
Narrow Ranges Beat Broad Blame
Point to the smallest code region that is directly responsible for the problem.
For example:
- an unknown field should usually highlight the field name,
- a wrong argument type should usually highlight the argument expression,
- a return-type mismatch should usually highlight the returned expression and optionally note the function signature.
If every message highlights a whole line or whole file, the compiler is technically reporting location data but not helping the user localize the repair.
Stop Cascades Without Hiding Root Causes
Semantic analyzers often face a choice after the first error. If x is undeclared, should the type checker also report that x + 1 is invalid, then that print(x + 1) has the wrong argument type, then that the enclosing function cannot infer a return type?
Usually not. The frontend should propagate an ErrorSymbol or ErrorType so later phases stay stable without turning one root cause into five low-value consequences.
Notes, Secondary Spans, and Repair Guidance
Many semantic errors need more than one source location.
| error kind | useful extra note |
|---|---|
| wrong argument type | where the callee parameter was declared |
| duplicate declaration | where the first declaration already exists |
| invalid return type | where the function's declared return type appears |
| inaccessible import/export | where the symbol is defined or hidden |
That does not mean every diagnostic needs a paragraph of prose. It means a good compiler knows when one message is not enough and adds a focused secondary note instead of a vague essay.
Worked Redesign
Compare these two messages:
bad:
type error
better:
cannot return bool from function returning int
note: function declared here: fn area(...) -> intThe second message teaches more because it identifies the operation, the expected type, the actual type category, and the declaration that created the contract. It is still short, but every clause earns its place.