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 emits are M0089 (redundant ignore), M0145/M0146 (pattern coverage), M0194/M0198 (unused identifier and field 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). moc's other warning codes are 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.3 (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.