diff --git a/crates/wasm-compose/src/encoding.rs b/crates/wasm-compose/src/encoding.rs index 5022f7f388..0e9c45db45 100644 --- a/crates/wasm-compose/src/encoding.rs +++ b/crates/wasm-compose/src/encoding.rs @@ -650,6 +650,7 @@ impl<'a> TypeEncoder<'a> { ComponentDefinedType::Record(r) => self.record(state, r), ComponentDefinedType::Variant(v) => self.variant(state, v), ComponentDefinedType::List(ty) => self.list(state, *ty), + ComponentDefinedType::Map(key, value) => self.map(state, *key, *value), ComponentDefinedType::FixedSizeList(ty, elements) => { self.fixed_size_list(state, *ty, *elements) } @@ -713,6 +714,19 @@ impl<'a> TypeEncoder<'a> { index } + fn map( + &self, + state: &mut TypeState<'a>, + key: ct::ComponentValType, + value: ct::ComponentValType, + ) -> u32 { + let key = self.component_val_type(state, key); + let value = self.component_val_type(state, value); + let index = state.cur.encodable.type_count(); + state.cur.encodable.ty().defined_type().map(key, value); + index + } + fn fixed_size_list( &self, state: &mut TypeState<'a>, @@ -1253,6 +1267,10 @@ impl DependencyRegistrar<'_, '_> { ComponentDefinedType::List(t) | ComponentDefinedType::FixedSizeList(t, _) | ComponentDefinedType::Option(t) => self.val_type(*t), + ComponentDefinedType::Map(k, v) => { + self.val_type(*k); + self.val_type(*v); + } ComponentDefinedType::Own(r) | ComponentDefinedType::Borrow(r) => { self.ty(ComponentAnyTypeId::Resource(*r)) } diff --git a/crates/wasm-encoder/src/component/types.rs b/crates/wasm-encoder/src/component/types.rs index f9887e0e7e..d44f812aa4 100644 --- a/crates/wasm-encoder/src/component/types.rs +++ b/crates/wasm-encoder/src/component/types.rs @@ -620,6 +620,13 @@ impl ComponentDefinedTypeEncoder<'_> { ty.into().encode(self.0); } + /// Define a map type. + pub fn map(self, key: impl Into, value: impl Into) { + self.0.push(0x63); + key.into().encode(self.0); + value.into().encode(self.0); + } + /// Define a fixed size list type. pub fn fixed_size_list(self, ty: impl Into, elements: u32) { self.0.push(0x67); diff --git a/crates/wasm-encoder/src/reencode/component.rs b/crates/wasm-encoder/src/reencode/component.rs index e6d5afe5b6..d8a6fc9fa9 100644 --- a/crates/wasm-encoder/src/reencode/component.rs +++ b/crates/wasm-encoder/src/reencode/component.rs @@ -769,6 +769,12 @@ pub mod component_utils { wasmparser::ComponentDefinedType::List(t) => { defined.list(reencoder.component_val_type(t)); } + wasmparser::ComponentDefinedType::Map(k, v) => { + defined.map( + reencoder.component_val_type(k), + reencoder.component_val_type(v), + ); + } wasmparser::ComponentDefinedType::FixedSizeList(t, elements) => { defined.fixed_size_list(reencoder.component_val_type(t), elements); } diff --git a/crates/wasmparser/src/readers/component/types.rs b/crates/wasmparser/src/readers/component/types.rs index b87c26da92..629b76c873 100644 --- a/crates/wasmparser/src/readers/component/types.rs +++ b/crates/wasmparser/src/readers/component/types.rs @@ -446,6 +446,8 @@ pub enum ComponentDefinedType<'a> { Variant(Box<[VariantCase<'a>]>), /// The type is a list of the given value type. List(ComponentValType), + /// The type is a map of the given key and value types. + Map(ComponentValType, ComponentValType), /// The type is a fixed size list of the given value type. FixedSizeList(ComponentValType, u32), /// The type is a tuple of the given value types. @@ -487,6 +489,7 @@ impl<'a> ComponentDefinedType<'a> { .collect::>()?, ), 0x70 => ComponentDefinedType::List(reader.read()?), + 0x63 => ComponentDefinedType::Map(reader.read()?, reader.read()?), 0x6f => ComponentDefinedType::Tuple( reader .read_iter(MAX_WASM_TUPLE_TYPES, "tuple types")? diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index 9dfe0a0ef1..2cbc38ddc0 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -951,6 +951,9 @@ impl ComponentState { ComponentDefinedType::List(ty) | ComponentDefinedType::FixedSizeList(ty, _) | ComponentDefinedType::Option(ty) => types.type_named_valtype(ty, set), + ComponentDefinedType::Map(k, v) => { + types.type_named_valtype(k, set) && types.type_named_valtype(v, set) + } // The resource referred to by own/borrow must be named. ComponentDefinedType::Own(id) | ComponentDefinedType::Borrow(id) => { @@ -3931,6 +3934,10 @@ impl ComponentState { crate::ComponentDefinedType::List(ty) => Ok(ComponentDefinedType::List( self.create_component_val_type(ty, offset)?, )), + crate::ComponentDefinedType::Map(key, value) => Ok(ComponentDefinedType::Map( + self.create_component_val_type(key, offset)?, + self.create_component_val_type(value, offset)?, + )), crate::ComponentDefinedType::FixedSizeList(ty, elements) => { if !self.features.cm_fixed_size_list() { bail!( diff --git a/crates/wasmparser/src/validator/component_types.rs b/crates/wasmparser/src/validator/component_types.rs index c736a7038f..3d4913b145 100644 --- a/crates/wasmparser/src/validator/component_types.rs +++ b/crates/wasmparser/src/validator/component_types.rs @@ -1468,6 +1468,8 @@ pub enum ComponentDefinedType { Variant(VariantType), /// The type is a list. List(ComponentValType), + /// The type is a map. + Map(ComponentValType, ComponentValType), /// The type is a fixed size list. FixedSizeList(ComponentValType, u32), /// The type is a tuple. @@ -1511,6 +1513,11 @@ impl TypeData for ComponentDefinedType { Self::Variant(v) => v.info, Self::Tuple(t) => t.info, Self::List(ty) | Self::FixedSizeList(ty, _) | Self::Option(ty) => ty.info(types), + Self::Map(k, v) => { + let mut info = k.info(types); + info.combine(v.info(types), 0).unwrap(); + info + } Self::Result { ok, err } => { let default = TypeInfo::new(); let mut info = ok.map(|ty| ty.type_info(types)).unwrap_or(default); @@ -1531,7 +1538,7 @@ impl ComponentDefinedType { .cases .values() .any(|case| case.ty.map(|ty| ty.contains_ptr(types)).unwrap_or(false)), - Self::List(_) => true, + Self::List(_) | Self::Map(_, _) => true, Self::Tuple(t) => t.types.iter().any(|ty| ty.contains_ptr(types)), Self::Flags(_) | Self::Enum(_) @@ -1559,7 +1566,7 @@ impl ComponentDefinedType { types, lowered_types, ), - Self::List(_) => { + Self::List(_) | Self::Map(_, _) => { lowered_types.try_push(ValType::I32) && lowered_types.try_push(ValType::I32) } Self::FixedSizeList(ty, length) => { @@ -1639,6 +1646,7 @@ impl ComponentDefinedType { ComponentDefinedType::Flags(_) => "flags", ComponentDefinedType::Option(_) => "option", ComponentDefinedType::List(_) => "list", + ComponentDefinedType::Map(_, _) => "map", ComponentDefinedType::FixedSizeList(_, _) => "fixed size list", ComponentDefinedType::Result { .. } => "result", ComponentDefinedType::Own(_) => "own", @@ -1683,6 +1691,11 @@ impl ComponentDefinedType { ty.lower_gc(types, abi, options, offset, array_ty.0.element_type.into()) } + ComponentDefinedType::Map(_, _) => bail!( + offset, + "GC lowering for component `map` type is not yet implemented" + ), + ComponentDefinedType::Tuple(ty) => ty.lower_gc(types, abi, options, offset, core), ComponentDefinedType::Flags(flags) => { @@ -2515,6 +2528,10 @@ impl TypeAlloc { | ComponentDefinedType::Option(ty) => { self.free_variables_valtype(ty, set); } + ComponentDefinedType::Map(k, v) => { + self.free_variables_valtype(k, set); + self.free_variables_valtype(v, set); + } ComponentDefinedType::Result { ok, err } => { if let Some(ok) = ok { self.free_variables_valtype(ok, set); @@ -2657,6 +2674,9 @@ impl TypeAlloc { ComponentDefinedType::List(ty) | ComponentDefinedType::FixedSizeList(ty, _) | ComponentDefinedType::Option(ty) => self.type_named_valtype(ty, set), + ComponentDefinedType::Map(k, v) => { + self.type_named_valtype(k, set) && self.type_named_valtype(v, set) + } // own/borrow themselves don't have to be named, but the resource // they refer to must be named. @@ -2849,6 +2869,10 @@ where | ComponentDefinedType::Option(ty) => { any_changed |= self.remap_valtype(ty, map); } + ComponentDefinedType::Map(k, v) => { + any_changed |= self.remap_valtype(k, map); + any_changed |= self.remap_valtype(v, map); + } ComponentDefinedType::Result { ok, err } => { if let Some(ok) = ok { any_changed |= self.remap_valtype(ok, map); @@ -3750,6 +3774,13 @@ impl<'a> SubtypeCx<'a> { (Variant(_), b) => bail!(offset, "expected {}, found variant", b.desc()), (List(a), List(b)) | (Option(a), Option(b)) => self.component_val_type(a, b, offset), (List(_), b) => bail!(offset, "expected {}, found list", b.desc()), + (Map(ak, av), Map(bk, bv)) => { + self.component_val_type(ak, bk, offset) + .with_context(|| "type mismatch in map key")?; + self.component_val_type(av, bv, offset) + .with_context(|| "type mismatch in map value") + } + (Map(_, _), b) => bail!(offset, "expected {}, found map", b.desc()), (FixedSizeList(a, asize), FixedSizeList(b, bsize)) => { if asize != bsize { bail!(offset, "expected fixed size {bsize}, found size {asize}") diff --git a/crates/wasmprinter/src/component.rs b/crates/wasmprinter/src/component.rs index 0c15f30fcc..87edfde7c2 100644 --- a/crates/wasmprinter/src/component.rs +++ b/crates/wasmprinter/src/component.rs @@ -169,6 +169,20 @@ impl Printer<'_, '_> { Ok(()) } + pub(crate) fn print_map_type( + &mut self, + state: &State, + key_ty: &ComponentValType, + value_ty: &ComponentValType, + ) -> Result<()> { + self.start_group("map ")?; + self.print_component_val_type(state, key_ty)?; + self.result.write_str(" ")?; + self.print_component_val_type(state, value_ty)?; + self.end_group()?; + Ok(()) + } + pub(crate) fn print_fixed_size_list_type( &mut self, state: &State, @@ -278,6 +292,7 @@ impl Printer<'_, '_> { ComponentDefinedType::Record(fields) => self.print_record_type(state, fields)?, ComponentDefinedType::Variant(cases) => self.print_variant_type(state, cases)?, ComponentDefinedType::List(ty) => self.print_list_type(state, ty)?, + ComponentDefinedType::Map(key, value) => self.print_map_type(state, key, value)?, ComponentDefinedType::FixedSizeList(ty, elements) => { self.print_fixed_size_list_type(state, ty, *elements)? } diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 5b8dbe4a7f..b30ca915b2 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -184,7 +184,7 @@ impl RequiredOptions { // If lists/strings are lowered into wasm then memory is required as // usual but `realloc` is also required to allow the external caller to // allocate space in the destination for the list/string. - if types.contains(TypeContents::LIST) { + if types.contains(TypeContents::NEEDS_MEMORY) { *self |= RequiredOptions::MEMORY | RequiredOptions::REALLOC; } if types.contains(TypeContents::STRING) { @@ -198,7 +198,7 @@ impl RequiredOptions { // Unlike for `lower` when lifting a string/list all that's needed is // memory, since the string/list already resides in memory `realloc` // isn't needed. - if types.contains(TypeContents::LIST) { + if types.contains(TypeContents::NEEDS_MEMORY) { *self |= RequiredOptions::MEMORY; } if types.contains(TypeContents::STRING) { @@ -277,7 +277,7 @@ bitflags::bitflags! { /// structure of a type. struct TypeContents: u8 { const STRING = 1 << 0; - const LIST = 1 << 1; + const NEEDS_MEMORY = 1 << 1; } } @@ -324,7 +324,10 @@ impl TypeContents { Self::for_optional_types(resolve, v.cases.iter().map(|c| c.ty.as_ref())) } TypeDefKind::Enum(_) => Self::empty(), - TypeDefKind::List(t) => Self::for_type(resolve, t) | Self::LIST, + TypeDefKind::List(t) => Self::for_type(resolve, t) | Self::NEEDS_MEMORY, + TypeDefKind::Map(k, v) => { + Self::for_type(resolve, k) | Self::for_type(resolve, v) | Self::NEEDS_MEMORY + } TypeDefKind::FixedSizeList(t, _elements) => Self::for_type(resolve, t), TypeDefKind::Type(t) => Self::for_type(resolve, t), TypeDefKind::Future(_) => Self::empty(), diff --git a/crates/wit-component/src/encoding/types.rs b/crates/wit-component/src/encoding/types.rs index 2dec9fbf5e..61a85080b0 100644 --- a/crates/wit-component/src/encoding/types.rs +++ b/crates/wit-component/src/encoding/types.rs @@ -187,6 +187,13 @@ pub trait ValtypeEncoder<'a> { encoder.list(ty); ComponentValType::Type(index) } + TypeDefKind::Map(key_ty, value_ty) => { + let key = self.encode_valtype(resolve, key_ty)?; + let value = self.encode_valtype(resolve, value_ty)?; + let (index, encoder) = self.defined_type(); + encoder.map(key, value); + ComponentValType::Type(index) + } TypeDefKind::FixedSizeList(ty, elements) => { let ty = self.encode_valtype(resolve, ty)?; let (index, encoder) = self.defined_type(); diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index e97e77bfb0..4a260fc00e 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -602,6 +602,14 @@ impl WitPrinter { self.print_type_name(resolve, ty)?; self.output.generic_args_end(); } + TypeDefKind::Map(key_ty, value_ty) => { + self.output.ty("map", TypeKind::BuiltIn); + self.output.generic_args_start(); + self.print_type_name(resolve, key_ty)?; + self.output.str(", "); + self.print_type_name(resolve, value_ty)?; + self.output.generic_args_end(); + } TypeDefKind::FixedSizeList(ty, size) => { self.output.ty("list", TypeKind::BuiltIn); self.output.generic_args_start(); @@ -783,6 +791,9 @@ impl WitPrinter { TypeDefKind::List(inner) => { self.declare_list(resolve, ty.name.as_deref(), inner)? } + TypeDefKind::Map(key, value) => { + self.declare_map(resolve, ty.name.as_deref(), key, value)? + } TypeDefKind::FixedSizeList(inner, size) => { self.declare_fixed_size_list(resolve, ty.name.as_deref(), inner, *size)? } @@ -999,6 +1010,31 @@ impl WitPrinter { Ok(()) } + fn declare_map( + &mut self, + resolve: &Resolve, + name: Option<&str>, + key_ty: &Type, + value_ty: &Type, + ) -> Result<()> { + if let Some(name) = name { + self.output.keyword("type"); + self.output.str(" "); + self.print_name_type(name, TypeKind::Map); + self.output.str(" = "); + self.output.ty("map", TypeKind::BuiltIn); + self.output.str("<"); + self.print_type_name(resolve, key_ty)?; + self.output.str(", "); + self.print_type_name(resolve, value_ty)?; + self.output.str(">"); + self.output.semicolon(); + return Ok(()); + } + + Ok(()) + } + fn declare_fixed_size_list( &mut self, resolve: &Resolve, @@ -1339,6 +1375,8 @@ pub enum TypeKind { InterfacePath, /// A list type name. List, + /// A map type name. + Map, /// A namespace declaration. NamespaceDeclaration, /// A namespace when printing a path, for example in `use`. diff --git a/crates/wit-component/tests/interfaces/maps.wat b/crates/wit-component/tests/interfaces/maps.wat new file mode 100644 index 0000000000..91e2f862aa --- /dev/null +++ b/crates/wit-component/tests/interfaces/maps.wat @@ -0,0 +1,186 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (map bool string)) + (export (;1;) "bool-map" (type (eq 0))) + (type (;2;) (map u8 string)) + (export (;3;) "u8-map" (type (eq 2))) + (type (;4;) (map u16 string)) + (export (;5;) "u16-map" (type (eq 4))) + (type (;6;) (map u32 string)) + (export (;7;) "u32-map" (type (eq 6))) + (type (;8;) (map u64 string)) + (export (;9;) "u64-map" (type (eq 8))) + (type (;10;) (map s8 string)) + (export (;11;) "s8-map" (type (eq 10))) + (type (;12;) (map s16 string)) + (export (;13;) "s16-map" (type (eq 12))) + (type (;14;) (map s32 string)) + (export (;15;) "s32-map" (type (eq 14))) + (type (;16;) (map s64 string)) + (export (;17;) "s64-map" (type (eq 16))) + (type (;18;) (map char string)) + (export (;19;) "char-map" (type (eq 18))) + (type (;20;) (map string u32)) + (export (;21;) "string-map" (type (eq 20))) + (type (;22;) (map string bool)) + (export (;23;) "string-to-bool" (type (eq 22))) + (type (;24;) (list u8)) + (type (;25;) (map string 24)) + (export (;26;) "string-to-list" (type (eq 25))) + (type (;27;) (option u32)) + (type (;28;) (map string 27)) + (export (;29;) "string-to-option" (type (eq 28))) + (type (;30;) (result u32 (error string))) + (type (;31;) (map string 30)) + (export (;32;) "string-to-result" (type (eq 31))) + (type (;33;) (tuple u32 string)) + (type (;34;) (map string 33)) + (export (;35;) "string-to-tuple" (type (eq 34))) + (type (;36;) (map string u32)) + (type (;37;) (map string 36)) + (export (;38;) "map-of-maps" (type (eq 37))) + (type (;39;) (list 36)) + (export (;40;) "list-of-maps" (type (eq 39))) + (type (;41;) (option 36)) + (export (;42;) "option-of-map" (type (eq 41))) + (type (;43;) (func (param "x" 36))) + (export (;0;) "map-param" (func (type 43))) + (type (;44;) (func (result 36))) + (export (;1;) "map-result" (func (type 44))) + (type (;45;) (map u32 string)) + (type (;46;) (func (param "x" 45) (result 45))) + (export (;2;) "map-roundtrip" (func (type 46))) + ) + ) + (export (;0;) "foo:maps-test/maps-interface" (instance (type 0))) + ) + ) + (export (;1;) "maps-interface" (type 0)) + (type (;2;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (map bool string)) + (export (;1;) "bool-map" (type (eq 0))) + (type (;2;) (map u8 string)) + (export (;3;) "u8-map" (type (eq 2))) + (type (;4;) (map u16 string)) + (export (;5;) "u16-map" (type (eq 4))) + (type (;6;) (map u32 string)) + (export (;7;) "u32-map" (type (eq 6))) + (type (;8;) (map u64 string)) + (export (;9;) "u64-map" (type (eq 8))) + (type (;10;) (map s8 string)) + (export (;11;) "s8-map" (type (eq 10))) + (type (;12;) (map s16 string)) + (export (;13;) "s16-map" (type (eq 12))) + (type (;14;) (map s32 string)) + (export (;15;) "s32-map" (type (eq 14))) + (type (;16;) (map s64 string)) + (export (;17;) "s64-map" (type (eq 16))) + (type (;18;) (map char string)) + (export (;19;) "char-map" (type (eq 18))) + (type (;20;) (map string u32)) + (export (;21;) "string-map" (type (eq 20))) + (type (;22;) (map string bool)) + (export (;23;) "string-to-bool" (type (eq 22))) + (type (;24;) (list u8)) + (type (;25;) (map string 24)) + (export (;26;) "string-to-list" (type (eq 25))) + (type (;27;) (option u32)) + (type (;28;) (map string 27)) + (export (;29;) "string-to-option" (type (eq 28))) + (type (;30;) (result u32 (error string))) + (type (;31;) (map string 30)) + (export (;32;) "string-to-result" (type (eq 31))) + (type (;33;) (tuple u32 string)) + (type (;34;) (map string 33)) + (export (;35;) "string-to-tuple" (type (eq 34))) + (type (;36;) (map string u32)) + (type (;37;) (map string 36)) + (export (;38;) "map-of-maps" (type (eq 37))) + (type (;39;) (list 36)) + (export (;40;) "list-of-maps" (type (eq 39))) + (type (;41;) (option 36)) + (export (;42;) "option-of-map" (type (eq 41))) + (type (;43;) (func (param "x" 36))) + (export (;0;) "map-param" (func (type 43))) + (type (;44;) (func (result 36))) + (export (;1;) "map-result" (func (type 44))) + (type (;45;) (map u32 string)) + (type (;46;) (func (param "x" 45) (result 45))) + (export (;2;) "map-roundtrip" (func (type 46))) + ) + ) + (import "foo:maps-test/maps-interface" (instance (;0;) (type 0))) + (type (;1;) + (instance + (type (;0;) (map bool string)) + (export (;1;) "bool-map" (type (eq 0))) + (type (;2;) (map u8 string)) + (export (;3;) "u8-map" (type (eq 2))) + (type (;4;) (map u16 string)) + (export (;5;) "u16-map" (type (eq 4))) + (type (;6;) (map u32 string)) + (export (;7;) "u32-map" (type (eq 6))) + (type (;8;) (map u64 string)) + (export (;9;) "u64-map" (type (eq 8))) + (type (;10;) (map s8 string)) + (export (;11;) "s8-map" (type (eq 10))) + (type (;12;) (map s16 string)) + (export (;13;) "s16-map" (type (eq 12))) + (type (;14;) (map s32 string)) + (export (;15;) "s32-map" (type (eq 14))) + (type (;16;) (map s64 string)) + (export (;17;) "s64-map" (type (eq 16))) + (type (;18;) (map char string)) + (export (;19;) "char-map" (type (eq 18))) + (type (;20;) (map string u32)) + (export (;21;) "string-map" (type (eq 20))) + (type (;22;) (map string bool)) + (export (;23;) "string-to-bool" (type (eq 22))) + (type (;24;) (list u8)) + (type (;25;) (map string 24)) + (export (;26;) "string-to-list" (type (eq 25))) + (type (;27;) (option u32)) + (type (;28;) (map string 27)) + (export (;29;) "string-to-option" (type (eq 28))) + (type (;30;) (result u32 (error string))) + (type (;31;) (map string 30)) + (export (;32;) "string-to-result" (type (eq 31))) + (type (;33;) (tuple u32 string)) + (type (;34;) (map string 33)) + (export (;35;) "string-to-tuple" (type (eq 34))) + (type (;36;) (map string u32)) + (type (;37;) (map string 36)) + (export (;38;) "map-of-maps" (type (eq 37))) + (type (;39;) (list 36)) + (export (;40;) "list-of-maps" (type (eq 39))) + (type (;41;) (option 36)) + (export (;42;) "option-of-map" (type (eq 41))) + (type (;43;) (func (param "x" 36))) + (export (;0;) "map-param" (func (type 43))) + (type (;44;) (func (result 36))) + (export (;1;) "map-result" (func (type 44))) + (type (;45;) (map u32 string)) + (type (;46;) (func (param "x" 45) (result 45))) + (export (;2;) "map-roundtrip" (func (type 46))) + ) + ) + (export (;1;) "foo:maps-test/maps-interface" (instance (type 1))) + ) + ) + (export (;0;) "foo:maps-test/maps-test-world" (component (type 0))) + ) + ) + (export (;3;) "maps-test-world" (type 2)) + (@custom "package-docs" "\01{\22interfaces\22:{\22maps-interface\22:{\22funcs\22:{\22map-param\22:{\22docs\22:\22Functions\22}},\22types\22:{\22bool-map\22:{\22docs\22:\22Test all primitive key types\22},\22string-to-bool\22:{\22docs\22:\22Test all value types\22},\22map-of-maps\22:{\22docs\22:\22Nested structures\22}}}}}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/maps.wit b/crates/wit-component/tests/interfaces/maps.wit new file mode 100644 index 0000000000..93bc64a92d --- /dev/null +++ b/crates/wit-component/tests/interfaces/maps.wit @@ -0,0 +1,38 @@ +package foo:maps-test; + +interface maps-interface { + // Test all primitive key types + type bool-map = map; + type u8-map = map; + type u16-map = map; + type u32-map = map; + type u64-map = map; + type s8-map = map; + type s16-map = map; + type s32-map = map; + type s64-map = map; + type char-map = map; + type string-map = map; + + // Test all value types + type string-to-bool = map; + type string-to-list = map>; + type string-to-option = map>; + type string-to-result = map>; + type string-to-tuple = map>; + + // Nested structures + type map-of-maps = map>; + type list-of-maps = list>; + type option-of-map = option>; + + // Functions + map-param: func(x: map); + map-result: func() -> map; + map-roundtrip: func(x: map) -> map; +} + +world maps-test-world { + import maps-interface; + export maps-interface; +} diff --git a/crates/wit-component/tests/interfaces/maps.wit.print b/crates/wit-component/tests/interfaces/maps.wit.print new file mode 100644 index 0000000000..f9235085bc --- /dev/null +++ b/crates/wit-component/tests/interfaces/maps.wit.print @@ -0,0 +1,57 @@ +package foo:maps-test; + +interface maps-interface { + /// Test all primitive key types + type bool-map = map; + + type u8-map = map; + + type u16-map = map; + + type u32-map = map; + + type u64-map = map; + + type s8-map = map; + + type s16-map = map; + + type s32-map = map; + + type s64-map = map; + + type char-map = map; + + type string-map = map; + + /// Test all value types + type string-to-bool = map; + + type string-to-list = map>; + + type string-to-option = map>; + + type string-to-result = map>; + + type string-to-tuple = map>; + + /// Nested structures + type map-of-maps = map>; + + type list-of-maps = list>; + + type option-of-map = option>; + + /// Functions + map-param: func(x: map); + + map-result: func() -> map; + + map-roundtrip: func(x: map) -> map; +} + +world maps-test-world { + import maps-interface; + + export maps-interface; +} diff --git a/crates/wit-dylib/src/bindgen.rs b/crates/wit-dylib/src/bindgen.rs index cd8ca366f1..b88a55cd47 100644 --- a/crates/wit-dylib/src/bindgen.rs +++ b/crates/wit-dylib/src/bindgen.rs @@ -1115,6 +1115,11 @@ impl<'a> FunctionCompiler<'a> { todo!("fixed-size-list") } + TypeDefKind::Map(k, v) => { + let _ = (k, v); + todo!("map") + } + // Should not be possible to hit during lowering. TypeDefKind::Resource => unreachable!(), TypeDefKind::Unknown => unreachable!(), @@ -1582,6 +1587,11 @@ impl<'a> FunctionCompiler<'a> { self.free_temp_local(l_len); } + TypeDefKind::Map(k, v) => { + let _ = (k, v); + todo!("map") + } + // Should not be possible to hit during lifting. TypeDefKind::Resource => unreachable!(), TypeDefKind::Unknown => unreachable!(), diff --git a/crates/wit-dylib/src/lib.rs b/crates/wit-dylib/src/lib.rs index b1671d2228..96e5784565 100644 --- a/crates/wit-dylib/src/lib.rs +++ b/crates/wit-dylib/src/lib.rs @@ -938,6 +938,9 @@ impl Adapter { TypeDefKind::Handle(Handle::Borrow(t)) => { metadata::Type::Borrow(self.resource_map[&dealias(resolve, *t)]) } + TypeDefKind::Map(_, _) => { + todo!("map") + } TypeDefKind::Unknown => unreachable!(), }; self.type_map.insert(id, result); diff --git a/crates/wit-encoder/src/from_parser.rs b/crates/wit-encoder/src/from_parser.rs index f11fa8e040..84d8b62c7c 100644 --- a/crates/wit-encoder/src/from_parser.rs +++ b/crates/wit-encoder/src/from_parser.rs @@ -243,6 +243,10 @@ impl<'a> Converter<'a> { let output = Type::list(self.convert_type(ty)); TypeDefKind::Type(output) } + wit_parser::TypeDefKind::Map(key, value) => { + let output = Type::map(self.convert_type(key), self.convert_type(value)); + TypeDefKind::Type(output) + } wit_parser::TypeDefKind::FixedSizeList(ty, size) => { let output = Type::fixed_size_list(self.convert_type(ty), *size); TypeDefKind::Type(output) @@ -308,6 +312,9 @@ impl<'a> Converter<'a> { wit_parser::TypeDefKind::List(type_) => { Type::list(self.convert_type(type_)) } + wit_parser::TypeDefKind::Map(key, value) => { + Type::map(self.convert_type(key), self.convert_type(value)) + } wit_parser::TypeDefKind::FixedSizeList(type_, size) => { Type::fixed_size_list(self.convert_type(type_), *size) } diff --git a/crates/wit-encoder/src/ty.rs b/crates/wit-encoder/src/ty.rs index 18aa2786d1..f0fdb7d56c 100644 --- a/crates/wit-encoder/src/ty.rs +++ b/crates/wit-encoder/src/ty.rs @@ -26,6 +26,7 @@ pub enum Type { Option(Box), Result(Box), List(Box), + Map(Box, Box), FixedSizeList(Box, u32), Tuple(Tuple), Future(Option>), @@ -59,6 +60,9 @@ impl Type { pub fn list(type_: Type) -> Self { Type::List(Box::new(type_)) } + pub fn map(key: Type, value: Type) -> Self { + Type::Map(Box::new(key), Box::new(value)) + } pub fn fixed_size_list(type_: Type, size: u32) -> Self { Type::FixedSizeList(Box::new(type_), size) } @@ -120,6 +124,9 @@ impl Display for Type { Type::List(type_) => { write!(f, "list<{type_}>") } + Type::Map(key, value) => { + write!(f, "map<{key}, {value}>") + } Type::FixedSizeList(type_, size) => { write!(f, "list<{type_}, {size}>") } diff --git a/crates/wit-parser/src/abi.rs b/crates/wit-parser/src/abi.rs index 3cba9eba95..147cad3f7d 100644 --- a/crates/wit-parser/src/abi.rs +++ b/crates/wit-parser/src/abi.rs @@ -335,6 +335,10 @@ impl Resolve { result.push(WasmType::Pointer) && result.push(WasmType::Length) } + TypeDefKind::Map(_, _) => { + result.push(WasmType::Pointer) && result.push(WasmType::Length) + } + TypeDefKind::FixedSizeList(ty, size) => { self.push_flat_list((0..*size).map(|_| ty), result) } diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index fba94d0e3e..b59805c2f0 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -754,6 +754,7 @@ enum Type<'a> { String(Span), Name(Id<'a>), List(List<'a>), + Map(Map<'a>), FixedSizeList(FixedSizeList<'a>), Handle(Handle<'a>), Resource(Resource<'a>), @@ -912,6 +913,12 @@ struct List<'a> { ty: Box>, } +struct Map<'a> { + span: Span, + key: Box>, + value: Box>, +} + struct FixedSizeList<'a> { span: Span, ty: Box>, @@ -1406,6 +1413,20 @@ impl<'a> Type<'a> { } } + // map + Some((span, Token::Map)) => { + tokens.expect(Token::LessThan)?; + let key = Type::parse(tokens)?; + tokens.expect(Token::Comma)?; + let value = Type::parse(tokens)?; + tokens.expect(Token::GreaterThan)?; + Ok(Type::Map(Map { + span, + key: Box::new(key), + value: Box::new(value), + })) + } + // option Some((span, Token::Option_)) => { tokens.expect(Token::LessThan)?; @@ -1516,6 +1537,7 @@ impl<'a> Type<'a> { | Type::ErrorContext(span) => *span, Type::Name(id) => id.span, Type::List(l) => l.span, + Type::Map(m) => m.span, Type::FixedSizeList(l) => l.span, Type::Handle(h) => h.span(), Type::Resource(r) => r.span, diff --git a/crates/wit-parser/src/ast/lex.rs b/crates/wit-parser/src/ast/lex.rs index ec383932d4..7c838d6c75 100644 --- a/crates/wit-parser/src/ast/lex.rs +++ b/crates/wit-parser/src/ast/lex.rs @@ -80,6 +80,7 @@ pub enum Token { Stream, ErrorContext, List, + Map, Underscore, As, From_, @@ -301,6 +302,7 @@ impl<'a> Tokenizer<'a> { "stream" => Stream, "error-context" => ErrorContext, "list" => List, + "map" => Map, "_" => Underscore, "as" => As, "from" => From_, @@ -558,6 +560,7 @@ impl Token { Stream => "keyword `stream`", ErrorContext => "keyword `error-context`", List => "keyword `list`", + Map => "keyword `map`", Underscore => "keyword `_`", Id => "an identifier", ExplicitId => "an '%' identifier", diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index a997add506..8fd884fde8 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -92,6 +92,7 @@ enum Key { Tuple(Vec), Enum(Vec), List(Type), + Map(Type, Type), FixedSizeList(Type, u32), Option(Type), Result(Option, Option), @@ -1180,6 +1181,11 @@ impl<'a> Resolver<'a> { let ty = self.resolve_type(&list.ty, stability)?; TypeDefKind::List(ty) } + ast::Type::Map(map) => { + let key_ty = self.resolve_type(&map.key, stability)?; + let value_ty = self.resolve_type(&map.value, stability)?; + TypeDefKind::Map(key_ty, value_ty) + } ast::Type::FixedSizeList(list) => { let ty = self.resolve_type(&list.ty, stability)?; TypeDefKind::FixedSizeList(ty, list.size) @@ -1367,6 +1373,9 @@ impl<'a> Resolver<'a> { TypeDefKind::List(ty) | TypeDefKind::FixedSizeList(ty, _) | TypeDefKind::Option(ty) => find_in_type(types, *ty), + TypeDefKind::Map(k, v) => { + find_in_type(types, *k).or_else(|| find_in_type(types, *v)) + } TypeDefKind::Future(ty) | TypeDefKind::Stream(ty) => { ty.as_ref().and_then(|ty| find_in_type(types, *ty)) } @@ -1458,6 +1467,7 @@ impl<'a> Resolver<'a> { Key::Enum(r.cases.iter().map(|f| f.name.clone()).collect::>()) } TypeDefKind::List(ty) => Key::List(*ty), + TypeDefKind::Map(k, v) => Key::Map(*k, *v), TypeDefKind::FixedSizeList(ty, size) => Key::FixedSizeList(*ty, *size), TypeDefKind::Option(t) => Key::Option(*t), TypeDefKind::Result(r) => Key::Result(r.ok, r.err), @@ -1747,6 +1757,10 @@ fn collect_deps<'a>(ty: &ast::Type<'a>, deps: &mut Vec>) { ast::Type::Option(ast::Option_ { ty, .. }) | ast::Type::List(ast::List { ty, .. }) | ast::Type::FixedSizeList(ast::FixedSizeList { ty, .. }) => collect_deps(ty, deps), + ast::Type::Map(ast::Map { key, value, .. }) => { + collect_deps(key, deps); + collect_deps(value, deps); + } ast::Type::Result(r) => { if let Some(ty) = &r.ok { collect_deps(ty, deps); diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index 54b023a9a3..d87628c8e0 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -1261,6 +1261,7 @@ impl WitPackageDecoder<'_> { match &kind { TypeDefKind::Type(_) | TypeDefKind::List(_) + | TypeDefKind::Map(_, _) | TypeDefKind::FixedSizeList(..) | TypeDefKind::Tuple(_) | TypeDefKind::Option(_) @@ -1302,6 +1303,12 @@ impl WitPackageDecoder<'_> { Ok(TypeDefKind::List(t)) } + ComponentDefinedType::Map(k, v) => { + let k = self.convert_valtype(k)?; + let v = self.convert_valtype(v)?; + Ok(TypeDefKind::Map(k, v)) + } + ComponentDefinedType::FixedSizeList(t, size) => { let t = self.convert_valtype(t)?; Ok(TypeDefKind::FixedSizeList(t, *size)) @@ -1600,6 +1607,16 @@ impl Registrar<'_> { self.valtype(t, ty) } + ComponentDefinedType::Map(k, v) => { + let (key_ty, value_ty) = match &self.resolve.types[id].kind { + TypeDefKind::Map(k, v) => (k, v), + TypeDefKind::Type(Type::Id(_)) => return Ok(()), + _ => bail!("expected a map"), + }; + self.valtype(k, key_ty)?; + self.valtype(v, value_ty) + } + ComponentDefinedType::FixedSizeList(t, elements) => { let ty = match &self.resolve.types[id].kind { TypeDefKind::FixedSizeList(r, elements2) if elements2 == elements => r, diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index e591e775d1..f6afdfe3fb 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -596,6 +596,7 @@ pub enum TypeDefKind { Option(Type), Result(Result_), List(Type), + Map(Type, Type), FixedSizeList(Type, u32), Future(Option), Stream(Option), @@ -625,6 +626,7 @@ impl TypeDefKind { TypeDefKind::Option(_) => "option", TypeDefKind::Result(_) => "result", TypeDefKind::List(_) => "list", + TypeDefKind::Map(_, _) => "map", TypeDefKind::FixedSizeList(..) => "fixed size list", TypeDefKind::Future(_) => "future", TypeDefKind::Stream(_) => "stream", @@ -1256,6 +1258,10 @@ fn find_futures_and_streams(resolve: &Resolve, ty: Type, results: &mut Vec { find_futures_and_streams(resolve, *ty, results); } + TypeDefKind::Map(k, v) => { + find_futures_and_streams(resolve, *k, results); + find_futures_and_streams(resolve, *v, results); + } TypeDefKind::Result(r) => { if let Some(ty) = r.ok { find_futures_and_streams(resolve, ty, results); diff --git a/crates/wit-parser/src/live.rs b/crates/wit-parser/src/live.rs index bc436faac2..6e21cbd379 100644 --- a/crates/wit-parser/src/live.rs +++ b/crates/wit-parser/src/live.rs @@ -140,6 +140,10 @@ pub trait TypeIdVisitor { | TypeDefKind::Option(t) | TypeDefKind::Future(Some(t)) | TypeDefKind::Stream(Some(t)) => self.visit_type(resolve, t), + TypeDefKind::Map(k, v) => { + self.visit_type(resolve, k); + self.visit_type(resolve, v); + } TypeDefKind::Handle(handle) => match handle { crate::Handle::Own(ty) => self.visit_type_id(resolve, *ty), crate::Handle::Borrow(ty) => self.visit_type_id(resolve, *ty), diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 15626d580e..0f1a9cd19b 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -574,6 +574,7 @@ package {name} is defined in two different locations:\n\ Type::Id(id) => match &self.types[*id].kind { TypeDefKind::List(_) + | TypeDefKind::Map(_, _) | TypeDefKind::Variant(_) | TypeDefKind::Enum(_) | TypeDefKind::Option(_) @@ -3312,6 +3313,10 @@ impl Remap { Option(t) | List(t, ..) | FixedSizeList(t, ..) | Future(Some(t)) | Stream(Some(t)) => { self.update_ty(resolve, t, span)? } + Map(k, v) => { + self.update_ty(resolve, k, span)?; + self.update_ty(resolve, v, span)?; + } Result(r) => { if let Some(ty) = &mut r.ok { self.update_ty(resolve, ty, span)?; @@ -3760,6 +3765,9 @@ impl Remap { | TypeDefKind::Future(Some(ty)) | TypeDefKind::Stream(Some(ty)) | TypeDefKind::Option(ty) => self.type_has_borrow(resolve, ty), + TypeDefKind::Map(k, v) => { + self.type_has_borrow(resolve, k) || self.type_has_borrow(resolve, v) + } TypeDefKind::Result(r) => [&r.ok, &r.err] .iter() .filter_map(|t| t.as_ref()) diff --git a/crates/wit-parser/src/resolve/clone.rs b/crates/wit-parser/src/resolve/clone.rs index f7beb6472f..07ab256e1c 100644 --- a/crates/wit-parser/src/resolve/clone.rs +++ b/crates/wit-parser/src/resolve/clone.rs @@ -144,6 +144,10 @@ impl<'a> Cloner<'a> { | TypeDefKind::FixedSizeList(ty, ..) => { self.ty(ty); } + TypeDefKind::Map(k, v) => { + self.ty(k); + self.ty(v); + } TypeDefKind::Tuple(list) => { for ty in list.types.iter_mut() { self.ty(ty); diff --git a/crates/wit-parser/src/sizealign.rs b/crates/wit-parser/src/sizealign.rs index a9b96dc008..a060f99ea5 100644 --- a/crates/wit-parser/src/sizealign.rs +++ b/crates/wit-parser/src/sizealign.rs @@ -274,6 +274,9 @@ impl SizeAlign { TypeDefKind::List(_) => { ElementInfo::new(ArchitectureSize::new(0, 2), Alignment::Pointer) } + TypeDefKind::Map(_, _) => { + ElementInfo::new(ArchitectureSize::new(0, 2), Alignment::Pointer) + } TypeDefKind::Record(r) => self.record(r.fields.iter().map(|f| &f.ty)), TypeDefKind::Tuple(t) => self.record(t.types.iter()), TypeDefKind::Flags(f) => match f.repr() { diff --git a/crates/wit-parser/tests/ui/maps.wit b/crates/wit-parser/tests/ui/maps.wit new file mode 100644 index 0000000000..deac260d00 --- /dev/null +++ b/crates/wit-parser/tests/ui/maps.wit @@ -0,0 +1,34 @@ +package foo:maps; + +interface maps { + // Basic maps + type string-to-u32 = map; + type u32-to-string = map; + + // Nested maps + type nested-map = map>; + + // Maps with complex types + record person { + name: string, + age: u32, + } + type person-map = map; + + // Maps with tuples, lists, options + type complex-map = map>>; + + // Functions using maps + get-value: func(m: map, key: string) -> option; + set-value: func(m: map, key: string, value: u32) -> map; + merge-maps: func(a: map, b: map) -> map; + + // Map with various key types + type int-key-map = map; + type char-key-map = map; +} + +world maps-world { + import maps; + export maps; +} diff --git a/crates/wit-parser/tests/ui/maps.wit.json b/crates/wit-parser/tests/ui/maps.wit.json new file mode 100644 index 0000000000..33f5065f12 --- /dev/null +++ b/crates/wit-parser/tests/ui/maps.wit.json @@ -0,0 +1,255 @@ +{ + "worlds": [ + { + "name": "maps-world", + "imports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "maps", + "types": { + "string-to-u32": 0, + "u32-to-string": 1, + "nested-map": 3, + "person": 4, + "person-map": 5, + "complex-map": 8, + "int-key-map": 9, + "char-key-map": 10 + }, + "functions": { + "get-value": { + "name": "get-value", + "kind": "freestanding", + "params": [ + { + "name": "m", + "type": 2 + }, + { + "name": "key", + "type": "string" + } + ], + "result": 11, + "docs": { + "contents": "Functions using maps" + } + }, + "set-value": { + "name": "set-value", + "kind": "freestanding", + "params": [ + { + "name": "m", + "type": 2 + }, + { + "name": "key", + "type": "string" + }, + { + "name": "value", + "type": "u32" + } + ], + "result": 2 + }, + "merge-maps": { + "name": "merge-maps", + "kind": "freestanding", + "params": [ + { + "name": "a", + "type": 2 + }, + { + "name": "b", + "type": 2 + } + ], + "result": 2 + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "string-to-u32", + "kind": { + "map": [ + "string", + "u32" + ] + }, + "owner": { + "interface": 0 + }, + "docs": { + "contents": "Basic maps" + } + }, + { + "name": "u32-to-string", + "kind": { + "map": [ + "u32", + "string" + ] + }, + "owner": { + "interface": 0 + } + }, + { + "name": null, + "kind": { + "map": [ + "string", + "u32" + ] + }, + "owner": null + }, + { + "name": "nested-map", + "kind": { + "map": [ + "string", + 2 + ] + }, + "owner": { + "interface": 0 + }, + "docs": { + "contents": "Nested maps" + } + }, + { + "name": "person", + "kind": { + "record": { + "fields": [ + { + "name": "name", + "type": "string" + }, + { + "name": "age", + "type": "u32" + } + ] + } + }, + "owner": { + "interface": 0 + }, + "docs": { + "contents": "Maps with complex types" + } + }, + { + "name": "person-map", + "kind": { + "map": [ + "string", + 4 + ] + }, + "owner": { + "interface": 0 + } + }, + { + "name": null, + "kind": { + "list": "u32" + }, + "owner": null + }, + { + "name": null, + "kind": { + "option": 6 + }, + "owner": null + }, + { + "name": "complex-map", + "kind": { + "map": [ + "string", + 7 + ] + }, + "owner": { + "interface": 0 + }, + "docs": { + "contents": "Maps with tuples, lists, options" + } + }, + { + "name": "int-key-map", + "kind": { + "map": [ + "u32", + "string" + ] + }, + "owner": { + "interface": 0 + }, + "docs": { + "contents": "Map with various key types" + } + }, + { + "name": "char-key-map", + "kind": { + "map": [ + "char", + "string" + ] + }, + "owner": { + "interface": 0 + } + }, + { + "name": null, + "kind": { + "option": "u32" + }, + "owner": null + } + ], + "packages": [ + { + "name": "foo:maps", + "interfaces": { + "maps": 0 + }, + "worlds": { + "maps-world": 0 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit b/crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit new file mode 100644 index 0000000000..75a5f899cc --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit @@ -0,0 +1,7 @@ +// Map keys must be primitive types only +package foo:invalid-map-key; + +interface test { + // This should fail - tuple is not a valid map key type + type invalid = map, u32>; +} diff --git a/crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit.result b/crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit.result new file mode 100644 index 0000000000..4d8b09c5d2 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit.result @@ -0,0 +1,5 @@ +invalid map key type: map keys must be bool, u8, u16, u32, u64, s8, s16, s32, s64, char, or string + --> tests/ui/parse-fail/map-invalid-key.wit:6:18 + | + 6 | type invalid = map, u32>; + | ^-- \ No newline at end of file