Summary
On the wasm target, a #valtype struct whose first field is a Double, when returned from a raise-capable function, is miscompiled: moonc emits an i32.wrap_i64 that consumes the struct's f64 slot, producing invalid Wasm that fails validation at instantiation:
CompileError: WebAssembly.Module(): Compiling function "..._run" failed:
i32.wrap_i64[0] expected type i64, found local.get of type f64
native, wasm-gc, and js all compile and run the same code correctly — it is wasm-specific.
Minimal reproduction (self-contained, builtin types only)
moon.mod.json
{ "name": "repro", "version": "0.1.0" }
moon.pkg.json
top.mbt
#valtype
priv struct W {
value : Double
}
priv suberror E
fn make(i : Int64) -> W raise E {
if i < 0L {
raise E
}
{ value: i.to_double() }
}
fn run(i : Int64) -> Double {
match (try? make(i)) {
Ok({ value }) => value
Err(_) => -1.0
}
}
test {
ignore(run(123L))
}
Run:
Trigger conditions
All three are required:
- the struct is annotated
#valtype,
- its first field is a
Double (f64), and
- it is produced by a function that can
raise (the Result-of-struct lowering).
The struct's other fields are irrelevant — even a single Double field reproduces it, and a non-Double first field (any reference, Option, or plain Int) does not. It is not about reference/Option fields; in the original @json case the StringView? second field was incidental.
| variant |
result |
{ value : Double } (single Double field) |
FAIL |
{ value : Double; other : Int } (two scalars) |
FAIL |
{ value : Double; repr : StringView? } (Double first) |
FAIL |
{ repr : StringView?; value : Double } (Double not first) |
PASS |
{ a : Int; value : Double } (Int first) |
PASS |
remove #valtype (plain boxed struct) |
PASS |
remove raise / try? (no exceptions) |
PASS |
Construction-and-return alone triggers it (no field read / destructure needed), as long as the producer is raise-capable.
Workarounds
- order the fields so the first field is not a
Double, or
- drop
#valtype, or
- make the producer non-
raise.
Toolchain
moon 0.1.20260618 (afb4494 2026-06-18)
moonc v0.10.1+a46be2066 (2026-06-22)
moonrun 0.1.20260618 (afb4494 2026-06-18)
Found while replacing a (Double, StringView?) tuple with a #valtype struct in @json number lexing (moonbitlang/core#3639); the field-order workaround is in use there.
Summary
On the
wasmtarget, a#valtypestruct whose first field is aDouble, when returned from araise-capable function, is miscompiled:mooncemits ani32.wrap_i64that consumes the struct'sf64slot, producing invalid Wasm that fails validation at instantiation:native,wasm-gc, andjsall compile and run the same code correctly — it is wasm-specific.Minimal reproduction (self-contained, builtin types only)
moon.mod.json{ "name": "repro", "version": "0.1.0" }moon.pkg.json{}top.mbtRun:
Trigger conditions
All three are required:
#valtype,Double(f64), andraise(theResult-of-struct lowering).The struct's other fields are irrelevant — even a single
Doublefield reproduces it, and a non-Doublefirst field (any reference,Option, or plainInt) does not. It is not about reference/Optionfields; in the original@jsoncase theStringView?second field was incidental.{ value : Double }(single Double field){ value : Double; other : Int }(two scalars){ value : Double; repr : StringView? }(Double first){ repr : StringView?; value : Double }(Double not first){ a : Int; value : Double }(Int first)#valtype(plain boxed struct)raise/try?(no exceptions)Construction-and-return alone triggers it (no field read / destructure needed), as long as the producer is
raise-capable.Workarounds
Double, or#valtype, orraise.Toolchain
Found while replacing a
(Double, StringView?)tuple with a#valtypestruct in@jsonnumber lexing (moonbitlang/core#3639); the field-order workaround is in use there.