Completes plan 2 Tasks 7-8. owner.ml: mutable-value-semantics flow analysis producing the four plan-3 emitter tables (moves, drops incl. LIVE-MASK for trap unwinding, rc with elision, residual borrow sites) plus WO-E301-304 two-site diagnostics. Alias questions run over canonicalized places, so a double-mut reached through let-bound aliases lands in the residual table like the direct form; dump.ml's contract notes the emitter must coalesce guards per operand. main.ml: directory discovery, cross-file programs (symbols merge before bodies check), diagnostics ordered by (file,line,col), new WO-E214 for a name declared in two files. New docs/plan/oop-vm/01-error-catalog.md (14 emitted + 10 reserved codes), un-ignored so both plan tracks can cite it; justfile regains woc-*. builtin_scalars is now the five that work: Int, Bool, Text, Timestamp, Id. Money/SKU/Float and the abstract_types allowlist are gone — `abstract` never lexed, and Float had no literal syntax and no wob kind, so no value could exist. Fixtures and samples retype Money->Int, SKU->Text. The abstract newtype feature is rejected outright (verdict row adopt->reject); haxe-parity Task 7 keeps `is`. nullable-types-implementation.md corrected: ?T is plumbed but UNENFORCED (E211-213 declared, never emitted; probe exits 0), handed to haxe-parity Task 6 as next work item. Records all 10 dead codes incl. E205 — interface satisfaction is unchecked. crates/rt keeps its Money/SKU fixtures (opaque strings, Stage 2). Gate: build warning-clean, 14 + 264 checks 0 failures, pricing golden exit 0, docs/examples histograms unchanged (13/70, zero WO-E225).
94 lines
7.6 KiB
Markdown
94 lines
7.6 KiB
Markdown
# The `woc` diagnostic catalog — normative reference
|
||
|
||
Every `WO-E###`/`WO-W###` code the `woc` front end (`compiler/`) actually
|
||
emits, as of plan 2 tasks 2–8. Code ranges are reserved per stage
|
||
(`compiler/src/diag.ml`): `WO-E0xx` lexing, `WO-E1xx` parsing, `WO-E2xx`
|
||
types, `WO-E3xx` ownership, `WO-W2xx` warnings from the types stage. This
|
||
is an enumeration of codes already in use, not an archaeology dig — see
|
||
"Completeness method" below for how that was verified, and "Reserved,
|
||
not yet emitted" for codes the source declares but no check yet raises.
|
||
One code (WO-E214) is emitted by the driver (`compiler/bin/main.ml`),
|
||
not one of the four stage modules — a Task 8 review finding — see its
|
||
row in the types table below for why it still uses that range.
|
||
|
||
Every diagnostic renders as `file:line:col: <severity> <code>: <message>`,
|
||
the source line, and a caret under the column (`compiler/src/diag.ml`);
|
||
ownership errors (`WO-E3xx`) add a second, indented site for the other
|
||
half of the story ("moved here" / "borrowed here" / etc.).
|
||
|
||
## WO-E0xx — lexing (Task 3, `compiler/src/lexer.ml`)
|
||
|
||
| code | meaning | example message |
|
||
| --- | --- | --- |
|
||
| WO-E001 | an input byte the lexer doesn't recognize as the start of any token. Reported once per bad byte, which is then skipped — one bad byte never stops the whole file. | `unknown character '$'` |
|
||
| WO-E002 | a string literal's backslash escape is the last byte of the file, with no character left to escape (a plain unterminated string with no dangling backslash is *not* an error — rt parity). | `unterminated string escape` |
|
||
|
||
## WO-E1xx — parsing (Tasks 4–5, `compiler/src/parser.ml`)
|
||
|
||
| code | meaning | example message |
|
||
| --- | --- | --- |
|
||
| WO-E101 | generic syntax error: an unexpected token where the grammar expected something else, including running off the end of the file inside an unclosed block/type/interface body. Declaration-level recovery syncs to the next top-level keyword so one bad declaration yields one diagnostic, not a cascade. | `expected ')' or ',', got NEWLINE` |
|
||
| WO-E102 | an invalid `@table(...)` configuration: `name` given twice, an `index` with no columns, or an argument key other than `name`/`index`. | `@table(name: ...) given twice` |
|
||
|
||
## WO-E2xx / WO-W2xx — types (Task 6, `compiler/src/types.ml`; WO-E214 Task 8, `compiler/bin/main.ml`)
|
||
|
||
| code | meaning | example message |
|
||
| --- | --- | --- |
|
||
| WO-W201 *(warning)* | a class has recursive/shared structure (a field, directly or through `ref`/`multi`/`map`/`?`, refers back to its own class) that the ownership pass cannot prove disjoint, has no `@table`, and has no `@unique` field — suggests `@gc`. | `Node has recursive/shared structure that borrow checker cannot prove. Consider adding @gc if this is an ephemeral in-memory cache. If this maps to a database table, keep owned (default).` |
|
||
| WO-E202 | a `.field` access names a field that the base's class (a *declared* class — an unresolved/placeholder expression type never triggers this) doesn't have. | `unknown field \`price\` on \`Product\`` |
|
||
| WO-E206 | a constructor literal (`ClassName { ... }`) omits a field the class declares (no default). | `missing field \`sku\` in constructor of \`Product\`` |
|
||
| WO-E207 | a constructor literal names a class that isn't declared anywhere in the (possibly multi-file) program. | `unknown type \`Widget\` in constructor` |
|
||
| WO-E214 | a class or interface name is declared more than once across the files a directory discovers (one program, multiple files — Task 8). Reported at the *later*-discovered declaration (sorted by path), with the first declaration as the related site; the merged symbol table keeps the first one, so this is what stops that silent keep from also hiding a real shape conflict. Driver-level, not `types.ml` — reuses the `types_prefix` range because it's a symbol-table concern, not a lexing/parsing/ownership one. | `class \`Dup\` already declared in \`a_first.wo\`` |
|
||
| WO-E225 | a field's declared type name isn't a builtin scalar, a declared class, or a declared interface. Checked once per field declaration, at the field's own position. | `unknown type \`Wdiget\`` |
|
||
|
||
### Reserved, not yet emitted
|
||
|
||
`type_mismatch_code` (WO-E201), `bad_arity_code` (WO-E203),
|
||
`unknown_fn_code` (WO-E204), `unsatisfied_interface_code` (WO-E205),
|
||
`non_exhaustive_switch_code` (WO-E208), `invalid_builtin_code` (WO-E209),
|
||
`module_not_imported_code` (WO-E210), `nullable_used_without_check_code`
|
||
(WO-E211), `nullable_assign_mismatch_code` (WO-E212), and
|
||
`missing_nil_check_code` (WO-E213) are declared in `types.ml` — the
|
||
range is reserved — but as of Task 7 nothing in the front end ever
|
||
raises them; there is no call site and therefore no real example
|
||
message to catalog. They read like placeholders for checks Task 6's own
|
||
plan brief named (type mismatch, bad arity, unsatisfied interface, …)
|
||
that the shipped typechecker doesn't yet implement. Listed here so a
|
||
conformance fixture (plan 3) or a future reader doesn't assume one of
|
||
these codes is reachable today; move a code up into the table above in
|
||
the same commit that wires its first real emission site.
|
||
|
||
## WO-E3xx — ownership / MVS (Task 7, `compiler/src/owner.ml`)
|
||
|
||
Every ownership diagnostic carries a second site (the module doc's
|
||
"two-site errors are the product") — the example messages below are the
|
||
primary message only; the related site's label (e.g. "`b` moved here")
|
||
renders indented beneath it.
|
||
|
||
| code | meaning | example message |
|
||
| --- | --- | --- |
|
||
| WO-E301 | a place is read (or moved again) after its value was already moved — by assignment, `take` argument passing, constructor field init, or `return`. | `use of \`b\` after it was moved` |
|
||
| WO-E302 | a place is moved while a live borrow of it, or of an overlapping place, still exists. | `cannot move \`bag.items\` while \`r\` is borrowed` |
|
||
| WO-E303 | two exclusive (`mut`) accesses of the same place, or two accesses the analysis can *prove* overlap, conflict in one region (e.g. two `mut` element accesses through the same provable index, or the same place borrowed and then mutated). Cases the analysis can't prove either way become a residual site for the VM to guard at runtime, not this diagnostic. | `cannot borrow \`bag.items[i]\` as \`mut\` twice in the same call` |
|
||
| WO-E304 | a borrow is returned or stored somewhere that outlives the scope it borrowed from. `@gc`-typed values are exempt (freely aliased by design). | `borrow of \`x\` returned — borrows cannot outlive their scope` |
|
||
|
||
## Completeness method
|
||
|
||
Every code in this catalog was found the same way: grep every
|
||
`Diag.error`/`Diag.warning` call site across `compiler/src/*.ml` (lexer,
|
||
parser, types, owner — dump.ml never constructs a diagnostic) and
|
||
`compiler/bin/main.ml` (the driver — added after a Task 8 review found
|
||
WO-E214 living there, outside the original src/*.ml-only sweep), then
|
||
cross-reference each `~code:` argument back to the `let <name>_code = ...`
|
||
constant it names. Every constant with at least one such call site is
|
||
in the table above; every constant with zero call sites is listed under
|
||
"Reserved, not yet emitted" instead of silently omitted. `parser.ml`'s
|
||
`fail`/`unexpected`, `owner.ml`'s `report`/`escape`/`check_against_borrows`,
|
||
and `main.ml`'s `report_collision` are the only indirection layers
|
||
between a bare `~code:` argument and the `Diag.error` call — each was
|
||
read to confirm which named constant ultimately reaches the collector,
|
||
not just the arity-generic wrapper name. This is why the catalog is an
|
||
enumeration, not a dig: `diag.ml` already reserves the numeric ranges,
|
||
so the only open question per stage was *which* reserved codes actually
|
||
fire — answered by exhaustive grep, not inspection of a handful of
|
||
samples.
|