Reference

Diagnostics

How to read compiler errors and use structured repair packets.

Reading An Error

A typical error looks like this:

error[NAM003]: Unknown identifier
  unknown identifier 'message'
  examples/hello.0:2:27
 
  2 |     check world.out.write(message)
    |                           ^^^^^^^
  rule: Names must be declared before use in the current lexical scope.
  expected: local binding, parameter, function, builtin value
  actual: no visible symbol named 'message'
  fix: Introduce a local binding before this use (local-edit)
  explain: zero explain NAM003

Read it from top to bottom:

  • The first line gives the stable code and short title.
  • The message says what went wrong.
  • The span points to the file, line, and column.
  • The source excerpt marks the exact token.
  • rule, expected, and actual explain the mismatch.
  • fix gives the safest repair shape.
  • explain points to deeper help.

In this case, the program tried to write message without declaring it. A local binding fixes it:

pub fun main(world: World) -> Void raises {    let message = "hello from zero\n"    check world.out.write(message)}

Plain Text By Default

Default diagnostics should be short and useful in terminal logs.

They should not include ANSI colors, bold styling, hyperlinks, OSC escapes, or terminal control sequences by default. Those bytes bloat agent context and make logs harder to compare.

Use:

zero check examples/hello.0

JSON For Tools

JSON is explicit. Use --json for agents, CI, editors, deep dives, and tools that need stable structured data.

zero check --json examples/hello.0

The native JSON shape is a versioned diagnostics packet:

{
  "schemaVersion": 1,
  "ok": false,
  "diagnostics": [
    {
      "severity": "error",
      "code": "NAM003",
      "message": "unknown identifier 'message'",
      "path": "examples/hello.0",
      "line": 2,
      "column": 27,
      "length": 7,
      "expected": "visible local, parameter, function, or builtin",
      "actual": "no visible symbol named 'message'",
      "help": "declare the name before using it",
      "fixSafety": "behavior-preserving",
      "repair": {
        "id": "manual-review",
        "summary": "Inspect the diagnostic fields and choose a repair manually."
      },
      "related": []
    }
  ]
}

Fix Safety

Fixes carry safety labels so agents know when they can act:

  • format-only: changes only formatting
  • behavior-preserving: preserves program behavior
  • local-edit: confined to the current local scope or file
  • api-changing: changes function signatures, exported names, package APIs, or call sites
  • requires-human-review: risky or ambiguous; show the plan but do not apply automatically

Current Native Diagnostics

