use wasm_encoder::{Function, Instruction, ValType};
use crate::ast::{BinOp, Expr, Literal, MatchArm, Pattern, Spanned};
use super::super::WasmGcError;
use super::super::types::{TypeRegistry, aver_to_wasm};
use super::builtins::{emit_dotted_builtin, emit_interpolated_str};
use super::infer::{
arm_is_option_pattern, arm_is_result_pattern, aver_type_canonical, aver_type_str_of,
wasm_type_of,
};
use super::{EmitCtx, SlotTable};
pub(super) fn emit_default_value(
func: &mut Function,
aver_ty: &str,
registry: &TypeRegistry,
) -> Result<(), WasmGcError> {
match aver_ty.trim() {
"Int" => {
func.instruction(&Instruction::I64Const(0));
Ok(())
}
"Float" => {
func.instruction(&Instruction::F64Const(0.0_f64.into()));
Ok(())
}
"Bool" => {
func.instruction(&Instruction::I32Const(0));
Ok(())
}
"Unit" => {
func.instruction(&Instruction::I32Const(0));
Ok(())
}
other => {
let val = aver_to_wasm(other, Some(registry))?;
match val {
Some(ValType::Ref(rt)) => {
func.instruction(&Instruction::RefNull(rt.heap_type));
Ok(())
}
Some(ValType::I64) => {
func.instruction(&Instruction::I64Const(0));
Ok(())
}
Some(ValType::F64) => {
func.instruction(&Instruction::F64Const(0.0_f64.into()));
Ok(())
}
Some(ValType::I32) => {
func.instruction(&Instruction::I32Const(0));
Ok(())
}
Some(other_ty) => Err(WasmGcError::Validation(format!(
"Option.None default for `{other}` resolved to {other_ty:?} — no default emitter for that wasm type"
))),
None => Err(WasmGcError::Validation(format!(
"Option.None over `{other}` has no wasm representation"
))),
}
}
}
}
pub(super) fn emit_option_constructor(
func: &mut Function,
payload: Option<&Spanned<Expr>>,
t_aver_hint: Option<&str>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let t_aver: String = match payload {
Some(p) => aver_type_str_of(p),
None => t_aver_hint
.ok_or(WasmGcError::Validation(
"Option.None without context — cannot infer the T in Option<T>. \
Add a type annotation on the surrounding binding or fn return."
.into(),
))?
.to_string(),
};
let canonical = if t_aver.starts_with("Option<") {
t_aver.clone()
} else {
format!("Option<{}>", t_aver)
};
let opt_idx = ctx
.registry
.option_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Option constructor: instantiation `{canonical}` was not registered. \
Discovery should have walked fn signatures + bodies."
)))?;
let inner_ty = TypeRegistry::option_element_type(&canonical).ok_or(WasmGcError::Validation(
format!("Option canonical `{canonical}` has no element type"),
))?;
match payload {
Some(p) => {
func.instruction(&Instruction::I32Const(1));
emit_expr(func, p, slots, ctx)?;
}
None => {
func.instruction(&Instruction::I32Const(0));
emit_default_value(func, inner_ty, ctx.registry)?;
}
}
func.instruction(&Instruction::StructNew(opt_idx));
Ok(())
}
pub(super) fn emit_expr(
func: &mut Function,
expr: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
match &expr.node {
Expr::Literal(Literal::Int(n)) => {
func.instruction(&Instruction::I64Const(*n));
}
Expr::Literal(Literal::Float(f)) => {
func.instruction(&Instruction::F64Const((*f).into()));
}
Expr::Literal(Literal::Bool(b)) => {
func.instruction(&Instruction::I32Const(if *b { 1 } else { 0 }));
}
Expr::Literal(Literal::Unit) => {}
Expr::Literal(Literal::Str(s)) => {
let bytes = s.as_bytes();
let seg_idx =
ctx.registry
.string_literal_segment(bytes)
.ok_or(WasmGcError::Validation(format!(
"String literal `{s:?}` was not registered in the data segment table"
)))?;
let string_type_idx =
ctx.registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"String literal reachable but no String type slot allocated".into(),
))?;
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I32Const(bytes.len() as i32));
func.instruction(&Instruction::ArrayNewData {
array_type_index: string_type_idx,
array_data_index: seg_idx,
});
}
Expr::InterpolatedStr(parts) => {
emit_interpolated_str(func, parts, slots, ctx)?;
}
Expr::List(items) => {
emit_list_literal(func, expr, items, slots, ctx)?;
}
Expr::MapLiteral(entries) => {
emit_map_literal(func, entries, slots, ctx)?;
}
Expr::Tuple(items) => {
emit_tuple_literal(func, items, slots, ctx)?;
}
Expr::IndependentProduct(items, unwrap) => {
emit_group_call(func, ctx, "__record_enter_group");
if *unwrap {
emit_independent_product_unwrap(func, items, slots, ctx)?;
} else {
emit_tuple_literal_with_branch_markers(func, items, slots, ctx)?;
}
emit_group_call(func, ctx, "__record_exit_group");
}
Expr::Ident(_) => {
return Err(WasmGcError::Unimplemented(
"bare Ident reached emitter (resolver should have produced Resolved)",
));
}
Expr::Resolved { slot, .. } => {
func.instruction(&Instruction::LocalGet(*slot as u32));
}
Expr::BinOp(op, l, r) => {
let lhs_aver = aver_type_str_of(l);
let lhs_aver_trim = lhs_aver.trim();
if matches!(op, BinOp::Add) && lhs_aver_trim == "String" {
emit_string_concat2(func, l, r, slots, ctx)?;
} else if matches!(op, BinOp::Eq | BinOp::Neq) && lhs_aver_trim == "String" {
emit_string_eq(func, l, r, slots, ctx, matches!(op, BinOp::Neq))?;
} else if matches!(op, BinOp::Lt | BinOp::Gt | BinOp::Lte | BinOp::Gte)
&& lhs_aver_trim == "String"
{
let cmp_idx = ctx
.fn_map
.builtins
.get("__wasmgc_string_compare")
.copied()
.ok_or(WasmGcError::Validation(
"String comparison requires __wasmgc_string_compare builtin".into(),
))?;
emit_expr(func, l, slots, ctx)?;
emit_expr(func, r, slots, ctx)?;
func.instruction(&Instruction::Call(cmp_idx));
func.instruction(&Instruction::I32Const(0));
let post = match op {
BinOp::Lt => Instruction::I32LtS,
BinOp::Gt => Instruction::I32GtS,
BinOp::Lte => Instruction::I32LeS,
BinOp::Gte => Instruction::I32GeS,
_ => unreachable!(),
};
func.instruction(&post);
} else if matches!(op, BinOp::Eq | BinOp::Neq)
&& let Some(variant_idx) = nullary_variant_idx(r, ctx)
{
emit_expr(func, l, slots, ctx)?;
func.instruction(&Instruction::RefTestNonNull(
wasm_encoder::HeapType::Concrete(variant_idx),
));
if matches!(op, BinOp::Neq) {
func.instruction(&Instruction::I32Eqz);
}
} else if matches!(op, BinOp::Eq | BinOp::Neq)
&& let Some(variant_idx) = nullary_variant_idx(l, ctx)
{
emit_expr(func, r, slots, ctx)?;
func.instruction(&Instruction::RefTestNonNull(
wasm_encoder::HeapType::Concrete(variant_idx),
));
if matches!(op, BinOp::Neq) {
func.instruction(&Instruction::I32Eqz);
}
} else if matches!(op, BinOp::Eq | BinOp::Neq)
&& let Some(eq_fn_idx) = sum_or_record_eq_fn(l, ctx)
{
emit_expr(func, l, slots, ctx)?;
emit_expr(func, r, slots, ctx)?;
func.instruction(&Instruction::Call(eq_fn_idx));
if matches!(op, BinOp::Neq) {
func.instruction(&Instruction::I32Eqz);
}
} else {
let l_ty = wasm_type_of(l, ctx.registry)?;
let r_ty = wasm_type_of(r, ctx.registry)?;
let operand = if l_ty == Some(ValType::F64) || r_ty == Some(ValType::F64) {
Some(ValType::F64)
} else {
l_ty
};
emit_expr(func, l, slots, ctx)?;
if operand == Some(ValType::F64) && l_ty == Some(ValType::I64) {
func.instruction(&Instruction::F64ConvertI64S);
}
emit_expr(func, r, slots, ctx)?;
if operand == Some(ValType::F64) && r_ty == Some(ValType::I64) {
func.instruction(&Instruction::F64ConvertI64S);
}
let inst = match (operand, op) {
(Some(ValType::F64), BinOp::Add) => Instruction::F64Add,
(Some(ValType::F64), BinOp::Sub) => Instruction::F64Sub,
(Some(ValType::F64), BinOp::Mul) => Instruction::F64Mul,
(Some(ValType::F64), BinOp::Div) => Instruction::F64Div,
(Some(ValType::F64), BinOp::Eq) => Instruction::F64Eq,
(Some(ValType::F64), BinOp::Neq) => Instruction::F64Ne,
(Some(ValType::F64), BinOp::Lt) => Instruction::F64Lt,
(Some(ValType::F64), BinOp::Gt) => Instruction::F64Gt,
(Some(ValType::F64), BinOp::Lte) => Instruction::F64Le,
(Some(ValType::F64), BinOp::Gte) => Instruction::F64Ge,
(_, BinOp::Add) => Instruction::I64Add,
(_, BinOp::Sub) => Instruction::I64Sub,
(_, BinOp::Mul) => Instruction::I64Mul,
(_, BinOp::Div) => Instruction::I64DivS,
(_, BinOp::Eq) => Instruction::I64Eq,
(_, BinOp::Neq) => Instruction::I64Ne,
(_, BinOp::Lt) => Instruction::I64LtS,
(_, BinOp::Gt) => Instruction::I64GtS,
(_, BinOp::Lte) => Instruction::I64LeS,
(_, BinOp::Gte) => Instruction::I64GeS,
};
func.instruction(&inst);
}
}
Expr::FnCall(callee, args) => {
if let Expr::Attr(parent, member) = &callee.node {
if let Expr::Ident(p) = &parent.node
&& p == "Option"
&& (member == "Some" || member == "None")
{
return match member.as_str() {
"Some" if args.len() == 1 => {
emit_option_constructor(func, Some(&args[0]), None, slots, ctx)
}
"None" => {
let stamped_canonical =
aver_type_canonical(expr, ctx.return_type, ctx.registry);
let hint: String = if let Some(inner) = stamped_canonical
.strip_prefix("Option<")
.and_then(|s| s.strip_suffix('>'))
{
inner.to_string()
} else {
ctx.return_type.to_string()
};
emit_option_constructor(func, None, Some(&hint), slots, ctx)
}
_ => Err(WasmGcError::Validation(format!(
"Option.{member} with {} args is not a valid constructor",
args.len()
))),
};
}
if let Expr::Ident(p) = &parent.node
&& p == "Result"
&& (member == "Ok" || member == "Err")
{
return emit_result_constructor(func, member, args.first(), slots, ctx);
}
if let Expr::Ident(p) = &parent.node
&& p == "List"
&& member == "prepend"
&& args.len() == 2
{
return emit_list_prepend(func, &args[0], &args[1], slots, ctx);
}
if let Expr::Ident(p) = &parent.node
&& p == "List"
&& member == "empty"
&& args.is_empty()
{
return emit_list_empty(func, ctx);
}
let parent_ident = match &parent.node {
Expr::Ident(n) => Some(n.as_str()),
Expr::Resolved { name, .. } => Some(name.as_str()),
_ => None,
};
let info = parent_ident
.and_then(|p| ctx.registry.variant_in(p, member))
.or_else(|| ctx.registry.variant(member))
.cloned();
if let Some(info) = info {
return emit_constructor_with_args(func, &info, args, slots, ctx);
}
if let Some(parent_name) = parent_ident {
return emit_dotted_builtin(func, parent_name, member, args, slots, ctx);
}
}
let name = match &callee.node {
Expr::Ident(n) => n.as_str(),
Expr::Resolved { name, .. } => name.as_str(),
_ => {
return Err(WasmGcError::Unimplemented(
"phase 3b — exotic callee shape (chained Attr, lambda, etc.)",
));
}
};
let entry = ctx.fn_map.by_name.get(name);
if entry.is_none() && ctx.self_local_slot(name).is_some() {
func.instruction(&Instruction::Unreachable);
return Ok(());
}
for arg in args {
emit_expr(func, arg, slots, ctx)?;
}
let entry = entry.ok_or(WasmGcError::Validation(format!(
"call to unknown fn `{name}`"
)))?;
func.instruction(&Instruction::Call(entry.wasm_idx));
}
Expr::Match { subject, arms } => emit_match(func, subject, arms, slots, ctx)?,
Expr::TailCall(boxed) => emit_tail_call(func, &boxed.target, &boxed.args, slots, ctx)?,
Expr::RecordCreate { type_name, fields } => {
emit_record_create(func, type_name, fields, slots, ctx)?
}
Expr::RecordUpdate {
type_name,
base,
updates,
} => emit_record_update(func, type_name, base, updates, slots, ctx)?,
Expr::Attr(obj, field) => {
if let Expr::Ident(p) = &obj.node
&& p == "Option"
&& field == "None"
{
let stamped_canonical = aver_type_canonical(expr, ctx.return_type, ctx.registry);
let hint: String = if let Some(inner) = stamped_canonical
.strip_prefix("Option<")
.and_then(|s| s.strip_suffix('>'))
{
inner.to_string()
} else {
ctx.return_type.to_string()
};
emit_option_constructor(func, None, Some(&hint), slots, ctx)?;
} else if let Expr::Ident(parent) = &obj.node
&& let Some(info) = ctx.registry.variant_in(parent, field).cloned()
&& info.fields.is_empty()
{
emit_constructor_with_args(func, &info, &[], slots, ctx)?;
} else {
emit_attr_get(func, obj, field, slots, ctx)?;
}
}
Expr::Constructor(name, payload) => {
emit_constructor(func, expr, name, payload.as_deref(), slots, ctx)?
}
Expr::ErrorProp(inner) => emit_error_prop(func, inner, slots, ctx)?,
#[allow(unreachable_patterns)]
other => {
eprintln!("UNIMPL EMIT shape={:?}", other);
return Err(WasmGcError::Unimplemented(
"expression shape outside phase 2/3/4",
));
}
}
Ok(())
}
fn sum_or_record_eq_fn(operand: &Spanned<Expr>, ctx: &EmitCtx<'_>) -> Option<u32> {
let ty = operand.ty()?;
match ty {
crate::types::Type::Named(name) => {
if ctx.registry.newtype_underlying(name).is_some() {
return None;
}
let is_record = ctx.registry.record_fields.contains_key(name);
let is_sum = ctx
.registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| &v.parent == name);
if !is_record && !is_sum {
return None;
}
ctx.fn_map.eq_helpers.get(name).copied()
}
crate::types::Type::Option(_)
| crate::types::Type::Result(_, _)
| crate::types::Type::Tuple(_) => {
let canonical: String = ty
.display()
.chars()
.filter(|c| !c.is_whitespace())
.collect();
ctx.fn_map.eq_helpers.get(&canonical).copied()
}
crate::types::Type::List(_) => {
let canonical: String = ty
.display()
.chars()
.filter(|c| !c.is_whitespace())
.collect();
ctx.fn_map.list_ops.get(&canonical).and_then(|ops| ops.eq)
}
crate::types::Type::Vector(_) => {
let canonical: String = ty
.display()
.chars()
.filter(|c| !c.is_whitespace())
.collect();
ctx.fn_map.vfl_ops.get(&canonical).and_then(|ops| ops.eq)
}
crate::types::Type::Map(_, _) => {
let canonical: String = ty
.display()
.chars()
.filter(|c| !c.is_whitespace())
.collect();
ctx.fn_map.eq_helpers.get(&canonical).copied()
}
_ => None,
}
}
fn nullary_variant_idx(expr: &Spanned<Expr>, ctx: &EmitCtx<'_>) -> Option<u32> {
match &expr.node {
Expr::Attr(obj, field) => {
let parent = match &obj.node {
Expr::Ident(p) => p.as_str(),
Expr::Resolved { name, .. } => name.as_str(),
_ => return None,
};
let info = ctx.registry.variant(field)?;
if info.parent == parent && info.fields.is_empty() {
Some(info.type_idx)
} else {
None
}
}
Expr::FnCall(callee, args) if args.is_empty() => {
if let Expr::Attr(obj, field) = &callee.node {
let parent = match &obj.node {
Expr::Ident(p) => p.as_str(),
Expr::Resolved { name, .. } => name.as_str(),
_ => return None,
};
let info = ctx.registry.variant(field)?;
if info.parent == parent && info.fields.is_empty() {
Some(info.type_idx)
} else {
None
}
} else {
None
}
}
Expr::Constructor(name, payload) if payload.is_none() => {
let bare = name.rsplit('.').next().unwrap_or(name);
let info = ctx.registry.variant(bare)?;
if info.fields.is_empty() {
Some(info.type_idx)
} else {
None
}
}
_ => None,
}
}
pub(super) fn emit_string_concat2(
func: &mut Function,
lhs: &Spanned<Expr>,
rhs: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let vec_idx = ctx
.registry
.vector_type_idx("Vector<String>")
.ok_or(WasmGcError::Validation(
"String `+` requires Vector<String> slot but it wasn't registered \
(discovery should eager-allocate it on any String concat use)"
.into(),
))?;
let concat_idx = ctx
.fn_map
.builtins
.get("__wasmgc_concat_n")
.copied()
.ok_or(WasmGcError::Validation(
"String `+` requires __wasmgc_concat_n builtin but it wasn't registered \
(discovery should register it on any String `+` site)"
.into(),
))?;
emit_expr(func, lhs, slots, ctx)?;
emit_expr(func, rhs, slots, ctx)?;
func.instruction(&Instruction::ArrayNewFixed {
array_type_index: vec_idx,
array_size: 2,
});
func.instruction(&Instruction::Call(concat_idx));
Ok(())
}
pub(super) fn emit_string_eq(
func: &mut Function,
lhs: &Spanned<Expr>,
rhs: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
invert: bool,
) -> Result<(), WasmGcError> {
let eq_idx = ctx
.fn_map
.builtins
.get("__wasmgc_string_eq")
.copied()
.ok_or(WasmGcError::Validation(
"String `==`/`!=` requires __wasmgc_string_eq builtin but it wasn't registered \
(discovery should register it on any String comparison site)"
.into(),
))?;
emit_expr(func, lhs, slots, ctx)?;
emit_expr(func, rhs, slots, ctx)?;
func.instruction(&Instruction::Call(eq_idx));
if invert {
func.instruction(&Instruction::I32Eqz);
}
Ok(())
}
pub(super) fn emit_error_prop(
func: &mut Function,
inner: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"ErrorProp (`?`) requires a subject scratch slot but none was reserved \
(slots::expr_needs_scratch must opt in for Expr::ErrorProp)"
.into(),
))?;
let subject_ty = aver_type_str_of(inner);
let canonical: String = subject_ty.chars().filter(|c| !c.is_whitespace()).collect();
let res_idx = ctx
.registry
.result_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"ErrorProp: subject type `{subject_ty}` is not a registered Result<T,E>"
)))?;
let (t_aver, _e_aver) = TypeRegistry::result_te(&canonical).ok_or(WasmGcError::Validation(
format!("ErrorProp: Result canonical `{canonical}` malformed"),
))?;
let unit_ok = t_aver.trim() == "Unit";
let block_ty = if unit_ok {
wasm_encoder::BlockType::Empty
} else {
let ok_wasm = aver_to_wasm(t_aver, Some(ctx.registry))?.ok_or(WasmGcError::Validation(
format!("ErrorProp: Ok type `{t_aver}` has no wasm representation"),
))?;
wasm_encoder::BlockType::Result(ok_wasm)
};
let enclosing_canonical: String = ctx
.return_type
.chars()
.filter(|c| !c.is_whitespace())
.collect();
let enclosing_idx =
ctx.registry
.result_type_idx(&enclosing_canonical)
.ok_or(WasmGcError::Validation(format!(
"ErrorProp: enclosing fn return `{}` is not a registered Result<T,E>",
ctx.return_type
)))?;
let (enclosing_t_aver, _) =
TypeRegistry::result_te(&enclosing_canonical).ok_or(WasmGcError::Validation(format!(
"ErrorProp: enclosing Result canonical `{enclosing_canonical}` malformed"
)))?;
emit_expr(func, inner, slots, ctx)?;
func.instruction(&Instruction::LocalSet(scratch));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: res_idx,
field_index: 0,
});
func.instruction(&Instruction::I32Const(1));
func.instruction(&Instruction::I32Eq);
func.instruction(&Instruction::If(block_ty));
if !unit_ok {
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: res_idx,
field_index: 1,
});
}
func.instruction(&Instruction::Else);
func.instruction(&Instruction::I32Const(0)); emit_default_value(func, enclosing_t_aver, ctx.registry)?;
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: res_idx,
field_index: 2,
});
func.instruction(&Instruction::StructNew(enclosing_idx));
func.instruction(&Instruction::Return);
func.instruction(&Instruction::End);
Ok(())
}
pub(super) fn emit_record_create(
func: &mut Function,
type_name: &str,
fields: &[(String, Spanned<Expr>)],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if ctx.registry.newtype_underlying(type_name).is_some() {
let field = fields.first().ok_or(WasmGcError::Validation(format!(
"newtype record `{type_name}` requires one field"
)))?;
return emit_expr(func, &field.1, slots, ctx);
}
let type_idx = ctx
.registry
.record_type_idx(type_name)
.ok_or(WasmGcError::Validation(format!(
"unknown record type `{type_name}`"
)))?;
let decl_fields = ctx
.registry
.record_fields
.get(type_name)
.ok_or(WasmGcError::Validation(format!(
"record `{type_name}` missing field list"
)))?;
for (decl_name, decl_ty) in decl_fields {
let provided =
fields
.iter()
.find(|(n, _)| n == decl_name)
.ok_or(WasmGcError::Validation(format!(
"record `{type_name}` missing field `{decl_name}`"
)))?;
if is_option_none_expr(&provided.1.node)
&& let Some(inner) = decl_ty
.trim()
.strip_prefix("Option<")
.and_then(|s| s.strip_suffix('>'))
{
emit_option_constructor(func, None, Some(inner.trim()), slots, ctx)?;
continue;
}
if let Expr::List(items) = &provided.1.node
&& items.is_empty()
{
let canonical: String = decl_ty.chars().filter(|c| !c.is_whitespace()).collect();
if let Some(list_idx) = ctx.registry.list_type_idx(&canonical) {
func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
list_idx,
)));
continue;
}
}
emit_expr(func, &provided.1, slots, ctx)?;
}
func.instruction(&Instruction::StructNew(type_idx));
Ok(())
}
fn is_option_none_expr(expr: &Expr) -> bool {
match expr {
Expr::Attr(obj, field) => {
field == "None" && matches!(&obj.node, Expr::Ident(name) if name == "Option")
}
Expr::Constructor(name, payload) => {
payload.is_none()
&& (name == "Option.None"
|| name.rsplit('.').next() == Some("None") && name.starts_with("Option"))
}
_ => false,
}
}
pub(super) fn emit_record_update(
func: &mut Function,
type_name: &str,
base: &Spanned<Expr>,
updates: &[(String, Spanned<Expr>)],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let type_idx = ctx
.registry
.record_type_idx(type_name)
.ok_or(WasmGcError::Validation(format!(
"unknown record type `{type_name}`"
)))?;
let decl_fields = ctx
.registry
.record_fields
.get(type_name)
.ok_or(WasmGcError::Validation(format!(
"record `{type_name}` missing field list"
)))?
.clone();
for (decl_name, decl_ty) in &decl_fields {
if let Some((_, override_expr)) = updates.iter().find(|(n, _)| n == decl_name) {
if is_option_none_expr(&override_expr.node)
&& let Some(inner) = decl_ty
.trim()
.strip_prefix("Option<")
.and_then(|s| s.strip_suffix('>'))
{
emit_option_constructor(func, None, Some(inner.trim()), slots, ctx)?;
continue;
}
if let Expr::List(items) = &override_expr.node
&& items.is_empty()
{
let canonical: String = decl_ty.chars().filter(|c| !c.is_whitespace()).collect();
if let Some(list_idx) = ctx.registry.list_type_idx(&canonical) {
func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
list_idx,
)));
continue;
}
}
emit_expr(func, override_expr, slots, ctx)?;
} else {
let field_idx = ctx
.registry
.record_field_index(type_name, decl_name)
.ok_or(WasmGcError::Validation(format!(
"record `{type_name}` has no field `{decl_name}` to copy from base"
)))?;
emit_expr(func, base, slots, ctx)?;
func.instruction(&Instruction::StructGet {
struct_type_index: type_idx,
field_index: field_idx,
});
}
}
func.instruction(&Instruction::StructNew(type_idx));
Ok(())
}
pub(super) fn emit_attr_get(
func: &mut Function,
obj: &Spanned<Expr>,
field: &str,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let record_name = aver_type_str_of(obj);
if ctx.registry.newtype_underlying(&record_name).is_some() {
return emit_expr(func, obj, slots, ctx);
}
let type_idx = ctx
.registry
.record_type_idx(&record_name)
.ok_or(WasmGcError::Validation(format!(
"unknown record type `{record_name}` for Attr"
)))?;
let field_idx =
ctx.registry
.record_field_index(&record_name, field)
.ok_or(WasmGcError::Validation(format!(
"record `{record_name}` has no field `{field}`"
)))?;
emit_expr(func, obj, slots, ctx)?;
func.instruction(&Instruction::StructGet {
struct_type_index: type_idx,
field_index: field_idx,
});
Ok(())
}
pub(super) fn emit_result_constructor(
func: &mut Function,
variant: &str,
payload: Option<&Spanned<Expr>>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let payload = payload.ok_or(WasmGcError::Validation(format!(
"Result.{variant} requires a payload"
)))?;
let payload_ty = aver_type_str_of(payload);
let canonical = if ctx.registry.result_order.len() == 1 {
ctx.registry.result_order[0].clone()
} else {
let return_canonical: String = ctx
.return_type
.chars()
.filter(|c| !c.is_whitespace())
.collect();
if ctx.registry.result_type_idx(&return_canonical).is_some() {
return_canonical
} else {
ctx.registry
.result_order
.iter()
.find(|c| {
if let Some((t, e)) = TypeRegistry::result_te(c) {
let match_pos = if variant == "Ok" { t } else { e };
match_pos == payload_ty.trim()
} else {
false
}
})
.cloned()
.ok_or(WasmGcError::Validation(format!(
"Result.{variant}({payload_ty}) — no registered Result<T,E> instantiation matches"
)))?
}
};
let res_idx = ctx
.registry
.result_type_idx(&canonical)
.expect("just-resolved canonical");
let (t_aver, e_aver) = TypeRegistry::result_te(&canonical).ok_or(WasmGcError::Validation(
format!("Result canonical `{canonical}` malformed"),
))?;
let emit_payload = |func: &mut Function, ty: &str| -> Result<(), WasmGcError> {
if ty.trim() == "Unit" {
func.instruction(&Instruction::I32Const(0));
Ok(())
} else {
emit_expr(func, payload, slots, ctx)
}
};
if variant == "Ok" {
func.instruction(&Instruction::I32Const(1));
emit_payload(func, t_aver)?;
emit_default_value(func, e_aver, ctx.registry)?;
} else {
func.instruction(&Instruction::I32Const(0));
emit_default_value(func, t_aver, ctx.registry)?;
emit_payload(func, e_aver)?;
}
func.instruction(&Instruction::StructNew(res_idx));
Ok(())
}
pub(super) fn emit_list_prepend(
func: &mut Function,
head: &Spanned<Expr>,
tail: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let tail_ty = aver_type_str_of(tail);
let canonical: String = tail_ty.chars().filter(|c| !c.is_whitespace()).collect();
let list_idx = ctx
.registry
.list_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"List.prepend: tail type `{tail_ty}` is not a registered List<T>"
)))?;
emit_expr(func, head, slots, ctx)?;
emit_expr(func, tail, slots, ctx)?;
func.instruction(&Instruction::StructNew(list_idx));
Ok(())
}
pub(super) fn emit_list_empty(func: &mut Function, ctx: &EmitCtx<'_>) -> Result<(), WasmGcError> {
let canonical = if ctx.registry.list_order.len() == 1 {
ctx.registry.list_order[0].clone()
} else {
ctx.return_type
.chars()
.filter(|c| !c.is_whitespace())
.collect::<String>()
};
let list_idx = ctx
.registry
.list_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"List.empty: cannot resolve list instantiation (got `{canonical}`)"
)))?;
func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
list_idx,
)));
Ok(())
}
pub(super) fn emit_independent_product_unwrap(
func: &mut Function,
items: &[Spanned<Expr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if items.len() < 2 {
return Err(WasmGcError::Validation(format!(
"Independent product `?!` needs at least 2 elements; got {}",
items.len()
)));
}
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"`?!` independent product requires a subject scratch slot but none was reserved \
(slots::expr_needs_scratch must opt in for Expr::IndependentProduct(_, true))"
.into(),
))?;
let enclosing_canonical: String = ctx
.return_type
.chars()
.filter(|c| !c.is_whitespace())
.collect();
let enclosing_idx = ctx
.registry
.result_type_idx(&enclosing_canonical)
.ok_or_else(|| {
WasmGcError::Validation(format!(
"`?!` independent product: enclosing fn return type `{enclosing_canonical}` \
is not a registered Result<X, E>"
))
})?;
let (enclosing_t_aver, _enclosing_e_aver) = TypeRegistry::result_te(&enclosing_canonical)
.ok_or_else(|| {
WasmGcError::Validation(format!(
"`?!` independent product: enclosing canonical `{enclosing_canonical}` malformed"
))
})?;
for (idx, item) in items.iter().enumerate() {
emit_branch_marker(func, ctx, idx as u32);
let elem_aver = aver_type_str_of(item);
let elem_canonical: String = elem_aver.chars().filter(|c| !c.is_whitespace()).collect();
let elem_res_idx = ctx
.registry
.result_type_idx(&elem_canonical)
.ok_or_else(|| {
WasmGcError::Validation(format!(
"`?!` element type `{elem_aver}` is not a registered Result<T_i, E>"
))
})?;
let (elem_t_aver, _elem_e_aver) =
TypeRegistry::result_te(&elem_canonical).ok_or_else(|| {
WasmGcError::Validation(format!(
"`?!` element canonical `{elem_canonical}` malformed"
))
})?;
let ok_wasm = aver_to_wasm(elem_t_aver, Some(ctx.registry))?.unwrap_or(ValType::I32);
let block_ty = wasm_encoder::BlockType::Result(ok_wasm);
emit_expr(func, item, slots, ctx)?;
func.instruction(&Instruction::LocalSet(scratch));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(elem_res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: elem_res_idx,
field_index: 0, });
func.instruction(&Instruction::I32Const(1)); func.instruction(&Instruction::I32Eq);
func.instruction(&Instruction::If(block_ty));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(elem_res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: elem_res_idx,
field_index: 1, });
func.instruction(&Instruction::Else);
func.instruction(&Instruction::I32Const(0)); emit_default_value(func, enclosing_t_aver, ctx.registry)?;
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(elem_res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: elem_res_idx,
field_index: 2, });
func.instruction(&Instruction::StructNew(enclosing_idx));
func.instruction(&Instruction::Return);
func.instruction(&Instruction::End);
}
let mut elem_t_avers: Vec<String> = Vec::with_capacity(items.len());
for item in items {
let elem_aver = aver_type_str_of(item);
let elem_canonical: String = elem_aver.chars().filter(|c| !c.is_whitespace()).collect();
let (t, _) = TypeRegistry::result_te(&elem_canonical).expect("re-parse");
elem_t_avers.push(t.to_string());
}
let tuple_canonical: String = format!("Tuple<{}>", elem_t_avers.join(","))
.chars()
.filter(|c| !c.is_whitespace())
.collect();
let tuple_idx = ctx
.registry
.tuple_type_idx(&tuple_canonical)
.ok_or_else(|| {
WasmGcError::Validation(format!(
"`?!` result tuple `{tuple_canonical}` not registered"
))
})?;
func.instruction(&Instruction::StructNew(tuple_idx));
Ok(())
}
fn emit_group_call(func: &mut Function, ctx: &EmitCtx<'_>, op: &str) {
if let Some(idx) = ctx.effect_idx_lookup.get(op) {
if emit_caller_fn_idx(func, ctx).is_err() {
return;
}
func.instruction(&Instruction::Call(*idx));
}
}
fn emit_branch_marker(func: &mut Function, ctx: &EmitCtx<'_>, branch_idx: u32) {
if let Some(idx) = ctx.effect_idx_lookup.get("__record_set_branch") {
func.instruction(&Instruction::I64Const(branch_idx as i64));
if emit_caller_fn_idx(func, ctx).is_err() {
return;
}
func.instruction(&Instruction::Call(*idx));
}
}
fn emit_tuple_literal_with_branch_markers(
func: &mut Function,
items: &[Spanned<Expr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if items.len() < 2 {
return Err(WasmGcError::Validation(format!(
"Independent-product `!` tuple needs at least 2 elements; got {}",
items.len()
)));
}
let elem_tys: Vec<String> = items.iter().map(aver_type_str_of).collect();
let canonical = format!("Tuple<{}>", elem_tys.join(","))
.chars()
.filter(|c| !c.is_whitespace())
.collect::<String>();
let tuple_idx = ctx
.registry
.tuple_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"`!` tuple: `{canonical}` slot not registered"
)))?;
for (idx, item) in items.iter().enumerate() {
emit_branch_marker(func, ctx, idx as u32);
emit_expr(func, item, slots, ctx)?;
}
func.instruction(&Instruction::StructNew(tuple_idx));
Ok(())
}
pub(super) fn emit_tuple_literal(
func: &mut Function,
items: &[Spanned<Expr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if items.len() < 2 {
return Err(WasmGcError::Validation(format!(
"Tuple literal needs at least 2 elements; got {}",
items.len()
)));
}
let elem_tys: Vec<String> = items.iter().map(aver_type_str_of).collect();
let canonical = format!("Tuple<{}>", elem_tys.join(","))
.chars()
.filter(|c| !c.is_whitespace())
.collect::<String>();
let tuple_idx = ctx
.registry
.tuple_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Tuple literal: `{canonical}` slot not registered"
)))?;
for item in items {
emit_expr(func, item, slots, ctx)?;
}
func.instruction(&Instruction::StructNew(tuple_idx));
Ok(())
}
pub(super) fn emit_map_literal(
func: &mut Function,
entries: &[(Spanned<Expr>, Spanned<Expr>)],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let canonical: String = if entries.is_empty() {
if ctx.registry.map_order.len() == 1 {
ctx.registry.map_order[0].clone()
} else {
return Err(WasmGcError::Validation(
"empty MapLiteral: cannot resolve Map<K,V> instantiation \
without context (multiple instantiations registered)"
.into(),
));
}
} else {
let k_aver = aver_type_str_of(&entries[0].0);
let v_aver = aver_type_str_of(&entries[0].1);
format!("Map<{},{}>", k_aver.trim(), v_aver.trim())
.chars()
.filter(|c| !c.is_whitespace())
.collect()
};
let helpers = ctx
.fn_map
.map_helpers
.get(&canonical)
.ok_or(WasmGcError::Validation(format!(
"MapLiteral: helpers missing for `{canonical}`"
)))?;
func.instruction(&Instruction::Call(helpers.empty));
for (k_expr, v_expr) in entries {
emit_expr(func, k_expr, slots, ctx)?;
emit_expr(func, v_expr, slots, ctx)?;
func.instruction(&Instruction::Call(helpers.set));
}
Ok(())
}
pub(super) fn emit_list_literal(
func: &mut Function,
outer: &Spanned<Expr>,
items: &[Spanned<Expr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let stamped = aver_type_canonical(outer, ctx.return_type, ctx.registry);
let canonical =
if stamped.starts_with("List<") && ctx.registry.list_type_idx(&stamped).is_some() {
stamped
} else if let Some(first) = items.first() {
let needs_hint = matches!(&first.node, Expr::List(xs) if xs.is_empty())
|| is_option_none_expr(&first.node);
let elem_ty = if needs_hint {
let ret: String = ctx
.return_type
.chars()
.filter(|c| !c.is_whitespace())
.collect();
if let Some(inner) = ret.strip_prefix("List<").and_then(|s| s.strip_suffix('>')) {
inner.to_string()
} else {
aver_type_str_of(first)
}
} else {
aver_type_str_of(first)
};
format!("List<{elem_ty}>")
.chars()
.filter(|c| !c.is_whitespace())
.collect::<String>()
} else if ctx.registry.list_order.len() == 1 {
ctx.registry.list_order[0].clone()
} else {
let ret: String = ctx
.return_type
.chars()
.filter(|c| !c.is_whitespace())
.collect();
if ret.starts_with("List<") {
ret
} else if let Some(first) = ctx.registry.list_order.first() {
first.clone()
} else {
ret
}
};
let list_idx = ctx
.registry
.list_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"List literal: cannot resolve list instantiation (got `{canonical}`)"
)))?;
if items.is_empty() {
func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
list_idx,
)));
return Ok(());
}
let cons_fn = ctx
.fn_map
.list_ops_lookup(&canonical)
.ok_or(WasmGcError::Validation(format!(
"List literal: cons helper for `{canonical}` not registered"
)))?
.cons;
let elem_ty = TypeRegistry::list_element_type(&canonical).map(|s| s.to_string());
for item in items {
if let Some(elem) = elem_ty.as_deref()
&& is_option_none_expr(&item.node)
&& let Some(inner) = elem
.strip_prefix("Option<")
.and_then(|s| s.strip_suffix('>'))
{
emit_option_constructor(func, None, Some(inner.trim()), slots, ctx)?;
continue;
}
if let Some(elem) = elem_ty.as_deref()
&& let Expr::List(xs) = &item.node
&& xs.is_empty()
&& let Some(list_idx) = ctx.registry.list_type_idx(elem)
{
func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
list_idx,
)));
continue;
}
emit_expr(func, item, slots, ctx)?;
}
func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
list_idx,
)));
for _ in 0..items.len() {
func.instruction(&Instruction::Call(cons_fn));
}
Ok(())
}
pub(super) fn emit_tuple_match(
func: &mut Function,
subject: &Spanned<Expr>,
arm: &MatchArm,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let pat_items = match &arm.pattern {
Pattern::Tuple(items) => items.as_slice(),
_ => {
return Err(WasmGcError::Validation(
"emit_tuple_match called on non-Tuple pattern".into(),
));
}
};
let arm_slots = arm.binding_slots.get().ok_or(WasmGcError::Validation(
"Tuple match arm reached emit without resolver-allocated binding_slots".into(),
))?;
let subj_ty = aver_type_str_of(subject);
let canonical: String = subj_ty.chars().filter(|c| !c.is_whitespace()).collect();
let tuple_idx = ctx
.registry
.tuple_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Tuple match: subject type `{subj_ty}` is not a registered Tuple<A,B>"
)))?;
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"Tuple match needs a subject scratch slot but none was reserved".into(),
))?;
emit_expr(func, subject, slots, ctx)?;
func.instruction(&Instruction::LocalSet(scratch));
for (field_idx, (pat, &slot)) in pat_items.iter().zip(arm_slots.iter()).enumerate() {
if matches!(pat, Pattern::Ident(_)) && slot != u16::MAX {
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(tuple_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: tuple_idx,
field_index: field_idx as u32,
});
func.instruction(&Instruction::LocalSet(slot as u32));
}
}
emit_expr(func, &arm.body, slots, ctx)?;
Ok(())
}
pub(super) fn emit_tuple_constructor_match(
func: &mut Function,
subject: &Spanned<Expr>,
arms: &[MatchArm],
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"multi-arm tuple match needs a subject scratch slot but none was reserved".into(),
))?;
let subject_ty = aver_type_str_of(subject);
let canonical: String = subject_ty.chars().filter(|c| !c.is_whitespace()).collect();
let tuple_idx = ctx
.registry
.tuple_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"multi-arm tuple match: subject type `{subject_ty}` is not a registered Tuple<...>"
)))?;
let elems_owned: Vec<String> = TypeRegistry::tuple_elements(&canonical)
.ok_or(WasmGcError::Validation(format!(
"multi-arm tuple match: cannot extract tuple element types from `{canonical}`"
)))?
.into_iter()
.map(|s| s.to_string())
.collect();
emit_expr(func, subject, slots, ctx)?;
func.instruction(&Instruction::LocalSet(scratch));
emit_tuple_constructor_arm_cascade(
func,
scratch,
tuple_idx,
&elems_owned,
arms,
block_ty,
slots,
ctx,
)
}
#[allow(clippy::too_many_arguments)]
fn emit_tuple_constructor_arm_cascade(
func: &mut Function,
scratch: u32,
tuple_idx: u32,
elems: &[String],
arms: &[MatchArm],
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if arms.is_empty() {
func.instruction(&Instruction::Unreachable);
return Ok(());
}
let arm = &arms[0];
match &arm.pattern {
Pattern::Wildcard => emit_expr(func, &arm.body, slots, ctx),
Pattern::Ident(_) => {
if let Some(arm_slots) = arm.binding_slots.get()
&& let Some(&slot) = arm_slots.first()
&& slot != u16::MAX
{
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::LocalSet(slot as u32));
}
emit_expr(func, &arm.body, slots, ctx)
}
Pattern::Tuple(items) => {
if items.len() != elems.len() {
return Err(WasmGcError::Validation(format!(
"tuple match arm has {} sub-patterns, subject is a {}-tuple",
items.len(),
elems.len()
)));
}
let mut tests_emitted = 0u32;
for (i, pat) in items.iter().enumerate() {
if let Pattern::Constructor(name, _) = pat {
let bare = name.rsplit('.').next().unwrap_or(name);
let elem_canonical: String =
elems[i].chars().filter(|c| !c.is_whitespace()).collect();
let res_idx = ctx.registry.result_type_idx(&elem_canonical).ok_or(
WasmGcError::Unimplemented(
"tuple-of-constructors match supports Result<T,E> elements only \
today (Option / user variants land separately)",
),
)?;
let expected_tag: i32 = match bare {
"Ok" => 1,
"Err" => 0,
_ => {
return Err(WasmGcError::Unimplemented(
"tuple-of-constructors match: unknown Result variant \
(expected Ok or Err)",
));
}
};
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(tuple_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: tuple_idx,
field_index: i as u32,
});
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: res_idx,
field_index: 0,
});
func.instruction(&Instruction::I32Const(expected_tag));
func.instruction(&Instruction::I32Eq);
if tests_emitted > 0 {
func.instruction(&Instruction::I32And);
}
tests_emitted += 1;
}
}
if tests_emitted == 0 {
func.instruction(&Instruction::I32Const(1));
}
func.instruction(&Instruction::If(block_ty));
let arm_slots = arm.binding_slots.get().ok_or(WasmGcError::Validation(
"tuple-of-constructors arm reached emit without binding_slots".into(),
))?;
let mut slot_idx = 0;
for (i, pat) in items.iter().enumerate() {
match pat {
Pattern::Ident(_) => {
let slot = arm_slots[slot_idx];
slot_idx += 1;
if slot != u16::MAX {
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(tuple_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: tuple_idx,
field_index: i as u32,
});
func.instruction(&Instruction::LocalSet(slot as u32));
}
}
Pattern::Constructor(name, bindings) => {
let bare = name.rsplit('.').next().unwrap_or(name);
let elem_canonical: String =
elems[i].chars().filter(|c| !c.is_whitespace()).collect();
let res_idx_opt = ctx.registry.result_type_idx(&elem_canonical);
let payload_field = match bare {
"Ok" => Some(1u32),
"Err" => Some(2u32),
_ => None,
};
for _binding in bindings {
let slot = arm_slots[slot_idx];
slot_idx += 1;
if slot == u16::MAX {
continue;
}
let (Some(res_idx), Some(payload_field)) = (res_idx_opt, payload_field)
else {
continue;
};
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(tuple_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: tuple_idx,
field_index: i as u32,
});
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: res_idx,
field_index: payload_field,
});
func.instruction(&Instruction::LocalSet(slot as u32));
}
}
_ => {}
}
}
emit_expr(func, &arm.body, slots, ctx)?;
func.instruction(&Instruction::Else);
emit_tuple_constructor_arm_cascade(
func,
scratch,
tuple_idx,
elems,
&arms[1..],
block_ty,
slots,
ctx,
)?;
func.instruction(&Instruction::End);
Ok(())
}
_ => Err(WasmGcError::Unimplemented(
"tuple-of-constructors match: arm pattern must be Tuple/Ident/Wildcard",
)),
}
}
pub(super) fn emit_list_match(
func: &mut Function,
subject: &Spanned<Expr>,
arms: &[MatchArm],
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"List match needs a subject scratch slot but none was reserved".into(),
))?;
let subject_ty = aver_type_str_of(subject);
let canonical: String = subject_ty.chars().filter(|c| !c.is_whitespace()).collect();
let list_idx = ctx
.registry
.list_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"List match: subject type `{subject_ty}` is not a registered List<T>"
)))?;
let mut empty_arm: Option<&MatchArm> = None;
let mut cons_arm: Option<&MatchArm> = None;
for arm in arms {
match &arm.pattern {
Pattern::EmptyList => empty_arm = Some(arm),
Pattern::Cons(_, _) => cons_arm = Some(arm),
Pattern::Wildcard => {
if empty_arm.is_none() {
empty_arm = Some(arm);
} else if cons_arm.is_none() {
cons_arm = Some(arm);
}
}
_ => {}
}
}
let empty_arm = empty_arm.ok_or(WasmGcError::Validation(
"List match missing empty arm".into(),
))?;
let cons_arm = cons_arm.ok_or(WasmGcError::Validation(
"List match missing cons arm".into(),
))?;
emit_expr(func, subject, slots, ctx)?;
func.instruction(&Instruction::LocalSet(scratch));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefIsNull);
func.instruction(&Instruction::If(block_ty));
emit_expr(func, &empty_arm.body, slots, ctx)?;
func.instruction(&Instruction::Else);
if let Pattern::Cons(_head_name, _tail_name) = &cons_arm.pattern {
let arm_slots = cons_arm.binding_slots.get().ok_or(WasmGcError::Validation(
"Cons match arm reached emit without resolver-allocated binding_slots".into(),
))?;
let head_slot = arm_slots[0];
if head_slot != u16::MAX {
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(list_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
func.instruction(&Instruction::LocalSet(head_slot as u32));
}
let tail_slot = arm_slots[1];
if tail_slot != u16::MAX {
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(list_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
func.instruction(&Instruction::LocalSet(tail_slot as u32));
}
}
emit_expr(func, &cons_arm.body, slots, ctx)?;
func.instruction(&Instruction::End);
Ok(())
}
pub(super) fn emit_result_match(
func: &mut Function,
subject: &Spanned<Expr>,
arms: &[MatchArm],
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"Result match needs a subject scratch slot but none was reserved".into(),
))?;
let subject_ty = aver_type_str_of(subject);
let canonical: String = subject_ty.chars().filter(|c| !c.is_whitespace()).collect();
let res_idx = ctx
.registry
.result_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Result match: subject type `{subject_ty}` is not a registered Result<T,E>"
)))?;
let mut ok_arm: Option<&MatchArm> = None;
let mut err_arm: Option<&MatchArm> = None;
for arm in arms {
match &arm.pattern {
Pattern::Constructor(name, _) => {
let bare = name.rsplit('.').next().unwrap_or(name);
if bare == "Ok" {
ok_arm = Some(arm);
} else if bare == "Err" {
err_arm = Some(arm);
}
}
Pattern::Wildcard => {
if err_arm.is_none() {
err_arm = Some(arm);
} else if ok_arm.is_none() {
ok_arm = Some(arm);
}
}
_ => {}
}
}
let ok_arm = ok_arm.ok_or(WasmGcError::Validation(
"Result match missing Ok arm".into(),
))?;
let err_arm = err_arm.ok_or(WasmGcError::Validation(
"Result match missing Err arm".into(),
))?;
emit_expr(func, subject, slots, ctx)?;
func.instruction(&Instruction::LocalSet(scratch));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: res_idx,
field_index: 0,
});
func.instruction(&Instruction::I32Const(1));
func.instruction(&Instruction::I32Eq);
func.instruction(&Instruction::If(block_ty));
if let Pattern::Constructor(_, _) = &ok_arm.pattern
&& let Some(arm_slots) = ok_arm.binding_slots.get()
&& let Some(&slot) = arm_slots.first()
&& slot != u16::MAX
{
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: res_idx,
field_index: 1,
});
func.instruction(&Instruction::LocalSet(slot as u32));
}
emit_expr(func, &ok_arm.body, slots, ctx)?;
func.instruction(&Instruction::Else);
if let Pattern::Constructor(_, _) = &err_arm.pattern
&& let Some(arm_slots) = err_arm.binding_slots.get()
&& let Some(&slot) = arm_slots.first()
&& slot != u16::MAX
{
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: res_idx,
field_index: 2,
});
func.instruction(&Instruction::LocalSet(slot as u32));
}
emit_expr(func, &err_arm.body, slots, ctx)?;
func.instruction(&Instruction::End);
Ok(())
}
pub(super) fn emit_map_get_match_fused(
func: &mut Function,
map: &Spanned<Expr>,
key: &Spanned<Expr>,
arms: &[MatchArm],
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let map_aver = aver_type_str_of(map);
let canonical: String = map_aver.chars().filter(|c| !c.is_whitespace()).collect();
let helpers = ctx
.fn_map
.map_helpers
.get(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.get match fusion: map type `{map_aver}` has no helpers"
)))?;
let mut some_arm: Option<&MatchArm> = None;
let mut none_arm: Option<&MatchArm> = None;
for arm in arms {
match &arm.pattern {
Pattern::Constructor(name, _) => {
let bare = name.rsplit('.').next().unwrap_or(name);
if bare == "Some" {
some_arm = Some(arm);
} else if bare == "None" {
none_arm = Some(arm);
}
}
Pattern::Wildcard => {
if none_arm.is_none() {
none_arm = Some(arm);
} else if some_arm.is_none() {
some_arm = Some(arm);
}
}
_ => {}
}
}
let some_arm = some_arm.ok_or(WasmGcError::Validation(
"Map.get match fusion missing Some arm".into(),
))?;
let none_arm = none_arm.ok_or(WasmGcError::Validation(
"Map.get match fusion missing None arm".into(),
))?;
emit_expr(func, map, slots, ctx)?;
emit_expr(func, key, slots, ctx)?;
func.instruction(&Instruction::Call(helpers.get_pair));
if let Pattern::Constructor(_, _) = &some_arm.pattern
&& let Some(arm_slots) = some_arm.binding_slots.get()
&& let Some(&slot) = arm_slots.first()
&& slot != u16::MAX
{
func.instruction(&Instruction::LocalSet(slot as u32));
} else {
func.instruction(&Instruction::Drop);
}
func.instruction(&Instruction::If(block_ty));
emit_expr(func, &some_arm.body, slots, ctx)?;
func.instruction(&Instruction::Else);
emit_expr(func, &none_arm.body, slots, ctx)?;
func.instruction(&Instruction::End);
Ok(())
}
pub(super) fn emit_option_match(
func: &mut Function,
subject: &Spanned<Expr>,
arms: &[MatchArm],
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"Option match needs a subject scratch slot but none was reserved".into(),
))?;
let subject_ty = aver_type_str_of(subject);
let canonical: String = subject_ty.chars().filter(|c| !c.is_whitespace()).collect();
let opt_idx = ctx
.registry
.option_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Option match: subject type `{subject_ty}` is not a registered Option<T>"
)))?;
let mut some_arm: Option<&MatchArm> = None;
let mut none_arm: Option<&MatchArm> = None;
for arm in arms {
match &arm.pattern {
Pattern::Constructor(name, _) => {
let bare = name.rsplit('.').next().unwrap_or(name);
if bare == "Some" {
some_arm = Some(arm);
} else if bare == "None" {
none_arm = Some(arm);
}
}
Pattern::Wildcard => {
if none_arm.is_none() {
none_arm = Some(arm);
} else if some_arm.is_none() {
some_arm = Some(arm);
}
}
_ => {}
}
}
let some_arm = some_arm.ok_or(WasmGcError::Validation(
"Option match missing Some arm".into(),
))?;
let none_arm = none_arm.ok_or(WasmGcError::Validation(
"Option match missing None arm".into(),
))?;
emit_expr(func, subject, slots, ctx)?;
func.instruction(&Instruction::LocalSet(scratch));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(opt_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: opt_idx,
field_index: 0,
});
func.instruction(&Instruction::I32Const(1));
func.instruction(&Instruction::I32Eq);
func.instruction(&Instruction::If(block_ty));
if let Pattern::Constructor(_, _) = &some_arm.pattern
&& let Some(arm_slots) = some_arm.binding_slots.get()
&& let Some(&slot) = arm_slots.first()
&& slot != u16::MAX
{
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(opt_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: opt_idx,
field_index: 1,
});
func.instruction(&Instruction::LocalSet(slot as u32));
}
emit_expr(func, &some_arm.body, slots, ctx)?;
func.instruction(&Instruction::Else);
emit_expr(func, &none_arm.body, slots, ctx)?;
func.instruction(&Instruction::End);
Ok(())
}
pub(super) fn emit_string_match(
func: &mut Function,
subject: &Spanned<Expr>,
arms: &[MatchArm],
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"String match needs a subject scratch slot but none was reserved".into(),
))?;
let eq_idx = ctx
.fn_map
.builtins
.get("__wasmgc_string_eq")
.copied()
.ok_or(WasmGcError::Validation(
"String match: __wasmgc_string_eq builtin wasn't registered".into(),
))?;
let s_idx = ctx
.registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"String match needs the String type slot allocated".into(),
))?;
let read_subject = |func: &mut Function| {
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNullable(
wasm_encoder::HeapType::Concrete(s_idx),
));
};
emit_expr(func, subject, slots, ctx)?;
func.instruction(&Instruction::LocalSet(scratch));
let mut literal_arms: Vec<(&str, &MatchArm)> = Vec::new();
let mut default_arm: Option<&MatchArm> = None;
for arm in arms {
if let Pattern::Literal(Literal::Str(s)) = &arm.pattern {
literal_arms.push((s.as_str(), arm));
} else if default_arm.is_none() {
default_arm = Some(arm);
}
}
let default_arm = default_arm.ok_or(WasmGcError::Validation(
"String match without a default arm — type checker should have rejected".into(),
))?;
for _ in &literal_arms {
}
let mut ends_to_close = 0usize;
for (lit, arm) in &literal_arms {
read_subject(func);
emit_string_literal_bytes(func, lit.as_bytes(), ctx)?;
func.instruction(&Instruction::Call(eq_idx));
func.instruction(&Instruction::If(block_ty));
emit_expr(func, &arm.body, slots, ctx)?;
func.instruction(&Instruction::Else);
ends_to_close += 1;
}
emit_expr(func, &default_arm.body, slots, ctx)?;
for _ in 0..ends_to_close {
func.instruction(&Instruction::End);
}
Ok(())
}
pub(super) fn emit_caller_fn_idx(
func: &mut Function,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let idx = ctx
.caller_fn_collector
.borrow_mut()
.register(ctx.self_fn_name);
func.instruction(&Instruction::I32Const(idx as i32));
Ok(())
}
pub(super) fn emit_string_literal_bytes(
func: &mut Function,
bytes: &[u8],
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let segment_idx = ctx
.registry
.string_literal_segment(bytes)
.ok_or(WasmGcError::Validation(format!(
"String literal `{:?}` was not registered in the data segment table",
std::str::from_utf8(bytes).unwrap_or("<non-utf8>")
)))?;
let s_idx = ctx
.registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"String literal needs string slot allocated".into(),
))?;
func.instruction(&Instruction::I32Const(0)); func.instruction(&Instruction::I32Const(bytes.len() as i32));
func.instruction(&Instruction::ArrayNewData {
array_type_index: s_idx,
array_data_index: segment_idx,
});
Ok(())
}
pub(super) fn emit_variant_dispatch(
func: &mut Function,
subject: &Spanned<Expr>,
arms: &[MatchArm],
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"multi-arm variant match needs a subject scratch slot but none was reserved".into(),
))?;
emit_expr(func, subject, slots, ctx)?;
func.instruction(&Instruction::LocalSet(scratch));
emit_variant_arm_cascade(func, arms, block_ty, scratch, slots, ctx)
}
pub(super) fn emit_variant_arm_cascade(
func: &mut Function,
arms: &[MatchArm],
block_ty: wasm_encoder::BlockType,
subject_scratch: u32,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if arms.is_empty() {
func.instruction(&Instruction::Unreachable);
return Ok(());
}
if arms.len() == 1 {
return emit_arm_body(func, &arms[0], subject_scratch, slots, ctx);
}
let arm = &arms[0];
match &arm.pattern {
Pattern::Constructor(name, _) => {
let bare = name.rsplit('.').next().unwrap_or(name);
let parent_hint = name.rsplit_once('.').map(|(p, _)| p);
let info = parent_hint
.and_then(|p| ctx.registry.variant_in(p, bare))
.or_else(|| ctx.registry.variant(bare))
.ok_or(WasmGcError::Validation(format!(
"unknown variant `{name}` in match"
)))?;
func.instruction(&Instruction::LocalGet(subject_scratch));
func.instruction(&Instruction::RefTestNonNull(
wasm_encoder::HeapType::Concrete(info.type_idx),
));
func.instruction(&Instruction::If(block_ty));
emit_arm_body(func, arm, subject_scratch, slots, ctx)?;
func.instruction(&Instruction::Else);
emit_variant_arm_cascade(func, &arms[1..], block_ty, subject_scratch, slots, ctx)?;
func.instruction(&Instruction::End);
}
Pattern::Wildcard => {
return emit_arm_body(func, arm, subject_scratch, slots, ctx);
}
_ => {
return Err(WasmGcError::Unimplemented(
"phase 3b — non-Constructor pattern in multi-arm variant match",
));
}
}
Ok(())
}
pub(super) fn emit_arm_body(
func: &mut Function,
arm: &MatchArm,
subject_scratch: u32,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if let Pattern::Constructor(name, bindings) = &arm.pattern {
let bare = name.rsplit('.').next().unwrap_or(name);
let parent_hint = name.rsplit_once('.').map(|(p, _)| p);
let info = parent_hint
.and_then(|p| ctx.registry.variant_in(p, bare))
.or_else(|| ctx.registry.variant(bare))
.ok_or(WasmGcError::Validation(format!(
"unknown variant `{name}` in match"
)))?;
let arm_slots = arm.binding_slots.get().ok_or(WasmGcError::Validation(
"match arm reached emit without resolver-allocated binding_slots".into(),
))?;
if ctx.registry.newtype_underlying(&info.parent).is_some() && bindings.len() == 1 {
let slot = arm_slots[0];
if slot != u16::MAX {
func.instruction(&Instruction::LocalGet(subject_scratch));
func.instruction(&Instruction::LocalSet(slot as u32));
}
return emit_expr(func, &arm.body, slots, ctx);
}
for (i, _binding_name) in bindings.iter().enumerate() {
let slot = arm_slots[i];
if slot == u16::MAX {
continue;
}
let slot = slot as u32;
func.instruction(&Instruction::LocalGet(subject_scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(info.type_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: info.type_idx,
field_index: i as u32,
});
func.instruction(&Instruction::LocalSet(slot));
}
return emit_expr(func, &arm.body, slots, ctx);
}
emit_expr(func, &arm.body, slots, ctx)
}
pub(super) fn emit_single_variant_match(
func: &mut Function,
subject: &Spanned<Expr>,
arm: &MatchArm,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let (constructor, bindings) = match &arm.pattern {
Pattern::Constructor(c, b) => (c.as_str(), b.as_slice()),
_ => {
return Err(WasmGcError::Validation(
"emit_single_variant_match called on non-Constructor pattern".into(),
));
}
};
let body = arm.body.as_ref();
let arm_slots = arm.binding_slots.get().ok_or(WasmGcError::Validation(
"single-arm match reached emit without resolver-allocated binding_slots".into(),
))?;
let bare = constructor.rsplit('.').next().unwrap_or(constructor);
let parent_hint = constructor.rsplit_once('.').map(|(p, _)| p);
let info = parent_hint
.and_then(|p| ctx.registry.variant_in(p, bare))
.or_else(|| ctx.registry.variant(bare))
.ok_or(WasmGcError::Validation(format!(
"unknown variant `{constructor}` in match pattern"
)))?;
if bindings.len() != info.fields.len() {
return Err(WasmGcError::Validation(format!(
"variant `{constructor}` has {} field(s) but pattern binds {}",
info.fields.len(),
bindings.len()
)));
}
if ctx.registry.newtype_underlying(&info.parent).is_some() && bindings.len() == 1 {
let slot = arm_slots[0];
emit_expr(func, subject, slots, ctx)?;
if slot != u16::MAX {
func.instruction(&Instruction::LocalSet(slot as u32));
} else {
func.instruction(&Instruction::Drop);
}
emit_expr(func, body, slots, ctx)?;
return Ok(());
}
let variant_idx = info.type_idx;
let cast_ty = wasm_encoder::HeapType::Concrete(variant_idx);
if bindings.is_empty() {
emit_expr(func, subject, slots, ctx)?;
func.instruction(&Instruction::Drop);
emit_expr(func, body, slots, ctx)?;
return Ok(());
}
emit_expr(func, subject, slots, ctx)?;
func.instruction(&Instruction::RefCastNonNull(cast_ty));
if bindings.len() == 1 {
let slot = arm_slots[0];
func.instruction(&Instruction::StructGet {
struct_type_index: variant_idx,
field_index: 0,
});
if slot != u16::MAX {
func.instruction(&Instruction::LocalSet(slot as u32));
} else {
func.instruction(&Instruction::Drop);
}
emit_expr(func, body, slots, ctx)?;
return Ok(());
}
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"multi-binding variant pattern needs subject_scratch but none was reserved \
(slots::expr_needs_scratch must opt in for any Constructor pattern)"
.into(),
))?;
func.instruction(&Instruction::LocalSet(scratch));
for (i, _binding_name) in bindings.iter().enumerate() {
let slot = arm_slots[i];
if slot == u16::MAX {
continue;
}
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(cast_ty));
func.instruction(&Instruction::StructGet {
struct_type_index: variant_idx,
field_index: i as u32,
});
func.instruction(&Instruction::LocalSet(slot as u32));
}
emit_expr(func, body, slots, ctx)?;
Ok(())
}
pub(super) fn emit_constructor_with_args(
func: &mut Function,
info: &super::super::types::VariantInfo,
args: &[Spanned<Expr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if args.len() != info.fields.len() {
return Err(WasmGcError::Validation(format!(
"variant has {} field(s) but call supplied {}",
info.fields.len(),
args.len()
)));
}
if ctx.registry.newtype_underlying(&info.parent).is_some() {
return emit_expr(func, &args[0], slots, ctx);
}
for arg in args {
emit_expr(func, arg, slots, ctx)?;
}
func.instruction(&Instruction::StructNew(info.type_idx));
Ok(())
}
pub(super) fn emit_constructor(
func: &mut Function,
outer: &Spanned<Expr>,
name: &str,
payload: Option<&Spanned<Expr>>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let bare = name.rsplit('.').next().unwrap_or(name);
if name == "Option.Some" || (bare == "Some" && name.starts_with("Option")) {
return emit_option_constructor(func, payload, None, slots, ctx);
}
if name == "Option.None" || (bare == "None" && name.starts_with("Option")) {
let stamped_canonical = aver_type_canonical(outer, ctx.return_type, ctx.registry);
let hint: String = if let Some(inner) = stamped_canonical
.strip_prefix("Option<")
.and_then(|s| s.strip_suffix('>'))
{
inner.to_string()
} else {
ctx.return_type.to_string()
};
return emit_option_constructor(func, None, Some(&hint), slots, ctx);
}
if name == "Result.Ok" || (bare == "Ok" && name.starts_with("Result")) {
return emit_result_constructor(func, "Ok", payload, slots, ctx);
}
if name == "Result.Err" || (bare == "Err" && name.starts_with("Result")) {
return emit_result_constructor(func, "Err", payload, slots, ctx);
}
let bare = name.rsplit('.').next().unwrap_or(name);
let parent_hint = name.rsplit_once('.').map(|(p, _)| p);
let info = parent_hint
.and_then(|p| ctx.registry.variant_in(p, bare))
.or_else(|| ctx.registry.variant(bare))
.ok_or(WasmGcError::Validation(format!(
"unknown variant constructor `{name}`"
)))?;
let payload_count = info.fields.len();
if payload_count == 0 {
func.instruction(&Instruction::StructNew(info.type_idx));
return Ok(());
}
if payload_count > 1 {
return Err(WasmGcError::Unimplemented(
"phase 3b — multi-field variant constructors (need Tuple lowering)",
));
}
let payload = payload.ok_or(WasmGcError::Validation(format!(
"variant `{name}` expects 1 payload but got 0"
)))?;
emit_expr(func, payload, slots, ctx)?;
func.instruction(&Instruction::StructNew(info.type_idx));
Ok(())
}
pub(super) fn emit_match(
func: &mut Function,
subject: &Spanned<Expr>,
arms: &[MatchArm],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if arms.is_empty() {
return Err(WasmGcError::Validation("match has no arms".into()));
}
let result_ty_str =
super::infer::aver_type_canonical(&arms[0].body, ctx.return_type, ctx.registry);
let result_wasm = aver_to_wasm(&result_ty_str, Some(ctx.registry))?;
let block_ty = match result_wasm {
Some(v) => wasm_encoder::BlockType::Result(v),
None => wasm_encoder::BlockType::Empty,
};
let subject_ty = aver_type_str_of(subject);
if subject_ty == "Bool" {
if arms.len() != 2 {
return Err(WasmGcError::Unimplemented(
"phase 4 — Bool match must have exactly 2 arms (true / false)",
));
}
let mut true_body: Option<&Spanned<Expr>> = None;
let mut false_body: Option<&Spanned<Expr>> = None;
for arm in arms {
match &arm.pattern {
Pattern::Literal(Literal::Bool(true)) => true_body = Some(&arm.body),
Pattern::Literal(Literal::Bool(false)) => false_body = Some(&arm.body),
Pattern::Wildcard => {
if true_body.is_none() {
true_body = Some(&arm.body);
} else {
false_body = Some(&arm.body);
}
}
_ => {
return Err(WasmGcError::Unimplemented(
"phase 4 — Bool match supports only Bool literals + wildcard",
));
}
}
}
let t = true_body.ok_or(WasmGcError::Validation(
"Bool match missing true arm".into(),
))?;
let f = false_body.ok_or(WasmGcError::Validation(
"Bool match missing false arm".into(),
))?;
emit_expr(func, subject, slots, ctx)?;
func.instruction(&Instruction::If(block_ty));
emit_expr(func, t, slots, ctx)?;
func.instruction(&Instruction::Else);
emit_expr(func, f, slots, ctx)?;
func.instruction(&Instruction::End);
return Ok(());
}
if arms
.iter()
.any(|a| matches!(&a.pattern, Pattern::EmptyList | Pattern::Cons(_, _)))
{
return emit_list_match(func, subject, arms, block_ty, slots, ctx);
}
if arms.iter().any(arm_is_result_pattern) {
return emit_result_match(func, subject, arms, block_ty, slots, ctx);
}
if arms.iter().any(arm_is_option_pattern) {
if let Expr::FnCall(callee, fn_args) = &subject.node
&& let Expr::Attr(parent, member) = &callee.node
&& let Expr::Ident(p) = &parent.node
&& p == "Map"
&& member == "get"
&& fn_args.len() == 2
{
return emit_map_get_match_fused(
func,
&fn_args[0],
&fn_args[1],
arms,
block_ty,
slots,
ctx,
);
}
return emit_option_match(func, subject, arms, block_ty, slots, ctx);
}
if arms.len() == 1
&& let Pattern::Tuple(items) = &arms[0].pattern
&& items.len() >= 2
{
return emit_tuple_match(func, subject, &arms[0], slots, ctx);
}
if arms.iter().any(|a| {
matches!(&a.pattern, Pattern::Tuple(items)
if items.iter().any(|i| matches!(i, Pattern::Constructor(_, _))))
}) && arms.last().is_some_and(|a| {
matches!(
&a.pattern,
Pattern::Wildcard | Pattern::Ident(_) | Pattern::Tuple(_)
)
}) {
return emit_tuple_constructor_match(func, subject, arms, block_ty, slots, ctx);
}
if arms.len() == 1
&& let Pattern::Constructor(_, _) = &arms[0].pattern
{
return emit_single_variant_match(func, subject, &arms[0], slots, ctx);
}
let has_constructor_arm = arms
.iter()
.any(|a| matches!(a.pattern, Pattern::Constructor(_, _)));
if has_constructor_arm {
return emit_variant_dispatch(func, subject, arms, block_ty, slots, ctx);
}
if subject_ty == "String" {
return emit_string_match(func, subject, arms, block_ty, slots, ctx);
}
if subject_ty != "Int" {
return Err(WasmGcError::Unimplemented(
"phase 3b — match subject must be Int / Bool / sum type",
));
}
let mut wildcard_body: Option<&Spanned<Expr>> = None;
let mut typed_arms: Vec<(i64, &Spanned<Expr>)> = Vec::new();
for arm in arms {
match &arm.pattern {
Pattern::Literal(Literal::Int(n)) => typed_arms.push((*n, &arm.body)),
Pattern::Wildcard => wildcard_body = Some(&arm.body),
_ => {
return Err(WasmGcError::Unimplemented(
"phase 4 — Int match supports only Int literal patterns + wildcard",
));
}
}
}
let wildcard = wildcard_body.ok_or(WasmGcError::Unimplemented(
"phase 4 — Int match without wildcard (exhaustive Int matching needs phase 5)",
))?;
emit_int_match_cascade(func, subject, &typed_arms, wildcard, block_ty, slots, ctx)?;
Ok(())
}
pub(super) fn emit_int_match_cascade(
func: &mut Function,
subject: &Spanned<Expr>,
typed_arms: &[(i64, &Spanned<Expr>)],
wildcard: &Spanned<Expr>,
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if typed_arms.is_empty() {
emit_expr(func, wildcard, slots, ctx)?;
return Ok(());
}
let (pat_lit, body) = typed_arms[0];
emit_expr(func, subject, slots, ctx)?;
func.instruction(&Instruction::I64Const(pat_lit));
func.instruction(&Instruction::I64Eq);
func.instruction(&Instruction::If(block_ty));
emit_expr(func, body, slots, ctx)?;
func.instruction(&Instruction::Else);
emit_int_match_cascade(
func,
subject,
&typed_arms[1..],
wildcard,
block_ty,
slots,
ctx,
)?;
func.instruction(&Instruction::End);
Ok(())
}
pub(super) fn emit_tail_call(
func: &mut Function,
target: &str,
args: &[Spanned<Expr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let entry = ctx
.fn_map
.by_name
.get(target)
.ok_or(WasmGcError::Validation(format!(
"tail call to unknown fn `{target}`"
)))?;
for arg in args {
emit_expr(func, arg, slots, ctx)?;
}
let no_tail_call = std::env::var_os("AVER_WASM_GC_NO_TAIL_CALL").is_some();
let target_idx = if target == ctx.self_fn_name {
ctx.self_wasm_idx
} else {
entry.wasm_idx
};
if no_tail_call {
func.instruction(&Instruction::Call(target_idx));
} else {
func.instruction(&Instruction::ReturnCall(target_idx));
}
Ok(())
}