Reading diagnosticsalpha
What do moxzi's error codes mean and what should I put in a bug report?
moxzi renders diagnostics the way moc does — file:line.col-line.col: kind [Mxxxx], message — using moc's own code numbering, so editor tooling, search results and habits carry over unchanged. A failing build prints its errors; a successful build prints its warnings and then the provenance line. Warning coverage is narrower than moc's — the codes the compiler currently emits are M0089 (unused pattern binding), M0254 (a stable field the migration chain does not produce), M0265 (mixin capability) and M0266 (a float literal with more precision than Float holds — a check that found real miscompiles when it was built, so take it seriously). Codes moc emits that moxzi does not yet, notably M0194 unused-identifier and the M0145/M0146 pattern-coverage family, are simply absent: a warning-clean moxzi build is not evidence of a warning-clean moc build.
Anatomy of a diagnostic#
/src/e.mo:1.33-1.37: type error [M0050], literal of type Text does not have expected type Nat
| Part | Meaning |
|---|---|
/src/e.mo | the VFS path, not your disk path — /src/… for project sources, /pkg/<name>/… for packages |
1.33-1.37 | line.column of the start and end of the offending region |
type error | category plus severity; parse error is the other common form |
[M0050] | the code, shared with moc |
| the rest | the message |
Because the location is a VFS path, moxzi deps is the tool that maps it back to a file on disk.
Codes you will actually meet#
Every one of these was produced by moxzi build on a three-line program:
| Code | You wrote | moxzi says |
|---|---|---|
M0000 | persistent actor { public func f() : async Nat { 1 }; | parse error [M0000], expected } (object body), got EOF |
M0020 | import X "missing"; | type error [M0020], unresolved import missing |
M0031 | a shared function taking a function argument | type error [M0031], shared function has non-shared parameter type … |
M0050 | var x : Nat = "hi" | type error [M0050], literal of type Text does not have expected type Nat |
M0057 | a reference to an undeclared name | type error [M0057], unbound variable y |
M0069 | an actor { … } nested inside a function | type error [M0069], non-toplevel actor; an actor can only be declared at the toplevel of a program |
M0072 | { a = 1 }.b | type error [M0072], field b does not exist in object with fields: a |
M0096 | a value that is not a literal failing a check-mode subtype test | type error [M0096], expression of type … cannot produce expected type … |
M0220 | actor { … } with no persistent | type error [M0220], this actor or actor class should be declared 'persistent' |
M0050 and M0096 are the pair worth understanding: moc reserves M0050 for the literal case and uses M0096 for every other check-mode subtype failure, and moxzi follows that split deliberately.
The code space#
mops/mo-langutils/src/ErrorCodes.mo is the canonical list — a port of moc's error_codes.ml. It carries 206 error codes and 42 warning codes, each flagged with whether moc ships a long-form explanation for it.
| Range | What lives there |
|---|---|
M0000–M0001 | parse and syntax errors; the message, not the code, carries the detail |
M0002–M0245 | type, definedness and lowering errors, mixed with warning codes drawn from the same space |
M0250–M0267 | the enhanced-migration family: M0250 (initializer on a chain-managed stable variable), M0254/M0267 (a field the chain does not produce — warning without a --stable-baseline, error with one), M0169/M0170/M0216 at each chain link and at the baseline boundary |
M9000 | moxzi-specific: unimplemented: … — a construct this compiler does not handle yet |
M9000 is the one code that is not moc's and is always worth reporting. It means the compiler reached a form it has no case for (infer_exp: unimplemented expression form, and similar) rather than that your program is wrong.
Four warning codes ship disabled by default — M0223, M0235, M0236 and M0237, the "redundant type instantiation" and contextual-dot suggestions — matching moc's own defaults.
The parser's depth cap#
The parser refuses to descend past 128 levels and says so before it unwinds:
/src/deep2.mo:1.183-1.184: parse error [M0000], expression nested too deeply (limit 128)
/src/deep2.mo:1.183-1.184: parse error [M0000], expected ) (parens), got (
…
The first line is the real diagnostic; the rest are the parser resynchronising. Hand-written code does not approach this. Machine-generated code should chunk its expressions.
Failures that are not diagnostics#
Two shapes of output mean something other than "your program is wrong":
| Output | Meaning |
|---|---|
warning: could not resolve import "…"; not uploading it | moxzi could not find the import on disk. Not fatal — the compiler may still resolve it — but it is the usual cause of a much more confusing type error a moment later |
Error: canister trapped: … | the compiler itself failed. There is no source location, because there was no diagnostic. This is always a bug |
Here is a real one, on a program moc rejects cleanly:
printf 'persistent actor { public func f() : Nat { 1 } };\n' > oneway.mo
moxzi build oneway.mo -o oneway.wasm
Error: canister trapped: await: oneway body not a block
moc --check oneway.mo
oneway.mo:1.38-1.41: type error [M0035], shared function must have syntactic return type '()' or 'async <typ>'
moc reports M0035 with a location; moxzi crashes in lowering. That gap — a missing front-end check letting a bad program through to a later stage — is exactly what a bug report should capture.
What a good bug report contains#
| Item | How to get it |
|---|---|
A minimal .mo | cut until removing one more line makes the failure disappear; three lines is a good target |
| The exact moxzi output | copy it verbatim, including the provenance line |
moxzi info output | names the compiler and linker wasm and their SHA-256 hashes — the build being reported |
| moc's output on the same file | moc --check file.mo, plus moc --version |
| Which is right | say whether you expect it to compile, and why |
The template:
moxzi --version # moxzi 0.1.0-alpha.1 (7eb76f61c9)
moxzi info # compiler + linker paths and sha256
moxzi build bug.mo -o bug.wasm # the failing command, verbatim output
moc --version && moc --check bug.mo # the reference compiler's verdict
moc is the oracle for this project. A report that pairs both outputs on one small file is actionable; one that describes the failure in prose usually is not. If the two disagree on which code to emit for the same rejection, that is still a bug — the codes are meant to match.
Things you cannot do#
| Why | |
|---|---|
| Get long-form explanations | moc's --explain and its per-code markdown files are not shipped; ErrorCodes.mo tracks which codes have one upstream |
| Get JSON or AI-formatted errors | --error-format and --ai-errors have no equivalent |
| Change a code's severity | -A, -W, -E and -Werror have no equivalent |
| Continue after the first batch | the compile stops at the failing step and reports what that step produced |
Next#
- Coming from moc — the rest of the surface, and where the two deliberately differ.
- Compiling and linking — which step a failure came from.
- Projects and packages — turning a
/src/…or/pkg/…path back into a file.