The native compiler keeps stable codes for implemented control-flow and type rules:

  • PAR100: parser syntax failures such as missing braces, commas, or malformed type argument lists
  • NAM003: unknown identifiers
  • NAM004: function calls with the wrong number of arguments
  • IMP001: unknown package-local imports, with repair id fix-import-path
  • IMP002: package-local import cycles
  • IMP003: duplicate public exports across imported modules
  • PKG001: a local package dependency path does not contain zero.json
  • PKG002: package dependencies form a cycle
  • PKG003: one package name resolves to conflicting versions
  • PKG004: a package dependency does not support the selected target
  • BLD002: bad project manifest or unsupported manifest target shape
  • ERR002: a caller's explicit error set is missing an error raised by a callee
  • ERR003: a fallible call was used without check or rescue
  • ABI001: unsupported C ABI export or extern layout surface
  • CIMP003: a foreign-target C dependency would use host include paths, host library paths, or implicit host pkg-config discovery
  • BOR001 and BOR002: local borrow conflicts and returning references to local bindings
  • OWN001: owned value use after move, or generic containers that would own unconstrained generic payloads
  • TYP010: conditions must be Bool
  • TYP002: type mismatch in assignments, literals, returns, or shape defaults
  • TYP011: null requires a Maybe<T> context
  • TYP012: break requires an enclosing loop
  • TYP013: continue requires an enclosing loop
  • TYP014: range loop bounds must be integer-compatible
  • TYP015: an integer literal must use valid digits, separators, radix prefixes, and integer suffixes
  • TYP016: an integer literal must fit the expected primitive integer width
  • TYP017: as casts are limited to primitive numeric and byte char source and target types
  • TYP018: a character literal must contain exactly one byte or a supported byte escape
  • TYP019: a float literal must use digits "." digits with an optional exponent
  • TYP020: a float literal must fit the expected primitive float width
  • TYP021: indexing, slicing, and indexed assignment require a supported target for that operation
  • TYP022: index expressions and present slice bounds must be integers
  • TYP023: generic call type argument count mismatch, or type arguments on a non-generic function
  • TYP024: generic inference found conflicting concrete types for one type parameter
  • TYP025: generic type arguments could not be inferred from local call arguments
  • TYP026: a type alias is duplicated, malformed, or cyclic
  • PUB001: a public declaration omitted required explicit API type metadata
  • MET001: a parsed meta expression requested compile-time behavior this compiler slice cannot evaluate yet
  • IFC001: an interface constraint is unknown or a concrete type argument is not a shape
  • IFC002: a constrained concrete shape is missing a required static interface method
  • IFC003: a concrete static method has the wrong parameter count for an interface
  • IFC004: a concrete static method has the wrong return type for an interface
  • IFC005: a concrete static method has the wrong parameter type for an interface
  • STC001: a static value parameter uses an unsupported non-integer type
  • STC002: a static value argument is not an integer literal or deterministic top-level const
  • STC003: an explicit static value argument conflicts with the value carried by an annotated type
  • SHM001: a generic shape method call cannot infer inherited shape type/static parameters
  • SHM002: arguments to a generic shape method imply conflicting Self instantiations
  • RCV001: a receiver-style call names an unknown method or a static method without self
  • RCV002: a receiver-style call needs an addressable receiver, or a mutable receiver for mutref<Self>
  • FLD001: a shape literal includes an unknown field
  • FLD002: a shape literal omitted a required field that has no default
  • TAR001: the requested target name is not in zero targets
  • TAR002: the selected non-host target does not provide the hosted filesystem capability required by std.fs
  • Bounds check failures: native executables print zero bounds check failed and abort when an index, indexed assignment, or slice range is outside the base length.
  • MAT004: a match arm can bind a payload only for a choice case that carries one

Standard Library Diagnostics

Standard library modules use the same repair-packet contract as compiler diagnostics.

  • MEM001 reports malformed memory type forms such as Maybe without its required type argument.
  • std.parse, std.json, and std.env diagnostics carry source spans where applicable.
  • std.time diagnostics can use offset-only spans for single-token inputs.
  • Standard library codes stay stable and package-local, while zero explain <code> provides human guidance and --json exposes structured fix metadata.

Common Repair Packets

Hosted filesystem helpers are host-only in the current compiler. This fails clearly on non-host targets:

bin/zero check --json --target linux-musl-x64 conformance/native/fail/std-fs-target-unsupported.0

The diagnostic uses TAR002, fixSafety: "requires-human-review", and repair id remove-hosted-fs-or-use-host-target.

The canonical repair is either to build for the host target or move the std.fs call out of the target-neutral entry point.

Writable byte helpers require mutable storage:

let dst: [4]u8 = [0, 0, 0, 0]let src: [4]u8 = [122, 101, 114, 111]let _copied = std.mem.copy(dst, src)

This reports TYP009 with repair id make-binding-mutable. The canonical repair is:

let mut dst: [4]u8 = [0, 0, 0, 0]let src: [4]u8 = [122, 101, 114, 111]let _copied = std.mem.copy(dst, src)

Named-error std.fs calls require explicit error flow:

let file = std.fs.createOrRaise(fs, ".zero/out.txt")

This reports ERR003 with repair id check-or-rescue-fallible-call.

Use check and include NotFound, TooLarge, and Io in the caller's raises { ... } set. Use rescue locally when the call should recover in place.

Generic calls use local inference only. This fails because T would need to be both i32 and u8:

fun first<T>(left: T, right: T) -> T {    return left} let value: i32 = first(1, 2_u8)

The repair is to make the arguments agree or pass explicit type arguments with compatible values:

