use crate::ir::component::concrete::ConcreteType;
use crate::ir::component::refs::GetItemRef;
use crate::ir::component::visitor::ResolvedItem;
use crate::Component;
fn bytes(wat: &str) -> Vec<u8> {
wat::parse_str(wat).expect("WAT parse failed")
}
fn parsed(b: &[u8]) -> Component<'_> {
Component::parse(b, false, false).unwrap()
}
fn resolve_export<'a>(comp: &'a Component<'a>, export_idx: usize) -> ResolvedItem<'a, 'a> {
let ref_ = comp.exports[export_idx].get_item_ref();
comp.resolve(&ref_.ref_)
}
#[test]
fn test_resolve_type_ref_from_export() {
let b = bytes(
r#"(component
(type $a u32) (;; index 0 ;)
(type $b u8) (;; index 1 ;)
(export "a" (type $a))
(export "b" (type $b))
)"#,
);
let comp = parsed(&b);
assert!(matches!(
resolve_export(&comp, 0),
ResolvedItem::CompType(0, _)
));
assert!(matches!(
resolve_export(&comp, 1),
ResolvedItem::CompType(1, _)
));
}
#[test]
fn test_resolve_imported_type_ref() {
let b = bytes(
r#"(component
(import "t" (type (sub resource)))
(export "t-out" (type 0))
)"#,
);
let comp = parsed(&b);
assert!(matches!(
resolve_export(&comp, 0),
ResolvedItem::Import(0, _)
));
}
#[test]
fn test_resolve_alias_ref() {
let b = bytes(
r#"(component
(type $outer u32)
(component $inner
(alias outer 1 0 (type)) (;; aliases outer type 0 → inner type 0 ;)
(export "t" (type 0))
)
)"#,
);
let outer = parsed(&b);
let inner = &outer.components[0];
assert!(matches!(
resolve_export(inner, 0),
ResolvedItem::Alias(0, _)
));
}
#[test]
fn test_resolve_on_inner_component() {
let b = bytes(
r#"(component
(component $inner
(type $a u32) (;; inner type 0 ;)
(type $b u8) (;; inner type 1 ;)
(export "a" (type $a))
(export "b" (type $b))
)
)"#,
);
let outer = parsed(&b);
let inner = &outer.components[0];
assert!(matches!(
resolve_export(inner, 0),
ResolvedItem::CompType(0, _)
));
assert!(matches!(
resolve_export(inner, 1),
ResolvedItem::CompType(1, _)
));
}
#[test]
fn test_resolve_on_two_independent_inner_components() {
let b = bytes(
r#"(component
(component $first
(type $x u32) (;; first's type 0 ;)
(export "x" (type $x))
)
(component $second
(type $p u8) (;; second's type 0 ;)
(type $q u16) (;; second's type 1 ;)
(export "p" (type $p))
(export "q" (type $q))
)
)"#,
);
let outer = parsed(&b);
let first = &outer.components[0];
let second = &outer.components[1];
assert!(matches!(
resolve_export(first, 0),
ResolvedItem::CompType(0, _)
));
assert!(matches!(
resolve_export(second, 0),
ResolvedItem::CompType(0, _)
));
assert!(matches!(
resolve_export(second, 1),
ResolvedItem::CompType(1, _)
));
}
#[test]
fn test_get_type_of_exported_lift_func() {
use crate::ir::id::ComponentExportId;
let b = bytes(
r#"(component
(core module $m
(func (export "add") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
)
)
(core instance $mi (instantiate $m))
(type $add-t (func (param "a" u32) (param "b" u32) (result u32)))
(func $add (type $add-t) (canon lift (core func $mi "add")))
(export "add" (func $add))
)"#,
);
let comp = parsed(&b);
let ty = comp.get_type_of_exported_lift_func(ComponentExportId(0));
assert!(
ty.is_some(),
"should find the type of the exported lift func"
);
assert!(
matches!(ty.unwrap(), wasmparser::ComponentType::Func(_)),
"resolved type should be ComponentType::Func"
);
}
#[test]
fn test_concretize_import_resolves_body_types() {
let b = bytes(
r#"(component
(type (instance
(type $elem u32)
(type $fn-type (func (param "x" 0)))
(export "my-func" (func (type 1)))
))
(import "my-iface" (instance (type 0)))
)"#,
);
let comp = parsed(&b);
let result = comp.concretize_import("my-iface");
assert!(
matches!(result, Some(ConcreteType::Instance(_))),
"expected Some(Instance), got {result:?}"
);
}
fn check_concretize_export(wat: &str) {
let b = bytes(wat);
let comp = parsed(&b);
let result = comp.concretize_export("iface");
let Some(ConcreteType::Instance(funcs)) = result else {
panic!("expected Some(Instance), got {result:?}");
};
assert_eq!(funcs.len(), 1);
assert_eq!(funcs[0].0, "f");
}
#[test]
fn concretize_export_from_exports_instance() {
check_concretize_export(
r#"(component
(import "iface" (instance $imp
(export "f" (func (param "x" u32) (result u8)))
))
(alias export $imp "f" (func $fn))
(instance $out (export "f" (func $fn)))
(export "iface" (instance $out))
)"#,
)
}
#[test]
fn concretize_export_instantiated_component() {
check_concretize_export(
r#"(component
(import "iface" (instance $imp
(export "f" (func (param "x" u32) (result u8)))
))
(component $shim
(type $sig (func (param "x" u32) (result u8)))
(import "import-func-f" (func (type $sig)))
(export "f" (func 0))
)
(alias export $imp "f" (func $fn))
(instance $shim-inst (instantiate $shim
(with "import-func-f" (func $fn))
))
(export "iface" (instance $shim-inst))
)"#,
);
}
#[test]
fn concretize_export_import_reexport() {
check_concretize_export(
r#"(component
(import "iface" (instance $imp
(export "f" (func (param "x" u32) (result u8)))
))
(export "iface" (instance $imp))
)"#,
);
}
#[test]
fn concretize_export_all_patterns_same_signature() {
fn single_func_sig(wat: &str) -> crate::ir::component::concrete::ConcreteFuncType<'static> {
let bytes = wat::parse_str(wat).expect("WAT parse failed");
let bytes: &'static [u8] = Box::leak(bytes.into_boxed_slice());
let comp = Box::leak(Box::new(Component::parse(bytes, false, false).unwrap()));
let Some(ConcreteType::Instance(mut funcs)) = comp.concretize_export("iface") else {
panic!("expected Instance");
};
funcs.remove(0).1
}
let from_exports = single_func_sig(
r#"(component
(import "iface" (instance $imp (export "f" (func (param "x" u32)))))
(alias export $imp "f" (func $fn))
(instance $out (export "f" (func $fn)))
(export "iface" (instance $out))
)"#,
);
let import_reexport = single_func_sig(
r#"(component
(import "iface" (instance $imp (export "f" (func (param "x" u32)))))
(export "iface" (instance $imp))
)"#,
);
let shim = single_func_sig(
r#"(component
(import "iface" (instance $imp (export "f" (func (param "x" u32)))))
(component $shim
(type $sig (func (param "x" u32)))
(import "import-func-f" (func (type $sig)))
(export "f" (func 0))
)
(alias export $imp "f" (func $fn))
(instance $shim-inst (instantiate $shim (with "import-func-f" (func $fn))))
(export "iface" (instance $shim-inst))
)"#,
);
for (label, sig) in [
("from-exports", &from_exports),
("import-reexport", &import_reexport),
("shim", &shim),
] {
assert_eq!(
sig.params.len(),
1,
"{label}: expected 1 param, got {}",
sig.params.len()
);
assert!(
matches!(
sig.params[0],
(
"x",
crate::ir::component::concrete::ConcreteValType::Primitive(
wasmparser::PrimitiveValType::U32
)
)
),
"{label}: expected (\"x\", Primitive(U32)), got {:?}",
sig.params[0]
);
assert!(sig.result.is_none(), "{label}: expected no result");
}
}
use crate::ir::component::concrete::ConcreteValType;
fn first_param_type(wat: &str) -> ConcreteValType<'_> {
let bytes = bytes(wat);
let bytes: &'static [u8] = Box::leak(bytes.into_boxed_slice());
let comp = Box::leak(Box::new(Component::parse(bytes, false, false).unwrap()));
let Some(ConcreteType::Instance(funcs)) = comp.concretize_import("iface") else {
panic!("expected Instance");
};
funcs
.into_iter()
.next()
.unwrap()
.1
.params
.into_iter()
.next()
.unwrap()
.1
}
#[test]
fn concretize_import_record_param() {
let ty = first_param_type(
r#"(component
(type (instance
(type $rec (record (field "a" u32) (field "b" string)))
(type $fn (func (param "r" 0)))
(export "f" (func (type 1)))
))
(import "iface" (instance (type 0)))
)"#,
);
assert!(
matches!(ty, ConcreteValType::Record(_)),
"expected Record, got {ty:?}"
);
let ConcreteValType::Record(fields) = ty else {
unreachable!()
};
assert_eq!(fields.len(), 2);
assert!(
matches!(
*fields[0].1,
ConcreteValType::Primitive(wasmparser::PrimitiveValType::U32)
),
"field 'a'"
);
assert!(
matches!(
*fields[1].1,
ConcreteValType::Primitive(wasmparser::PrimitiveValType::String)
),
"field 'b'"
);
}
#[test]
fn concretize_import_variant_param() {
let ty = first_param_type(
r#"(component
(type (instance
(type $var (variant (case "a" u32) (case "b")))
(type $fn (func (param "v" 0)))
(export "f" (func (type 1)))
))
(import "iface" (instance (type 0)))
)"#,
);
assert!(
matches!(ty, ConcreteValType::Variant(_)),
"expected Variant, got {ty:?}"
);
let ConcreteValType::Variant(cases) = ty else {
unreachable!()
};
assert_eq!(cases.len(), 2);
assert!(matches!(cases[0], ("a", Some(_))));
assert!(matches!(cases[1], ("b", None)));
}
#[test]
fn concretize_import_list_param() {
let ty = first_param_type(
r#"(component
(type (instance
(type $lst (list u8))
(type $fn (func (param "l" 0)))
(export "f" (func (type 1)))
))
(import "iface" (instance (type 0)))
)"#,
);
assert!(
matches!(ty, ConcreteValType::List(_)),
"expected List, got {ty:?}"
);
}
#[test]
fn concretize_import_tuple_param() {
let ty = first_param_type(
r#"(component
(type (instance
(type $tup (tuple u32 string))
(type $fn (func (param "t" 0)))
(export "f" (func (type 1)))
))
(import "iface" (instance (type 0)))
)"#,
);
assert!(
matches!(ty, ConcreteValType::Tuple(_)),
"expected Tuple, got {ty:?}"
);
let ConcreteValType::Tuple(elems) = ty else {
unreachable!()
};
assert_eq!(elems.len(), 2);
}
#[test]
fn concretize_import_option_param() {
let ty = first_param_type(
r#"(component
(type (instance
(type $opt (option string))
(type $fn (func (param "o" 0)))
(export "f" (func (type 1)))
))
(import "iface" (instance (type 0)))
)"#,
);
assert!(
matches!(ty, ConcreteValType::Option(_)),
"expected Option, got {ty:?}"
);
}
#[test]
fn concretize_import_result_param() {
let ty = first_param_type(
r#"(component
(type (instance
(type $res (result u32 (error string)))
(type $fn (func (param "r" 0)))
(export "f" (func (type 1)))
))
(import "iface" (instance (type 0)))
)"#,
);
assert!(
matches!(ty, ConcreteValType::Result { .. }),
"expected Result, got {ty:?}"
);
let ConcreteValType::Result { ok, err } = ty else {
unreachable!()
};
assert!(ok.is_some(), "expected ok type");
assert!(err.is_some(), "expected err type");
}
#[test]
fn concretize_import_flags_param() {
let ty = first_param_type(
r#"(component
(type (instance
(type $flg (flags "read" "write" "exec"))
(type $fn (func (param "f" 0)))
(export "f" (func (type 1)))
))
(import "iface" (instance (type 0)))
)"#,
);
assert!(
matches!(ty, ConcreteValType::Flags(_)),
"expected Flags, got {ty:?}"
);
let ConcreteValType::Flags(names) = ty else {
unreachable!()
};
assert_eq!(names, vec!["read", "write", "exec"]);
}
#[test]
fn concretize_import_enum_param() {
let ty = first_param_type(
r#"(component
(type (instance
(type $enm (enum "low" "medium" "high"))
(type $fn (func (param "e" 0)))
(export "f" (func (type 1)))
))
(import "iface" (instance (type 0)))
)"#,
);
assert!(
matches!(ty, ConcreteValType::Enum(_)),
"expected Enum, got {ty:?}"
);
let ConcreteValType::Enum(variants) = ty else {
unreachable!()
};
assert_eq!(variants, vec!["low", "medium", "high"]);
}
#[test]
fn server_and_middleware_concretize_to_same_func_type() {
let server_b = bytes(
r#"(component
(component $shim
(type $ev (variant (case "a") (case "b" u32)))
(type $fn-type (func (param "x" u32) (result 0)))
(import "handle" (func $h (type $fn-type)))
(export "handle" (func $h))
)
(import "handle" (func $h
(param "x" u32) (result (variant (case "a") (case "b" u32)))))
(instance $si (instantiate $shim (with "handle" (func $h))))
(export "my:iface@1.0" (instance $si))
)"#,
);
let middleware_a = bytes(
r#"(component
(import "my:iface@1.0" (instance $iface
(type (variant (case "a") (case "b" u32)))
(export "ev" (type (eq 0)))
(type $fn (func (param "x" u32) (result 0)))
(export "handle" (func (type $fn)))
))
(export "my:iface@1.0" (instance $iface))
)"#,
);
let server_b_s: &'static [u8] = Box::leak(server_b.into_boxed_slice());
let middleware_a_s: &'static [u8] = Box::leak(middleware_a.into_boxed_slice());
let sb = Box::leak(Box::new(
Component::parse(server_b_s, false, false).unwrap(),
));
let ma = Box::leak(Box::new(
Component::parse(middleware_a_s, false, false).unwrap(),
));
let Some(ConcreteType::Instance(sb_funcs)) = sb.concretize_export("my:iface@1.0") else {
panic!("server_b: expected Some(Instance)");
};
let Some(ConcreteType::Instance(ma_funcs)) = ma.concretize_export("my:iface@1.0") else {
panic!("middleware_a: expected Some(Instance)");
};
assert_eq!(sb_funcs.len(), 1, "server_b should export 1 function");
assert_eq!(ma_funcs.len(), 1, "middleware_a should export 1 function");
assert_eq!(sb_funcs[0].0, "handle");
assert_eq!(ma_funcs[0].0, "handle");
let sb_result = sb_funcs[0].1.result.as_ref();
let ma_result = ma_funcs[0].1.result.as_ref();
assert!(
matches!(sb_result, Some(ConcreteValType::Variant(_))),
"server_b result should be Variant, got {sb_result:?}"
);
assert!(
matches!(ma_result, Some(ConcreteValType::Variant(_))),
"middleware_a result should be Variant, got {ma_result:?}"
);
}
#[test]
fn server_and_middleware_same_func_type_explicit_type_decl() {
let server_b = bytes(
r#"(component
(component $shim
(type $ev (variant (case "a") (case "b" u32)))
(type $fn-type (func (param "x" u32) (result 0)))
(import "handle" (func $h (type $fn-type)))
(export "handle" (func $h))
)
(import "handle" (func $h
(param "x" u32) (result (variant (case "a") (case "b" u32)))))
(instance $si (instantiate $shim (with "handle" (func $h))))
(export "my:iface@1.0" (instance $si))
)"#,
);
let middleware_a = bytes(
r#"(component
(type $iface-type (instance
(type $var (variant (case "a") (case "b" u32)))
(type $fn (func (param "x" u32) (result 0)))
(export "handle" (func (type 1)))
))
(import "my:iface@1.0" (instance $iface (type $iface-type)))
(export "my:iface@1.0" (instance $iface))
)"#,
);
let server_b_s: &'static [u8] = Box::leak(server_b.into_boxed_slice());
let middleware_a_s: &'static [u8] = Box::leak(middleware_a.into_boxed_slice());
let sb = Box::leak(Box::new(
Component::parse(server_b_s, false, false).unwrap(),
));
let ma = Box::leak(Box::new(
Component::parse(middleware_a_s, false, false).unwrap(),
));
let Some(ConcreteType::Instance(sb_funcs)) = sb.concretize_export("my:iface@1.0") else {
panic!("server_b: expected Some(Instance)");
};
let Some(ConcreteType::Instance(ma_funcs)) = ma.concretize_export("my:iface@1.0") else {
panic!("middleware_a (explicit type decl): expected Some(Instance)");
};
assert_eq!(sb_funcs.len(), 1, "server_b should export 1 function");
assert_eq!(
ma_funcs.len(),
1,
"middleware_a (explicit type decl) should export 1 function"
);
assert_eq!(sb_funcs[0].0, "handle");
assert_eq!(ma_funcs[0].0, "handle");
let sb_result = sb_funcs[0].1.result.as_ref();
let ma_result = ma_funcs[0].1.result.as_ref();
assert!(
matches!(sb_result, Some(ConcreteValType::Variant(_))),
"server_b result should be Variant, got {sb_result:?}"
);
assert!(
matches!(ma_result, Some(ConcreteValType::Variant(_))),
"middleware_a (explicit type decl) result should be Variant, got {ma_result:?}"
);
}
#[test]
fn concretize_func_param_via_alias_to_imported_instance_type_direct() {
let b = bytes(
r#"(component
(import "types" (instance $types
(type (variant (case "a") (case "b" u32)))
(export "my-variant" (type (eq 0)))
))
(alias export $types "my-variant" (type $mv))
(type $fn-type (func (param "x" $mv)))
(import "handle" (func (type $fn-type)))
)"#,
);
let b_s: &'static [u8] = Box::leak(b.into_boxed_slice());
let comp = Box::leak(Box::new(Component::parse(b_s, false, false).unwrap()));
let Some(ConcreteType::Func(ft)) = comp.concretize_import("handle") else {
panic!("expected ConcreteType::Func for 'handle' import");
};
assert_eq!(ft.params.len(), 1, "expected 1 param");
assert!(
matches!(ft.params[0].1, ConcreteValType::Variant(_)),
"param type should be Variant (resolve_type_from_import_instance), got {:?}",
ft.params[0].1
);
let ConcreteValType::Variant(cases) = &ft.params[0].1 else {
unreachable!()
};
assert_eq!(cases.len(), 2);
assert_eq!(cases[0].0, "a");
assert!(cases[0].1.is_none());
assert_eq!(cases[1].0, "b");
assert!(matches!(
cases[1].1.as_deref(),
Some(ConcreteValType::Primitive(
wasmparser::PrimitiveValType::U32
))
));
}