Skip to content

wasm-gc: trait object from #external type causes validation error #1123

@bikallem

Description

@bikallem

Summary

Creating a trait object (&TEvent) from an #external type (backed by externref) produces invalid wasm-gc code. The module passes moon check but fails at WebAssembly instantiation time with a type error.

Error

WebAssembly.instantiate(): Compiling function #117 failed:
type error in fallthru[0] (expected (ref 8), got externref) @+6010

Minimal Reproduction

Given this code where CustomEvent is an #external type and dispatch_event takes a trait object &TEvent:

#external
pub type JsValue

pub trait TJsValue {
  to_js(Self) -> JsValue
}

pub impl TJsValue for JsValue with to_js(self) -> JsValue { self }

#external
pub type Event

pub impl TJsValue for Event with to_js(self) -> JsValue = "%identity"

pub trait TEvent: TJsValue {
  event_type(Self) -> String
}

fn event_type_ffi(event : JsValue) -> String = "events" "type"

pub impl TEvent for Event with event_type(self) {
  event_type_ffi(TJsValue::to_js(self))
}

#external
pub type CustomEvent

pub impl TJsValue for CustomEvent with to_js(self) -> JsValue = "%identity"

pub impl TEvent for CustomEvent with event_type(self) {
  event_type_ffi(TJsValue::to_js(self))
}

fn dispatch_event_ffi(target : JsValue, event : JsValue) -> JsValue = "events" "dispatchEvent"

// This function takes a trait object &TEvent
pub fn dispatch_event(event : &TEvent) -> Unit {
  let js = TJsValue::to_js(event)
  let _ = dispatch_event_ffi(js, js)
}

fn new_custom_event_ffi(type_ : String) -> CustomEvent = "events" "newCustomEvent"

fn main {
  let event : CustomEvent = new_custom_event_ffi("test")
  // Passing an #external type as a trait object triggers the bug
  dispatch_event(event)
}

Steps:

moon check --target wasm-gc   # passes
moon build --target wasm-gc   # produces .wasm

# Instantiation fails:
node --input-type=module -e "
import fs from 'fs';
const bytes = fs.readFileSync('_build/wasm-gc/debug/build/externref_bug.wasm');
try {
  await WebAssembly.instantiate(bytes, {
    events: {
      type: (e) => e.type,
      dispatchEvent: (t, e) => t.dispatchEvent(e),
      newCustomEvent: (t) => new CustomEvent(t),
    },
  }, { builtins: ['js-string'], importedStringConstants: '_' });
  console.log('OK');
} catch(e) {
  console.error('ERROR:', e.message);
}
"

Note: the standalone minimal repro above may not trigger the bug on its own — it reliably reproduces when #external types and trait objects are used in a larger project (e.g. bikallem/webapi). In that context, calling EventTarget.dispatch_event(event : &TEvent) with a CustomEvent always fails.

Root Cause

The compiler generates a fallthru instruction that yields externref where a wasm-gc struct reference (ref N) is expected. The conversion from #external type (externref) to trait object (GC struct) is not handled correctly.

Workaround

Avoid passing #external types as trait objects on the wasm-gc target.

Environment

  • moonc v0.8.1+6decb4ecd (2026-02-09)
  • moon 0.1.20260209 (b129ae2 2026-02-09)
  • Node.js v25.6.0
  • Platform: Linux x86_64

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions