Diagnostics
Diagnostics are a product feature. Every error points at the user’s .lyth
source, uses Lyth’s vocabulary, explains expected versus actual, and suggests a
fix when one is known. Generated names and generated line numbers never appear
in the primary message.
1. Model
Section titled “1. Model”type Diagnostic struct { Severity Severity // Error, Warning, Note Code Code // "E0201" — stable, documented, testable Message string // one line, lower-case, no trailing period Span source.Span // primary location (file + byte range) Label string // short text shown under the primary span's carets Related []Related // secondary spans with labels ("declared here") Notes []string // "= note: ..." explanations Hints []Hint // "= hint: ..." suggestions, optionally with a concrete edit Cause *GoCause // original Go toolchain message + generated position (verbose mode)}All stages report through diag.Sink, which deduplicates (same code, span, and
message), caps cascades (at most one error per expression node, and nothing
reported on BadExpr), and sorts diagnostics by file and position before
rendering. The parser records each statement or declaration it had to recover
from (syntax.File.Damaged); the checker reports nothing inside those, and no
warnings at all in a file with syntax errors, since what the parser built there
is a guess. Rejected spellings the parser understands (E0005, E0008) are not
damage: the parser reports the Lyth spelling and continues normally. The lambda argument of a call whose callee failed to check gets
parameters of unknown type, so only errors of its own are reported.
2. Rendering
Section titled “2. Rendering”Human format (default, colored when stderr is a TTY):
users.lyth:12:27: error[E0201]: type mismatch in argument id of repo.find |12 | return repo.find(ctx, id)? | ^^ expected int64, found string | = hint: convert explicitly: id.toInt64()? (parsing can fail)- The first line uses the
path:line:col:prefix, so editors,grep, vim quickfix, and VS Code problem matchers parse it unchanged. - Columns are 1-based Unicode code points. Carets span the whole primary range. Tabs are expanded consistently in the snippet and the caret line.
--diagnostics=jsonemits one JSON object per diagnostic with byte offsets, line/column pairs, and all fields above, for editors and tests.-vaddsCauseblocks: the Go message, the generated file and position, and the mapping kind (exact, line-level, or unmapped).
3. Code catalog (v0.1)
Section titled “3. Code catalog (v0.1)”| Range | Area | Codes |
|---|---|---|
| E00xx | Syntax | E0001 invalid character · E0002 unterminated literal · E0003 unexpected token · E0004 invalid number · E0005 := declares nothing new (the name is already declared in this scope; hint: use =) · E0006 $name template without braces · E0007 invalid escape · E0008 syntax from another language or an earlier Lyth spelling (fun, local val/var, this, Map<K, V>, List<T>, <T>, Go receivers, range, pub, <-), with the Lyth spelling, usually as an exact edit |
| E01xx | Names & packages | E0101 undefined name (an old type name such as Int gets the edit int) · E0102 duplicate declaration / Go-name collision · E0103 import cannot be loaded · E0104 not a package/type/value where one is needed · E0105 unexported declaration used from another package · E0106 unknown member of package |
| E02xx | Types | E0201 type mismatch · E0202 mismatched operand types · E0203 wrong argument count · E0204 not callable · E0205 unknown field or method · E0206 missing return · E0207 wrong number/types of return values · E0208 cannot infer type · E0209 invalid operation for type · E0210 multiple values in single-value context · E0211 invalid struct construction · E0212 does not implement interface · E0213 invalid type arguments · E0214 invalid conversion · E0215 constant overflow · E0216 invalid use of a construct (e.g. a non-exhaustive when, self outside a method) |
| E03xx | Errors | E0301 ? outside fallible function · E0302 ? on non-fallible expression · E0303 unhandled error result · E0304 throw outside fallible function · E0305 invalid throw operand · E0306 catch on non-fallible expression · E0307 catch block must yield a value or exit |
| E04xx | Nullability | E0401 nullable value used as non-null · E0402 null for non-nullable type · E0405 non-zeroable type without initializer · E0406 nullable slice or map · E0407 nullable type parameter (not supported yet) |
| E05xx | Mutability | E0501 assigning to a read-only binding (parameter, loop variable, catch binding, self, constant) · E0502 mutating through a read-only binding (a value receiver’s fields) · E0503 address of a read-only binding · E0504 pointer-receiver call on a read-only value |
| E06xx | Interop | E0601 unsupported construct (with the specific reason) · E0602 Go toolchain/version problem |
| E09xx | Internal/toolchain | E0901 internal compiler error · E0902 generated Go failed type check · E0903 Go compiler error · E0904 Go linker / toolchain error |
| W0xxx | Warnings | W0001 unused local · W0002 unused import · W0003 prefer throws · W0004 unnecessary safe call / !! · W0005 unreachable code · W0006 a declaration hides a variable of an enclosing scope or a field of self |
Codes never change meaning once released. lyth explain E0301 printing long-form
explanations is [planned].
4. Message style
Section titled “4. Message style”- State the problem, then the facts: “type mismatch in argument id of repo.find”, then a label “expected int64, found string”.
- Use Lyth names for types (types.md §12), which are Go’s spellings (
int,map[string]int) plus Lyth’s own (User?, platformerror!,untyped int), and Lyth names for Lyth declarations. Go declarations keep their Go names, because that is what the user wrote. - Never mention generated temporaries (
err2), generated files, or Go-only concepts unless the error concerns interop, and then label them as Go (“Go method FindUser in package example.com/repo”). - Hints carry concrete edits where possible (insert
?, addthrows, wrap withfloat64(...), writefuncforfun, use=instead of:=), so an LSP can offer quick fixes.
5. Diagnostics by class
Section titled “5. Diagnostics by class”| Class | Strategy |
|---|---|
| Syntax errors | Expected-token sets from the parser state (“expected ) to close the call started at 3:14”). Recovery prevents cascades. Specific diagnostics for common slips, each with the Lyth spelling: Kotlin habits and earlier Lyth spellings (fun, val, this, for (x in xs), List<T>, <T>), Go habits Lyth spells differently (receivers func (s *S) M(), for i, x := range xs, ch <- v, [T C]), a := that declares nothing new, a body without braces, "$name". |
| Undefined names | Nearest-match suggestions (edit distance ≤ 2) from the visible scope, and, for selectors on Go packages, exported names of that package (“did you mean http.StatusOK?”). |
| Invalid imports | Go loader errors at the import span, with go get suggestions. |
| Inference failures | Point at the expression with no type source and suggest an annotation. |
| Generic arguments | Name the type parameter and constraint, and give go/types’ reason. |
Invalid ? |
E0301/E0302 with “add throws to the declaration of X” or “use ?: for nullable values”. |
| Return types | Point at the return expression, plus a related span at the declared result type. |
| Interfaces | List the missing or mismatched methods, with each expected signature and the reason (pointer receiver, a name that differs in case, wrong nullability). |
| Overloads | Lyth has no overloading, so there is nothing to disambiguate. A duplicate name is E0102. |
| Duplicate declarations | Primary span on the second declaration, related span on the first. |
| Unsupported interop | E0601 naming the construct and the planned alternative. |
| Go compiler errors | §6. |
| Linker errors | E0904 with the Go linker text. When it mentions a symbol, the symbol is mapped back to its Lyth declaration via the name table. |
6. Mapping Go toolchain errors
Section titled “6. Mapping Go toolchain errors”Three sources, in order of preference:
- Lyth checker: most errors, native spans.
- In-process
go/typesvalidation of generated code: structuredtypes.Error{Pos, Msg, Soft}. The position is read unadjusted (raw generated offset) and mapped through the exact source map to the innermost Lyth span covering it. The message is translated: Go type spellings are rewritten to Lyth spellings token-wise using the types involved, and generated temporaries are replaced by the Lyth expression they stand for (the span table knows). Reported as E0902 with the original message preserved asCause. go build -json: build events with structured package boundaries (ImportPath,Action,Output). Lines insideOutputare compiler diagnosticsfile:line[:col]: message. They are parsed with a position parser that accepts only the known generated and Lyth file paths, never by regex-guessing arbitrary text. Positions already point at.lythlines thanks to//line. Lines that don’t match are kept verbatim as notes, never dropped. Non-JSON stderr (early fatal errors) is attached as a cause.
Fallback when exact mapping is impossible: use line-level mapping (the
//line file and line, column unknown). If there is no mapping at all (an error
in a synthesized package-level helper, or a linker error), attach the
diagnostic to the nearest enclosing Lyth declaration, or to the package, and
mark it “location approximate”, with the full Go output in Cause. A Go
diagnostic is never shown to the user without its original text available.
7. Testing diagnostics
Section titled “7. Testing diagnostics”tests/check/*.lythcontain inline expectations:The harness compares code, file, line, column (the column of the comment’sx: int = "a" // ERROR E0201 "expected int, found string"^marker when present), and a message substring. Every expected diagnostic must occur, and no unexpected diagnostic may occur.- Snapshot tests (
*.diag.golden) pin the full rendered output of selected cases, including snippets and carets. - Toolchain-mapping tests deliberately produce
go/typesvalidation errors andgo builderrors (e.g. via unsupported constructs or a linker failure) and assert the mapped positions. - A fuzz test feeds random token streams to the parser and checker and asserts that no panic escapes (internal errors must be diagnostics).