michaelficarra opened issue #12200:
Test Case
#[test] fn test_arrayref() -> Result<(), Box<dyn std::error::Error>> { let module_a = wat::parse_str(r#" (module (func (export "len") (param $a arrayref) (result i32) (array.len (local.get $a)) ) ) "#)?; let module_b = wat::parse_str(r#" (module (type $concrete_array (array (mut i32))) (import "module_a" "len" (func $len (param (ref null $concrete_array)) (result i32))) (func (export "use_len") (result i32) (call $len (array.new_fixed $concrete_array 3 (i32.const 1) (i32.const 2) (i32.const 3) ) ) ) ) "#)?; let mut config = wasmtime::Config::new(); config.wasm_gc(true); config.wasm_function_references(true); let engine = wasmtime::Engine::new(&config)?; let mut store = wasmtime::Store::new(&engine, ()); let module_a = wasmtime::Module::from_binary(&engine, &module_a)?; let module_b = wasmtime::Module::from_binary(&engine, &module_b)?; let empty_imports = vec![]; let instance_a = wasmtime::Instance::new(&mut store, &module_a, &empty_imports)?; let exports_a = instance_a.exports(&mut store).collect::<Vec<_>>(); let mut positional_imports = vec![]; for import in module_b.imports() { let export = exports_a.iter().find(|e| e.name() == import.name()).unwrap(); positional_imports.push(export.clone().into_extern()); } let instance_b = wasmtime::Instance::new(&mut store, &module_b, &positional_imports)?; let use_len = instance_b.get_typed_func::<(), i32>(&mut store, "use_len")?; let result = use_len.call(&mut store, ())?; assert_eq!(result, 3); Ok(()) }or, if you prefer to use
Linkerinstead of manually wiring up the imports, this should be equivalent:#[test] fn test_arrayref() -> Result<(), Box<dyn std::error::Error>> { let module_a = wat::parse_str(r#" (module (func (export "len") (param $a arrayref) (result i32) (array.len (local.get $a)) ) ) "#)?; let module_b = wat::parse_str(r#" (module (type $concrete_array (array (mut i32))) (import "module_a" "len" (func $len (param (ref null $concrete_array)) (result i32))) (func (export "use_len") (result i32) (call $len (array.new_fixed $concrete_array 3 (i32.const 1) (i32.const 2) (i32.const 3) ) ) ) ) "#)?; let mut config = wasmtime::Config::new(); config.wasm_gc(true); config.wasm_function_references(true); let engine = wasmtime::Engine::new(&config)?; let mut store = wasmtime::Store::new(&engine, ()); let module_a = wasmtime::Module::from_binary(&engine, &module_a)?; let module_b = wasmtime::Module::from_binary(&engine, &module_b)?; let mut linker = wasmtime::Linker::new(&engine); linker.module(&mut store, "module_a", &module_a)?; linker.module(&mut store, "module_b", &module_b)?; let instance_b = linker.instantiate(&mut store, &module_b)?; let use_len = instance_b.get_typed_func::<(), i32>(&mut store, "use_len")?; let result = use_len.call(&mut store, ())?; assert_eq!(result, 3); Ok(()) }Steps to Reproduce
- Run the test.
Expected Results
I expect the wasm to validate and run. I expect the Rust assertion
assert_eq!(result, 3);to then pass.Actual Results
---- test::manual::test_arrayref stdout ---- Error: incompatible import type for `module_a::len` Caused by: types incompatible: expected type `(func (param (ref null array (engine 2))) (result i32))`, found type `(func (param (ref null array)) (result i32))`If you remove the import and replace the function call (
call $len) with thearray.leninstruction, it works.Versions and Environment
Wasmtime version or commit: 40.0.0
Operating system:
Darwin L6QHWH6D3R 24.5.0 Darwin Kernel Version 24.5.0: Tue Apr 22 19:54:29 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T6030 arm64 arm DarwinArchitecture: arm64
Extra Info
michaelficarra added the bug label to Issue #12200.
alexcrichton added the wasm-proposal:gc label to Issue #12200.
alexcrichton commented on issue #12200:
I believe this is expected where subtyping doesn't apply at the function-level like this. @fitzgen would know for sure though
fitzgen closed issue #12200:
Test Case
#[test] fn test_arrayref() -> Result<(), Box<dyn std::error::Error>> { let module_a = wat::parse_str(r#" (module (func (export "len") (param $a arrayref) (result i32) (array.len (local.get $a)) ) ) "#)?; let module_b = wat::parse_str(r#" (module (type $concrete_array (array (mut i32))) (import "module_a" "len" (func $len (param (ref null $concrete_array)) (result i32))) (func (export "use_len") (result i32) (call $len (array.new_fixed $concrete_array 3 (i32.const 1) (i32.const 2) (i32.const 3) ) ) ) ) "#)?; let mut config = wasmtime::Config::new(); config.wasm_gc(true); config.wasm_function_references(true); let engine = wasmtime::Engine::new(&config)?; let mut store = wasmtime::Store::new(&engine, ()); let module_a = wasmtime::Module::from_binary(&engine, &module_a)?; let module_b = wasmtime::Module::from_binary(&engine, &module_b)?; let empty_imports = vec![]; let instance_a = wasmtime::Instance::new(&mut store, &module_a, &empty_imports)?; let exports_a = instance_a.exports(&mut store).collect::<Vec<_>>(); let mut positional_imports = vec![]; for import in module_b.imports() { let export = exports_a.iter().find(|e| e.name() == import.name()).unwrap(); positional_imports.push(export.clone().into_extern()); } let instance_b = wasmtime::Instance::new(&mut store, &module_b, &positional_imports)?; let use_len = instance_b.get_typed_func::<(), i32>(&mut store, "use_len")?; let result = use_len.call(&mut store, ())?; assert_eq!(result, 3); Ok(()) }or, if you prefer to use
Linkerinstead of manually wiring up the imports, this should be equivalent:#[test] fn test_arrayref() -> Result<(), Box<dyn std::error::Error>> { let module_a = wat::parse_str(r#" (module (func (export "len") (param $a arrayref) (result i32) (array.len (local.get $a)) ) ) "#)?; let module_b = wat::parse_str(r#" (module (type $concrete_array (array (mut i32))) (import "module_a" "len" (func $len (param (ref null $concrete_array)) (result i32))) (func (export "use_len") (result i32) (call $len (array.new_fixed $concrete_array 3 (i32.const 1) (i32.const 2) (i32.const 3) ) ) ) ) "#)?; let mut config = wasmtime::Config::new(); config.wasm_gc(true); config.wasm_function_references(true); let engine = wasmtime::Engine::new(&config)?; let mut store = wasmtime::Store::new(&engine, ()); let module_a = wasmtime::Module::from_binary(&engine, &module_a)?; let module_b = wasmtime::Module::from_binary(&engine, &module_b)?; let mut linker = wasmtime::Linker::new(&engine); linker.module(&mut store, "module_a", &module_a)?; linker.module(&mut store, "module_b", &module_b)?; let instance_b = linker.instantiate(&mut store, &module_b)?; let use_len = instance_b.get_typed_func::<(), i32>(&mut store, "use_len")?; let result = use_len.call(&mut store, ())?; assert_eq!(result, 3); Ok(()) }Steps to Reproduce
- Run the test.
Expected Results
I expect the wasm to validate and run. I expect the Rust assertion
assert_eq!(result, 3);to then pass.Actual Results
---- test::manual::test_arrayref stdout ---- Error: incompatible import type for `module_a::len` Caused by: types incompatible: expected type `(func (param (ref null array (engine 2))) (result i32))`, found type `(func (param (ref null array)) (result i32))`If you remove the import and replace the function call (
call $len) with thearray.leninstruction, it works.Versions and Environment
Wasmtime version or commit: 40.0.0
Operating system:
Darwin L6QHWH6D3R 24.5.0 Darwin Kernel Version 24.5.0: Tue Apr 22 19:54:29 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T6030 arm64 arm DarwinArchitecture: arm64
Extra Info
fitzgen commented on issue #12200:
Yes, this is expected because the imported
module_a::lentype is not exactly same as nor an explicitly-declared subtype of the exportedlenfunction's type.You could adjust the test case to make linking succeed by either:
Changing the import to this:
wat (import "module_a" "len" (func $len (param arrayref) (result i32)))Changing the
lenfunction's definition to this:
wat (type $concrete_array (array (mut i32))) (func (export "len") (param $a (ref null $concrete_array)) (result i32) (array.len (local.get $a)) )Declaring the function as non-final[^short-hand] so that it is subtype-able:
wat ;; module_a (type $len_ty (sub (func (param arrayref) (result i32)))) (func (export "len") (type $len_ty) (array.len (local.get 0)) )and then when importing it, you can still import the function as one which takes the concrete type, but you have to declare the function types and the subtyping relationship:
wat ;; module_b (type $sup_len_ty (sub (func (param arrayref) (result i32)))) (type $sub_len_ty (sub $sup_len_ty (func (param (ref null $concrete_array)) (result i32))))) (import "module_a" "len" (func (type $sub_len_ty)))[^short-hand]: When you do
(type (func ...)), which is what declaring a function's type inline is short-hand for, then that in turn is short hand for(sub final (func ...)).Closing this as working as expected, but feel free to ask more follow up questions / clarifications or open new issues if you run into anything else!
Last updated: Jan 09 2026 at 13:15 UTC