From 76351db0679549fbae14021f695a0c9b97ed7b0e Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 26 Aug 2026 09:43:15 -0600 Subject: [PATCH] add support for the WIT `map` type Fixes #231 --- src/lib.rs | 1 + src/summary.rs | 25 ++++++++++++++++++++++++- src/test.rs | 1 + src/test/echoes.rs | 32 ++++++++++++++++++++++++++++++++ src/test/wit/echoes.wit | 1 + 5 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 66164909..6ccb33df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -813,6 +813,7 @@ impl ComponentGenerator<'_> { let mut config = Config::new(); config.wasm_component_model(true); config.wasm_component_model_async(true); + config.wasm_component_model_map(true); let engine = Engine::new(&config)?; diff --git a/src/summary.rs b/src/summary.rs index d843d90e..5fbe884c 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -308,6 +308,10 @@ impl<'a> Summary<'a> { TypeDefKind::List(ty) => { self.visit_type(*ty, world); } + TypeDefKind::Map(k, v) => { + self.visit_type(*k, world); + self.visit_type(*v, world); + } TypeDefKind::Type(ty) => { // When visiting a type alias, we must use the state // already stored for any `use`d resources rather than @@ -1127,6 +1131,10 @@ impl<'a> Summary<'a> { TypeDefKind::List(ty) => { self.sort(*ty, sorted, visited); } + TypeDefKind::Map(k, v) => { + self.sort(*k, sorted, visited); + self.sort(*v, sorted, visited); + } TypeDefKind::Type(ty) => { self.sort(*ty, sorted, visited); } @@ -1759,6 +1767,7 @@ class {camel}(Protocol): } TypeDefKind::Tuple(_) | TypeDefKind::List(_) + | TypeDefKind::Map(_, _) | TypeDefKind::Option(_) | TypeDefKind::Result(_) | TypeDefKind::Handle(_) => (None, Vec::new()), @@ -2089,7 +2098,7 @@ def {snake}_future(default: Callable[[], {camel}]) -> tuple[FutureWriter[{camel} } let python_imports = - "from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable + "from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Mapping, Any, Self, Callable from types import TracebackType from enum import Flag, Enum, auto from dataclasses import dataclass @@ -2356,6 +2365,10 @@ from componentize_py_types import Result, Ok, Err, Some TypeDefKind::Option(ty) | TypeDefKind::List(ty) | TypeDefKind::Type(ty) => { self.has_imported_and_exported_resource(*ty) } + TypeDefKind::Map(k, v) => { + self.has_imported_and_exported_resource(*k) + || self.has_imported_and_exported_resource(*v) + } TypeDefKind::Resource => { let empty = &ResourceInfo::default(); let info = self.resource_info.get(&id).unwrap_or(empty); @@ -2465,6 +2478,13 @@ impl<'a> TypeNames<'a> { format!("List[{}]", self.type_name(*ty, seen, resource)) } } + TypeDefKind::Map(k, v) => { + format!( + "Mapping[{}, {}]", + self.type_name(*k, seen, resource), + self.type_name(*v, seen, resource) + ) + } TypeDefKind::Tuple(tuple) => { let types = tuple .types @@ -2565,6 +2585,9 @@ impl<'a> TypeNames<'a> { TypeDefKind::List(ty) => { format!("list_{}", self.mangle_name(*ty)) } + TypeDefKind::Map(k, v) => { + format!("map_{}_{}", self.mangle_name(*k), self.mangle_name(*v)) + } TypeDefKind::Tuple(tuple) => { let types = tuple .types diff --git a/src/test.rs b/src/test.rs index 9e6f8e72..7e90aff7 100644 --- a/src/test.rs +++ b/src/test.rs @@ -38,6 +38,7 @@ static ENGINE: Lazy = Lazy::new(|| { let mut config = Config::new(); config.wasm_component_model(true); config.wasm_component_model_async(true); + config.wasm_component_model_map(true); Engine::new(&config).unwrap() }); diff --git a/src/test/echoes.rs b/src/test/echoes.rs index c7095f53..a651dc74 100644 --- a/src/test/echoes.rs +++ b/src/test/echoes.rs @@ -3,6 +3,7 @@ use { anyhow::Result, once_cell::sync::Lazy, proptest::strategy::{Just, Strategy}, + std::collections::HashMap, wasmtime::{ Store, component::{HasSelf, InstancePre, Linker}, @@ -136,6 +137,13 @@ impl componentize_py::test::echoes::Host for Ctx { Ok(v) } + async fn echo_map_u32_string( + &mut self, + v: HashMap, + ) -> wasmtime::Result> { + Ok(v) + } + async fn echo_option_u8(&mut self, v: Option) -> wasmtime::Result> { Ok(v) } @@ -307,6 +315,9 @@ class Echoes(exports.Echoes): def echo_list_list_list_u8(self, v): return echoes.echo_list_list_list_u8(v) + def echo_map_u32_string(self, v): + return echoes.echo_map_u32_string(v) + def echo_option_u8(self, v): return echoes.echo_option_u8(v) @@ -742,6 +753,27 @@ fn list_f64s() -> Result<()> { ) } +#[test] +fn map_u32_strings() -> Result<()> { + TESTER.all_eq( + &proptest::collection::hash_map( + proptest::num::u32::ANY, + &proptest::string::string_regex(".*")?, + 0..MAX_SIZE, + ), + |v, instance, store, runtime| { + Ok(runtime.block_on( + instance + .componentize_py_test_echoes() + .call_echo_map_u32_string( + store, + v.iter().map(|(k, v)| (*k, v.as_str())).collect(), + ), + )?) + }, + ) +} + #[test] fn many() -> Result<()> { TESTER.all_eq( diff --git a/src/test/wit/echoes.wit b/src/test/wit/echoes.wit index 5ce0aa5a..19dee25f 100644 --- a/src/test/wit/echoes.wit +++ b/src/test/wit/echoes.wit @@ -30,6 +30,7 @@ interface echoes { echo-list-string: func(v: list) -> list; echo-list-list-u8: func(v: list>) -> list>; echo-list-list-list-u8: func(v: list>>) -> list>>; + echo-map-u32-string: func(v: map) -> map; echo-option-u8: func(v: option) -> option; echo-option-option-u8: func(v: option>) -> option>; echo-many: func(v1: bool, v2: u8, v3: u16, v4: u32, v5: u64, v6: s8, v7: s16, v8: s32, v9: s64, v10: f32, v11: f64, v12: char, v13: string, v14: list, v15: list, v16: list) -> tuple, list, list>;