let value: i32 = first<i32>(1, 2)

Public constants need explicit API shape:

pub const answer = 42

This reports PUB001. The behavior-preserving repair is:

pub const answer: i32 = 42

C interop keeps host and target discovery separate. This fails for a foreign target because the manifest asks for host include/library discovery:

bin/zero build --json --target linux-musl-x64 conformance/c/host-leak-package --out .zero/out/host-leak-package

This reports CIMP003 with repair id configure-target-c-dependency.

The canonical repair is to use package-relative vendored headers/libraries or configure the target sysroot. Do not rely on host include paths, host library paths, or host pkg-config discovery for cross-target builds.

Package dependency diagnostics are graph-level repairs:

CodeMeaning
PKG001A local dependency path is wrong or missing.
PKG002Two package manifests depend on each other cyclically.
PKG003The graph resolved one package name to multiple versions.
PKG004The selected target is outside the dependency's target list.

These all use requires-human-review because the correct repair may change package topology or target support.

Type aliases are compile-time spellings and cannot cycle:

type A = Btype B = A

This reports TYP026. Point the alias at a concrete type such as Span<u8> or remove the cycle.

Unsupported compile-time execution reports MET001, for example const os: String = meta target.os, until target facts are implemented in the native compiler.

Generic shape methods must specialize from a concrete Self value or explicit shape arguments:

shape FixedVec<T, static N: usize> {    fun cap() -> usize {        return N    }} let cap = FixedVec.cap()

This reports SHM001.

Repair it in one of two ways:

  • pass explicit shape arguments, such as FixedVec.cap<u8, 4>()
  • call a method that receives a concrete Self value, such as FixedVec.push(&mut vec, value) or vec.push(value)

SHM002 means the explicit method arguments and the receiver's annotated shape disagree.

Receiver calls require a declared method whose first parameter is self: ref<Self> or self: mutref<Self>:

let vec: FixedVec<u8,4> = FixedVec { len: 0, items: [0, 0, 0, 0] }check vec.push(1)

This reports RCV002 because push needs mutref<Self> and vec is immutable.

Unknown receiver methods report RCV001. Static methods without self should be called through the shape namespace.

Shape field defaults allow omitted fields, but only when the declaration provides a compatible default:

shape NeedsItem {    count: usize = 0,    item: u8,} let value: NeedsItem = NeedsItem {}

This reports FLD002 with repair id initialize-missing-field because item has no default. A default with the wrong type reports TYP002 at the default expression.

Static interfaces are checked at generic specialization time. This fails because Counter does not provide the required static method:

interface Readable<T> {    fun read(self: ref<T>) -> i32} shape Counter {    value: i32,} fun readValue<T: Readable<T>>(value: ref<T>) -> i32 {    return T.read(value)}

This reports IFC002. Add a concrete static method with the matching signature:

fun read(self: ref<Self>) -> i32 {    return self.value}

Static value parameters are checked before emission so fixed-size layouts stay concrete:

shape FixedVec<T, static N: usize> {    len: usize,    items: [N]T,} fun first<T, static N: usize>(vec: ref<FixedVec<T,N>>) -> T {    return vec.items[0]} let vec: FixedVec<u8,4> = FixedVec { len: 4, items: [1, 2, 3, 4] }let bad = first<u8, 8>(&vec)

This reports STC003 because the explicit 8 conflicts with the annotated FixedVec<u8,4>.

Related static-value diagnostics:

  • STC001: unsupported static parameter type
  • STC002: runtime value used where a compile-time integer is required

Commands

  • zero check <input>: human-first plain text by default
  • zero check --json <input>: full repair packets
  • zero explain <code>: human explanation for a diagnostic code
  • zero explain <code> --json: machine-readable explanation
  • zero fix --plan --json <input>: proposed typed fixes without editing files

Useful examples:

zero explain TAR002
zero explain --json TYP009
zero fix --plan --json conformance/native/fail/mem-copy-immutable-dst.0
zero fix --plan --json --target linux-musl-x64 conformance/native/fail/std-fs-target-unsupported.0