Skip to main content

Diagnostics

Every diagnostic is an error. There is no warning or note severity anywhere in the compiler — if it reports something, the module will not be emitted.

Format

<phase> error at <line>:<col>: <message>
type error at 2:36: field access requires a non-null declared-struct reference, found ?Point

Line and column are 1-indexed. The position marks where the construct starts — spans have no end, so nothing points at a range.

Types in messages use source spelling (&Foo, ?any, i32), never WAT spelling ((ref $Foo)).

Only one phase is ever reported

Diagnostics are bucketed into five phases, and only the earliest non-empty bucket is shown:

syntax > resolution > feature > type > lowering

Later-phase errors on a broken file are almost always cascades, so they are suppressed entirely. Practical consequence: fixing all your syntax errors can reveal a completely new set of type errors. The count going up is normal, not a regression.

Phases are per-diagnostic, not per-pass — the type-checker emits diagnostics tagged resolution and feature too.

Syntax

MessageCause
expected an expression, found …generic parse failure
expected a declaration, found …junk at top level
expected identifier, found …missing or malformed name
unexpected character '…'not a valid token
unterminated string literal / … (line break)missing closing "
invalid numeric separator in decimal literalmisplaced _ (also hex / fractional / exponent variants)
left-hand side of assignment must be a local name, a ->field place, or an indexed [...] placeassigning to a non-place
expected 'null', '&Type', or '?Type' after 'is'malformed type test
a tuple expression requires at least two elements(x,)

Known wart: unhandled token kinds fall through to Rust Debug formatting, so you will occasionally see internal spellings like StringLit("x") or LParen in a message.

Resolution

MessageCause
function name 'X' must use snake_casenaming convention (also parameter, global)
global name 'x' must use PascalCasenaming convention
type name 'x' must use PascalCasestruct/array/functype naming
local name 'X' must use snake_caselet/var/for-loop variable
unknown local 'x'undefined, out of scope, or wrong case
unknown global 'X' / unknown function 'x' / unknown type 'X'undefined name
duplicate function name 'x'also type, global, memory, table, tag, data
struct 'S' has no field 'f'unknown field
struct 'S' redeclares field 'f'shadowing an inherited field — not allowed
cyclic struct inheritance involving 'S'parent links must be acyclic
unknown label 'l'branch to an out-of-scope label
'break' outside of a loop or forunlabelled branch with no target

If a name "obviously exists" but reports unknown, check the case first — that is the single most common cause.

Feature

MessageCause
'X' is not available in the selected target's instruction catalog (wastc-core-gc-0)unknown or out-of-scope opcode. This is the catch-all arm, so a typo'd opcode lands here, not in resolution
exception handling ('throw') is not enabled in the selected target feature setthrow
tag 'T' requires exception handling, which is not enabledtag declaration

Type

MessageCause
value of type X is not assignable to Ythe general mismatch
function must return a value of type X on every reachable patha reachable path falls off the end
function 'f' expects N argument(s), found Marity; also indirect call expects …
field access requires a non-null declared-struct reference, found ?Tnarrow the nullable first
expected a non-null declared-array reference, found …same, for indexing
'null' requires a nullable-reference contextual typenull with no ?T context
integer literal requires a contextual 'i32' or 'i64' typeuntyped literal (also the float variant)
integer literal 'N' out of range for i32literal exceeds the width
global 'X' is not mutableassign to a non-mut global
local 'x' is not a mutable 'var' bindingassign to a let
field 'f' is not declared 'mut'assign to an immutable field
array 'A' element is not mutableassign to an immutable element
field 'f' was not initialized / … initialized more than oncenew must set every field exactly once
a packed field or array element cannot be read without an explicit '.s' or '.u' suffixmissing suffix
'.s'/'.u' suffixes are only valid on a packed 'i8'/'i16' field or array elementsuffix on a non-packed read
'is' requires a reference-typed left operandtesting a number
reaching the end of this control body is only valid when its result type is '()'non-unit block/loop falls through
'return;' is only valid for a function with result '()'bare return in a value function
binding declares N name(s) but M type(s)destructuring arity
expression statement produces N results; expected zero or onemulti-value as a statement
operator requires numeric operandsalso '%' requires integer operands, bitwise operators require integer operands, shift operators require integer operands
cannot determine the operand type for this operator; add a typed bindingno contextual type reached the expression
function 'f' does not exactly match the signature of function type 'T'&f — function references are invariant

Global initializers

Reported at type phase, from the constant-expression checker.

Message
a global initializer cannot call a function
a global initializer cannot read a local
a global initializer may only read an imported immutable global
this expression is not a valid constant expression for a global initializer
'X' is not marked as a constant expression in a global initializer
global 'X' has type A, not assignable to B

Lowering

Only three exist — all module-assembly limits, all reported last.

Message
memory32 limits must not exceed 65536 pages
table32 limits must fit in an unsigned 32-bit integer
active data segment 'D' requires exactly one memory in the module, found N

What produces no diagnostic at all

These are silent. Nothing warns you.

  • A narrow that doesn't apply. is narrows only a bare immutable local. On a var, a field, or a call result, the test still runs and the type simply does not change.
  • A statically impossible is test. It folds to a constant-false condition; the true arm becomes dead code, silently.
  • Ignored opcode immediates. wasm.i32.add<Junk>(a, b) discards the immediate without complaint.

Reading them in editors

wastc check <file> prints diagnostics without writing output. The LSP publishes the same phase-ordered set on open/change/close. Because only one phase surfaces at a time, an editor showing "1 error" on a badly broken file is expected — the rest are hidden behind the syntax bucket.