use crate::ir::component::refs::{RefKind, ReferencedIndices};
use crate::ir::component::visitor::{
walk_structural, walk_topological, ComponentVisitor, ItemKind, ResolvedItem, VisitCtx,
};
use crate::ir::types::CustomSection;
use crate::{Component, Module};
use wasmparser::{
CanonicalFunction, ComponentAlias, ComponentExport, ComponentImport, ComponentInstance,
ComponentStartFunction, ComponentType, ComponentTypeDeclaration, CoreType, Instance,
InstanceTypeDeclaration, ModuleTypeDeclaration, SubType,
};
#[derive(Default)]
struct ParanoidVisitor {
resolved_count: usize,
}
impl ParanoidVisitor {
fn resolve_refs<T>(&mut self, cx: &VisitCtx, item: &T)
where
T: ReferencedIndices,
{
for r in item.referenced_indices() {
let _ = cx.resolve(&r.ref_);
self.resolved_count += 1;
}
}
}
impl<'a> ComponentVisitor<'a> for ParanoidVisitor {
fn visit_module(&mut self, cx: &VisitCtx<'a>, _: u32, item: &Module<'a>) {
self.resolve_refs(cx, item);
}
fn visit_comp_type(&mut self, cx: &VisitCtx<'a>, _id: u32, item: &ComponentType<'a>) {
self.resolve_refs(cx, item);
}
fn visit_comp_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_decl_idx: usize,
_id: u32,
_parent: &ComponentType<'a>,
item: &ComponentTypeDeclaration<'a>,
) {
self.resolve_refs(cx, item);
}
fn visit_inst_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_decl_idx: usize,
_id: u32,
_parent: &ComponentType<'a>,
decl: &InstanceTypeDeclaration<'a>,
) {
self.resolve_refs(cx, decl);
}
fn enter_component_type_inst(
&mut self,
_cx: &VisitCtx<'a>,
_id: u32,
_item: &ComponentType<'a>,
) {
}
fn exit_component_type_inst(
&mut self,
_cx: &VisitCtx<'a>,
_id: u32,
_item: &ComponentType<'a>,
) {
}
fn enter_component_type_comp(
&mut self,
_cx: &VisitCtx<'a>,
_id: u32,
_item: &ComponentType<'a>,
) {
}
fn exit_component_type_comp(
&mut self,
_cx: &VisitCtx<'a>,
_id: u32,
_item: &ComponentType<'a>,
) {
}
fn visit_comp_instance(&mut self, cx: &VisitCtx<'a>, _id: u32, item: &ComponentInstance<'a>) {
self.resolve_refs(cx, item);
}
fn visit_canon(
&mut self,
cx: &VisitCtx<'a>,
_kind: ItemKind,
_id: u32,
item: &CanonicalFunction,
) {
self.resolve_refs(cx, item);
}
fn visit_alias(
&mut self,
cx: &VisitCtx<'a>,
_kind: ItemKind,
_id: u32,
item: &ComponentAlias<'a>,
) {
self.resolve_refs(cx, item);
}
fn visit_comp_import(
&mut self,
cx: &VisitCtx<'a>,
_kind: ItemKind,
_id: u32,
item: &ComponentImport<'a>,
) {
self.resolve_refs(cx, item);
}
fn visit_comp_export(
&mut self,
cx: &VisitCtx<'a>,
_kind: ItemKind,
_id: u32,
item: &ComponentExport<'a>,
) {
self.resolve_refs(cx, item);
}
fn enter_core_rec_group(&mut self, cx: &VisitCtx<'a>, _count: usize, item: &CoreType<'a>) {
self.resolve_refs(cx, item);
}
fn visit_core_subtype(&mut self, cx: &VisitCtx<'a>, _id: u32, item: &SubType) {
self.resolve_refs(cx, item);
}
fn enter_core_module_type(&mut self, _cx: &VisitCtx<'a>, _id: u32, _item: &CoreType<'a>) {}
fn visit_module_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_decl_idx: usize,
_id: u32,
_parent: &CoreType<'a>,
decl: &ModuleTypeDeclaration<'a>,
) {
self.resolve_refs(cx, decl);
}
fn exit_core_module_type(&mut self, cx: &VisitCtx<'a>, _id: u32, item: &CoreType<'a>) {
self.resolve_refs(cx, item);
}
fn visit_core_instance(&mut self, cx: &VisitCtx<'a>, _id: u32, item: &Instance<'a>) {
self.resolve_refs(cx, item);
}
fn visit_custom_section(&mut self, cx: &VisitCtx<'a>, item: &CustomSection<'a>) {
self.resolve_refs(cx, item);
}
fn visit_start_section(&mut self, cx: &VisitCtx<'a>, item: &ComponentStartFunction) {
self.resolve_refs(cx, item);
}
}
fn run_on_bytes(bytes: Vec<u8>) -> usize {
let comp = Component::parse(&bytes, false, false).expect("component parse failed");
let mut structural = ParanoidVisitor::default();
walk_structural(&comp, &mut structural);
let mut topological = ParanoidVisitor::default();
walk_topological(&comp, &mut topological);
assert_eq!(
structural.resolved_count, topological.resolved_count,
"structural and topological walks resolved a different number of refs"
);
structural.resolved_count
}
fn run_paranoid(wat: &str) -> usize {
run_on_bytes(wat::parse_str(wat).expect("WAT parse failed"))
}
fn run_paranoid_file(path: &str) {
let bytes = wat::parse_file(path).unwrap_or_else(|e| panic!("{path}: {e}"));
run_on_bytes(bytes);
}
fn walk_wat<V: for<'a> ComponentVisitor<'a>>(wat: &str, visitor: &mut V) {
let bytes = wat::parse_str(wat).expect("WAT parse failed");
let comp = Component::parse(&bytes, false, false).expect("component parse failed");
walk_structural(&comp, visitor);
}
fn check_refs<'a>(
cx: &VisitCtx<'a>,
refs: Vec<RefKind>,
pred: impl Fn(&ResolvedItem<'a, 'a>) -> bool,
msg: &'static str,
) -> usize {
let mut n = 0;
for r in refs {
let resolved = cx.resolve(&r.ref_);
assert!(pred(&resolved), "{msg}: got {resolved:?}");
n += 1;
}
n
}
#[test]
fn test_scoped_resolution_no_panic_fixture_files() {
let fixtures = [
"./tests/test_inputs/handwritten/components/add.wat",
"./tests/test_inputs/handwritten/components/mul_mod.wat",
"./tests/test_inputs/dfinity/components/exports.wat",
"./tests/test_inputs/dfinity/components/func.wat",
"./tests/test_inputs/dfinity/components/func_locals.wat",
"./tests/test_inputs/spin/hello_world.wat",
];
for path in fixtures {
run_paranoid_file(path);
}
}
#[test]
fn test_scoped_resolution_instance_type_body() {
let count = run_paranoid(
r#"(component
(type (instance
(type $t u32)
(export "n" (type (eq $t)))
))
)"#,
);
assert_eq!(count, 1);
}
#[test]
fn test_scoped_resolution_instance_type_compound_ref() {
let count = run_paranoid(
r#"(component
(type (instance
(type $item u8)
(type $list (list $item))
(export "data" (type (eq $list)))
))
)"#,
);
assert_eq!(count, 2);
}
#[test]
fn test_scoped_resolution_component_type_body() {
let count = run_paranoid(
r#"(component
(type (component
(type $t u32)
(export "value" (type (eq $t)))
))
)"#,
);
assert_eq!(count, 1);
}
#[test]
fn test_scoped_resolution_core_module_type_body() {
let count = run_paranoid(
r#"(component
(core type (module
(type (func (param i32) (result i64)))
(import "env" "compute" (func (type 0)))
(export "result" (func (type 0)))
))
)"#,
);
assert_eq!(count, 2);
}
#[test]
fn test_scoped_resolution_outer_alias_in_instance_type() {
let count = run_paranoid(
r#"(component
(type $outer u32)
(type (instance
(alias outer 1 0 (type))
(export "n" (type (eq 0)))
))
)"#,
);
assert_eq!(count, 2);
}
#[test]
fn test_scoped_resolution_nesting_resets_between_scopes() {
assert_eq!(
run_paranoid(
r#"(component
(type (instance
(type $a u32)
(export "a" (type (eq $a)))
))
(type (instance
(type $b string)
(export "b" (type (eq $b)))
))
)"#,
),
2
)
}
#[test]
fn test_scoped_resolution_nested_instance_types() {
let count = run_paranoid(
r#"(component
(type (instance
(type $inner (instance
(type $t u8)
(export "v" (type (eq $t)))
))
(export "inner" (type (eq $inner)))
))
)"#,
);
assert_eq!(count, 2);
}
#[test]
fn test_resolve_result_instance_type_export_ref_is_comp_type() {
struct V {
checked: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn visit_inst_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &ComponentType<'a>,
decl: &InstanceTypeDeclaration<'a>,
) {
if !matches!(decl, InstanceTypeDeclaration::Export { .. }) {
return;
}
self.checked += check_refs(
cx,
decl.referenced_indices(),
|r| matches!(r, ResolvedItem::CompType(..)),
"export type ref in instance body should resolve to CompType",
);
}
}
let mut v = V { checked: 0 };
walk_wat(
r#"(component (type (instance (type $t u32) (export "n" (type (eq $t))))))"#,
&mut v,
);
assert_eq!(
v.checked, 1,
"expected exactly 1 export type ref to be resolved"
);
}
#[test]
fn test_resolve_result_component_type_export_ref_is_comp_type() {
struct V {
checked: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn visit_comp_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &ComponentType<'a>,
decl: &ComponentTypeDeclaration<'a>,
) {
if !matches!(decl, ComponentTypeDeclaration::Export { .. }) {
return;
}
self.checked += check_refs(
cx,
decl.referenced_indices(),
|r| matches!(r, ResolvedItem::CompType(..)),
"export type ref in component body should resolve to CompType",
);
}
}
let mut v = V { checked: 0 };
walk_wat(
r#"(component (type (component (type $t u32) (export "value" (type (eq $t))))))"#,
&mut v,
);
assert_eq!(
v.checked, 1,
"expected exactly 1 export type ref to be resolved"
);
}
#[test]
fn test_resolve_result_core_module_import_ref_is_module_ty_decl() {
struct V {
checked: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn visit_module_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &CoreType<'a>,
decl: &ModuleTypeDeclaration<'a>,
) {
if !matches!(decl, ModuleTypeDeclaration::Import(..)) {
return;
}
self.checked += check_refs(
cx,
decl.referenced_indices(),
|r| matches!(r, ResolvedItem::ModuleTyDecl(..)),
"import type ref in module type body should resolve to ModuleTyDecl",
);
}
}
let mut v = V { checked: 0 };
walk_wat(
r#"(component (core type (module
(type (func (param i32) (result i64)))
(import "env" "compute" (func (type 0)))
)))"#,
&mut v,
);
assert_eq!(
v.checked, 1,
"expected exactly 1 import type ref to be resolved"
);
}
#[test]
fn test_resolve_index_single_type_in_instance_scope() {
struct V {
checked: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn visit_inst_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &ComponentType<'a>,
decl: &InstanceTypeDeclaration<'a>,
) {
if !matches!(decl, InstanceTypeDeclaration::Export { .. }) {
return;
}
self.checked += check_refs(
cx,
decl.referenced_indices(),
|r| matches!(r, ResolvedItem::CompType(0, _)),
"sole type in instance scope must resolve to index 0",
);
}
}
let mut v = V { checked: 0 };
walk_wat(
r#"(component (type (instance (type $t u32) (export "n" (type (eq $t))))))"#,
&mut v,
);
assert_eq!(v.checked, 1);
}
#[test]
fn test_resolve_index_second_type_in_instance_scope() {
struct V {
checked: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn visit_inst_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &ComponentType<'a>,
decl: &InstanceTypeDeclaration<'a>,
) {
if !matches!(decl, InstanceTypeDeclaration::Export { .. }) {
return;
}
self.checked += check_refs(
cx,
decl.referenced_indices(),
|r| matches!(r, ResolvedItem::CompType(1, _)),
"export referencing second type must resolve to index 1",
);
}
}
let mut v = V { checked: 0 };
walk_wat(
r#"(component (type (instance
(type $a u8) (;; index 0 ;)
(type $b string) (;; index 1 ;)
(export "x" (type (eq $b)))
)))"#,
&mut v,
);
assert_eq!(v.checked, 1);
}
#[test]
fn test_resolve_index_outer_alias_to_second_outer_type() {
struct V {
checked: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn visit_inst_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &ComponentType<'a>,
decl: &InstanceTypeDeclaration<'a>,
) {
if !matches!(decl, InstanceTypeDeclaration::Alias(..)) {
return;
}
self.checked += check_refs(
cx,
decl.referenced_indices(),
|r| matches!(r, ResolvedItem::CompType(1, _)),
"alias to second outer type must resolve to index 1",
);
}
}
let mut v = V { checked: 0 };
walk_wat(
r#"(component
(type $first u8) (;; outer index 0 ;)
(type $second u32) (;; outer index 1 ;)
(type (instance
(alias outer 1 1 (type)) (;; depth=1, index=1 → $second ;)
(export "n" (type (eq 0)))
))
)"#,
&mut v,
);
assert_eq!(v.checked, 1);
}
#[test]
fn test_resolve_index_module_type_import_refs_second_func_type() {
struct V {
checked: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn visit_module_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &CoreType<'a>,
decl: &ModuleTypeDeclaration<'a>,
) {
if !matches!(decl, ModuleTypeDeclaration::Import(..)) {
return;
}
self.checked += check_refs(
cx,
decl.referenced_indices(),
|r| matches!(r, ResolvedItem::ModuleTyDecl(1, _)),
"import referencing second func type must resolve to index 1",
);
}
}
let mut v = V { checked: 0 };
walk_wat(
r#"(component (core type (module
(type (func (param i32))) (;; index 0 ;)
(type (func (param i32) (result i64))) (;; index 1 ;)
(import "env" "f" (func (type 1)))
)))"#,
&mut v,
);
assert_eq!(v.checked, 1);
}
#[test]
fn test_resolve_result_outer_alias_in_instance_type_resolves_to_comp_type() {
struct V {
checked: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn visit_inst_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &ComponentType<'a>,
decl: &InstanceTypeDeclaration<'a>,
) {
if !matches!(decl, InstanceTypeDeclaration::Alias(..)) {
return;
}
self.checked += check_refs(cx, decl.referenced_indices(),
|r| matches!(r, ResolvedItem::CompType(..)),
"outer alias in instance type should resolve to CompType in the outer component scope");
}
}
let mut v = V { checked: 0 };
walk_wat(
r#"(component
(type $outer u32)
(type (instance (alias outer 1 0 (type)) (export "n" (type (eq 0)))))
)"#,
&mut v,
);
assert_eq!(
v.checked, 1,
"expected exactly 1 outer alias ref to be resolved"
);
}
#[test]
fn test_scoped_resolution_comp_type_contains_nested_instance_type() {
assert_eq!(
run_paranoid(
r#"(component
(type (component
(type $ct u32)
(type (instance
(type $item u8)
(export "x" (type (eq $item)))
))
(export "inner" (type (eq 1)))
))
)"#,
),
2,
);
}
#[test]
fn test_scoped_resolution_comp_type_contains_nested_comp_type() {
assert_eq!(
run_paranoid(
r#"(component
(type (component
(type $ct u32)
(type (component
(type $inner u32)
(export "n" (type (eq $inner)))
))
(export "nested" (type (eq 1)))
))
)"#,
),
2,
);
}
#[test]
fn test_scoped_resolution_comp_type_contains_core_module_type() {
assert_eq!(
run_paranoid(
r#"(component
(type (component
(core type (module
(type (func (param i32) (result i64)))
(import "env" "f" (func (type 0)))
(export "g" (func (type 0)))
))
))
)"#,
),
2,
);
}
#[test]
fn test_scoped_resolution_outer_alias_in_comp_type_body() {
assert_eq!(
run_paranoid(
r#"(component
(type $outer u32)
(type (component
(alias outer 1 0 (type))
(export "n" (type (eq 0)))
))
)"#,
),
2,
);
}
#[test]
fn test_scoped_resolution_outer_depth1_from_inst_inside_comp_type() {
assert_eq!(
run_paranoid(
r#"(component
(type (component
(type $ct u32)
(type (instance
(alias outer 1 0 (type))
(export "n" (type (eq 0)))
))
))
)"#,
),
2,
);
}
#[test]
fn test_scoped_resolution_outer_depth2_from_inst_inside_comp_type() {
assert_eq!(
run_paranoid(
r#"(component
(type $outer u32)
(type (component
(type $ct u32)
(type (instance
(alias outer 2 0 (type))
(export "n" (type (eq 0)))
))
))
)"#,
),
2,
);
}
#[test]
fn test_scoped_resolution_nesting_resets_comp_type_then_inst_type() {
assert_eq!(
run_paranoid(
r#"(component
(type (component
(type $a u32)
(export "a" (type (eq $a)))
))
(type (instance
(type $b string)
(export "b" (type (eq $b)))
))
)"#,
),
2,
);
}
#[test]
fn test_resolve_result_outer_alias_in_comp_type_body_is_comp_type() {
struct V {
checked: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn visit_comp_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &ComponentType<'a>,
decl: &ComponentTypeDeclaration<'a>,
) {
if !matches!(decl, ComponentTypeDeclaration::Alias(..)) {
return;
}
self.checked += check_refs(
cx,
decl.referenced_indices(),
|r| matches!(r, ResolvedItem::CompType(..)),
"outer alias in comp-type body must resolve to CompType, not Import",
);
}
}
let mut v = V { checked: 0 };
walk_wat(
r#"(component
(import "ty" (type (sub resource))) (;; CompType index 0: Import — wrong index lands here ;)
(type $target u32) (;; CompType index 1: Main → correct target ;)
(type (component
(alias outer 1 1 (type)) (;; depth=1, index=1 → $target ;)
(export "n" (type (eq 0)))
))
)"#,
&mut v,
);
assert_eq!(v.checked, 1, "expected exactly 1 outer alias ref resolved");
}
#[test]
fn test_resolve_result_outer_depth2_from_doubly_nested_is_comp_type() {
struct V {
checked: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn visit_inst_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &ComponentType<'a>,
decl: &InstanceTypeDeclaration<'a>,
) {
if !matches!(decl, InstanceTypeDeclaration::Alias(..)) {
return;
}
self.checked += check_refs(
cx,
decl.referenced_indices(),
|r| matches!(r, ResolvedItem::CompType(..)),
"depth-2 outer alias must resolve to CompType, not Import",
);
}
}
let mut v = V { checked: 0 };
walk_wat(
r#"(component
(import "outer-ty" (type (sub resource))) (;; component CompType index 0: Import (wrong index) ;)
(type $outer u32) (;; component CompType index 1: CompType ← target ;)
(type (component
(import "inner-ty0" (type (sub resource))) (;; comp-type CompType index 0: Import (wrong index) ;)
(import "inner-ty1" (type (sub resource))) (;; comp-type CompType index 1: Import (wrong depth) ;)
(type $ct u32)
(type (instance
(alias outer 2 1 (type)) (;; depth=2, index=1 → $outer → CompType ;)
(export "n" (type (eq 0)))
))
))
)"#,
&mut v,
);
assert_eq!(
v.checked, 1,
"expected exactly 1 depth-2 outer alias ref resolved"
);
}
#[test]
fn test_resolve_index_outer_in_comp_type_body_to_second_outer_type() {
struct V {
checked: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn visit_comp_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &ComponentType<'a>,
decl: &ComponentTypeDeclaration<'a>,
) {
if !matches!(decl, ComponentTypeDeclaration::Alias(..)) {
return;
}
self.checked += check_refs(
cx,
decl.referenced_indices(),
|r| matches!(r, ResolvedItem::CompType(1, _)),
"outer alias to second outer type must resolve to index 1",
);
}
}
let mut v = V { checked: 0 };
walk_wat(
r#"(component
(type $first u8) (;; outer index 0 ;)
(type $second u32) (;; outer index 1 ;)
(type (component
(alias outer 1 1 (type)) (;; depth=1, index=1 → $second ;)
(export "n" (type (eq 0)))
))
)"#,
&mut v,
);
assert_eq!(v.checked, 1);
}
#[test]
fn test_resolve_index_outer_depth2_from_doubly_nested_to_second_outer() {
struct V {
checked: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn visit_inst_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &ComponentType<'a>,
decl: &InstanceTypeDeclaration<'a>,
) {
if !matches!(decl, InstanceTypeDeclaration::Alias(..)) {
return;
}
self.checked += check_refs(
cx,
decl.referenced_indices(),
|r| matches!(r, ResolvedItem::CompType(1, _)),
"depth-2 outer alias to second outer type must resolve to index 1",
);
}
}
let mut v = V { checked: 0 };
walk_wat(
r#"(component
(type $first u8) (;; outer index 0 ;)
(type $second u32) (;; outer index 1 ;)
(type (component
(type $ct u32)
(type (instance
(alias outer 2 1 (type)) (;; depth=2, index=1 → $second ;)
(export "n" (type (eq 0)))
))
))
)"#,
&mut v,
);
assert_eq!(v.checked, 1);
}
#[test]
fn test_type_body_stack_inst_nested_in_inst_inner_resolves_independently() {
assert_eq!(
run_paranoid(
r#"(component
(type (instance
(type $outer_t u32) (;; outer body, index 0 ;)
(type (instance
(type $inner_t u8) (;; inner body, index 0 ;)
(export "iv" (type (eq $inner_t)))
))
(export "ov" (type (eq $outer_t)))
))
)"#,
),
2,
);
}
#[test]
fn test_type_body_stack_outer_scope_resolves_after_inner_exits() {
struct V {
inst_export_count: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn visit_inst_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &ComponentType<'a>,
decl: &InstanceTypeDeclaration<'a>,
) {
if !matches!(decl, InstanceTypeDeclaration::Export { .. }) {
return;
}
for r in decl.referenced_indices() {
let resolved = cx.resolve(&r.ref_);
assert!(
matches!(resolved, ResolvedItem::CompType(..)),
"export ref must resolve to CompType, got {resolved:?}"
);
self.inst_export_count += 1;
}
}
}
let mut v = V {
inst_export_count: 0,
};
walk_wat(
r#"(component
(type (instance
(type $a u32) (;; outer body index 0 ;)
(type (instance
(type $b u8) (;; inner body index 0 ;)
(export "b" (type (eq $b)))
))
(export "a" (type (eq $a))) (;; must use outer frame, not inner ;)
))
)"#,
&mut v,
);
assert_eq!(v.inst_export_count, 2);
}
#[test]
fn test_type_body_stack_sibling_scopes_isolated() {
assert_eq!(
run_paranoid(
r#"(component
(type (instance
(type $x u8)
(export "x" (type (eq $x)))
))
(type (instance
(type $y string)
(export "y" (type (eq $y)))
))
)"#,
),
2,
);
}
#[test]
fn test_type_body_stack_triple_depth() {
assert_eq!(
run_paranoid(
r#"(component
(type (component
(type (instance
(type (instance
(type $leaf u8)
(export "leaf" (type (eq $leaf)))
))
(type $mid u32)
(export "mid" (type (eq $mid)))
))
))
)"#,
),
2,
);
}
#[test]
fn test_type_body_stack_module_type_inside_comp_type() {
assert_eq!(
run_paranoid(
r#"(component
(type (component
(core type (module
(type (func (param i32)))
(import "m" "f" (func (type 0)))
))
))
)"#,
),
1,
);
}
#[test]
fn test_type_body_stack_inst_scope_restored_after_module_type_exit() {
assert_eq!(
run_paranoid(
r#"(component
(type (instance
(type $t u32) (;; inst body index 0 ;)
(core type (module
(type (func (param i32)))
(import "m" "f" (func (type 0)))
))
(export "t" (type (eq $t))) (;; must resolve against inst frame ;)
))
)"#,
),
2,
);
}
#[test]
fn test_type_body_stack_triple_depth_exact_index() {
struct V {
inner_checked: usize,
middle_checked: usize,
depth: usize,
}
impl<'a> ComponentVisitor<'a> for V {
fn enter_component_type_inst(&mut self, _: &VisitCtx<'a>, _: u32, _: &ComponentType<'a>) {
self.depth += 1;
}
fn exit_component_type_inst(&mut self, _: &VisitCtx<'a>, _: u32, _: &ComponentType<'a>) {
self.depth -= 1;
}
fn visit_inst_type_decl(
&mut self,
cx: &VisitCtx<'a>,
_: usize,
_: u32,
_: &ComponentType<'a>,
decl: &InstanceTypeDeclaration<'a>,
) {
if !matches!(decl, InstanceTypeDeclaration::Export { .. }) {
return;
}
if self.depth >= 2 {
self.inner_checked += check_refs(
cx,
decl.referenced_indices(),
|r| matches!(r, ResolvedItem::CompType(0, _)),
"inner export ref must resolve to CompType(0, _)",
);
} else {
self.middle_checked += check_refs(
cx,
decl.referenced_indices(),
|r| matches!(r, ResolvedItem::CompType(1, _)),
"middle export ref must resolve to CompType(1, _)",
);
}
}
}
let mut v = V {
inner_checked: 0,
middle_checked: 0,
depth: 0,
};
walk_wat(
r#"(component
(type (component
(type (instance
(type (instance
(type $leaf u8)
(export "leaf" (type (eq $leaf)))
))
(type $mid u32)
(export "mid" (type (eq $mid)))
))
))
)"#,
&mut v,
);
assert_eq!(
v.inner_checked, 1,
"inner body export should have been checked once"
);
assert_eq!(
v.middle_checked, 1,
"middle body export should have been checked once"
);
}