diff --git a/crates/wasm-encoder/src/component/types.rs b/crates/wasm-encoder/src/component/types.rs index 417b10f7d9..14e8a99336 100644 --- a/crates/wasm-encoder/src/component/types.rs +++ b/crates/wasm-encoder/src/component/types.rs @@ -744,6 +744,12 @@ impl ComponentTypeSection { pub fn defined_type(&mut self) -> ComponentDefinedTypeEncoder<'_> { self.ty().defined_type() } + + /// Defines a new resource type. + pub fn resource(&mut self, rep: ValType, dtor: Option) -> &mut Self { + self.ty().resource(rep, dtor); + self + } } impl Encode for ComponentTypeSection { diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs index 72e68f3104..96f5d878cc 100644 --- a/crates/wasmprinter/src/lib.rs +++ b/crates/wasmprinter/src/lib.rs @@ -1785,7 +1785,7 @@ impl Printer { self.print_valtype(rep)?; self.result.push_str(")"); if let Some(dtor) = dtor { - self.result.push_str("(dtor (func "); + self.result.push_str(" (dtor (func "); self.print_idx(&states.last().unwrap().core.func_names, dtor)?; self.result.push_str("))"); } diff --git a/crates/wit-component/src/builder.rs b/crates/wit-component/src/builder.rs index ce7a96c425..4bbf5459ab 100644 --- a/crates/wit-component/src/builder.rs +++ b/crates/wit-component/src/builder.rs @@ -169,6 +169,11 @@ impl ComponentBuilder { (inc(&mut self.types), self.types().function()) } + pub fn resource(&mut self, rep: ValType, dtor: Option) -> u32 { + self.types().resource(rep, dtor); + inc(&mut self.types) + } + pub fn alias_type_export(&mut self, instance: u32, name: &str) -> u32 { self.aliases().alias(Alias::InstanceExport { instance, @@ -200,6 +205,21 @@ impl ComponentBuilder { pub fn add_producers(&mut self, producers: &Producers) { self.producers.merge(producers) } + + pub fn resource_drop(&mut self, ty: ComponentValType) -> u32 { + self.canonical_functions().resource_drop(ty); + inc(&mut self.core_funcs) + } + + pub fn resource_new(&mut self, ty: u32) -> u32 { + self.canonical_functions().resource_new(ty); + inc(&mut self.core_funcs) + } + + pub fn resource_rep(&mut self, ty: u32) -> u32 { + self.canonical_functions().resource_rep(ty); + inc(&mut self.core_funcs) + } } // Helper macro to generate methods on `ComponentBuilder` to get specific diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index f383cef4c4..095f26d905 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -99,6 +99,7 @@ impl<'a> ComponentInfo<'a> { foreign_packages: Default::default(), iface_to_package_index: Default::default(), named_interfaces: Default::default(), + resources: Default::default(), }; let mut pkg = None; @@ -150,6 +151,7 @@ impl<'a> ComponentInfo<'a> { foreign_packages: Default::default(), iface_to_package_index: Default::default(), named_interfaces: Default::default(), + resources: Default::default(), }; let mut package = Package { // Similar to `world_name` above this is arbitrarily chosen as it's @@ -237,6 +239,15 @@ struct WitPackageDecoder<'a> { iface_to_package_index: HashMap, named_interfaces: HashMap, + /// A map which tracks named resources to what their corresponding `TypeId` + /// is. This first layer of key in this map is the owner scope of a + /// resource, more-or-less the `world` or `interface` that it's defined + /// within. The second layer of this map is keyed by name of the resource + /// and points to the actual ID of the resource. + /// + /// This map is populated in `register_type_export`. + resources: HashMap>, + /// A map from a type id to what it's been translated to. type_map: HashMap, } @@ -316,6 +327,7 @@ impl WitPackageDecoder<'_> { .types .component_entity_type_of_import(import.name.as_str()) .unwrap(); + let owner = TypeOwner::World(world); let (name, item) = match ty { types::ComponentEntityType::Instance(i) => { let ty = match self.info.types.type_from_id(i) { @@ -337,7 +349,7 @@ impl WitPackageDecoder<'_> { _ => unreachable!(), }; let func = self - .convert_function(name, ty) + .convert_function(name, ty, owner) .with_context(|| format!("failed to decode function from import `{name}`"))?; (WorldKey::Name(name.to_string()), WorldItem::Function(func)) } @@ -346,7 +358,7 @@ impl WitPackageDecoder<'_> { created, } => { let id = self - .register_type_export(name, TypeOwner::World(world), referenced, created) + .register_type_export(name, owner, referenced, created) .with_context(|| format!("failed to decode type from export `{name}`"))?; (WorldKey::Name(name.to_string()), WorldItem::Type(id)) } @@ -373,7 +385,7 @@ impl WitPackageDecoder<'_> { _ => unreachable!(), }; let func = self - .convert_function(name, ty) + .convert_function(name, ty, TypeOwner::World(world)) .with_context(|| format!("failed to decode function from export `{name}`"))?; (WorldKey::Name(name.to_string()), WorldItem::Function(func)) @@ -418,18 +430,13 @@ impl WitPackageDecoder<'_> { Some(id) => (true, *id), None => (false, self.extract_dep_interface(name)?), }; - + let owner = TypeOwner::Interface(interface); for (name, ty) in ty.exports.iter() { match *ty { types::ComponentEntityType::Type { referenced, created, } => { - let def = match self.info.types.type_from_id(referenced) { - Some(types::Type::Defined(ty)) => ty, - _ => unreachable!(), - }; - match self.resolve.interfaces[interface] .types .get(name.as_str()) @@ -453,7 +460,11 @@ impl WitPackageDecoder<'_> { // is not strictly necessary but assists with // roundtripping assertions during fuzzing. Some(id) => { - self.register_defined(id, def)?; + match self.info.types.type_from_id(referenced) { + Some(types::Type::Defined(ty)) => self.register_defined(id, ty)?, + Some(types::Type::Resource(_)) => {} + _ => unreachable!(), + } let prev = self.type_map.insert(created, id); assert!(prev.is_none()); } @@ -477,7 +488,7 @@ impl WitPackageDecoder<'_> { } let id = self.register_type_export( name.as_str(), - TypeOwner::Interface(interface), + owner, referenced, created, )?; @@ -508,7 +519,7 @@ impl WitPackageDecoder<'_> { if is_local { bail!("instance function export `{name}` not defined in interface"); } - let func = self.convert_function(name.as_str(), def)?; + let func = self.convert_function(name.as_str(), def, owner)?; let prev = self.resolve.interfaces[interface] .functions .insert(name.to_string(), func); @@ -639,6 +650,7 @@ impl WitPackageDecoder<'_> { package: None, }; + let owner = TypeOwner::Interface(self.resolve.interfaces.next_id()); for (name, ty) in ty.exports.iter() { match *ty { types::ComponentEntityType::Type { @@ -646,12 +658,7 @@ impl WitPackageDecoder<'_> { created, } => { let ty = self - .register_type_export( - name.as_str(), - TypeOwner::Interface(self.resolve.interfaces.next_id()), - referenced, - created, - ) + .register_type_export(name.as_str(), owner, referenced, created) .with_context(|| format!("failed to register type export '{name}'"))?; let prev = interface.types.insert(name.to_string(), ty); assert!(prev.is_none()); @@ -663,7 +670,7 @@ impl WitPackageDecoder<'_> { _ => unreachable!(), }; let func = self - .convert_function(name.as_str(), ty) + .convert_function(name.as_str(), ty, owner) .with_context(|| format!("failed to convert function '{name}'"))?; let prev = interface.functions.insert(name.to_string(), func); assert!(prev.is_none()); @@ -712,10 +719,6 @@ impl WitPackageDecoder<'_> { referenced: types::TypeId, created: types::TypeId, ) -> Result { - let ty = match self.info.types.type_from_id(referenced) { - Some(types::Type::Defined(ty)) => ty, - _ => unreachable!(), - }; let kind = match self.find_alias(referenced) { // If this `TypeId` points to a type which has // previously been defined, meaning we're aliasing a @@ -724,9 +727,13 @@ impl WitPackageDecoder<'_> { // ... or this `TypeId`'s source definition has never // been seen before, so declare the full type. - None => self - .convert_defined(ty) - .context("failed to convert unaliased type")?, + None => match self.info.types.type_from_id(referenced) { + Some(types::Type::Defined(ty)) => self + .convert_defined(ty) + .context("failed to convert unaliased type")?, + Some(types::Type::Resource(_)) => TypeDefKind::Resource, + _ => unreachable!(), + }, }; let ty = self.resolve.types.alloc(TypeDef { name: Some(name.to_string()), @@ -735,6 +742,18 @@ impl WitPackageDecoder<'_> { owner, }); + // If this is a resource then doubly-register it in `self.resources` so + // the ID allocated here can be looked up via name later on during + // `convert_function`. + if let TypeDefKind::Resource = self.resolve.types[ty].kind { + let prev = self + .resources + .entry(owner) + .or_insert(HashMap::new()) + .insert(name.to_string(), ty); + assert!(prev.is_none()); + } + let prev = self.type_map.insert(created, ty); assert!(prev.is_none()); Ok(ty) @@ -759,6 +778,7 @@ impl WitPackageDecoder<'_> { package: None, }; + let owner = TypeOwner::World(self.resolve.worlds.next_id()); for (name, ty) in ty.imports.iter() { let (name, item) = match ty { types::ComponentEntityType::Instance(idx) => { @@ -785,12 +805,8 @@ impl WitPackageDecoder<'_> { created, referenced, } => { - let ty = self.register_type_export( - name.as_str(), - TypeOwner::World(self.resolve.worlds.next_id()), - *referenced, - *created, - )?; + let ty = + self.register_type_export(name.as_str(), owner, *referenced, *created)?; (WorldKey::Name(name.to_string()), WorldItem::Type(ty)) } types::ComponentEntityType::Func(idx) => { @@ -798,7 +814,7 @@ impl WitPackageDecoder<'_> { Some(types::Type::ComponentFunc(ty)) => ty, _ => unreachable!(), }; - let func = self.convert_function(name.as_str(), ty)?; + let func = self.convert_function(name.as_str(), ty, owner)?; (WorldKey::Name(name.to_string()), WorldItem::Function(func)) } _ => bail!("component import `{name}` is not an instance, func, or type"), @@ -833,7 +849,7 @@ impl WitPackageDecoder<'_> { Some(types::Type::ComponentFunc(ty)) => ty, _ => unreachable!(), }; - let func = self.convert_function(name.as_str(), ty)?; + let func = self.convert_function(name.as_str(), ty, owner)?; (WorldKey::Name(name.to_string()), WorldItem::Function(func)) } @@ -847,7 +863,13 @@ impl WitPackageDecoder<'_> { Ok(id) } - fn convert_function(&mut self, name: &str, ty: &types::ComponentFuncType) -> Result { + fn convert_function( + &mut self, + name: &str, + ty: &types::ComponentFuncType, + owner: TypeOwner, + ) -> Result { + let name = KebabName::new(ComponentExternName::Kebab(name), 0).unwrap(); let params = ty .params .iter() @@ -875,7 +897,26 @@ impl WitPackageDecoder<'_> { }; Ok(Function { docs: Default::default(), - kind: FunctionKind::Freestanding, + kind: match name.kind() { + KebabNameKind::Normal(_) => FunctionKind::Freestanding, + KebabNameKind::Constructor(resource) => { + FunctionKind::Constructor(self.resources[&owner][resource.as_str()]) + } + KebabNameKind::Method { resource, .. } => { + FunctionKind::Method(self.resources[&owner][resource.as_str()]) + } + KebabNameKind::Static { resource, .. } => { + FunctionKind::Static(self.resources[&owner][resource.as_str()]) + } + + // Functions shouldn't have ID-based names at this time. + KebabNameKind::Id { .. } => unreachable!(), + }, + + // Note that this name includes "name mangling" such as + // `[method]foo.bar` which is intentional. The `FunctionKind` + // discriminant calculated above indicates how to interpret this + // name. name: name.to_string(), params, results, @@ -1049,8 +1090,15 @@ impl WitPackageDecoder<'_> { Ok(TypeDefKind::Enum(Enum { cases })) } - types::ComponentDefinedType::Own(_) => unimplemented!(), - types::ComponentDefinedType::Borrow(_) => unimplemented!(), + types::ComponentDefinedType::Own(id) => { + let id = self.type_map[id]; + Ok(TypeDefKind::Handle(Handle::Own(id))) + } + + types::ComponentDefinedType::Borrow(id) => { + let id = self.type_map[id]; + Ok(TypeDefKind::Handle(Handle::Borrow(id))) + } } } diff --git a/crates/wit-component/src/dummy.rs b/crates/wit-component/src/dummy.rs index bb88d1fa54..0d76f64bd1 100644 --- a/crates/wit-component/src/dummy.rs +++ b/crates/wit-component/src/dummy.rs @@ -1,5 +1,5 @@ use wit_parser::abi::{AbiVariant, WasmType}; -use wit_parser::{Function, Resolve, WorldId, WorldItem}; +use wit_parser::{Function, Resolve, TypeDefKind, TypeId, WorldId, WorldItem}; /// Generate a dummy implementation core Wasm module for a given WIT document pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec { @@ -17,17 +17,46 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec { wat.push_str("))\n"); } WorldItem::Interface(import) => { + let name = resolve.name_world_key(name); for (_, func) in resolve.interfaces[*import].functions.iter() { let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); - let name = resolve.name_world_key(name); wat.push_str(&format!("(import \"{name}\" \"{}\" (func", func.name)); push_tys(&mut wat, "param", &sig.params); push_tys(&mut wat, "result", &sig.results); wat.push_str("))\n"); } + for (_, ty) in resolve.interfaces[*import].types.iter() { + push_resource_func_imports(&mut wat, resolve, &name, *ty); + } } - WorldItem::Type(_) => {} + WorldItem::Type(id) => { + push_resource_func_imports(&mut wat, resolve, "$root", *id); + } + } + } + + // Import any resource-related functions for exports. + for (name, export) in world.exports.iter() { + let export = match export { + WorldItem::Interface(export) => *export, + _ => continue, + }; + let module = format!("[export]{}", resolve.name_world_key(name)); + for (name, ty) in resolve.interfaces[export].types.iter() { + let ty = &resolve.types[*ty]; + match ty.kind { + TypeDefKind::Resource => {} + _ => continue, + } + wat.push_str(&format!( + "\ +(import \"{module}\" \"[resource-drop-own]{name}\" (func (param i32))) +(import \"{module}\" \"[resource-drop-borrow]{name}\" (func (param i32))) +(import \"{module}\" \"[resource-new]{name}\" (func (param i32) (result i32))) +(import \"{module}\" \"[resource-rep]{name}\" (func (param i32) (result i32))) + " + )); } } @@ -42,6 +71,19 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec { let name = func.core_export_name(Some(&name)); push_func(&mut wat, &name, resolve, func); } + + // Feign destructors for any resource that this interface + // exports + for (resource_name, ty) in resolve.interfaces[*export].types.iter() { + let ty = &resolve.types[*ty]; + match ty.kind { + TypeDefKind::Resource => {} + _ => continue, + } + wat.push_str(&format!( + "(func (export \"{name}#[dtor]{resource_name}\") (param i32))" + )); + } } WorldItem::Type(_) => {} } @@ -55,6 +97,19 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec { return wat::parse_str(&wat).unwrap(); + fn push_resource_func_imports(wat: &mut String, resolve: &Resolve, module: &str, ty: TypeId) { + let ty = &resolve.types[ty]; + match ty.kind { + TypeDefKind::Resource => {} + _ => return, + } + let name = ty.name.as_ref().unwrap(); + wat.push_str(&format!( + "(import \"{module}\" \"[resource-drop-own]{name}\"" + )); + wat.push_str(" (func (param i32)))\n"); + } + fn push_func(wat: &mut String, name: &str, resolve: &Resolve, func: &Function) { let sig = resolve.wasm_signature(AbiVariant::GuestExport, func); wat.push_str(&format!("(func (export \"{name}\")")); diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 07281456fe..e52f67456e 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -83,8 +83,8 @@ use wasm_encoder::*; use wasmparser::{Validator, WasmFeatures}; use wit_parser::{ abi::{AbiVariant, WasmSignature, WasmType}, - Function, InterfaceId, LiveTypes, Resolve, Type, TypeDefKind, TypeId, TypeOwner, WorldItem, - WorldKey, + Function, FunctionKind, InterfaceId, LiveTypes, Resolve, Type, TypeDefKind, TypeId, TypeOwner, + WorldItem, WorldKey, }; const INDIRECT_TABLE_NAME: &str = "$imports"; @@ -95,7 +95,7 @@ pub use wit::{encode, encode_component}; mod types; use types::{InstanceTypeEncoder, RootTypeEncoder, ValtypeEncoder}; mod world; -use world::{ComponentWorld, ImportedInterface}; +use world::{ComponentWorld, ImportedInterface, Lowering}; fn to_val_type(ty: &WasmType) -> ValType { match ty { @@ -361,10 +361,14 @@ pub struct EncodingState<'a> { imported_funcs: IndexMap, exported_instances: IndexMap, - /// Map of types defined within the component's root index space. - type_map: HashMap, - /// Map of function types defined within the component's root index space. - func_type_map: HashMap, u32>, + /// Maps used when translating types to the component model binary format. + /// Note that imports and exports are stored in separate maps since they + /// need fresh hierarchies of types in case the same interface is both + /// imported and exported. + import_type_map: HashMap, + import_func_type_map: HashMap, u32>, + export_type_map: HashMap, + export_func_type_map: HashMap, u32>, /// Metadata about the world inferred from the input to `ComponentEncoder`. info: &'a ComponentWorld<'a>, @@ -390,7 +394,21 @@ impl<'a> EncodingState<'a> { } } - fn root_type_encoder(&mut self, interface: Option) -> RootTypeEncoder<'_, 'a> { + fn root_import_type_encoder( + &mut self, + interface: Option, + ) -> RootTypeEncoder<'_, 'a> { + RootTypeEncoder { + state: self, + interface, + import_types: true, + } + } + + fn root_export_type_encoder( + &mut self, + interface: Option, + ) -> RootTypeEncoder<'_, 'a> { RootTypeEncoder { state: self, interface, @@ -421,9 +439,8 @@ impl<'a> EncodingState<'a> { let world = &resolve.worlds[self.info.encoder.metadata.world]; for (_name, item) in world.imports.iter() { if let WorldItem::Type(ty) = item { - let mut enc = self.root_type_encoder(None); - enc.import_types = true; - enc.encode_valtype(resolve, &Type::Id(*ty))?; + self.root_import_type_encoder(None) + .encode_valtype(resolve, &Type::Id(*ty))?; } } @@ -442,21 +459,7 @@ impl<'a> EncodingState<'a> { log::trace!("encoding imports for `{name}` as {:?}", interface_id); let mut encoder = self.instance_type_encoder(interface_id); - // Encode all required functions from this imported interface - // into the instance type. - for (_, func) in interface.functions.iter() { - if !info.required.contains(func.name.as_str()) { - continue; - } - log::trace!("encoding function type for `{}`", func.name); - let idx = encoder.encode_func_type(resolve, func)?; - - encoder.ty.export(&func.name, ComponentTypeRef::Func(idx)); - } - - // If there were any live types from this instance which weren't - // otherwise reached through the above function types then this - // will forward them through. + // First encode all type information if let Some(live) = encoder.state.info.live_types.get(&interface_id) { for ty in live { log::trace!( @@ -467,6 +470,18 @@ impl<'a> EncodingState<'a> { } } + // Next encode all required functions from this imported interface + // into the instance type. + for (_, func) in interface.functions.iter() { + if !info.lowerings.contains_key(&func.name) { + continue; + } + log::trace!("encoding function type for `{}`", func.name); + let idx = encoder.encode_func_type(resolve, func)?; + + encoder.ty.export(&func.name, ComponentTypeRef::Func(idx)); + } + let ty = encoder.ty; // Don't encode empty instance types since they're not // meaningful to the runtime of the component anyway. @@ -491,12 +506,13 @@ impl<'a> EncodingState<'a> { WorldItem::Interface(_) | WorldItem::Type(_) => continue, }; let name = resolve.name_world_key(name); - if !info.required.contains(name.as_str()) { + if !info.lowerings.contains_key(&name) { continue; } log::trace!("encoding function type for `{}`", func.name); - let mut encoder = self.root_type_encoder(None); - let idx = encoder.encode_func_type(resolve, func)?; + let idx = self + .root_import_type_encoder(None) + .encode_func_type(resolve, func)?; let func_idx = self.component.import(&name, ComponentTypeRef::Func(idx)); let prev = self.imported_funcs.insert(name, func_idx); assert!(prev.is_none()); @@ -504,29 +520,23 @@ impl<'a> EncodingState<'a> { Ok(()) } - fn index_of_type_export(&mut self, id: TypeId) -> u32 { - // Using the original `interface` definition of `id` and its name create - // an alias which refers to the type export of that instance which must - // have previously been imported. + fn alias_imported_type(&mut self, interface: InterfaceId, id: TypeId) -> u32 { let ty = &self.info.encoder.metadata.resolve.types[id]; - let interface = match ty.owner { - TypeOwner::Interface(id) => id, - _ => panic!("cannot import anonymous type across interfaces"), - }; - let name = ty - .name - .as_ref() - .expect("cannot import anonymous type across interfaces"); - let instance = self - .exported_instances - .get(&interface) - .copied() - .unwrap_or_else(|| self.imported_instances[&interface]); + let name = ty.name.as_ref().expect("type must have a name"); + let instance = self.imported_instances[&interface]; + self.component.alias_type_export(instance, name) + } + + fn alias_exported_type(&mut self, interface: InterfaceId, id: TypeId) -> u32 { + let ty = &self.info.encoder.metadata.resolve.types[id]; + let name = ty.name.as_ref().expect("type must have a name"); + let instance = self.exported_instances[&interface]; self.component.alias_type_export(instance, name) } fn encode_core_instantiation(&mut self) -> Result<()> { let info = &self.info.info; + // Encode a shim instantiation if needed let shims = self.encode_shim_instantiation(); @@ -566,6 +576,52 @@ impl<'a> EncodingState<'a> { args.push((*adapter, ModuleArg::Instance(index))); } + for (import, info) in info.required_resource_funcs.iter() { + let mut exports = Vec::new(); + for (resource, info) in info { + // Destructors for resources live on the shim module previously + // created, so if one is specified create the resource with + // the shim module that currently exists. The shim will get + // filled in later with the actual destructor after the main + // module is instantiated. + let dtor = info.dtor_export.map(|_| { + self.component.alias_core_item( + self.shim_instance_index.unwrap(), + ExportKind::Func, + &shims.shim_names[&ShimKind::ResourceDtor { import, resource }], + ) + }); + let resource_idx = self.component.resource(ValType::I32, dtor); + let prev = self.export_type_map.insert(info.id, resource_idx); + assert!(prev.is_none()); + + if let Some(name) = info.drop_own_import { + let (idx, ty) = self.component.defined_type(); + ty.own(resource_idx); + let index = self.component.resource_drop(ComponentValType::Type(idx)); + exports.push((name, ExportKind::Func, index)); + } + if let Some(name) = info.drop_borrow_import { + let (idx, ty) = self.component.defined_type(); + ty.borrow(resource_idx); + let index = self.component.resource_drop(ComponentValType::Type(idx)); + exports.push((name, ExportKind::Func, index)); + } + if let Some(name) = info.rep_import { + let index = self.component.resource_rep(resource_idx); + exports.push((name, ExportKind::Func, index)); + } + if let Some(name) = info.new_import { + let index = self.component.resource_new(resource_idx); + exports.push((name, ExportKind::Func, index)); + } + } + if !exports.is_empty() { + let index = self.component.instantiate_core_exports(exports); + args.push((import.as_str(), ModuleArg::Instance(index))); + } + } + // Instantiate the main module now that all of its arguments have been // prepared. With this we know have the main linear memory for // liftings/lowerings later on as well as the adapter modules, if any, @@ -605,50 +661,80 @@ impl<'a> EncodingState<'a> { &self.info.adapters[name].0.required_imports[core_wasm_name] } }; - let mut exports = Vec::with_capacity(import.direct.len() + import.indirect.len()); + let mut exports = Vec::with_capacity(import.lowerings.len()); - // Add an entry for all indirect lowerings which come as an export of - // the shim module. - for (i, lowering) in import.indirect.iter().enumerate() { - if !required_imports.contains(&lowering.name) { + for (index, (name, lowering)) in import.lowerings.iter().enumerate() { + if !required_imports.funcs.contains(name.as_str()) { continue; } - let encoding = - metadata.import_encodings[&(core_wasm_name.to_string(), lowering.name.to_string())]; - let index = self.component.alias_core_item( - self.shim_instance_index - .expect("shim should be instantiated"), - ExportKind::Func, - &shims.shim_names[&ShimKind::IndirectLowering { - interface: interface.clone(), - indirect_index: i, - realloc: for_module, - encoding, - }], - ); - exports.push((lowering.name, ExportKind::Func, index)); - } + let index = match lowering { + // All direct lowerings can be `canon lower`'d here immediately + // and passed as arguments. + Lowering::Direct => { + let func_index = match &import.interface { + Some(interface) => { + let instance_index = self.imported_instances[interface]; + self.component.alias_func(instance_index, name) + } + None => self.imported_funcs[name], + }; + self.component.lower_func(func_index, []) + } - // All direct lowerings can be `canon lower`'d here immediately and - // passed as arguments. - for lowering in &import.direct { - if !required_imports.contains(&lowering.name) { - continue; - } - let func_index = match &import.interface { - Some(interface) => { - let instance_index = self.imported_instances[interface]; - self.component.alias_func(instance_index, lowering.name) + // Add an entry for all indirect lowerings which come as an + // export of the shim module. + Lowering::Indirect { .. } => { + let encoding = + metadata.import_encodings[&(core_wasm_name.to_string(), name.clone())]; + self.component.alias_core_item( + self.shim_instance_index + .expect("shim should be instantiated"), + ExportKind::Func, + &shims.shim_names[&ShimKind::IndirectLowering { + interface: interface.clone(), + index, + realloc: for_module, + encoding, + }], + ) + } + + Lowering::ResourceDropOwn(id) => { + let resource_idx = self.lookup_resource_index(*id); + let (idx, ty) = self.component.defined_type(); + ty.own(resource_idx); + self.component.resource_drop(ComponentValType::Type(idx)) + } + Lowering::ResourceDropBorrow(id) => { + let resource_idx = self.lookup_resource_index(*id); + let (idx, ty) = self.component.defined_type(); + ty.borrow(resource_idx); + self.component.resource_drop(ComponentValType::Type(idx)) } - None => self.imported_funcs[lowering.name], }; - let core_func_index = self.component.lower_func(func_index, []); - exports.push((lowering.name, ExportKind::Func, core_func_index)); + exports.push((name.as_str(), ExportKind::Func, index)); } self.component.instantiate_core_exports(exports) } + fn lookup_resource_index(&mut self, id: TypeId) -> u32 { + let resolve = &self.info.encoder.metadata.resolve; + let ty = &resolve.types[id]; + match ty.owner { + // If this resource is owned by a world then it's a top-level + // resource which means it must have already been translated so + // it's available for lookup in `import_type_map`. + TypeOwner::World(_) => self.import_type_map[&id], + TypeOwner::Interface(i) => { + let instance = self.imported_instances[&i]; + let name = ty.name.as_ref().expect("resources must be named"); + self.component.alias_type_export(instance, name) + } + TypeOwner::None => panic!("resources must have an owner"), + } + } + fn encode_exports(&mut self, module: CustomModule) -> Result<()> { let resolve = &self.info.encoder.metadata.resolve; let exports = match module { @@ -660,8 +746,9 @@ impl<'a> EncodingState<'a> { let export_string = resolve.name_world_key(export_name); match &world.exports[export_name] { WorldItem::Function(func) => { - let mut enc = self.root_type_encoder(None); - let ty = enc.encode_func_type(resolve, func)?; + let ty = self + .root_import_type_encoder(None) + .encode_func_type(resolve, func)?; let core_name = func.core_export_name(None); let idx = self.encode_lift(module, &core_name, func, ty)?; self.component @@ -683,6 +770,7 @@ impl<'a> EncodingState<'a> { module: CustomModule<'_>, export: InterfaceId, ) -> Result<()> { + log::trace!("encode interface export `{export_name}`"); let resolve = &self.info.encoder.metadata.resolve; // First execute a `canon lift` for all the functions in this interface @@ -692,13 +780,13 @@ impl<'a> EncodingState<'a> { // function is saved off into an `imports` array to get imported into // the nested component synthesized below. let mut imports = Vec::new(); - let mut root = self.root_type_encoder(Some(export)); + let mut root = self.root_export_type_encoder(Some(export)); for (_, func) in &resolve.interfaces[export].functions { let core_name = func.core_export_name(Some(export_name)); let ty = root.encode_func_type(resolve, func)?; let func_index = root.state.encode_lift(module, &core_name, func, ty)?; imports.push(( - format!("import-func-{}", func.name), + import_func_name(func), ComponentExportKind::Func, func_index, )); @@ -728,6 +816,7 @@ impl<'a> EncodingState<'a> { // all the type information to the outer context. let mut types_to_import = LiveTypes::default(); types_to_import.add_interface(resolve, export); + let exports_used = &nested.state.info.exports_used[&export]; for ty in types_to_import.iter() { if let TypeOwner::Interface(owner) = resolve.types[ty].owner { if owner == export { @@ -738,10 +827,12 @@ impl<'a> EncodingState<'a> { // Ensure that `self` has encoded this type before. If so this // is a noop but otherwise it generates the type here. - nested - .state - .root_type_encoder(Some(export)) - .encode_valtype(resolve, &Type::Id(ty))?; + let mut encoder = if exports_used.contains(&owner) { + nested.state.root_export_type_encoder(Some(export)) + } else { + nested.state.root_import_type_encoder(Some(export)) + }; + encoder.encode_valtype(resolve, &Type::Id(ty))?; // Next generate the same type but this time within the // component itself. The type generated above (or prior) will be @@ -757,15 +848,31 @@ impl<'a> EncodingState<'a> { // will refer to these. let imported_types = nested.type_map.clone(); - // Next importing each function of this interface. This will end up + // Handle resource types for this instance specially, namely importing + // them into the nested component. This models how the resource is + // imported from its definition in the outer component to get reexported + // internally. This chiefly avoids creating a second resource which is + // not desired in this situation. + let mut resources = HashMap::new(); + for (_name, ty) in resolve.interfaces[export].types.iter() { + if !matches!(resolve.types[*ty].kind, TypeDefKind::Resource) { + continue; + } + let idx = match nested.encode_valtype(resolve, &Type::Id(*ty))? { + ComponentValType::Type(idx) => idx, + _ => unreachable!(), + }; + resources.insert(*ty, idx); + } + + // Next import each function of this interface. This will end up // defining local types as necessary or using the types as imported // above. for (_, func) in resolve.interfaces[export].functions.iter() { let ty = nested.encode_func_type(resolve, func)?; - nested.component.import( - &format!("import-func-{}", func.name), - ComponentTypeRef::Func(ty), - ); + nested + .component + .import(&import_func_name(func), ComponentTypeRef::Func(ty)); } // Swap the `nested.type_map` which was previously from `TypeId` to @@ -781,7 +888,17 @@ impl<'a> EncodingState<'a> { .collect::>(); for (name, idx) in nested.imports.drain(..) { let id = reverse_map[&idx]; - let idx = nested.state.type_map[&id]; + let owner = match resolve.types[id].owner { + TypeOwner::Interface(id) => id, + _ => unreachable!(), + }; + let idx = if owner == export || exports_used.contains(&owner) { + log::trace!("consulting exports for {id:?}"); + nested.state.export_type_map[&id] + } else { + log::trace!("consulting imports for {id:?}"); + nested.state.import_type_map[&id] + }; imports.push((name, ComponentExportKind::Type, idx)) } @@ -799,6 +916,32 @@ impl<'a> EncodingState<'a> { // flows through the type encoding and when types are emitted. nested.export_types = true; nested.func_type_map.clear(); + + // To start off all type information is encoded. This will be used by + // functions below but notably this also has special handling for + // resources. Resources reexport their imported resource type under + // the final name which achieves the desired goal of threading through + // the original resource without creating a new one. + if let Some(live) = nested.state.info.live_types.get(&export) { + for id in live { + let ty = &resolve.types[*id]; + match ty.kind { + TypeDefKind::Resource => { + let idx = nested.component.export( + ty.name.as_ref().expect("resources must be named"), + ComponentExportKind::Type, + resources[id], + None, + ); + nested.type_map.insert(*id, idx); + } + _ => { + nested.encode_valtype(resolve, &Type::Id(*id))?; + } + } + } + } + for (i, (_, func)) in resolve.interfaces[export].functions.iter().enumerate() { let ty = nested.encode_func_type(resolve, func)?; nested.component.export( @@ -808,14 +951,6 @@ impl<'a> EncodingState<'a> { Some(ComponentTypeRef::Func(ty)), ); } - // Be sure that if any live types are needed from this interface that - // they're encoded. This will pick up any stragglers that weren't - // already encoded through exported functions. - if let Some(live) = nested.state.info.live_types.get(&export) { - for ty in live { - nested.encode_valtype(resolve, &Type::Id(*ty))?; - } - } // Embed the component within our component and then instantiate it with // the lifted functions. That final instance is then exported under the @@ -833,6 +968,18 @@ impl<'a> EncodingState<'a> { ); let prev = self.exported_instances.insert(export, idx); assert!(prev.is_none()); + + // After everything is all said and done remove all the type information + // about type exports of this interface. Any entries in the map + // currently were used to create the instance above but aren't the + // actual copy of the exported type since that comes from the exported + // instance itself. Entries will be re-inserted into this map as + // necessary via aliases from the exported instance which is the new + // source of truth for all these types. + for (_name, id) in resolve.interfaces[export].types.iter() { + self.export_type_map.remove(id); + } + return Ok(()); struct NestedComponentTypeEncoder<'state, 'a> { @@ -859,13 +1006,7 @@ impl<'a> EncodingState<'a> { .export(name, ComponentExportKind::Type, idx, None), ) } else { - let base = format!("import-type-{name}"); - let mut name = base.clone(); - let mut n = 0; - while self.imports.contains_key(&name) { - name = format!("{name}{n}"); - n += 1; - } + let name = self.unique_import_name(name); let ret = self .component .import(&name, ComponentTypeRef::Type(TypeBounds::Eq(idx))); @@ -873,6 +1014,18 @@ impl<'a> EncodingState<'a> { Some(ret) } } + fn export_resource(&mut self, name: &'a str) -> u32 { + if self.export_types { + panic!("resources should already be exported") + } else { + let name = self.unique_import_name(name); + let ret = self + .component + .import(&name, ComponentTypeRef::Type(TypeBounds::SubResource)); + self.imports.insert(name, ret); + ret + } + } fn import_type(&mut self, _: InterfaceId, _id: TypeId) -> u32 { unreachable!() } @@ -886,6 +1039,43 @@ impl<'a> EncodingState<'a> { Some(self.interface) } } + + impl NestedComponentTypeEncoder<'_, '_> { + fn unique_import_name(&mut self, name: &str) -> String { + let base = format!("import-type-{name}"); + let mut name = base.clone(); + let mut n = 0; + while self.imports.contains_key(&name) { + name = format!("{name}{n}"); + n += 1; + } + name + } + } + + fn import_func_name(f: &Function) -> String { + match f.kind { + FunctionKind::Freestanding => { + format!("import-func-{}", f.name) + } + + // transform `[method]foo.bar` into `import-method-foo-bar` to + // have it be a valid kebab-name which can't conflict with + // anything else. + // + // There's probably a better and more "formal" way to do this + // but quick-and-dirty string manipulation should work well + // enough for now hopefully. + FunctionKind::Method(_) + | FunctionKind::Static(_) + | FunctionKind::Constructor(_) => { + format!( + "import-{}", + f.name.replace("[", "").replace("]", "-").replace(".", "-") + ) + } + } + } } fn encode_lift( @@ -954,7 +1144,7 @@ impl<'a> EncodingState<'a> { core_wasm_name, CustomModule::Main, import, - required, + &required.funcs, info.metadata, &mut signatures, ); @@ -970,7 +1160,7 @@ impl<'a> EncodingState<'a> { name, CustomModule::Adapter(adapter), import, - required, + &required.funcs, info.metadata, &mut signatures, ); @@ -999,6 +1189,33 @@ impl<'a> EncodingState<'a> { }); } } + + // Any resource destructors are encoded through the shim module. The + // core wasm probably imports resource intrinsics which requires the + // resource definition, but the resource definition requires + // the destructor to be available. The shim module breaks this + // circular dependency. + for (import, info) in self.info.info.required_resource_funcs.iter() { + for (resource, info) in info { + if info.dtor_export.is_none() { + continue; + } + signatures.push(WasmSignature { + params: vec![WasmType::I32], + results: Vec::new(), + indirect_params: false, + retptr: false, + }); + let name = ret.list.len().to_string(); + ret.list.push(Shim { + name, + debug_name: format!("dtor-{import}-{resource}"), + options: RequiredOptions::empty(), + kind: ShimKind::ResourceDtor { import, resource }, + }); + } + } + if ret.list.is_empty() { return ret; } @@ -1153,12 +1370,12 @@ impl<'a> EncodingState<'a> { // memory always comes from the main module. ShimKind::IndirectLowering { interface, - indirect_index, + index, realloc, encoding, } => { let interface = &self.info.import_map[interface]; - let name = interface.indirect[*indirect_index].name; + let (name, _) = interface.lowerings.get_index(*index).unwrap(); let func_index = match &interface.interface { Some(interface_id) => { let instance_index = self.imported_instances[interface_id]; @@ -1188,6 +1405,18 @@ impl<'a> EncodingState<'a> { ExportKind::Func, func, ), + + // Resources are required for a module to be instantiated + // meaning that any destructor for the resource must be called + // indirectly due to the otherwise circular dependency between + // the module and the resource itself. + ShimKind::ResourceDtor { import, resource } => self.component.alias_core_item( + self.instance_index.unwrap(), + ExportKind::Func, + self.info.info.required_resource_funcs[*import][*resource] + .dtor_export + .unwrap(), + ), }; exports.push((shim.name.as_str(), ExportKind::Func, core_func_index)); @@ -1357,8 +1586,8 @@ enum ShimKind<'a> { IndirectLowering { /// The name of the interface that's being lowered. interface: Option, - /// The index within the `indirect` array of the function being lowered. - indirect_index: usize, + /// The index within the `lowerings` array of the function being lowered. + index: usize, /// Which instance to pull the `realloc` function from, if necessary. realloc: CustomModule<'a>, /// The string encoding that this lowering is going to use. @@ -1372,6 +1601,14 @@ enum ShimKind<'a> { /// The name of the export in the adapter module this shim points to. func: &'a str, }, + /// A shim used as the destructor for a resource which allows defining the + /// resource before the core module being instantiated. + ResourceDtor { + /// The import that the resource was defined for. + import: &'a str, + /// The name of the resource being destroyed. + resource: &'a str, + }, } /// Indicator for which module is being used for a lowering or where options @@ -1400,8 +1637,8 @@ impl<'a> Shims<'a> { &mut self, core_wasm_module: &'a str, for_module: CustomModule<'a>, - import: &ImportedInterface<'a>, - required: &IndexSet<&str>, + import: &ImportedInterface, + required: &IndexSet, metadata: &ModuleMetadata, sigs: &mut Vec, ) { @@ -1410,29 +1647,36 @@ impl<'a> Shims<'a> { } else { Some(core_wasm_module.to_string()) }; - for (indirect_index, lowering) in import.indirect.iter().enumerate() { - if !required.contains(&lowering.name) { + for (index, (name, lowering)) in import.lowerings.iter().enumerate() { + if !required.contains(name.as_str()) { continue; } let shim_name = self.list.len().to_string(); log::debug!( - "shim {shim_name} is import `{core_wasm_module}` lowering {indirect_index} `{}`", - lowering.name + "shim {shim_name} is import `{core_wasm_module}` lowering {index} `{name}`", ); - sigs.push(lowering.sig.clone()); - let encoding = metadata.import_encodings - [&(core_wasm_module.to_string(), lowering.name.to_string())]; - self.list.push(Shim { - name: shim_name, - debug_name: format!("indirect-{core_wasm_module}-{}", lowering.name), - options: lowering.options, - kind: ShimKind::IndirectLowering { - interface: interface.clone(), - indirect_index, - realloc: for_module, - encoding, - }, - }); + match lowering { + Lowering::Direct + | Lowering::ResourceDropOwn(_) + | Lowering::ResourceDropBorrow(_) => {} + + Lowering::Indirect { sig, options } => { + sigs.push(sig.clone()); + let encoding = + metadata.import_encodings[&(core_wasm_module.to_string(), name.clone())]; + self.list.push(Shim { + name: shim_name, + debug_name: format!("indirect-{core_wasm_module}-{name}"), + options: *options, + kind: ShimKind::IndirectLowering { + interface: interface.clone(), + index, + realloc: for_module, + encoding, + }, + }); + } + } } } } @@ -1555,8 +1799,10 @@ impl ComponentEncoder { adapter_instances: IndexMap::new(), adapter_import_reallocs: IndexMap::new(), adapter_export_reallocs: IndexMap::new(), - type_map: HashMap::new(), - func_type_map: HashMap::new(), + import_type_map: HashMap::new(), + import_func_type_map: HashMap::new(), + export_type_map: HashMap::new(), + export_func_type_map: HashMap::new(), imported_instances: Default::default(), imported_funcs: Default::default(), exported_instances: Default::default(), diff --git a/crates/wit-component/src/encoding/types.rs b/crates/wit-component/src/encoding/types.rs index 6f388f0ad7..4e03715925 100644 --- a/crates/wit-component/src/encoding/types.rs +++ b/crates/wit-component/src/encoding/types.rs @@ -3,8 +3,8 @@ use anyhow::Result; use std::collections::HashMap; use wasm_encoder::*; use wit_parser::{ - Enum, Flags, Function, InterfaceId, Params, Record, Resolve, Result_, Results, Tuple, Type, - TypeDefKind, TypeId, TypeOwner, Union, Variant, + Enum, Flags, Function, Handle, InterfaceId, Params, Record, Resolve, Result_, Results, Tuple, + Type, TypeDefKind, TypeId, TypeOwner, Union, Variant, }; /// Represents a key type for interface function definitions. @@ -36,6 +36,10 @@ pub trait ValtypeEncoder<'a> { /// Creates an export item for the specified type index. fn export_type(&mut self, index: u32, name: &'a str) -> Option; + /// Creates a new `(type (sub resource))` export with the given name, + /// returning the type index that refers to the fresh type created. + fn export_resource(&mut self, name: &'a str) -> u32; + /// Returns a map of all types previously defined in this type index space. fn type_map(&mut self) -> &mut HashMap; @@ -153,8 +157,30 @@ pub trait ValtypeEncoder<'a> { TypeDefKind::Future(_) => todo!("encoding for future type"), TypeDefKind::Stream(_) => todo!("encoding for stream type"), TypeDefKind::Unknown => unreachable!(), - TypeDefKind::Resource => todo!(), - TypeDefKind::Handle(_) => todo!(), + TypeDefKind::Resource => { + let name = ty.name.as_ref().expect("resources must be named"); + let index = self.export_resource(name); + self.type_map().insert(id, index); + return Ok(ComponentValType::Type(index)); + } + TypeDefKind::Handle(Handle::Own(id)) => { + let ty = match self.encode_valtype(resolve, &Type::Id(*id))? { + ComponentValType::Type(index) => index, + _ => panic!("must be an indexed type"), + }; + let (index, encoder) = self.defined_type(); + encoder.own(ty); + ComponentValType::Type(index) + } + TypeDefKind::Handle(Handle::Borrow(id)) => { + let ty = match self.encode_valtype(resolve, &Type::Id(*id))? { + ComponentValType::Type(index) => index, + _ => panic!("must be an indexed type"), + }; + let (index, encoder) = self.defined_type(); + encoder.borrow(ty); + ComponentValType::Type(index) + } }; if let Some(name) = &ty.name { @@ -335,14 +361,37 @@ impl<'a> ValtypeEncoder<'a> for RootTypeEncoder<'_, 'a> { None } } - fn import_type(&mut self, _: InterfaceId, id: TypeId) -> u32 { - self.state.index_of_type_export(id) + fn export_resource(&mut self, name: &'a str) -> u32 { + assert!(self.interface.is_none()); + assert!(self.import_types); + self.state + .component + .import(name, ComponentTypeRef::Type(TypeBounds::SubResource)) + } + fn import_type(&mut self, interface: InterfaceId, id: TypeId) -> u32 { + if !self.import_types { + if let Some(cur) = self.interface { + let set = &self.state.info.exports_used[&cur]; + if set.contains(&interface) { + return self.state.alias_exported_type(interface, id); + } + } + } + self.state.alias_imported_type(interface, id) } fn type_map(&mut self) -> &mut HashMap { - &mut self.state.type_map + if self.import_types { + &mut self.state.import_type_map + } else { + &mut self.state.export_type_map + } } fn func_type_map(&mut self) -> &mut HashMap, u32> { - &mut self.state.func_type_map + if self.import_types { + &mut self.state.import_func_type_map + } else { + &mut self.state.export_func_type_map + } } } @@ -367,16 +416,22 @@ impl<'a> ValtypeEncoder<'a> for InstanceTypeEncoder<'_, 'a> { .export(name, ComponentTypeRef::Type(TypeBounds::Eq(idx))); Some(ret) } + fn export_resource(&mut self, name: &str) -> u32 { + let ret = self.ty.type_count(); + self.ty + .export(name, ComponentTypeRef::Type(TypeBounds::SubResource)); + ret + } fn type_map(&mut self) -> &mut HashMap { &mut self.type_map } fn interface(&self) -> Option { Some(self.interface) } - fn import_type(&mut self, _: InterfaceId, id: TypeId) -> u32 { + fn import_type(&mut self, interface: InterfaceId, id: TypeId) -> u32 { self.ty.alias(Alias::Outer { count: 1, - index: self.state.index_of_type_export(id), + index: self.state.alias_imported_type(interface, id), kind: ComponentOuterAliasKind::Type, }); self.ty.type_count() - 1 diff --git a/crates/wit-component/src/encoding/wit.rs b/crates/wit-component/src/encoding/wit.rs index 8a3ecd6aad..b0bccb9ad0 100644 --- a/crates/wit-component/src/encoding/wit.rs +++ b/crates/wit-component/src/encoding/wit.rs @@ -111,7 +111,29 @@ impl Encoder<'_> { let mut component = InterfaceEncoder::new(self.resolve); log::trace!("encoding world {}", world.name); - for (name, import) in world.imports.iter() { + // This sort is similar in purpose to the sort below in + // `encode_instance`, but different in its sort. The purpose here is + // to ensure that when a document is either printed as WIT or + // encoded as wasm that decoding from those artifacts produces the + // same WIT package. Namely both encoding processes should encode + // things in the same order. + // + // When printing worlds in WIT freestanding function imports are + // printed first, then types. Resource functions are attached to + // types which means that they all come last. Sort all + // resource-related functions here to the back of the `imports` list + // while keeping everything else in front, using a stable sort to + // preserve preexisting ordering. + let mut imports = world.imports.iter().collect::>(); + imports.sort_by_key(|(_name, import)| match import { + WorldItem::Function(f) => match f.kind { + FunctionKind::Freestanding => 0, + _ => 1, + }, + _ => 0, + }); + + for (name, import) in imports { let name = self.resolve.name_world_key(name); log::trace!("encoding import {name}"); let ty = match import { @@ -229,10 +251,36 @@ impl InterfaceEncoder<'_> { fn encode_instance(&mut self, interface: InterfaceId) -> Result { self.push_instance(); let iface = &self.resolve.interfaces[interface]; + let mut type_order = IndexSet::new(); for (_, id) in iface.types.iter() { self.encode_valtype(self.resolve, &Type::Id(*id))?; + type_order.insert(*id); } - for (name, func) in iface.functions.iter() { + + // Sort functions based on whether or not they're associated with + // resources. + // + // This is done here to ensure that when a WIT package is printed as WIT + // then decoded, or if it's printed as Wasm then decoded, the final + // result is the same. When printing via WIT resource methods are + // attached to the resource types themselves meaning that they'll appear + // intermingled with the rest of the types, namely first before all + // other functions. The purpose of this sort is to perform a stable sort + // over all functions by shuffling the resource-related functions first, + // in order of when their associated resource was encoded, and putting + // freestanding functions last. + // + // Note that this is not actually required for correctness, it's + // basically here to make fuzzing happy. + let mut funcs = iface.functions.iter().collect::>(); + funcs.sort_by_key(|(_name, func)| match func.kind { + FunctionKind::Freestanding => type_order.len(), + FunctionKind::Method(id) | FunctionKind::Constructor(id) | FunctionKind::Static(id) => { + type_order.get_index_of(&id).unwrap() + } + }); + + for (name, func) in funcs { let ty = self.encode_func_type(self.resolve, func)?; self.ty .as_mut() @@ -299,6 +347,24 @@ impl<'a> ValtypeEncoder<'a> for InterfaceEncoder<'a> { } } } + fn export_resource(&mut self, name: &'a str) -> u32 { + let type_ref = ComponentTypeRef::Type(TypeBounds::SubResource); + match &mut self.ty { + Some(ty) => { + assert!(!self.import_types); + ty.export(name, type_ref); + ty.type_count() - 1 + } + None => { + if self.import_types { + self.outer.import(name, type_ref); + } else { + self.outer.export(name, type_ref); + } + self.outer.type_count() - 1 + } + } + } fn type_map(&mut self) -> &mut HashMap { &mut self.type_map } diff --git a/crates/wit-component/src/encoding/world.rs b/crates/wit-component/src/encoding/world.rs index 7c57d9b749..5d6796cb18 100644 --- a/crates/wit-component/src/encoding/world.rs +++ b/crates/wit-component/src/encoding/world.rs @@ -1,17 +1,18 @@ use super::{ComponentEncoder, RequiredOptions}; use crate::validation::{ - validate_adapter_module, validate_module, ValidatedAdapter, ValidatedModule, - BARE_FUNC_MODULE_NAME, + validate_adapter_module, validate_module, RequiredImports, ValidatedAdapter, ValidatedModule, + BARE_FUNC_MODULE_NAME, RESOURCE_DROP_BORROW, RESOURCE_DROP_OWN, }; use anyhow::{Context, Result}; use indexmap::{IndexMap, IndexSet}; use std::borrow::Borrow; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::hash::Hash; use wasmparser::FuncType; use wit_parser::{ abi::{AbiVariant, WasmSignature, WasmType}, - Function, InterfaceId, LiveTypes, Resolve, TypeId, TypeOwner, WorldId, WorldItem, WorldKey, + Function, InterfaceId, LiveTypes, Resolve, Type, TypeDefKind, TypeId, TypeOwner, WorldId, + WorldItem, WorldKey, }; /// Metadata discovered from the state configured in a `ComponentEncoder`. @@ -29,32 +30,33 @@ pub struct ComponentWorld<'a> { /// adapters. Additionally stores the gc'd wasm for each adapter. pub adapters: IndexMap<&'a str, (ValidatedAdapter<'a>, Vec)>, /// Map of all imports and descriptions of what they're importing. - pub import_map: IndexMap, ImportedInterface<'a>>, + pub import_map: IndexMap, ImportedInterface>, /// Set of all live types which must be exported either because they're /// directly used or because they're transitively used. pub live_types: IndexMap>, + /// For each exported interface in the desired world this map lists + /// the set of interfaces that it depends on which are also exported. + /// + /// This set is used to determine when types are imported/used whether they + /// come from imports or exports. + pub exports_used: HashMap>, } #[derive(Debug)] -pub struct ImportedInterface<'a> { - pub direct: Vec>, - pub indirect: Vec>, - /// Required functions on the interface, or the filter on the functions list - /// in `interface`. - pub required: HashSet<&'a str>, +pub struct ImportedInterface { + pub lowerings: IndexMap, pub interface: Option, } #[derive(Debug)] -pub struct DirectLowering<'a> { - pub name: &'a str, -} - -#[derive(Debug)] -pub struct IndirectLowering<'a> { - pub name: &'a str, - pub sig: WasmSignature, - pub options: RequiredOptions, +pub enum Lowering { + Direct, + Indirect { + sig: WasmSignature, + options: RequiredOptions, + }, + ResourceDropOwn(TypeId), + ResourceDropBorrow(TypeId), } impl<'a> ComponentWorld<'a> { @@ -77,11 +79,13 @@ impl<'a> ComponentWorld<'a> { adapters: IndexMap::new(), import_map: IndexMap::new(), live_types: Default::default(), + exports_used: HashMap::new(), }; ret.process_adapters()?; ret.process_imports()?; ret.process_live_types(); + ret.process_exports_used(); Ok(ret) } @@ -175,14 +179,14 @@ impl<'a> ComponentWorld<'a> { all_required_imports .entry(k.as_str()) .or_insert_with(IndexSet::new) - .extend(v); + .extend(v.funcs.iter().map(|v| v.as_str())); } } for (k, v) in self.info.required_imports.iter() { all_required_imports .entry(*k) .or_insert_with(IndexSet::new) - .extend(v); + .extend(v.funcs.iter().map(|v| v.as_str())); } for (name, item) in resolve.worlds[world].imports.iter() { add_item( @@ -195,77 +199,50 @@ impl<'a> ComponentWorld<'a> { } return Ok(()); - fn add_item<'a>( - import_map: &mut IndexMap, ImportedInterface<'a>>, - resolve: &'a Resolve, + fn add_item( + import_map: &mut IndexMap, ImportedInterface>, + resolve: &Resolve, name: &WorldKey, - item: &'a WorldItem, + item: &WorldItem, required: &IndexMap<&str, IndexSet<&str>>, ) -> Result<()> { let name = resolve.name_world_key(name); log::trace!("register import `{name}`"); let empty = IndexSet::new(); + let import_map_key = match item { + WorldItem::Function(_) | WorldItem::Type(_) => None, + WorldItem::Interface(_) => Some(name), + }; + let interface_id = match item { + WorldItem::Function(_) | WorldItem::Type(_) => None, + WorldItem::Interface(id) => Some(*id), + }; + let required = required + .get(import_map_key.as_deref().unwrap_or(BARE_FUNC_MODULE_NAME)) + .unwrap_or(&empty); + let interface = import_map + .entry(import_map_key) + .or_insert_with(|| ImportedInterface { + interface: interface_id, + lowerings: Default::default(), + }); + assert_eq!(interface.interface, interface_id); match item { WorldItem::Function(func) => { - let required = required.get(BARE_FUNC_MODULE_NAME).unwrap_or(&empty); - // If this function isn't actually required then skip it - if !required.contains(name.as_str()) { - return Ok(()); - } - let interface = import_map.entry(None).or_insert_with(|| ImportedInterface { - interface: None, - direct: Default::default(), - indirect: Default::default(), - required: Default::default(), - }); - assert!(interface.interface.is_none()); - add_import(interface, resolve, func) + interface.add_func(required, resolve, func); + } + WorldItem::Type(ty) => { + interface.add_type(required, resolve, *ty); } WorldItem::Interface(id) => { - let required = required.get(name.as_str()).unwrap_or(&empty); - let interface = - import_map - .entry(Some(name)) - .or_insert_with(|| ImportedInterface { - interface: Some(*id), - direct: Default::default(), - indirect: Default::default(), - required: Default::default(), - }); - assert_eq!(interface.interface.unwrap(), *id); + for (_name, ty) in resolve.interfaces[*id].types.iter() { + interface.add_type(required, resolve, *ty); + } for (_name, func) in resolve.interfaces[*id].functions.iter() { - // If this function isn't actually required then skip it - if required.contains(func.name.as_str()) { - add_import(interface, resolve, func)?; - } + interface.add_func(required, resolve, func); } - Ok(()) } - WorldItem::Type(_) => Ok(()), - } - } - - fn add_import<'a>( - interface: &mut ImportedInterface<'a>, - resolve: &'a Resolve, - func: &'a Function, - ) -> Result<()> { - if !interface.required.insert(func.name.as_str()) { - return Ok(()); - } - log::trace!("add func {}", func.name); - let options = RequiredOptions::for_import(resolve, func); - if options.is_empty() { - interface.direct.push(DirectLowering { name: &func.name }); - } else { - let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); - interface.indirect.push(IndirectLowering { - name: &func.name, - sig, - options, - }); } - Ok(()) } } @@ -302,7 +279,7 @@ impl<'a> ComponentWorld<'a> { fn add_live_imports( &self, world: WorldId, - required: &IndexMap>, + required: &IndexMap, live: &mut LiveTypes, ) where S: Borrow + Hash + Eq, @@ -316,7 +293,7 @@ impl<'a> ComponentWorld<'a> { Some(set) => set, None => continue, }; - if !required.contains(name.as_str()) { + if !required.funcs.contains(name.as_str()) { continue; } log::trace!("add live function import `{name}`"); @@ -329,14 +306,109 @@ impl<'a> ComponentWorld<'a> { }; log::trace!("add live interface import `{name}`"); for (name, func) in resolve.interfaces[*id].functions.iter() { - if required.contains(name.as_str()) { + if required.funcs.contains(name.as_str()) { log::trace!("add live func `{name}`"); live.add_func(resolve, func); } } + for (name, ty) in resolve.interfaces[*id].types.iter() { + if required.resources.contains(name.as_str()) { + live.add_type_id(resolve, *ty); + } + } } WorldItem::Type(id) => live.add_type_id(resolve, *id), } } } + + fn process_exports_used(&mut self) { + let resolve = &self.encoder.metadata.resolve; + let world = self.encoder.metadata.world; + + let exports = &resolve.worlds[world].exports; + for (_name, item) in exports.iter() { + let id = match item { + WorldItem::Function(_) => continue, + WorldItem::Interface(id) => *id, + WorldItem::Type(_) => unreachable!(), + }; + let mut set = HashSet::new(); + + for (_name, ty) in resolve.interfaces[id].types.iter() { + // Find `other` which `ty` is defined within to determine which + // interfaces this interface depends on. + let dep = match resolve.types[*ty].kind { + TypeDefKind::Type(Type::Id(id)) => id, + _ => continue, + }; + let other = match resolve.types[dep].owner { + TypeOwner::Interface(id) => id, + _ => continue, + }; + if other == id { + continue; + } + + // If this dependency is not exported, then it'll show up + // through an import, so we're not interested in it. + if !exports.contains_key(&WorldKey::Interface(other)) { + continue; + } + + // Otherwise this is a new exported dependency of ours, and + // additionally this interface inherits all the transitive + // dependencies too. + if set.insert(other) { + set.extend(self.exports_used[&other].iter().copied()); + } + } + let prev = self.exports_used.insert(id, set); + assert!(prev.is_none()); + } + } +} + +impl ImportedInterface { + fn add_func(&mut self, required: &IndexSet<&str>, resolve: &Resolve, func: &Function) { + if !required.contains(func.name.as_str()) { + return; + } + log::trace!("add func {}", func.name); + let options = RequiredOptions::for_import(resolve, func); + let lowering = if options.is_empty() { + Lowering::Direct + } else { + let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); + Lowering::Indirect { sig, options } + }; + + let prev = self.lowerings.insert(func.name.clone(), lowering); + assert!(prev.is_none()); + } + + fn add_type(&mut self, required: &IndexSet<&str>, resolve: &Resolve, id: TypeId) { + let ty = &resolve.types[id]; + match &ty.kind { + TypeDefKind::Resource => {} + _ => return, + } + let name = ty.name.as_deref().expect("resources must be named"); + + let mut maybe_add = |name: String, lowering: Lowering| { + if !required.contains(name.as_str()) { + return; + } + let prev = self.lowerings.insert(name, lowering); + assert!(prev.is_none()); + }; + maybe_add( + format!("{RESOURCE_DROP_OWN}{name}"), + Lowering::ResourceDropOwn(id), + ); + maybe_add( + format!("{RESOURCE_DROP_BORROW}{name}"), + Lowering::ResourceDropBorrow(id), + ); + } } diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 216d01d968..90d95a9aad 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, bail, Result}; +use std::collections::HashMap; use std::fmt::{self, Write}; use std::mem; use wit_parser::*; @@ -56,6 +57,16 @@ impl WitPrinter { let prev_items = mem::replace(&mut self.any_items, false); let interface = &resolve.interfaces[id]; + let mut resource_funcs = HashMap::new(); + let mut freestanding = Vec::new(); + for (name, func) in interface.functions.iter() { + if let Some(id) = resource_func(func) { + resource_funcs.entry(id).or_insert(Vec::new()).push(func); + } else { + freestanding.push((name, func)); + } + } + self.print_types( resolve, TypeOwner::Interface(id), @@ -63,9 +74,10 @@ impl WitPrinter { .types .iter() .map(|(name, id)| (name.as_str(), *id)), + &resource_funcs, )?; - for (name, func) in interface.functions.iter() { + for (name, func) in freestanding { self.new_item(); self.print_name(name); self.output.push_str(": "); @@ -83,6 +95,7 @@ impl WitPrinter { resolve: &Resolve, owner: TypeOwner, types: impl Iterator, + resource_funcs: &HashMap>, ) -> Result<()> { // Partition types defined in this interface into either those imported // from foreign interfaces or those defined locally. @@ -149,15 +162,63 @@ impl WitPrinter { for id in types_to_declare { self.new_item(); - self.declare_type(resolve, &Type::Id(id))?; + match resolve.types[id].kind { + TypeDefKind::Resource => self.print_resource( + resolve, + id, + resource_funcs.get(&id).unwrap_or(&Vec::new()), + )?, + _ => self.declare_type(resolve, &Type::Id(id))?, + } } Ok(()) } + fn print_resource(&mut self, resolve: &Resolve, id: TypeId, funcs: &[&Function]) -> Result<()> { + let ty = &resolve.types[id]; + self.output.push_str("resource "); + self.print_name(ty.name.as_ref().expect("resources must be named")); + if funcs.is_empty() { + self.output.push_str("\n"); + return Ok(()); + } + self.output.push_str(" {\n"); + for func in funcs { + match &func.kind { + FunctionKind::Constructor(_) => {} + FunctionKind::Method(_) => { + self.print_name(func.item_name()); + self.output.push_str(": "); + } + FunctionKind::Static(_) => { + self.output.push_str("static "); + self.print_name(func.item_name()); + self.output.push_str(": "); + } + FunctionKind::Freestanding => unreachable!(), + } + self.print_function(resolve, func)?; + self.output.push_str("\n"); + } + self.output.push_str("}\n"); + + Ok(()) + } + fn print_function(&mut self, resolve: &Resolve, func: &Function) -> Result<()> { - self.output.push_str("func("); - for (i, (name, ty)) in func.params.iter().enumerate() { + // Constructors are named slightly differently. + match &func.kind { + FunctionKind::Constructor(_) => self.output.push_str("constructor("), + _ => self.output.push_str("func("), + } + + // Methods don't print their `self` argument + let params_to_skip = match &func.kind { + FunctionKind::Method(_) => 1, + _ => 0, + }; + for (i, (name, ty)) in func.params.iter().skip(params_to_skip).enumerate() { if i > 0 { self.output.push_str(", "); } @@ -167,6 +228,11 @@ impl WitPrinter { } self.output.push_str(")"); + // constructors don't have their results printed + if let FunctionKind::Constructor(_) = func.kind { + return Ok(()); + } + match &func.results { Results::Named(rs) => match rs.len() { 0 => (), @@ -196,6 +262,7 @@ impl WitPrinter { let world = &resolve.worlds[id]; let pkgid = world.package.unwrap(); let mut types = Vec::new(); + let mut resource_funcs = HashMap::new(); for (name, import) in world.imports.iter() { match import { WorldItem::Type(t) => match name { @@ -203,6 +270,12 @@ impl WitPrinter { WorldKey::Interface(_) => unreachable!(), }, _ => { + if let WorldItem::Function(f) = import { + if let Some(id) = resource_func(f) { + resource_funcs.entry(id).or_insert(Vec::new()).push(f); + continue; + } + } self.print_world_item(resolve, name, import, pkgid, "import")?; // Don't put a blank line between imports, but count // imports as having printed something so if anything comes @@ -211,7 +284,12 @@ impl WitPrinter { } } } - self.print_types(resolve, TypeOwner::World(id), types.into_iter())?; + self.print_types( + resolve, + TypeOwner::World(id), + types.into_iter(), + &resource_funcs, + )?; if !world.exports.is_empty() { self.new_item(); } @@ -311,7 +389,7 @@ impl WitPrinter { match &ty.kind { TypeDefKind::Handle(h) => { - self.print_handle_type(resolve, h)?; + self.print_handle_type(resolve, h, false)?; } TypeDefKind::Resource => { bail!("resolve has an unnamed resource type"); @@ -360,17 +438,26 @@ impl WitPrinter { Ok(()) } - fn print_handle_type(&mut self, resolve: &Resolve, handle: &Handle) -> Result<()> { + fn print_handle_type( + &mut self, + resolve: &Resolve, + handle: &Handle, + force_handle_type_printed: bool, + ) -> Result<()> { match handle { Handle::Own(ty) => { - self.output.push_str("own<"); let ty = &resolve.types[*ty]; + if force_handle_type_printed { + self.output.push_str("own<"); + } self.print_name( ty.name .as_ref() .ok_or_else(|| anyhow!("unnamed resource type"))?, ); - self.output.push_str(">"); + if force_handle_type_printed { + self.output.push_str(">"); + } } Handle::Borrow(ty) => { @@ -468,7 +555,7 @@ impl WitPrinter { TypeDefKind::Handle(h) => { self.declare_handle(resolve, ty.name.as_deref(), h)? } - TypeDefKind::Resource => self.declare_resource(resolve, ty.name.as_deref())?, + TypeDefKind::Resource => panic!("resources should be processed separately"), TypeDefKind::Record(r) => { self.declare_record(resolve, ty.name.as_deref(), r)? } @@ -515,9 +602,15 @@ impl WitPrinter { ) -> Result<()> { match name { Some(name) => { + self.output.push_str("type "); self.print_name(name); self.output.push_str(" = "); - self.print_handle_type(resolve, handle)?; + // Note that the `true` here forces owned handles to be printed + // as `own`. The purpose of this is because `type a = b`, if + // `b` is a resource, is encoded differently as `type a = + // own`. By forcing a handle to be printed here it's staying + // true to what's in the WIT document. + self.print_handle_type(resolve, handle, true)?; self.output.push_str("\n"); Ok(()) @@ -526,18 +619,6 @@ impl WitPrinter { } } - fn declare_resource(&mut self, _resolve: &Resolve, name: Option<&str>) -> Result<()> { - match name { - Some(name) => { - self.output.push_str("resource "); - self.print_name(name); - self.output.push_str("\n\n"); - Ok(()) - } - None => bail!("document has unnamed resource type"), - } - } - fn declare_record( &mut self, resolve: &Resolve, @@ -712,13 +793,22 @@ impl WitPrinter { } } +fn resource_func(f: &Function) -> Option { + match f.kind { + FunctionKind::Freestanding => None, + FunctionKind::Method(id) | FunctionKind::Constructor(id) | FunctionKind::Static(id) => { + Some(id) + } + } +} + fn is_keyword(name: &str) -> bool { match name { "use" | "type" | "func" | "u8" | "u16" | "u32" | "u64" | "s8" | "s16" | "s32" | "s64" | "float32" | "float64" | "char" | "resource" | "record" | "flags" | "variant" | "enum" | "union" | "bool" | "string" | "option" | "result" | "future" | "stream" | "list" | "own" | "borrow" | "_" | "as" | "from" | "static" | "interface" | "tuple" | "world" - | "import" | "export" | "package" | "with" => true, + | "import" | "export" | "package" | "with" | "include" | "constructor" => true, _ => false, } } diff --git a/crates/wit-component/src/validation.rs b/crates/wit-component/src/validation.rs index a755ba247f..92ee6e4e8f 100644 --- a/crates/wit-component/src/validation.rs +++ b/crates/wit-component/src/validation.rs @@ -1,5 +1,5 @@ use crate::metadata::{Bindgen, ModuleMetadata}; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{bail, Context, Result}; use indexmap::{map::Entry, IndexMap, IndexSet}; use wasmparser::names::{KebabName, KebabNameKind}; use wasmparser::{ @@ -8,7 +8,7 @@ use wasmparser::{ }; use wit_parser::{ abi::{AbiVariant, WasmSignature, WasmType}, - Function, InterfaceId, PackageName, Resolve, WorldId, WorldItem, WorldKey, + Function, InterfaceId, PackageName, Resolve, TypeDefKind, TypeId, WorldId, WorldItem, WorldKey, }; fn is_canonical_function(name: &str) -> bool { @@ -40,6 +40,11 @@ pub const MAIN_MODULE_IMPORT_NAME: &str = "__main_module__"; /// turns into `env`. pub const BARE_FUNC_MODULE_NAME: &str = "$root"; +pub const RESOURCE_DROP_OWN: &str = "[resource-drop-own]"; +pub const RESOURCE_DROP_BORROW: &str = "[resource-drop-borrow]"; +pub const RESOURCE_REP: &str = "[resource-rep]"; +pub const RESOURCE_NEW: &str = "[resource-new]"; + /// Metadata about a validated module and what was found internally. /// /// All imports to the module are described by the union of `required_imports` @@ -54,7 +59,7 @@ pub struct ValidatedModule<'a> { /// from and the value is the set of functions required from that interface. /// This is used to generate an appropriate instance import in the generated /// component which imports only the set of required functions. - pub required_imports: IndexMap<&'a str, IndexSet<&'a str>>, + pub required_imports: IndexMap<&'a str, RequiredImports>, /// This is the set of imports into the module which were not satisfied by /// imported interfaces but are required to be satisfied by adapter modules. @@ -66,6 +71,15 @@ pub struct ValidatedModule<'a> { /// export (and subsequently import) extraneous functions. pub adapters_required: IndexMap<&'a str, IndexMap<&'a str, FuncType>>, + /// Resource-related functions required and imported which work over + /// exported resources from the final component. + /// + /// Note that this is disjoint from `required_imports` which handles + /// imported resources and this is only for exported resources. Exported + /// resources still require intrinsics to be imported into the core module + /// itself. + pub required_resource_funcs: IndexMap>>, + /// Whether or not this module exported a linear memory. pub has_memory: bool, @@ -79,6 +93,21 @@ pub struct ValidatedModule<'a> { pub metadata: &'a ModuleMetadata, } +#[derive(Default)] +pub struct RequiredImports { + pub funcs: IndexSet, + pub resources: IndexSet, +} + +pub struct ResourceInfo<'a> { + pub drop_own_import: Option<&'a str>, + pub drop_borrow_import: Option<&'a str>, + pub new_import: Option<&'a str>, + pub rep_import: Option<&'a str>, + pub dtor_export: Option<&'a str>, + pub id: TypeId, +} + /// This function validates the following: /// /// * The `bytes` represent a valid core WebAssembly module. @@ -107,6 +136,7 @@ pub fn validate_module<'a>( realloc: None, adapter_realloc: None, metadata: &metadata.metadata, + required_resource_funcs: Default::default(), }; for payload in Parser::new(0).parse_all(bytes) { @@ -173,24 +203,30 @@ pub fn validate_module<'a>( let types = types.unwrap(); let world = &metadata.resolve.worlds[metadata.world]; + let mut exported_resource_funcs = Vec::new(); for (name, funcs) in &import_funcs { // An empty module name is indicative of the top-level import namespace, // so look for top-level functions here. if *name == BARE_FUNC_MODULE_NAME { - validate_imports_top_level(&metadata.resolve, metadata.world, funcs, &types)?; - let funcs = funcs.keys().cloned().collect(); - let prev = ret.required_imports.insert(BARE_FUNC_MODULE_NAME, funcs); + let required = + validate_imports_top_level(&metadata.resolve, metadata.world, funcs, &types)?; + let prev = ret.required_imports.insert(BARE_FUNC_MODULE_NAME, required); assert!(prev.is_none()); continue; } + if let Some(interface_name) = name.strip_prefix("[export]") { + exported_resource_funcs.push((name, interface_name, &import_funcs[name])); + continue; + } + match world.imports.get(&world_key(&metadata.resolve, name)) { Some(WorldItem::Interface(interface)) => { - let funcs = + let required = validate_imported_interface(&metadata.resolve, *interface, name, funcs, &types) .with_context(|| format!("failed to validate import interface `{name}`"))?; - let prev = ret.required_imports.insert(name, funcs); + let prev = ret.required_imports.insert(name, required); assert!(prev.is_none()); } None if adapters.contains(name) => { @@ -200,9 +236,10 @@ pub fn validate_module<'a>( map.insert(func, ty.clone()); } } - None | Some(WorldItem::Function(_) | WorldItem::Type(_)) => { - bail!("module requires an import interface named `{}`", name) + Some(WorldItem::Function(_) | WorldItem::Type(_)) => { + bail!("import `{}` is not an interface", name) } + None => bail!("module requires an import interface named `{name}`"), } } @@ -213,12 +250,72 @@ pub fn validate_module<'a>( &metadata.resolve.name_world_key(name), &export_funcs, &types, + &mut ret, )?; } + for (name, interface_name, funcs) in exported_resource_funcs { + let world_key = world_key(&metadata.resolve, interface_name); + match world.exports.get(&world_key) { + Some(WorldItem::Interface(i)) => { + validate_exported_interface_resource_imports( + &metadata.resolve, + *i, + name, + funcs, + &types, + &mut ret, + )?; + } + _ => bail!("import from `{name}` does not correspond to exported interface"), + } + } + Ok(ret) } +fn validate_exported_interface_resource_imports<'a>( + resolve: &Resolve, + interface: InterfaceId, + import_module: &str, + funcs: &IndexMap<&'a str, u32>, + types: &Types, + info: &mut ValidatedModule<'a>, +) -> Result<()> { + let is_resource = |name: &str| match resolve.interfaces[interface].types.get(name) { + Some(ty) => match resolve.types[*ty].kind { + TypeDefKind::Resource => true, + _ => false, + }, + None => false, + }; + for (func_name, ty) in funcs { + if valid_exported_resource_func(func_name, *ty, types, &is_resource)?.is_none() { + bail!("import of `{func_name}` is not a valid resource function"); + } + let info = info.required_resource_funcs.get_mut(import_module).unwrap(); + if let Some(resource_name) = func_name.strip_prefix(RESOURCE_DROP_OWN) { + info[resource_name].drop_own_import = Some(func_name); + continue; + } + if let Some(resource_name) = func_name.strip_prefix(RESOURCE_DROP_BORROW) { + info[resource_name].drop_borrow_import = Some(func_name); + continue; + } + if let Some(resource_name) = func_name.strip_prefix(RESOURCE_NEW) { + info[resource_name].new_import = Some(func_name); + continue; + } + if let Some(resource_name) = func_name.strip_prefix(RESOURCE_REP) { + info[resource_name].rep_import = Some(func_name); + continue; + } + + unreachable!(); + } + Ok(()) +} + /// Validation information from an "adapter module" which is distinct from a /// "main module" validated above. /// @@ -227,7 +324,7 @@ pub struct ValidatedAdapter<'a> { /// If specified this is the list of required imports from the original set /// of possible imports along with the set of functions required from each /// imported interface. - pub required_imports: IndexMap>, + pub required_imports: IndexMap, /// This is the module and field name of the memory import, if one is /// specified. @@ -370,39 +467,18 @@ pub fn validate_adapter_module<'a>( // An empty module name is indicative of the top-level import namespace, // so look for top-level functions here. if name == BARE_FUNC_MODULE_NAME { - validate_imports_top_level(&resolve, world, &funcs, &types)?; - let funcs = resolve.worlds[world] - .imports - .iter() - .filter_map(|(name, item)| { - let name = match name { - WorldKey::Name(name) => name, - WorldKey::Interface(_) => return None, - }; - match item { - WorldItem::Function(_) if funcs.contains_key(name.as_str()) => { - Some(name.as_str()) - } - _ => None, - } - }) - .collect(); + let required = validate_imports_top_level(&resolve, world, &funcs, &types)?; ret.required_imports - .insert(BARE_FUNC_MODULE_NAME.to_string(), funcs); + .insert(BARE_FUNC_MODULE_NAME.to_string(), required); continue; } match resolve.worlds[world].imports.get(&world_key(resolve, name)) { Some(WorldItem::Interface(interface)) => { - validate_imported_interface(resolve, *interface, name, &funcs, &types) - .with_context(|| format!("failed to validate import interface `{name}`"))?; - let funcs = resolve.interfaces[*interface] - .functions - .keys() - .map(|s| s.as_str()) - .filter(|s| funcs.contains_key(s)) - .collect(); - let prev = ret.required_imports.insert(name.to_string(), funcs); + let required = + validate_imported_interface(resolve, *interface, name, &funcs, &types) + .with_context(|| format!("failed to validate import interface `{name}`"))?; + let prev = ret.required_imports.insert(name.to_string(), required); assert!(prev.is_none()); } None | Some(WorldItem::Function(_) | WorldItem::Type(_)) => { @@ -476,17 +552,77 @@ fn validate_imports_top_level<'a>( world: WorldId, funcs: &IndexMap<&'a str, u32>, types: &Types, -) -> Result<()> { +) -> Result { + let is_resource = |name: &str| match resolve.worlds[world] + .imports + .get(&WorldKey::Name(name.to_string())) + { + Some(WorldItem::Type(r)) => { + matches!(resolve.types[*r].kind, TypeDefKind::Resource) + } + _ => false, + }; + let mut required = RequiredImports::default(); for (name, ty) in funcs { - let func = match resolve.worlds[world].imports.get(&world_key(resolve, name)) { - Some(WorldItem::Function(func)) => func, + match resolve.worlds[world].imports.get(&world_key(resolve, name)) { + Some(WorldItem::Function(func)) => { + let ty = types.func_type_at(*ty).unwrap(); + validate_func(resolve, ty, func, AbiVariant::GuestImport)?; + } Some(_) => bail!("expected world top-level import `{name}` to be a function"), - None => bail!("no top-level imported function `{name}` specified"), - }; - let ty = types.func_type_at(*ty).unwrap(); - validate_func(resolve, ty, func, AbiVariant::GuestImport)?; + None => match valid_imported_resource_func(name, *ty, types, &is_resource)? { + Some(name) => { + required.resources.insert(name.to_string()); + } + None => bail!("no top-level imported function `{name}` specified"), + }, + } + required.funcs.insert(name.to_string()); } - Ok(()) + Ok(required) +} + +fn valid_imported_resource_func<'a>( + func_name: &'a str, + ty: u32, + types: &Types, + is_resource: impl Fn(&str) -> bool, +) -> Result> { + if let Some(resource_name) = func_name + .strip_prefix(RESOURCE_DROP_OWN) + .or_else(|| func_name.strip_prefix(RESOURCE_DROP_BORROW)) + { + if is_resource(resource_name) { + let ty = types.func_type_at(ty).unwrap(); + let expected = FuncType::new([ValType::I32], []); + validate_func_sig(func_name, &expected, ty)?; + return Ok(Some(resource_name)); + } + } + Ok(None) +} + +fn valid_exported_resource_func<'a>( + func_name: &'a str, + ty: u32, + types: &Types, + is_resource: impl Fn(&str) -> bool, +) -> Result> { + if let Some(name) = valid_imported_resource_func(func_name, ty, types, &is_resource)? { + return Ok(Some(name)); + } + if let Some(resource_name) = func_name + .strip_prefix(RESOURCE_REP) + .or_else(|| func_name.strip_prefix(RESOURCE_NEW)) + { + if is_resource(resource_name) { + let ty = types.func_type_at(ty).unwrap(); + let expected = FuncType::new([ValType::I32], [ValType::I32]); + validate_func_sig(func_name, &expected, ty)?; + return Ok(Some(resource_name)); + } + } + Ok(None) } fn validate_imported_interface<'a>( @@ -495,25 +631,38 @@ fn validate_imported_interface<'a>( name: &str, imports: &IndexMap<&str, u32>, types: &Types, -) -> Result> { - let mut funcs = IndexSet::new(); +) -> Result { + let mut required = RequiredImports::default(); + let is_resource = |name: &str| { + let ty = match resolve.interfaces[interface].types.get(name) { + Some(ty) => *ty, + None => return false, + }; + match resolve.types[ty].kind { + TypeDefKind::Resource => true, + _ => false, + } + }; for (func_name, ty) in imports { - let f = resolve.interfaces[interface] - .functions - .get(*func_name) - .ok_or_else(|| { - anyhow!( - "import interface `{name}` is missing function `{func_name}` that is required by the module", - ) - })?; - - let ty = types.func_type_at(*ty).unwrap(); - validate_func(resolve, ty, f, AbiVariant::GuestImport)?; - - funcs.insert(f.name.as_str()); + match resolve.interfaces[interface].functions.get(*func_name) { + Some(f) => { + let ty = types.func_type_at(*ty).unwrap(); + validate_func(resolve, ty, f, AbiVariant::GuestImport)?; + } + None => match valid_imported_resource_func(func_name, *ty, types, &is_resource)? { + Some(name) => { + required.resources.insert(name.to_string()); + } + None => bail!( + "import interface `{name}` is missing function \ + `{func_name}` that is required by the module", + ), + }, + } + required.funcs.insert(func_name.to_string()); } - Ok(funcs) + Ok(required) } fn validate_func( @@ -522,11 +671,18 @@ fn validate_func( func: &Function, abi: AbiVariant, ) -> Result<()> { - let expected = wasm_sig_to_func_type(resolve.wasm_signature(abi, func)); - if ty != &expected { + validate_func_sig( + &func.name, + &wasm_sig_to_func_type(resolve.wasm_signature(abi, func)), + ty, + ) +} + +fn validate_func_sig(name: &str, expected: &FuncType, ty: &wasmparser::FuncType) -> Result<()> { + if ty != expected { bail!( "type mismatch for function `{}`: expected `{:?} -> {:?}` but found `{:?} -> {:?}`", - func.name, + name, expected.params(), expected.results(), ty.params(), @@ -537,12 +693,13 @@ fn validate_func( Ok(()) } -fn validate_exported_item( - resolve: &Resolve, - item: &WorldItem, +fn validate_exported_item<'a>( + resolve: &'a Resolve, + item: &'a WorldItem, export_name: &str, - exports: &IndexMap<&str, u32>, + exports: &IndexMap<&'a str, u32>, types: &Types, + info: &mut ValidatedModule<'a>, ) -> Result<()> { let validate = |func: &Function, name: Option<&str>| { let expected_export_name = func.core_export_name(name); @@ -560,11 +717,39 @@ fn validate_exported_item( match item { WorldItem::Function(func) => validate(func, None)?, WorldItem::Interface(interface) => { - for (_, f) in &resolve.interfaces[*interface].functions { + let interface = &resolve.interfaces[*interface]; + for (_, f) in interface.functions.iter() { validate(f, Some(export_name)).with_context(|| { format!("failed to validate exported interface `{export_name}`") })?; } + let mut map = IndexMap::new(); + for (name, id) in interface.types.iter() { + if !matches!(resolve.types[*id].kind, TypeDefKind::Resource) { + continue; + } + let mut info = ResourceInfo { + id: *id, + dtor_export: None, + drop_own_import: None, + drop_borrow_import: None, + rep_import: None, + new_import: None, + }; + let dtor = format!("{export_name}#[dtor]{name}"); + if let Some((_, name, func_idx)) = exports.get_full(dtor.as_str()) { + let ty = types.function_at(*func_idx).unwrap(); + let expected = FuncType::new([ValType::I32], []); + validate_func_sig(name, &expected, &ty)?; + info.dtor_export = Some(name); + } + let prev = map.insert(name.as_str(), info); + assert!(prev.is_none()); + } + let prev = info + .required_resource_funcs + .insert(format!("[export]{export_name}"), map); + assert!(prev.is_none()); } // not required to have anything exported in the core wasm module WorldItem::Type(_) => {} diff --git a/crates/wit-component/tests/components/bare-funcs/component.wat b/crates/wit-component/tests/components/bare-funcs/component.wat index 7b5640175d..7c081962f5 100644 --- a/crates/wit-component/tests/components/bare-funcs/component.wat +++ b/crates/wit-component/tests/components/bare-funcs/component.wat @@ -55,11 +55,11 @@ ) ) (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (core func (;1;) (canon lower (func 0))) + (core func (;0;) (canon lower (func 0))) + (alias core export 0 "0" (core func (;1;))) (core instance (;1;) - (export "bar" (func 0)) - (export "foo" (func 1)) + (export "foo" (func 0)) + (export "bar" (func 1)) ) (core instance (;2;) (instantiate 0 (with "$root" (instance 1)) diff --git a/crates/wit-component/tests/components/error-import-resource-rep/error.txt b/crates/wit-component/tests/components/error-import-resource-rep/error.txt new file mode 100644 index 0000000000..5a9a5e1804 --- /dev/null +++ b/crates/wit-component/tests/components/error-import-resource-rep/error.txt @@ -0,0 +1 @@ +no top-level imported function `[resource-rep]a` specified \ No newline at end of file diff --git a/crates/wit-component/tests/components/error-import-resource-rep/module.wat b/crates/wit-component/tests/components/error-import-resource-rep/module.wat new file mode 100644 index 0000000000..791c84cfb5 --- /dev/null +++ b/crates/wit-component/tests/components/error-import-resource-rep/module.wat @@ -0,0 +1,3 @@ +(module + (import "$root" "[resource-rep]a" (func (param i32) (result i32))) +) diff --git a/crates/wit-component/tests/components/error-import-resource-rep/module.wit b/crates/wit-component/tests/components/error-import-resource-rep/module.wit new file mode 100644 index 0000000000..142cbde3e0 --- /dev/null +++ b/crates/wit-component/tests/components/error-import-resource-rep/module.wit @@ -0,0 +1,5 @@ +package foo:bar + +world module { + resource a +} diff --git a/crates/wit-component/tests/components/error-import-resource-wrong-signature/error.txt b/crates/wit-component/tests/components/error-import-resource-wrong-signature/error.txt new file mode 100644 index 0000000000..4351d79160 --- /dev/null +++ b/crates/wit-component/tests/components/error-import-resource-wrong-signature/error.txt @@ -0,0 +1 @@ +type mismatch for function `[resource-drop-own]a`: expected `[I32] -> []` but found `[I32] -> [I32]` \ No newline at end of file diff --git a/crates/wit-component/tests/components/error-import-resource-wrong-signature/module.wat b/crates/wit-component/tests/components/error-import-resource-wrong-signature/module.wat new file mode 100644 index 0000000000..61a1ec878f --- /dev/null +++ b/crates/wit-component/tests/components/error-import-resource-wrong-signature/module.wat @@ -0,0 +1,3 @@ +(module + (import "$root" "[resource-drop-own]a" (func (param i32) (result i32))) +) diff --git a/crates/wit-component/tests/components/error-import-resource-wrong-signature/module.wit b/crates/wit-component/tests/components/error-import-resource-wrong-signature/module.wit new file mode 100644 index 0000000000..142cbde3e0 --- /dev/null +++ b/crates/wit-component/tests/components/error-import-resource-wrong-signature/module.wit @@ -0,0 +1,5 @@ +package foo:bar + +world module { + resource a +} diff --git a/crates/wit-component/tests/components/export-resource/component.wat b/crates/wit-component/tests/components/export-resource/component.wat new file mode 100644 index 0000000000..8b755d2bbc --- /dev/null +++ b/crates/wit-component/tests/components/export-resource/component.wat @@ -0,0 +1,107 @@ +(component + (core module (;0;) + (type (;0;) (func (param i32) (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (result i32))) + (import "[export]foo" "[resource-new]a" (func $new (;0;) (type 0))) + (import "[export]foo" "[resource-rep]a" (func (;1;) (type 0))) + (import "[export]foo" "[resource-drop-borrow]a" (func (;2;) (type 1))) + (import "[export]foo" "[resource-drop-own]a" (func (;3;) (type 1))) + (func (;4;) (type 2) (result i32) + i32.const 100 + call $new + ) + (func (;5;) (type 2) (result i32) + i32.const 200 + call $new + ) + (func (;6;) (type 1) (param i32)) + (export "foo#[constructor]a" (func 4)) + (export "foo#[static]a.other-new" (func 5)) + (export "foo#[dtor]a" (func 6)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (core module (;1;) + (type (;0;) (func (param i32))) + (func $#func0 (@name "dtor-[export]foo-a") (;0;) (type 0) (param i32) + local.get 0 + i32.const 0 + call_indirect (type 0) + ) + (table (;0;) 1 1 funcref) + (export "0" (func $#func0)) + (export "$imports" (table 0)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core module (;2;) + (type (;0;) (func (param i32))) + (import "" "0" (func (;0;) (type 0))) + (import "" "$imports" (table (;0;) 1 1 funcref)) + (elem (;0;) (i32.const 0) func 0) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core instance (;0;) (instantiate 1)) + (alias core export 0 "0" (core func (;0;))) + (type (;0;) (resource (rep i32) (dtor (func 0)))) + (type (;1;) (own 0)) + (core func (;1;) (canon resource.drop 1)) + (type (;2;) (borrow 0)) + (core func (;2;) (canon resource.drop 2)) + (core func (;3;) (canon resource.rep 0)) + (core func (;4;) (canon resource.new 0)) + (core instance (;1;) + (export "[resource-drop-own]a" (func 1)) + (export "[resource-drop-borrow]a" (func 2)) + (export "[resource-rep]a" (func 3)) + (export "[resource-new]a" (func 4)) + ) + (core instance (;2;) (instantiate 0 + (with "[export]foo" (instance 1)) + ) + ) + (alias core export 0 "$imports" (core table (;0;))) + (alias core export 2 "foo#[dtor]a" (core func (;5;))) + (core instance (;3;) + (export "$imports" (table 0)) + (export "0" (func 5)) + ) + (core instance (;4;) (instantiate 2 + (with "" (instance 3)) + ) + ) + (type (;3;) (own 0)) + (type (;4;) (func (result 3))) + (alias core export 2 "foo#[constructor]a" (core func (;6;))) + (func (;0;) (type 4) (canon lift (core func 6))) + (alias core export 2 "foo#[static]a.other-new" (core func (;7;))) + (func (;1;) (type 4) (canon lift (core func 7))) + (component (;0;) + (import "import-type-a" (type (;0;) (sub resource))) + (type (;1;) (own 0)) + (type (;2;) (func (result 1))) + (import "import-constructor-a" (func (;0;) (type 2))) + (import "import-static-a-other-new" (func (;1;) (type 2))) + (export (;3;) "a" (type 0)) + (type (;4;) (own 3)) + (type (;5;) (func (result 4))) + (export (;2;) "[constructor]a" (func 0) (func (type 5))) + (export (;3;) "[static]a.other-new" (func 1) (func (type 5))) + ) + (instance (;0;) (instantiate 0 + (with "import-constructor-a" (func 0)) + (with "import-static-a-other-new" (func 1)) + (with "import-type-a" (type 0)) + ) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + (export (;1;) "foo" (instance 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/components/export-resource/component.wit.print b/crates/wit-component/tests/components/export-resource/component.wit.print new file mode 100644 index 0000000000..93bffd6ce3 --- /dev/null +++ b/crates/wit-component/tests/components/export-resource/component.wit.print @@ -0,0 +1,10 @@ +package root:component + +world root { + export foo: interface { + resource a { + constructor() + static other-new: func() -> a + } + } +} diff --git a/crates/wit-component/tests/components/export-resource/module.wat b/crates/wit-component/tests/components/export-resource/module.wat new file mode 100644 index 0000000000..ce297d2029 --- /dev/null +++ b/crates/wit-component/tests/components/export-resource/module.wat @@ -0,0 +1,16 @@ +(module + (import "[export]foo" "[resource-new]a" (func $new (param i32) (result i32))) + (import "[export]foo" "[resource-rep]a" (func (param i32) (result i32))) + (import "[export]foo" "[resource-drop-borrow]a" (func (param i32))) + (import "[export]foo" "[resource-drop-own]a" (func (param i32))) + + (func (export "foo#[constructor]a") (result i32) + (call $new (i32.const 100)) + ) + (func (export "foo#[static]a.other-new") (result i32) + (call $new (i32.const 200)) + ) + (func (export "foo#[dtor]a") (param i32) + ;; ... + ) +) diff --git a/crates/wit-component/tests/components/export-resource/module.wit b/crates/wit-component/tests/components/export-resource/module.wit new file mode 100644 index 0000000000..44fa365012 --- /dev/null +++ b/crates/wit-component/tests/components/export-resource/module.wit @@ -0,0 +1,11 @@ +package foo:bar + +world module { + export foo: interface { + resource a { + constructor() + + static other-new: func() -> a + } + } +} diff --git a/crates/wit-component/tests/components/export-type-name-conflict/component.wat b/crates/wit-component/tests/components/export-type-name-conflict/component.wat index 41b6527377..2756352701 100644 --- a/crates/wit-component/tests/components/export-type-name-conflict/component.wat +++ b/crates/wit-component/tests/components/export-type-name-conflict/component.wat @@ -20,6 +20,7 @@ (type (;2;) (func (result 1))) (alias core export 0 "bar#foo" (core func (;0;))) (func (;0;) (type 2) (canon lift (core func 0))) + (alias export 0 "foo" (type (;3;))) (component (;0;) (type (;0;) (record)) (import "import-type-foo" (type (;1;) (eq 0))) @@ -32,7 +33,7 @@ ) (instance (;1;) (instantiate 0 (with "import-func-foo" (func 0)) - (with "import-type-foo" (type 1)) + (with "import-type-foo" (type 3)) (with "import-type-bar" (type 1)) ) ) diff --git a/crates/wit-component/tests/components/exports/component.wat b/crates/wit-component/tests/components/exports/component.wat index 62bf8a65d2..b172c02fd5 100644 --- a/crates/wit-component/tests/components/exports/component.wat +++ b/crates/wit-component/tests/components/exports/component.wat @@ -102,13 +102,13 @@ (import "import-func-b" (func (;1;) (type 3))) (type (;4;) (func (param "x" 2) (result string))) (import "import-func-c" (func (;2;) (type 4))) - (type (;5;) (func)) - (export (;3;) "a" (func 0) (func (type 5))) - (type (;6;) (variant (case "a") (case "b" string) (case "c" s64))) - (export (;7;) "x" (type 6)) - (type (;8;) (func (param "x" string) (result 7))) + (type (;5;) (variant (case "a") (case "b" string) (case "c" s64))) + (export (;6;) "x" (type 5)) + (type (;7;) (func)) + (export (;3;) "a" (func 0) (func (type 7))) + (type (;8;) (func (param "x" string) (result 6))) (export (;4;) "b" (func 1) (func (type 8))) - (type (;9;) (func (param "x" 7) (result string))) + (type (;9;) (func (param "x" 6) (result string))) (export (;5;) "c" (func 2) (func (type 9))) ) (instance (;2;) (instantiate 1 @@ -119,18 +119,19 @@ ) ) (export (;3;) "foo" (instance 2)) + (type (;6;) (func)) (alias core export 0 "a" (core func (;7;))) - (func (;4;) (type 2) (canon lift (core func 7))) + (func (;4;) (type 6) (canon lift (core func 7))) (export (;5;) "a" (func 4)) - (type (;6;) (func (param "a" s8) (param "b" s16) (param "c" s32) (param "d" s64) (result string))) + (type (;7;) (func (param "a" s8) (param "b" s16) (param "c" s32) (param "d" s64) (result string))) (alias core export 0 "b" (core func (;8;))) (alias core export 0 "cabi_post_b" (core func (;9;))) - (func (;6;) (type 6) (canon lift (core func 8) (memory 0) string-encoding=utf8 (post-return 9))) + (func (;6;) (type 7) (canon lift (core func 8) (memory 0) string-encoding=utf8 (post-return 9))) (export (;7;) "b" (func 6)) - (type (;7;) (tuple s8 s16 s32 s64)) - (type (;8;) (func (result 7))) + (type (;8;) (tuple s8 s16 s32 s64)) + (type (;9;) (func (result 8))) (alias core export 0 "c" (core func (;10;))) - (func (;8;) (type 8) (canon lift (core func 10) (memory 0))) + (func (;8;) (type 9) (canon lift (core func 10) (memory 0))) (@producers (processed-by "wit-component" "$CARGO_PKG_VERSION") ) diff --git a/crates/wit-component/tests/components/import-and-export-resource/component.wat b/crates/wit-component/tests/components/import-and-export-resource/component.wat new file mode 100644 index 0000000000..59a6cf6584 --- /dev/null +++ b/crates/wit-component/tests/components/import-and-export-resource/component.wat @@ -0,0 +1,64 @@ +(component + (type (;0;) + (instance + (export (;0;) "a" (type (sub resource))) + ) + ) + (import (interface "foo:bar/foo") (instance (;0;) (type 0))) + (alias export 0 "a" (type (;1;))) + (import "a" (type (;2;) (eq 1))) + (core module (;0;) + (type (;0;) (func (param i32))) + (type (;1;) (func (param i32) (result i32))) + (import "foo:bar/foo" "[resource-drop-own]a" (func (;0;) (type 0))) + (import "foo:bar/foo" "[resource-drop-borrow]a" (func (;1;) (type 0))) + (import "[export]foo:bar/foo" "[resource-drop-own]a" (func (;2;) (type 0))) + (import "[export]foo:bar/foo" "[resource-drop-borrow]a" (func (;3;) (type 0))) + (import "[export]foo:bar/foo" "[resource-rep]a" (func (;4;) (type 1))) + (import "[export]foo:bar/foo" "[resource-new]a" (func (;5;) (type 1))) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (alias export 0 "a" (type (;3;))) + (type (;4;) (own 3)) + (core func (;0;) (canon resource.drop 4)) + (alias export 0 "a" (type (;5;))) + (type (;6;) (borrow 5)) + (core func (;1;) (canon resource.drop 6)) + (core instance (;0;) + (export "[resource-drop-own]a" (func 0)) + (export "[resource-drop-borrow]a" (func 1)) + ) + (type (;7;) (resource (rep i32))) + (type (;8;) (own 7)) + (core func (;2;) (canon resource.drop 8)) + (type (;9;) (borrow 7)) + (core func (;3;) (canon resource.drop 9)) + (core func (;4;) (canon resource.rep 7)) + (core func (;5;) (canon resource.new 7)) + (core instance (;1;) + (export "[resource-drop-own]a" (func 2)) + (export "[resource-drop-borrow]a" (func 3)) + (export "[resource-rep]a" (func 4)) + (export "[resource-new]a" (func 5)) + ) + (core instance (;2;) (instantiate 0 + (with "foo:bar/foo" (instance 0)) + (with "[export]foo:bar/foo" (instance 1)) + ) + ) + (component (;0;) + (import "import-type-a" (type (;0;) (sub resource))) + (export (;1;) "a" (type 0)) + ) + (instance (;1;) (instantiate 0 + (with "import-type-a" (type 7)) + ) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + (export (;2;) (interface "foo:bar/foo") (instance 1)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/components/import-and-export-resource/component.wit.print b/crates/wit-component/tests/components/import-and-export-resource/component.wit.print new file mode 100644 index 0000000000..3714cd5772 --- /dev/null +++ b/crates/wit-component/tests/components/import-and-export-resource/component.wit.print @@ -0,0 +1,8 @@ +package root:component + +world root { + import foo:bar/foo + use foo:bar/foo.{a} + + export foo:bar/foo +} diff --git a/crates/wit-component/tests/components/import-and-export-resource/module.wat b/crates/wit-component/tests/components/import-and-export-resource/module.wat new file mode 100644 index 0000000000..0384cdf97a --- /dev/null +++ b/crates/wit-component/tests/components/import-and-export-resource/module.wat @@ -0,0 +1,8 @@ +(module + (import "foo:bar/foo" "[resource-drop-own]a" (func (param i32))) + (import "foo:bar/foo" "[resource-drop-borrow]a" (func (param i32))) + (import "[export]foo:bar/foo" "[resource-drop-own]a" (func (param i32))) + (import "[export]foo:bar/foo" "[resource-drop-borrow]a" (func (param i32))) + (import "[export]foo:bar/foo" "[resource-rep]a" (func (param i32) (result i32))) + (import "[export]foo:bar/foo" "[resource-new]a" (func (param i32) (result i32))) +) diff --git a/crates/wit-component/tests/components/import-and-export-resource/module.wit b/crates/wit-component/tests/components/import-and-export-resource/module.wit new file mode 100644 index 0000000000..03e553511e --- /dev/null +++ b/crates/wit-component/tests/components/import-and-export-resource/module.wit @@ -0,0 +1,12 @@ +package foo:bar + +interface foo { + resource a +} + +world module { + import foo + use foo.{a} + + export foo +} diff --git a/crates/wit-component/tests/components/import-in-adapter-and-main-module/component.wat b/crates/wit-component/tests/components/import-in-adapter-and-main-module/component.wat index 7eeff34d5b..0880924928 100644 --- a/crates/wit-component/tests/components/import-in-adapter-and-main-module/component.wat +++ b/crates/wit-component/tests/components/import-in-adapter-and-main-module/component.wat @@ -140,17 +140,17 @@ ) ) (core instance (;0;) (instantiate 2)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) (alias export 0 "f1" (func (;0;))) - (core func (;2;) (canon lower (func 0))) + (core func (;0;) (canon lower (func 0))) (alias export 0 "f2" (func (;1;))) - (core func (;3;) (canon lower (func 1))) + (core func (;1;) (canon lower (func 1))) + (alias core export 0 "0" (core func (;2;))) + (alias core export 0 "1" (core func (;3;))) (core instance (;1;) - (export "g1" (func 0)) - (export "g2" (func 1)) - (export "f1" (func 2)) - (export "f2" (func 3)) + (export "f1" (func 0)) + (export "f2" (func 1)) + (export "g1" (func 2)) + (export "g2" (func 3)) ) (alias export 2 "foo" (func (;2;))) (core func (;4;) (canon lower (func 2))) @@ -169,17 +169,17 @@ ) (alias core export 4 "memory" (core memory (;0;))) (alias core export 4 "cabi_realloc" (core func (;6;))) - (alias core export 0 "2" (core func (;7;))) - (alias core export 0 "3" (core func (;8;))) (alias export 0 "f1" (func (;3;))) - (core func (;9;) (canon lower (func 3))) + (core func (;7;) (canon lower (func 3))) (alias export 0 "f3" (func (;4;))) - (core func (;10;) (canon lower (func 4))) + (core func (;8;) (canon lower (func 4))) + (alias core export 0 "2" (core func (;9;))) + (alias core export 0 "3" (core func (;10;))) (core instance (;5;) - (export "g1" (func 7)) - (export "g3" (func 8)) - (export "f1" (func 9)) - (export "f3" (func 10)) + (export "f1" (func 7)) + (export "f3" (func 8)) + (export "g1" (func 9)) + (export "g3" (func 10)) ) (alias export 3 "foo" (func (;5;))) (core func (;11;) (canon lower (func 5))) diff --git a/crates/wit-component/tests/components/import-resource-in-interface/component.wat b/crates/wit-component/tests/components/import-resource-in-interface/component.wat new file mode 100644 index 0000000000..96b0cb5f06 --- /dev/null +++ b/crates/wit-component/tests/components/import-resource-in-interface/component.wat @@ -0,0 +1,47 @@ +(component + (type (;0;) + (instance + (export (;0;) "a" (type (sub resource))) + (type (;1;) (own 0)) + (type (;2;) (func (result 1))) + (export (;0;) "[constructor]a" (func (type 2))) + (export (;1;) "[static]a.other-new" (func (type 2))) + ) + ) + (import "foo" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (import "foo" "[constructor]a" (func (;0;) (type 0))) + (import "foo" "[static]a.other-new" (func (;1;) (type 0))) + (import "foo" "[resource-drop-own]a" (func (;2;) (type 1))) + (import "foo" "[resource-drop-borrow]a" (func (;3;) (type 1))) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (alias export 0 "a" (type (;1;))) + (type (;2;) (own 1)) + (core func (;0;) (canon resource.drop 2)) + (alias export 0 "a" (type (;3;))) + (type (;4;) (borrow 3)) + (core func (;1;) (canon resource.drop 4)) + (alias export 0 "[constructor]a" (func (;0;))) + (core func (;2;) (canon lower (func 0))) + (alias export 0 "[static]a.other-new" (func (;1;))) + (core func (;3;) (canon lower (func 1))) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + (core instance (;0;) + (export "[resource-drop-own]a" (func 0)) + (export "[resource-drop-borrow]a" (func 1)) + (export "[constructor]a" (func 2)) + (export "[static]a.other-new" (func 3)) + ) + (core instance (;1;) (instantiate 0 + (with "foo" (instance 0)) + ) + ) +) \ No newline at end of file diff --git a/crates/wit-component/tests/components/import-resource-in-interface/component.wit.print b/crates/wit-component/tests/components/import-resource-in-interface/component.wit.print new file mode 100644 index 0000000000..fb53610dfd --- /dev/null +++ b/crates/wit-component/tests/components/import-resource-in-interface/component.wit.print @@ -0,0 +1,10 @@ +package root:component + +world root { + import foo: interface { + resource a { + constructor() + static other-new: func() -> a + } + } +} diff --git a/crates/wit-component/tests/components/import-resource-in-interface/module.wat b/crates/wit-component/tests/components/import-resource-in-interface/module.wat new file mode 100644 index 0000000000..726aa1948f --- /dev/null +++ b/crates/wit-component/tests/components/import-resource-in-interface/module.wat @@ -0,0 +1,6 @@ +(module + (import "foo" "[constructor]a" (func (result i32))) + (import "foo" "[static]a.other-new" (func (result i32))) + (import "foo" "[resource-drop-own]a" (func (param i32))) + (import "foo" "[resource-drop-borrow]a" (func (param i32))) +) diff --git a/crates/wit-component/tests/components/import-resource-in-interface/module.wit b/crates/wit-component/tests/components/import-resource-in-interface/module.wit new file mode 100644 index 0000000000..2d8f097ec1 --- /dev/null +++ b/crates/wit-component/tests/components/import-resource-in-interface/module.wit @@ -0,0 +1,11 @@ +package foo:bar + +world module { + import foo: interface { + resource a { + constructor() + + static other-new: func() -> a + } + } +} diff --git a/crates/wit-component/tests/components/import-resource-simple/component.wat b/crates/wit-component/tests/components/import-resource-simple/component.wat new file mode 100644 index 0000000000..2f92d2aa74 --- /dev/null +++ b/crates/wit-component/tests/components/import-resource-simple/component.wat @@ -0,0 +1,38 @@ +(component + (import "a" (type (;0;) (sub resource))) + (type (;1;) (own 0)) + (type (;2;) (func (result 1))) + (import "[constructor]a" (func (;0;) (type 2))) + (import "[static]a.other-new" (func (;1;) (type 2))) + (core module (;0;) + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (import "$root" "[constructor]a" (func (;0;) (type 0))) + (import "$root" "[static]a.other-new" (func (;1;) (type 0))) + (import "$root" "[resource-drop-own]a" (func (;2;) (type 1))) + (import "$root" "[resource-drop-borrow]a" (func (;3;) (type 1))) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (type (;3;) (own 0)) + (core func (;0;) (canon resource.drop 3)) + (type (;4;) (borrow 0)) + (core func (;1;) (canon resource.drop 4)) + (core func (;2;) (canon lower (func 0))) + (core func (;3;) (canon lower (func 1))) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + (core instance (;0;) + (export "[resource-drop-own]a" (func 0)) + (export "[resource-drop-borrow]a" (func 1)) + (export "[constructor]a" (func 2)) + (export "[static]a.other-new" (func 3)) + ) + (core instance (;1;) (instantiate 0 + (with "$root" (instance 0)) + ) + ) +) \ No newline at end of file diff --git a/crates/wit-component/tests/components/import-resource-simple/component.wit.print b/crates/wit-component/tests/components/import-resource-simple/component.wit.print new file mode 100644 index 0000000000..0280967f0d --- /dev/null +++ b/crates/wit-component/tests/components/import-resource-simple/component.wit.print @@ -0,0 +1,8 @@ +package root:component + +world root { + resource a { + constructor() + static other-new: func() -> a + } +} diff --git a/crates/wit-component/tests/components/import-resource-simple/module.wat b/crates/wit-component/tests/components/import-resource-simple/module.wat new file mode 100644 index 0000000000..471e99b88d --- /dev/null +++ b/crates/wit-component/tests/components/import-resource-simple/module.wat @@ -0,0 +1,6 @@ +(module + (import "$root" "[constructor]a" (func (result i32))) + (import "$root" "[static]a.other-new" (func (result i32))) + (import "$root" "[resource-drop-own]a" (func (param i32))) + (import "$root" "[resource-drop-borrow]a" (func (param i32))) +) diff --git a/crates/wit-component/tests/components/import-resource-simple/module.wit b/crates/wit-component/tests/components/import-resource-simple/module.wit new file mode 100644 index 0000000000..d25a60b5ab --- /dev/null +++ b/crates/wit-component/tests/components/import-resource-simple/module.wit @@ -0,0 +1,9 @@ +package foo:bar + +world module { + resource a { + constructor() + + static other-new: func() -> a + } +} diff --git a/crates/wit-component/tests/components/imports/component.wat b/crates/wit-component/tests/components/imports/component.wat index 160b60ff21..303edc9f05 100644 --- a/crates/wit-component/tests/components/imports/component.wat +++ b/crates/wit-component/tests/components/imports/component.wat @@ -1,25 +1,25 @@ (component (type (;0;) (instance - (type (;0;) (func (param "x" string))) - (export (;0;) "bar1" (func (type 0))) - (type (;1;) (record (field "a" u8))) - (export (;2;) "x" (type (eq 1))) - (type (;3;) (func (param "x" 2))) + (type (;0;) (record (field "a" u8))) + (export (;1;) "x" (type (eq 0))) + (type (;2;) (func (param "x" string))) + (export (;0;) "bar1" (func (type 2))) + (type (;3;) (func (param "x" 1))) (export (;1;) "bar2" (func (type 3))) ) ) (import "bar" (instance (;0;) (type 0))) (type (;1;) (instance - (type (;0;) (list string)) - (type (;1;) (func (param "x" 0))) - (export (;0;) "baz1" (func (type 1))) - (type (;2;) (func)) - (export (;1;) "baz2" (func (type 2))) - (type (;3;) s8) - (export (;4;) "x" (type (eq 3))) - (type (;5;) (func (param "x" 4))) + (type (;0;) s8) + (export (;1;) "x" (type (eq 0))) + (type (;2;) (list string)) + (type (;3;) (func (param "x" 2))) + (export (;0;) "baz1" (func (type 3))) + (type (;4;) (func)) + (export (;1;) "baz2" (func (type 4))) + (type (;5;) (func (param "x" 1))) (export (;2;) "baz3" (func (type 5))) ) ) diff --git a/crates/wit-component/tests/components/lift-options/component.wat b/crates/wit-component/tests/components/lift-options/component.wat index ccaf935746..ffa1ede938 100644 --- a/crates/wit-component/tests/components/lift-options/component.wat +++ b/crates/wit-component/tests/components/lift-options/component.wat @@ -209,31 +209,31 @@ (type (;28;) (list 13)) (type (;29;) (func (result 28))) (import "import-func-p" (func (;15;) (type 29))) - (type (;30;) (func)) - (export (;16;) "a" (func 0) (func (type 30))) - (type (;31;) (list string)) - (type (;32;) (func (param "x" 31))) - (export (;17;) "b" (func 1) (func (type 32))) - (type (;33;) (record (field "s" string))) - (export (;34;) "r" (type 33)) - (type (;35;) (func (param "x" 34))) - (export (;18;) "c" (func 2) (func (type 35))) - (type (;36;) (variant (case "s" string))) - (export (;37;) "v" (type 36)) - (type (;38;) (func (param "x" 37))) - (export (;19;) "d" (func 3) (func (type 38))) - (type (;39;) (record (field "s" u32))) - (export (;40;) "r-no-string" (type 39)) - (type (;41;) (func (param "x" 40))) - (export (;20;) "e" (func 4) (func (type 41))) - (type (;42;) (variant (case "s" u32))) - (export (;43;) "v-no-string" (type 42)) - (type (;44;) (func (param "x" 43))) + (type (;30;) (record (field "s" string))) + (export (;31;) "r" (type 30)) + (type (;32;) (record (field "s" u32))) + (export (;33;) "r-no-string" (type 32)) + (type (;34;) (variant (case "s" string))) + (export (;35;) "v" (type 34)) + (type (;36;) (variant (case "s" u32))) + (export (;37;) "v-no-string" (type 36)) + (type (;38;) (func)) + (export (;16;) "a" (func 0) (func (type 38))) + (type (;39;) (list string)) + (type (;40;) (func (param "x" 39))) + (export (;17;) "b" (func 1) (func (type 40))) + (type (;41;) (func (param "x" 31))) + (export (;18;) "c" (func 2) (func (type 41))) + (type (;42;) (func (param "x" 35))) + (export (;19;) "d" (func 3) (func (type 42))) + (type (;43;) (func (param "x" 33))) + (export (;20;) "e" (func 4) (func (type 43))) + (type (;44;) (func (param "x" 37))) (export (;21;) "f" (func 5) (func (type 44))) - (type (;45;) (list 34)) + (type (;45;) (list 31)) (type (;46;) (func (param "x" 45))) (export (;22;) "g" (func 6) (func (type 46))) - (type (;47;) (list 37)) + (type (;47;) (list 35)) (type (;48;) (func (param "x" 47))) (export (;23;) "h" (func 7) (func (type 48))) (type (;49;) (list u32)) @@ -250,9 +250,9 @@ (export (;28;) "m" (func 12) (func (type 55))) (type (;56;) (func (result u32))) (export (;29;) "n" (func 13) (func (type 56))) - (type (;57;) (func (result 37))) + (type (;57;) (func (result 35))) (export (;30;) "o" (func 14) (func (type 57))) - (type (;58;) (list 43)) + (type (;58;) (list 37)) (type (;59;) (func (result 58))) (export (;31;) "p" (func 15) (func (type 59))) ) diff --git a/crates/wit-component/tests/components/lower-options/component.wat b/crates/wit-component/tests/components/lower-options/component.wat index f402a5aff9..2848a9002e 100644 --- a/crates/wit-component/tests/components/lower-options/component.wat +++ b/crates/wit-component/tests/components/lower-options/component.wat @@ -1,31 +1,31 @@ (component (type (;0;) (instance - (type (;0;) (func)) - (export (;0;) "a" (func (type 0))) - (type (;1;) (list string)) - (type (;2;) (func (param "x" 1))) - (export (;1;) "b" (func (type 2))) - (type (;3;) (record (field "s" string))) - (export (;4;) "r" (type (eq 3))) - (type (;5;) (func (param "x" 4))) - (export (;2;) "c" (func (type 5))) - (type (;6;) (variant (case "s" string))) - (export (;7;) "v" (type (eq 6))) - (type (;8;) (func (param "x" 7))) - (export (;3;) "d" (func (type 8))) - (type (;9;) (record (field "s" u32))) - (export (;10;) "r-no-string" (type (eq 9))) - (type (;11;) (func (param "x" 10))) - (export (;4;) "e" (func (type 11))) - (type (;12;) (variant (case "s" u32))) - (export (;13;) "v-no-string" (type (eq 12))) - (type (;14;) (func (param "x" 13))) + (type (;0;) (record (field "s" string))) + (export (;1;) "r" (type (eq 0))) + (type (;2;) (variant (case "s" string))) + (export (;3;) "v" (type (eq 2))) + (type (;4;) (record (field "s" u32))) + (export (;5;) "r-no-string" (type (eq 4))) + (type (;6;) (variant (case "s" u32))) + (export (;7;) "v-no-string" (type (eq 6))) + (type (;8;) (func)) + (export (;0;) "a" (func (type 8))) + (type (;9;) (list string)) + (type (;10;) (func (param "x" 9))) + (export (;1;) "b" (func (type 10))) + (type (;11;) (func (param "x" 1))) + (export (;2;) "c" (func (type 11))) + (type (;12;) (func (param "x" 3))) + (export (;3;) "d" (func (type 12))) + (type (;13;) (func (param "x" 5))) + (export (;4;) "e" (func (type 13))) + (type (;14;) (func (param "x" 7))) (export (;5;) "f" (func (type 14))) - (type (;15;) (list 4)) + (type (;15;) (list 1)) (type (;16;) (func (param "x" 15))) (export (;6;) "g" (func (type 16))) - (type (;17;) (list 7)) + (type (;17;) (list 3)) (type (;18;) (func (param "x" 17))) (export (;7;) "h" (func (type 18))) (type (;19;) (list u32)) @@ -42,9 +42,9 @@ (export (;12;) "m" (func (type 25))) (type (;26;) (func (result u32))) (export (;13;) "n" (func (type 26))) - (type (;27;) (func (result 7))) + (type (;27;) (func (result 3))) (export (;14;) "o" (func (type 27))) - (type (;28;) (list 13)) + (type (;28;) (list 7)) (type (;29;) (func (result 28))) (export (;15;) "p" (func (type 29))) ) @@ -189,44 +189,44 @@ ) ) (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) - (alias core export 0 "2" (core func (;2;))) - (alias core export 0 "3" (core func (;3;))) - (alias core export 0 "4" (core func (;4;))) - (alias core export 0 "5" (core func (;5;))) - (alias core export 0 "6" (core func (;6;))) - (alias core export 0 "7" (core func (;7;))) - (alias core export 0 "8" (core func (;8;))) - (alias core export 0 "9" (core func (;9;))) - (alias core export 0 "10" (core func (;10;))) (alias export 0 "a" (func (;0;))) - (core func (;11;) (canon lower (func 0))) + (core func (;0;) (canon lower (func 0))) + (alias core export 0 "0" (core func (;1;))) + (alias core export 0 "1" (core func (;2;))) + (alias core export 0 "2" (core func (;3;))) (alias export 0 "e" (func (;1;))) - (core func (;12;) (canon lower (func 1))) + (core func (;4;) (canon lower (func 1))) (alias export 0 "f" (func (;2;))) - (core func (;13;) (canon lower (func 2))) + (core func (;5;) (canon lower (func 2))) + (alias core export 0 "3" (core func (;6;))) + (alias core export 0 "4" (core func (;7;))) + (alias core export 0 "5" (core func (;8;))) (alias export 0 "j" (func (;3;))) - (core func (;14;) (canon lower (func 3))) + (core func (;9;) (canon lower (func 3))) + (alias core export 0 "6" (core func (;10;))) + (alias core export 0 "7" (core func (;11;))) + (alias core export 0 "8" (core func (;12;))) (alias export 0 "n" (func (;4;))) - (core func (;15;) (canon lower (func 4))) + (core func (;13;) (canon lower (func 4))) + (alias core export 0 "9" (core func (;14;))) + (alias core export 0 "10" (core func (;15;))) (core instance (;1;) - (export "b" (func 0)) - (export "c" (func 1)) - (export "d" (func 2)) - (export "g" (func 3)) - (export "h" (func 4)) - (export "i" (func 5)) - (export "k" (func 6)) - (export "l" (func 7)) - (export "m" (func 8)) - (export "o" (func 9)) - (export "p" (func 10)) - (export "a" (func 11)) - (export "e" (func 12)) - (export "f" (func 13)) - (export "j" (func 14)) - (export "n" (func 15)) + (export "a" (func 0)) + (export "b" (func 1)) + (export "c" (func 2)) + (export "d" (func 3)) + (export "e" (func 4)) + (export "f" (func 5)) + (export "g" (func 6)) + (export "h" (func 7)) + (export "i" (func 8)) + (export "j" (func 9)) + (export "k" (func 10)) + (export "l" (func 11)) + (export "m" (func 12)) + (export "n" (func 13)) + (export "o" (func 14)) + (export "p" (func 15)) ) (core instance (;2;) (instantiate 0 (with "foo:foo/foo" (instance 1)) diff --git a/crates/wit-component/tests/components/many-same-names/component.wat b/crates/wit-component/tests/components/many-same-names/component.wat index c953eae14a..f233822fc8 100644 --- a/crates/wit-component/tests/components/many-same-names/component.wat +++ b/crates/wit-component/tests/components/many-same-names/component.wat @@ -38,9 +38,9 @@ (import "import-type-r2" (type (;3;) (eq 2))) (type (;4;) (func)) (import "import-func-a" (func (;0;) (type 4))) - (type (;5;) (func)) - (export (;1;) "a" (func 0) (func (type 5))) - (export (;6;) "r2" (type 3)) + (export (;5;) "r2" (type 3)) + (type (;6;) (func)) + (export (;1;) "a" (func 0) (func (type 6))) ) (instance (;3;) (instantiate 1 (with "import-func-a" (func 0)) diff --git a/crates/wit-component/tests/components/resource-intrinsics-with-just-import/component.wat b/crates/wit-component/tests/components/resource-intrinsics-with-just-import/component.wat new file mode 100644 index 0000000000..5cd27b0d40 --- /dev/null +++ b/crates/wit-component/tests/components/resource-intrinsics-with-just-import/component.wat @@ -0,0 +1,29 @@ +(component + (type (;0;) + (instance + (export (;0;) "a" (type (sub resource))) + ) + ) + (import "foo" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func (param i32))) + (import "foo" "[resource-drop-own]a" (func (;0;) (type 0))) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (alias export 0 "a" (type (;1;))) + (type (;2;) (own 1)) + (core func (;0;) (canon resource.drop 2)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + (core instance (;0;) + (export "[resource-drop-own]a" (func 0)) + ) + (core instance (;1;) (instantiate 0 + (with "foo" (instance 0)) + ) + ) +) \ No newline at end of file diff --git a/crates/wit-component/tests/components/resource-intrinsics-with-just-import/component.wit.print b/crates/wit-component/tests/components/resource-intrinsics-with-just-import/component.wit.print new file mode 100644 index 0000000000..8cc7cfa730 --- /dev/null +++ b/crates/wit-component/tests/components/resource-intrinsics-with-just-import/component.wit.print @@ -0,0 +1,7 @@ +package root:component + +world root { + import foo: interface { + resource a + } +} diff --git a/crates/wit-component/tests/components/resource-intrinsics-with-just-import/module.wat b/crates/wit-component/tests/components/resource-intrinsics-with-just-import/module.wat new file mode 100644 index 0000000000..cd793643da --- /dev/null +++ b/crates/wit-component/tests/components/resource-intrinsics-with-just-import/module.wat @@ -0,0 +1,3 @@ +(module + (import "foo" "[resource-drop-own]a" (func (param i32))) +) diff --git a/crates/wit-component/tests/components/resource-intrinsics-with-just-import/module.wit b/crates/wit-component/tests/components/resource-intrinsics-with-just-import/module.wit new file mode 100644 index 0000000000..850aec0a46 --- /dev/null +++ b/crates/wit-component/tests/components/resource-intrinsics-with-just-import/module.wit @@ -0,0 +1,7 @@ +package foo:bar + +world module { + import foo: interface { + resource a + } +} diff --git a/crates/wit-component/tests/components/resource-used-through-import/component.wat b/crates/wit-component/tests/components/resource-used-through-import/component.wat new file mode 100644 index 0000000000..87a958054b --- /dev/null +++ b/crates/wit-component/tests/components/resource-used-through-import/component.wat @@ -0,0 +1,58 @@ +(component + (type (;0;) + (instance + (export (;0;) "r" (type (sub resource))) + ) + ) + (import (interface "foo:bar/a") (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func (param i32))) + (type (;1;) (func (result i32))) + (import "foo:bar/a" "[resource-drop-own]r" (func (;0;) (type 0))) + (func (;1;) (type 1) (result i32) + i32.const 0 + ) + (export "b#foo" (func 1)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (alias export 0 "r" (type (;1;))) + (type (;2;) (own 1)) + (core func (;0;) (canon resource.drop 2)) + (core instance (;0;) + (export "[resource-drop-own]r" (func 0)) + ) + (core instance (;1;) (instantiate 0 + (with "foo:bar/a" (instance 0)) + ) + ) + (alias export 0 "r" (type (;3;))) + (type (;4;) (own 3)) + (type (;5;) (func (result 4))) + (alias core export 1 "b#foo" (core func (;1;))) + (func (;0;) (type 5) (canon lift (core func 1))) + (alias export 0 "r" (type (;6;))) + (component (;0;) + (import "import-type-r" (type (;0;) (sub resource))) + (import "import-type-r0" (type (;1;) (eq 0))) + (type (;2;) (own 1)) + (type (;3;) (func (result 2))) + (import "import-func-foo" (func (;0;) (type 3))) + (export (;4;) "r" (type 0)) + (type (;5;) (own 4)) + (type (;6;) (func (result 5))) + (export (;1;) "foo" (func 0) (func (type 6))) + ) + (instance (;1;) (instantiate 0 + (with "import-func-foo" (func 0)) + (with "import-type-r" (type 6)) + (with "import-type-r0" (type 3)) + ) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + (export (;2;) "b" (instance 1)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/components/resource-used-through-import/component.wit.print b/crates/wit-component/tests/components/resource-used-through-import/component.wit.print new file mode 100644 index 0000000000..33604827ec --- /dev/null +++ b/crates/wit-component/tests/components/resource-used-through-import/component.wit.print @@ -0,0 +1,11 @@ +package root:component + +world root { + import foo:bar/a + + export b: interface { + use foo:bar/a.{r} + + foo: func() -> r + } +} diff --git a/crates/wit-component/tests/components/resource-used-through-import/module.wat b/crates/wit-component/tests/components/resource-used-through-import/module.wat new file mode 100644 index 0000000000..8743c4acd8 --- /dev/null +++ b/crates/wit-component/tests/components/resource-used-through-import/module.wat @@ -0,0 +1,6 @@ +(module + (import "foo:bar/a" "[resource-drop-own]r" (func (param i32))) + (func (export "b#foo") (result i32) + (i32.const 0) + ) +) diff --git a/crates/wit-component/tests/components/resource-used-through-import/module.wit b/crates/wit-component/tests/components/resource-used-through-import/module.wit new file mode 100644 index 0000000000..789eeee3c1 --- /dev/null +++ b/crates/wit-component/tests/components/resource-used-through-import/module.wit @@ -0,0 +1,13 @@ +package foo:bar + +interface a { + resource r +} + +world module { + export b: interface { + use a.{r} + + foo: func() -> r + } +} diff --git a/crates/wit-component/tests/components/resource-using-export/component.wat b/crates/wit-component/tests/components/resource-using-export/component.wat new file mode 100644 index 0000000000..0cf7ec128b --- /dev/null +++ b/crates/wit-component/tests/components/resource-using-export/component.wat @@ -0,0 +1,63 @@ +(component + (core module (;0;) + (type (;0;) (func (param i32) (result i32))) + (type (;1;) (func (result i32))) + (import "[export]foo:bar/foo" "[resource-new]r" (func $new (;0;) (type 0))) + (func (;1;) (type 1) (result i32) + i32.const 100 + call $new + ) + (export "anon#f" (func 1)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (type (;0;) (resource (rep i32))) + (core func (;0;) (canon resource.new 0)) + (core instance (;0;) + (export "[resource-new]r" (func 0)) + ) + (core instance (;1;) (instantiate 0 + (with "[export]foo:bar/foo" (instance 0)) + ) + ) + (component (;0;) + (import "import-type-r" (type (;0;) (sub resource))) + (export (;1;) "r" (type 0)) + (type (;2;) (own 1)) + (export (;3;) "handle" (type 2)) + ) + (instance (;0;) (instantiate 0 + (with "import-type-r" (type 0)) + ) + ) + (export (;1;) (interface "foo:bar/foo") (instance 0)) + (alias export 1 "handle" (type (;1;))) + (type (;2;) (func (result 1))) + (alias core export 1 "anon#f" (core func (;1;))) + (func (;0;) (type 2) (canon lift (core func 1))) + (alias export 1 "r" (type (;3;))) + (component (;1;) + (import "import-type-r" (type (;0;) (sub resource))) + (type (;1;) (own 0)) + (import "import-type-handle" (type (;2;) (eq 1))) + (import "import-type-handle0" (type (;3;) (eq 2))) + (type (;4;) (func (result 3))) + (import "import-func-f" (func (;0;) (type 4))) + (export (;5;) "handle" (type 2)) + (type (;6;) (func (result 5))) + (export (;1;) "f" (func 0) (func (type 6))) + ) + (instance (;2;) (instantiate 1 + (with "import-func-f" (func 0)) + (with "import-type-r" (type 3)) + (with "import-type-handle" (type 1)) + (with "import-type-handle0" (type 1)) + ) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + (export (;3;) "anon" (instance 2)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/components/resource-using-export/component.wit.print b/crates/wit-component/tests/components/resource-using-export/component.wit.print new file mode 100644 index 0000000000..3de043990a --- /dev/null +++ b/crates/wit-component/tests/components/resource-using-export/component.wit.print @@ -0,0 +1,10 @@ +package root:component + +world root { + export foo:bar/foo + export anon: interface { + use foo:bar/foo.{handle} + + f: func() -> handle + } +} diff --git a/crates/wit-component/tests/components/resource-using-export/module.wat b/crates/wit-component/tests/components/resource-using-export/module.wat new file mode 100644 index 0000000000..38c372c877 --- /dev/null +++ b/crates/wit-component/tests/components/resource-using-export/module.wat @@ -0,0 +1,6 @@ +(module + (import "[export]foo:bar/foo" "[resource-new]r" (func $new (param i32) (result i32))) + (func (export "anon#f") (result i32) + (call $new (i32.const 100)) + ) +) diff --git a/crates/wit-component/tests/components/resource-using-export/module.wit b/crates/wit-component/tests/components/resource-using-export/module.wit new file mode 100644 index 0000000000..de8c41bffb --- /dev/null +++ b/crates/wit-component/tests/components/resource-using-export/module.wit @@ -0,0 +1,16 @@ +package foo:bar + +interface foo { + resource r + + type handle = own +} + +world module { + export foo + export anon: interface { + use foo.{handle} + f: func() -> handle + } +} + diff --git a/crates/wit-component/tests/components/tricky-resources/component.wat b/crates/wit-component/tests/components/tricky-resources/component.wat new file mode 100644 index 0000000000..7373e6cee9 --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources/component.wat @@ -0,0 +1,76 @@ +(component + (core module (;0;) + (type (;0;) (func (param i32))) + (type (;1;) (func (param i32) (result i32))) + (type (;2;) (func (result i32))) + (import "[export]foo:bar/a" "[resource-drop-own]r" (func (;0;) (type 0))) + (import "[export]foo:bar/a" "[resource-rep]r" (func (;1;) (type 1))) + (func (;2;) (type 2) (result i32) + unreachable + ) + (export "some-name#f" (func 2)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (type (;0;) (resource (rep i32))) + (type (;1;) (own 0)) + (core func (;0;) (canon resource.drop 1)) + (core func (;1;) (canon resource.rep 0)) + (core instance (;0;) + (export "[resource-drop-own]r" (func 0)) + (export "[resource-rep]r" (func 1)) + ) + (core instance (;1;) (instantiate 0 + (with "[export]foo:bar/a" (instance 0)) + ) + ) + (component (;0;) + (import "import-type-r" (type (;0;) (sub resource))) + (export (;1;) "r" (type 0)) + ) + (instance (;0;) (instantiate 0 + (with "import-type-r" (type 0)) + ) + ) + (export (;1;) (interface "foo:bar/a") (instance 0)) + (alias export 1 "r" (type (;2;))) + (component (;1;) + (import "import-type-r" (type (;0;) (sub resource))) + (export (;1;) "r" (type 0)) + ) + (instance (;2;) (instantiate 1 + (with "import-type-r" (type 2)) + ) + ) + (export (;3;) (interface "foo:bar/b") (instance 2)) + (alias export 3 "r" (type (;3;))) + (type (;4;) (own 3)) + (type (;5;) (func (result 4))) + (alias core export 1 "some-name#f" (core func (;2;))) + (func (;0;) (type 5) (canon lift (core func 2))) + (component (;2;) + (import "import-type-r" (type (;0;) (sub resource))) + (import "import-type-r0" (type (;1;) (eq 0))) + (import "import-type-r01" (type (;2;) (eq 1))) + (type (;3;) (own 2)) + (type (;4;) (func (result 3))) + (import "import-func-f" (func (;0;) (type 4))) + (export (;5;) "r" (type 1)) + (type (;6;) (own 5)) + (type (;7;) (func (result 6))) + (export (;1;) "f" (func 0) (func (type 7))) + ) + (instance (;4;) (instantiate 2 + (with "import-func-f" (func 0)) + (with "import-type-r" (type 2)) + (with "import-type-r0" (type 3)) + (with "import-type-r01" (type 3)) + ) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + (export (;5;) "some-name" (instance 4)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/components/tricky-resources/component.wit.print b/crates/wit-component/tests/components/tricky-resources/component.wit.print new file mode 100644 index 0000000000..d400c25403 --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources/component.wit.print @@ -0,0 +1,11 @@ +package root:component + +world root { + export foo:bar/a + export foo:bar/b + export some-name: interface { + use foo:bar/b.{r} + + f: func() -> r + } +} diff --git a/crates/wit-component/tests/components/tricky-resources/module.wat b/crates/wit-component/tests/components/tricky-resources/module.wat new file mode 100644 index 0000000000..39d80fa0c5 --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources/module.wat @@ -0,0 +1,7 @@ +(module + (import "[export]foo:bar/a" "[resource-drop-own]r" (func (param i32))) + (import "[export]foo:bar/a" "[resource-rep]r" (func (param i32) (result i32))) + + (func (export "some-name#f") (result i32) + unreachable) +) diff --git a/crates/wit-component/tests/components/tricky-resources/module.wit b/crates/wit-component/tests/components/tricky-resources/module.wit new file mode 100644 index 0000000000..c6e16e3d87 --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources/module.wit @@ -0,0 +1,19 @@ +package foo:bar + +interface a { + resource r +} + +interface b { + use a.{r} +} + +world module { + export b + export some-name: interface { + use b.{r} + + f: func() -> r + } + export a +} diff --git a/crates/wit-component/tests/components/tricky-resources2/component.wat b/crates/wit-component/tests/components/tricky-resources2/component.wat new file mode 100644 index 0000000000..8f51a7103b --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources2/component.wat @@ -0,0 +1,50 @@ +(component + (core module (;0;) + (type (;0;) (func (result i32))) + (func (;0;) (type 0) (result i32) + unreachable + ) + (export "anon#foo" (func 0)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (type (;0;) (resource (rep i32))) + (core instance (;0;) (instantiate 0)) + (component (;0;) + (import "import-type-r" (type (;0;) (sub resource))) + (export (;1;) "r" (type 0)) + ) + (instance (;0;) (instantiate 0 + (with "import-type-r" (type 0)) + ) + ) + (export (;1;) (interface "foo:bar/a") (instance 0)) + (alias export 1 "r" (type (;1;))) + (type (;2;) (own 1)) + (type (;3;) (func (result 2))) + (alias core export 0 "anon#foo" (core func (;0;))) + (func (;0;) (type 3) (canon lift (core func 0))) + (component (;1;) + (import "import-type-r" (type (;0;) (sub resource))) + (import "import-type-r0" (type (;1;) (eq 0))) + (type (;2;) (own 1)) + (type (;3;) (func (result 2))) + (import "import-func-foo" (func (;0;) (type 3))) + (export (;4;) "r" (type 0)) + (type (;5;) (own 4)) + (type (;6;) (func (result 5))) + (export (;1;) "foo" (func 0) (func (type 6))) + ) + (instance (;2;) (instantiate 1 + (with "import-func-foo" (func 0)) + (with "import-type-r" (type 1)) + (with "import-type-r0" (type 1)) + ) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + (export (;3;) "anon" (instance 2)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/components/tricky-resources2/component.wit.print b/crates/wit-component/tests/components/tricky-resources2/component.wit.print new file mode 100644 index 0000000000..ababf1db4c --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources2/component.wit.print @@ -0,0 +1,10 @@ +package root:component + +world root { + export foo:bar/a + export anon: interface { + use foo:bar/a.{r} + + foo: func() -> r + } +} diff --git a/crates/wit-component/tests/components/tricky-resources2/module.wat b/crates/wit-component/tests/components/tricky-resources2/module.wat new file mode 100644 index 0000000000..bb3630ceff --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources2/module.wat @@ -0,0 +1,4 @@ +(module + (func (export "anon#foo") (result i32) + unreachable) +) diff --git a/crates/wit-component/tests/components/tricky-resources2/module.wit b/crates/wit-component/tests/components/tricky-resources2/module.wit new file mode 100644 index 0000000000..9051906167 --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources2/module.wit @@ -0,0 +1,14 @@ +package foo:bar + +interface a { + resource r +} + +world module { + export anon: interface { + use a.{r} + + foo: func() -> r + } + export a +} diff --git a/crates/wit-component/tests/components/tricky-resources3/component.wat b/crates/wit-component/tests/components/tricky-resources3/component.wat new file mode 100644 index 0000000000..d0a3d16024 --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources3/component.wat @@ -0,0 +1,44 @@ +(component + (core module (;0;) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (type (;0;) (resource (rep i32))) + (core instance (;0;) (instantiate 0)) + (component (;0;) + (import "import-type-name" (type (;0;) (sub resource))) + (export (;1;) "name" (type 0)) + ) + (instance (;0;) (instantiate 0 + (with "import-type-name" (type 0)) + ) + ) + (export (;1;) (interface "foo:bar/foo") (instance 0)) + (alias export 1 "name" (type (;1;))) + (component (;1;) + (import "import-type-name" (type (;0;) (sub resource))) + (export (;1;) "name" (type 0)) + ) + (instance (;2;) (instantiate 1 + (with "import-type-name" (type 1)) + ) + ) + (export (;3;) (interface "foo:bar/name") (instance 2)) + (alias export 3 "name" (type (;2;))) + (component (;2;) + (import "import-type-name" (type (;0;) (sub resource))) + (import "import-type-name0" (type (;1;) (eq 0))) + (export (;2;) "name" (type 1)) + ) + (instance (;4;) (instantiate 2 + (with "import-type-name" (type 1)) + (with "import-type-name0" (type 2)) + ) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + (export (;5;) "name" (instance 4)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/components/tricky-resources3/component.wit.print b/crates/wit-component/tests/components/tricky-resources3/component.wit.print new file mode 100644 index 0000000000..082f52f829 --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources3/component.wit.print @@ -0,0 +1,9 @@ +package root:component + +world root { + export foo:bar/foo + export foo:bar/name + export name: interface { + use foo:bar/name.{name} + } +} diff --git a/crates/wit-component/tests/components/tricky-resources3/module.wat b/crates/wit-component/tests/components/tricky-resources3/module.wat new file mode 100644 index 0000000000..3af8f25454 --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources3/module.wat @@ -0,0 +1 @@ +(module) diff --git a/crates/wit-component/tests/components/tricky-resources3/module.wit b/crates/wit-component/tests/components/tricky-resources3/module.wit new file mode 100644 index 0000000000..23fd7ce899 --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources3/module.wit @@ -0,0 +1,17 @@ +package foo:bar + +interface foo { + resource name +} + +interface name { + use foo.{name} +} + +world module { + export foo + export name + export name: interface { + use name.{name} + } +} diff --git a/crates/wit-component/tests/components/tricky-resources4/component.wat b/crates/wit-component/tests/components/tricky-resources4/component.wat new file mode 100644 index 0000000000..b89f516d22 --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources4/component.wat @@ -0,0 +1,64 @@ +(component + (core module (;0;) + (type (;0;) (func (param i32) (result i32))) + (type (;1;) (func (result i32))) + (import "[export]foo:bar/name" "[resource-new]name" (func $new (;0;) (type 0))) + (func (;1;) (type 1) (result i32) + i32.const 100 + call $new + ) + (export "foo:bar/name#foo" (func 1)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (type (;0;) (resource (rep i32))) + (core func (;0;) (canon resource.new 0)) + (core instance (;0;) + (export "[resource-new]name" (func 0)) + ) + (core instance (;1;) (instantiate 0 + (with "[export]foo:bar/name" (instance 0)) + ) + ) + (type (;1;) (own 0)) + (type (;2;) (func (result 1))) + (alias core export 1 "foo:bar/name#foo" (core func (;1;))) + (func (;0;) (type 2) (canon lift (core func 1))) + (component (;0;) + (import "import-type-name" (type (;0;) (sub resource))) + (import "import-type-handle" (type (;1;) (eq 0))) + (type (;2;) (own 1)) + (type (;3;) (func (result 2))) + (import "import-func-foo" (func (;0;) (type 3))) + (export (;4;) "name" (type 0)) + (export (;5;) "handle" (type 4)) + (type (;6;) (own 5)) + (type (;7;) (func (result 6))) + (export (;1;) "foo" (func 0) (func (type 7))) + ) + (instance (;0;) (instantiate 0 + (with "import-func-foo" (func 0)) + (with "import-type-name" (type 0)) + (with "import-type-handle" (type 0)) + ) + ) + (export (;1;) (interface "foo:bar/name") (instance 0)) + (alias export 1 "name" (type (;3;))) + (alias export 1 "handle" (type (;4;))) + (component (;1;) + (import "import-type-name" (type (;0;) (sub resource))) + (import "import-type-handle" (type (;1;) (eq 0))) + (export (;2;) "handle" (type 1)) + ) + (instance (;2;) (instantiate 1 + (with "import-type-name" (type 3)) + (with "import-type-handle" (type 4)) + ) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + (export (;3;) "name" (instance 2)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/components/tricky-resources4/component.wit.print b/crates/wit-component/tests/components/tricky-resources4/component.wit.print new file mode 100644 index 0000000000..5bc857709d --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources4/component.wit.print @@ -0,0 +1,8 @@ +package root:component + +world root { + export foo:bar/name + export name: interface { + use foo:bar/name.{handle} + } +} diff --git a/crates/wit-component/tests/components/tricky-resources4/module.wat b/crates/wit-component/tests/components/tricky-resources4/module.wat new file mode 100644 index 0000000000..bb3ade4901 --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources4/module.wat @@ -0,0 +1,6 @@ +(module + (import "[export]foo:bar/name" "[resource-new]name" + (func $new (param i32) (result i32))) + (func (export "foo:bar/name#foo") (result i32) + (call $new (i32.const 100))) +) diff --git a/crates/wit-component/tests/components/tricky-resources4/module.wit b/crates/wit-component/tests/components/tricky-resources4/module.wit new file mode 100644 index 0000000000..5dbeb19174 --- /dev/null +++ b/crates/wit-component/tests/components/tricky-resources4/module.wit @@ -0,0 +1,16 @@ +package foo:bar + +interface name { + resource name + + type handle = name + + foo: func() -> handle +} + +world module { + export name + export name: interface { + use name.{handle} + } +} diff --git a/crates/wit-component/tests/components/worlds-with-type-renamings/component.wat b/crates/wit-component/tests/components/worlds-with-type-renamings/component.wat index a86c319ddf..396e108458 100644 --- a/crates/wit-component/tests/components/worlds-with-type-renamings/component.wat +++ b/crates/wit-component/tests/components/worlds-with-type-renamings/component.wat @@ -41,9 +41,10 @@ ) (alias core export 1 "memory" (core memory (;0;))) (alias core export 1 "cabi_realloc" (core func (;1;))) - (type (;3;) (func (result 1))) + (type (;3;) (record)) + (type (;4;) (func (result 3))) (alias core export 1 "foo:foo/i#the-func" (core func (;2;))) - (func (;1;) (type 3) (canon lift (core func 2))) + (func (;1;) (type 4) (canon lift (core func 2))) (component (;0;) (type (;0;) (record)) (import "import-type-some-type" (type (;1;) (eq 0))) @@ -56,7 +57,7 @@ ) (instance (;1;) (instantiate 0 (with "import-func-the-func" (func 1)) - (with "import-type-some-type" (type 1)) + (with "import-type-some-type" (type 3)) ) ) (@producers diff --git a/crates/wit-component/tests/interfaces/resources.wat b/crates/wit-component/tests/interfaces/resources.wat new file mode 100644 index 0000000000..3a1f864240 --- /dev/null +++ b/crates/wit-component/tests/interfaces/resources.wat @@ -0,0 +1,152 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (export (;0;) "bar" (type (sub resource))) + (export (;1;) "t" (type (eq 0))) + (type (;2;) (own 0)) + (export (;3;) "t2" (type (eq 2))) + (type (;4;) (own 0)) + (type (;5;) (func (result 4))) + (export (;0;) "[constructor]bar" (func (type 5))) + (type (;6;) (func)) + (export (;1;) "[static]bar.a" (func (type 6))) + (type (;7;) (borrow 0)) + (type (;8;) (func (param "self" 7))) + (export (;2;) "[method]bar.b" (func (type 8))) + (type (;9;) (func (param "x" 4) (result 4))) + (export (;3;) "a" (func (type 9))) + ) + ) + (export (;0;) (interface "foo:bar/foo") (instance (type 0))) + (alias export 0 "bar" (type (;1;))) + (alias export 0 "t" (type (;2;))) + (type (;3;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "bar" (type (eq 0))) + (alias outer 1 2 (type (;2;))) + (export (;3;) "t" (type (eq 2))) + (type (;4;) (own 1)) + (type (;5;) (func (param "x" 4) (result 4))) + (export (;0;) "a" (func (type 5))) + (type (;6;) (own 3)) + (type (;7;) (func (result 6))) + (export (;1;) "b" (func (type 7))) + ) + ) + (export (;1;) (interface "foo:bar/baz") (instance (type 3))) + (type (;4;) + (instance + (export (;0;) "a" (type (sub resource))) + (type (;1;) (own 0)) + (type (;2;) (func (param "a1" 1) (param "a2" 1) (result 1))) + (export (;0;) "b" (func (type 2))) + (type (;3;) (list 1)) + (type (;4;) (func (result 3))) + (export (;1;) "c" (func (type 4))) + ) + ) + (export (;2;) (interface "foo:bar/implicit-own-handles") (instance (type 4))) + (type (;5;) + (instance + (export (;0;) "a" (type (sub resource))) + (export (;1;) "b" (type (sub resource))) + (export (;2;) "c" (type (sub resource))) + (type (;3;) (own 0)) + (type (;4;) (list 3)) + (type (;5;) (func (param "a" 4) (result 3))) + (export (;0;) "[constructor]a" (func (type 5))) + (type (;6;) (own 1)) + (type (;7;) (list 6)) + (type (;8;) (func (param "a" 7) (param "b" 6) (result 6))) + (export (;1;) "[constructor]b" (func (type 8))) + (type (;9;) (own 2)) + (type (;10;) (func (param "a" 9) (result 9))) + (export (;2;) "[static]c.a" (func (type 10))) + ) + ) + (export (;3;) (interface "foo:bar/implicit-own-handles2") (instance (type 5))) + (type (;6;) + (component + (type (;0;) + (instance + (export (;0;) "a" (type (sub resource))) + (type (;1;) (own 0)) + (type (;2;) (func (result 1))) + (export (;0;) "[constructor]a" (func (type 2))) + (type (;3;) (func)) + (export (;1;) "[static]a.b" (func (type 3))) + (type (;4;) (borrow 0)) + (type (;5;) (func (param "self" 4))) + (export (;2;) "[method]a.c" (func (type 5))) + ) + ) + (import "anon" (instance (;0;) (type 0))) + (type (;1;) + (instance + (export (;0;) "bar" (type (sub resource))) + (export (;1;) "t" (type (eq 0))) + (type (;2;) (own 0)) + (export (;3;) "t2" (type (eq 2))) + (type (;4;) (own 0)) + (type (;5;) (func (result 4))) + (export (;0;) "[constructor]bar" (func (type 5))) + (type (;6;) (func)) + (export (;1;) "[static]bar.a" (func (type 6))) + (type (;7;) (borrow 0)) + (type (;8;) (func (param "self" 7))) + (export (;2;) "[method]bar.b" (func (type 8))) + (type (;9;) (func (param "x" 4) (result 4))) + (export (;3;) "a" (func (type 9))) + ) + ) + (import (interface "foo:bar/foo") (instance (;1;) (type 1))) + (alias export 1 "bar" (type (;2;))) + (alias export 1 "t" (type (;3;))) + (type (;4;) + (instance + (alias outer 1 2 (type (;0;))) + (export (;1;) "bar" (type (eq 0))) + (alias outer 1 3 (type (;2;))) + (export (;3;) "t" (type (eq 2))) + (type (;4;) (own 1)) + (type (;5;) (func (param "x" 4) (result 4))) + (export (;0;) "a" (func (type 5))) + (type (;6;) (own 3)) + (type (;7;) (func (result 6))) + (export (;1;) "b" (func (type 7))) + ) + ) + (import (interface "foo:bar/baz") (instance (;2;) (type 4))) + (alias export 2 "bar" (type (;5;))) + (import "bar" (type (;6;) (eq 5))) + (import "a" (type (;7;) (sub resource))) + (type (;8;) (own 7)) + (type (;9;) (func (result 8))) + (import "[constructor]a" (func (;0;) (type 9))) + (export (;1;) "x" (func (type 9))) + (type (;10;) (own 6)) + (type (;11;) (func (result 10))) + (export (;2;) "y" (func (type 11))) + ) + ) + (export (;0;) (interface "foo:bar/some-world") (component (type 6))) + (type (;7;) + (component + (import "a" (type (;0;) (sub resource))) + (type (;1;) (own 0)) + (type (;2;) (list 1)) + (type (;3;) (func (param "a" 2) (param "b" 2) (result 1))) + (import "[constructor]a" (func (;0;) (type 3))) + ) + ) + (export (;1;) (interface "foo:bar/implicit-own-handles3") (component (type 7))) + ) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + (export (;1;) (interface "foo:bar/wit") (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/resources.wit b/crates/wit-component/tests/interfaces/resources.wit new file mode 100644 index 0000000000..b2a036736a --- /dev/null +++ b/crates/wit-component/tests/interfaces/resources.wit @@ -0,0 +1,76 @@ +package foo:bar + +interface foo { + resource bar { + constructor() + static a: func() + b: func() + } + + a: func(x: bar) -> bar + type t = bar + type t2 = own +} + +interface baz { + use foo.{bar,t} + a: func(x: bar) -> bar + + b: func() -> t +} + + +world some-world { + use baz.{bar} + + resource a { + constructor() + } + + export x: func() -> a + export y: func() -> bar + + import anon: interface { + resource a { + constructor() + + static b: func() + + c: func() + } + } +} + +interface implicit-own-handles { + resource a + + b: func(a1: a, a2: a) -> own + c: func() -> list> +} + +interface implicit-own-handles2 { + // the `own` return and list param should be the same `own` + resource a { + constructor(a: list) + } + + // same as above, even when the `list` implicitly-defined `own` comes + // before an explicitly defined `own` + resource b { + constructor(a: list, b: own) + } + + // same as the above, the `own` argument should have the same type as the + // return value + resource c { + static a: func(a: own) -> c + } +} + +world implicit-own-handles3 { + // there should only be one `list` type despite there looking like two + // list types here + resource a { + constructor(a: list, b: list>) + } +} diff --git a/crates/wit-component/tests/interfaces/resources.wit.print b/crates/wit-component/tests/interfaces/resources.wit.print new file mode 100644 index 0000000000..85dfd65716 --- /dev/null +++ b/crates/wit-component/tests/interfaces/resources.wit.print @@ -0,0 +1,70 @@ +package foo:bar + +interface foo { + resource bar { + constructor() + static a: func() + b: func() + } + + type t = bar + + type t2 = own + + a: func(x: bar) -> bar +} + +interface baz { + use foo.{bar, t} + + a: func(x: bar) -> bar + + b: func() -> t +} + +interface implicit-own-handles { + resource a + + b: func(a1: a, a2: a) -> a + + c: func() -> list +} + +interface implicit-own-handles2 { + resource a { + constructor(a: list) + } + + resource b { + constructor(a: list, b: b) + } + + resource c { + static a: func(a: c) -> c + } +} + +world some-world { + import anon: interface { + resource a { + constructor() + static b: func() + c: func() + } + } + import foo + import baz + use baz.{bar} + + resource a { + constructor() + } + + export x: func() -> a + export y: func() -> bar +} +world implicit-own-handles3 { + resource a { + constructor(a: list, b: list) + } +} diff --git a/crates/wit-parser/src/abi.rs b/crates/wit-parser/src/abi.rs index 43c3dcdab2..c805d12991 100644 --- a/crates/wit-parser/src/abi.rs +++ b/crates/wit-parser/src/abi.rs @@ -1,6 +1,6 @@ use crate::sizealign::align_to; use crate::{ - Enum, Flags, FlagsRepr, Function, Int, Record, Resolve, Result_, Results, Tuple, Type, + Enum, Flags, FlagsRepr, Function, Handle, Int, Record, Resolve, Result_, Results, Tuple, Type, TypeDefKind, TypeId, Union, Variant, }; @@ -772,7 +772,9 @@ impl Resolve { Type::Id(id) => match &self.types[*id].kind { TypeDefKind::Type(t) => self.push_wasm(variant, t, result), - TypeDefKind::Handle(_) => todo!(), + TypeDefKind::Handle(Handle::Own(_) | Handle::Borrow(_)) => { + result.push(WasmType::I32); + } TypeDefKind::Resource => todo!(), @@ -903,8 +905,8 @@ impl Resolve { Type::Id(id) => match &self.types[*id].kind { TypeDefKind::List(_) => true, TypeDefKind::Type(t) => self.needs_post_return(t), - TypeDefKind::Handle(_) => true, - TypeDefKind::Resource => true, + TypeDefKind::Handle(_) => false, + TypeDefKind::Resource => false, TypeDefKind::Record(r) => r.fields.iter().any(|f| self.needs_post_return(&f.ty)), TypeDefKind::Tuple(t) => t.types.iter().any(|t| self.needs_post_return(t)), TypeDefKind::Union(t) => t.cases.iter().any(|t| self.needs_post_return(&t.ty)), diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 6f3ae6f982..9fa7ec213c 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1317,7 +1317,7 @@ impl<'a> Resolver<'a> { FunctionKind::Method(id) => { let shared = self.anon_type_def(TypeDef { docs: Docs::default(), - kind: TypeDefKind::Handle(Handle::Own(id)), + kind: TypeDefKind::Handle(Handle::Borrow(id)), name: None, owner: TypeOwner::None, }); diff --git a/crates/wit-parser/tests/ui/name-both-resource-and-type/foo.wit b/crates/wit-parser/tests/ui/name-both-resource-and-type/foo.wit index 767c231eff..fa6174955d 100644 --- a/crates/wit-parser/tests/ui/name-both-resource-and-type/foo.wit +++ b/crates/wit-parser/tests/ui/name-both-resource-and-type/foo.wit @@ -5,4 +5,5 @@ interface foo { type t1 = a type t2 = borrow + type t3 = borrow } diff --git a/crates/wit-parser/tests/ui/parse-fail/type-and-resource-same-name.wit.result b/crates/wit-parser/tests/ui/parse-fail/type-and-resource-same-name.wit.result index e70a457d93..92159eba37 100644 --- a/crates/wit-parser/tests/ui/parse-fail/type-and-resource-same-name.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/type-and-resource-same-name.wit.result @@ -1,4 +1,4 @@ -type is not a resource +type used in a handle must be a resource --> tests/ui/parse-fail/type-and-resource-same-name/foo.wit:7:20 | 7 | type t2 = borrow diff --git a/crates/wit-parser/tests/ui/resources.wit b/crates/wit-parser/tests/ui/resources.wit index ae45f832fc..278076dfd6 100644 --- a/crates/wit-parser/tests/ui/resources.wit +++ b/crates/wit-parser/tests/ui/resources.wit @@ -37,3 +37,11 @@ world w { constructor() } } + +interface i { + resource a + + type t1 = a + type t2 = borrow + type t3 = borrow +} diff --git a/crates/wit-smith/src/config.rs b/crates/wit-smith/src/config.rs index ab2d9304b9..ea837c5cfd 100644 --- a/crates/wit-smith/src/config.rs +++ b/crates/wit-smith/src/config.rs @@ -17,6 +17,8 @@ pub struct Config { pub max_type_parts: usize, #[cfg_attr(feature = "clap", clap(long, default_value_t = Config::default().max_files_per_package))] pub max_files_per_package: usize, + #[cfg_attr(feature = "clap", clap(long, default_value_t = Config::default().max_resource_items))] + pub max_resource_items: usize, } impl Default for Config { @@ -29,6 +31,7 @@ impl Default for Config { max_pkg_items: 10, max_type_parts: 10, max_files_per_package: 10, + max_resource_items: 10, } } } @@ -43,6 +46,7 @@ impl Arbitrary<'_> for Config { max_world_items: u.int_in_range(0..=10)?, max_pkg_items: u.int_in_range(0..=10)?, max_type_parts: u.int_in_range(1..=10)?, + max_resource_items: u.int_in_range(0..=10)?, }) } } diff --git a/crates/wit-smith/src/generate.rs b/crates/wit-smith/src/generate.rs index e11e04ab60..673e94bfa6 100644 --- a/crates/wit-smith/src/generate.rs +++ b/crates/wit-smith/src/generate.rs @@ -15,7 +15,7 @@ pub struct Generator { next_interface_id: u32, } -type TypeList = Vec<(String, usize)>; +type TypeList = Vec; type InterfaceList = IndexMap; type PackageList = Vec<(PackageName, InterfaceList)>; @@ -27,6 +27,13 @@ struct InterfaceGenerator<'a> { types_in_interface: TypeList, } +#[derive(Clone)] +struct Type { + name: String, + size: usize, + is_resource: bool, +} + pub struct Package { pub name: PackageName, pub sources: SourceMap, @@ -337,9 +344,15 @@ impl<'a> InterfaceGenerator<'a> { } Generate::Type => { let name = self.gen_unique_name(u)?; - let (size, typedef) = self.gen_typedef(u, &name)?; + let (ty, mut typedef) = self.gen_typedef(u, &name)?; + let is_resource = ty.is_resource; + self.types_in_interface.push(ty); + if is_resource && u.arbitrary()? { + typedef.push_str(" {\n"); + self.gen_resource_funcs(u, &mut typedef)?; + typedef.push_str("}"); + } parts.push(typedef); - self.types_in_interface.push((name, size)); } Generate::Function => { parts.push(self.gen_func(u)?); @@ -413,7 +426,7 @@ impl<'a> InterfaceGenerator<'a> { match kind { ItemKind::Func(_) => { - self.gen_func_sig(u, &mut part)?; + self.gen_func_sig(u, &mut part, false)?; } ItemKind::Interface(dir) => { let id = match self.gen.gen_path(u, self.file, &mut part)? { @@ -445,10 +458,17 @@ impl<'a> InterfaceGenerator<'a> { ItemKind::Type => { let name = name.unwrap(); - let (size, typedef) = self.gen_typedef(u, &name)?; + let (ty, typedef) = self.gen_typedef(u, &name)?; assert!(part.is_empty()); part = typedef; - self.types_in_interface.push((name, size)); + let is_resource = ty.is_resource; + self.types_in_interface.push(ty); + + if is_resource && u.arbitrary()? { + part.push_str(" {\n"); + self.gen_resource_funcs(u, &mut part)?; + part.push_str("}"); + } } ItemKind::Use => { @@ -472,6 +492,53 @@ impl<'a> InterfaceGenerator<'a> { Ok(ret) } + fn gen_resource_funcs(&mut self, u: &mut Unstructured<'_>, ret: &mut String) -> Result<()> { + let mut parts = Vec::new(); + + #[derive(Arbitrary)] + enum Item { + Constructor, + Static, + Method, + } + + let mut has_constructor = false; + let mut names = HashSet::new(); + while parts.len() < self.config.max_resource_items && !u.is_empty() && u.arbitrary()? { + match u.arbitrary()? { + Item::Constructor if has_constructor => {} + Item::Constructor => { + has_constructor = true; + let mut part = format!("constructor"); + self.gen_params(u, &mut part, false)?; + parts.push(part); + } + Item::Static => { + let mut part = format!("static %"); + part.push_str(&gen_unique_name(u, &mut names)?); + part.push_str(": "); + self.gen_func_sig(u, &mut part, false)?; + parts.push(part); + } + Item::Method => { + let mut part = format!("%"); + part.push_str(&gen_unique_name(u, &mut names)?); + part.push_str(": "); + self.gen_func_sig(u, &mut part, true)?; + parts.push(part); + } + } + } + + shuffle(u, &mut parts)?; + + for part in parts { + ret.push_str(&part); + ret.push_str("\n"); + } + Ok(()) + } + fn gen_use(&mut self, u: &mut Unstructured<'_>, part: &mut String) -> Result { let mut path = String::new(); let (_name, _id, types) = match self.gen.gen_path(u, self.file, &mut path)? { @@ -481,25 +548,30 @@ impl<'a> InterfaceGenerator<'a> { part.push_str("use "); part.push_str(&path); part.push_str(".{"); - let (name, size) = u.choose(types)?; - let size = *size; + let ty = u.choose(types)?; part.push_str("%"); - part.push_str(name); - let name = if self.unique_names.contains(name) || u.arbitrary()? { + part.push_str(&ty.name); + let size = ty.size; + let is_resource = ty.is_resource; + let name = if self.unique_names.contains(&ty.name) || u.arbitrary()? { part.push_str(" as %"); let name = self.gen_unique_name(u)?; part.push_str(&name); name } else { - assert!(self.unique_names.insert(name.clone())); - name.clone() + assert!(self.unique_names.insert(ty.name.clone())); + ty.name.clone() }; - self.types_in_interface.push((name, size)); + self.types_in_interface.push(Type { + name, + size, + is_resource, + }); part.push_str("}"); Ok(true) } - fn gen_typedef(&mut self, u: &mut Unstructured<'_>, name: &str) -> Result<(usize, String)> { + fn gen_typedef(&mut self, u: &mut Unstructured<'_>, name: &str) -> Result<(Type, String)> { #[derive(Arbitrary)] pub enum Kind { Record, @@ -508,10 +580,12 @@ impl<'a> InterfaceGenerator<'a> { Enum, Union, Anonymous, + Resource, } let mut fuel = self.config.max_type_size; let mut ret = String::new(); + let mut is_resource = false; match u.arbitrary()? { Kind::Record => { ret.push_str("record %"); @@ -581,9 +655,19 @@ impl<'a> InterfaceGenerator<'a> { ret.push_str(" = "); self.gen_type(u, &mut fuel, &mut ret)?; } + Kind::Resource => { + is_resource = true; + ret.push_str("resource %"); + ret.push_str(name); + } } - Ok((self.config.max_type_size - fuel, ret)) + let ty = Type { + size: self.config.max_type_size - fuel, + is_resource, + name: name.to_string(), + }; + Ok((ty, ret)) } fn gen_type( @@ -640,13 +724,22 @@ impl<'a> InterfaceGenerator<'a> { if self.types_in_interface.is_empty() { continue; } - let (name, type_size) = u.choose(&self.types_in_interface)?; - *fuel = match fuel.checked_sub(*type_size) { + let ty = u.choose(&self.types_in_interface)?; + *fuel = match fuel.checked_sub(ty.size) { Some(fuel) => fuel, None => continue, }; + let own_wrapper = if ty.is_resource && u.arbitrary()? { + dst.push_str("own<"); + true + } else { + false + }; dst.push_str("%"); - dst.push_str(name); + dst.push_str(&ty.name); + if own_wrapper { + dst.push_str(">"); + } } Kind::Tuple => { let fields = u.int_in_range(0..=self.config.max_type_parts)?; @@ -720,16 +813,21 @@ impl<'a> InterfaceGenerator<'a> { let mut ret = "%".to_string(); ret.push_str(&self.gen_unique_name(u)?); ret.push_str(": "); - self.gen_func_sig(u, &mut ret)?; + self.gen_func_sig(u, &mut ret, false)?; Ok(ret) } - fn gen_func_sig(&mut self, u: &mut Unstructured<'_>, dst: &mut String) -> Result<()> { + fn gen_func_sig( + &mut self, + u: &mut Unstructured<'_>, + dst: &mut String, + method: bool, + ) -> Result<()> { dst.push_str("func"); - self.gen_params(u, dst)?; + self.gen_params(u, dst, method)?; if u.arbitrary()? { dst.push_str(" -> "); - self.gen_params(u, dst)?; + self.gen_params(u, dst, false)?; } else if u.arbitrary()? { dst.push_str(" -> "); let mut fuel = self.config.max_type_size; @@ -738,15 +836,24 @@ impl<'a> InterfaceGenerator<'a> { Ok(()) } - fn gen_params(&mut self, u: &mut Unstructured<'_>, dst: &mut String) -> Result<()> { + fn gen_params( + &mut self, + u: &mut Unstructured<'_>, + dst: &mut String, + method: bool, + ) -> Result<()> { dst.push_str("("); + let mut names = HashSet::new(); + if method { + names.insert("self".to_string()); + } let mut fuel = self.config.max_type_size; for i in 0..u.int_in_range(0..=self.config.max_type_parts)? { if i > 0 { dst.push_str(", "); } dst.push_str("%"); - dst.push_str(&self.gen_unique_name(u)?); + dst.push_str(&gen_unique_name(u, &mut names)?); dst.push_str(": "); self.gen_type(u, &mut fuel, dst)?; } diff --git a/crates/wit-smith/src/lib.rs b/crates/wit-smith/src/lib.rs index 4dfdb8deb2..d16c70b1ef 100644 --- a/crates/wit-smith/src/lib.rs +++ b/crates/wit-smith/src/lib.rs @@ -24,7 +24,14 @@ pub fn smith(config: &Config, u: &mut Unstructured<'_>) -> Result> { let unresolved = pkg.sources.parse().unwrap(); let id = match resolve.push(unresolved) { Ok(id) => id, - Err(e) => panic!("bad wit parse: {e:?}"), + Err(e) => { + if e.to_string() + .contains("both an imported and exported copy of the same interface") + { + return Err(arbitrary::Error::IncorrectFormat); + } + panic!("bad wit parse: {e:?}") + } }; last = Some(id); } diff --git a/tests/snapshots/local/component-model/resources.wast/3.print b/tests/snapshots/local/component-model/resources.wast/3.print index bb0da286ce..b1aaacf507 100644 --- a/tests/snapshots/local/component-model/resources.wast/3.print +++ b/tests/snapshots/local/component-model/resources.wast/3.print @@ -6,6 +6,6 @@ ) (core instance $m (;0;) (instantiate $m)) (alias core export $m "dtor" (core func (;0;))) - (type $x (;0;) (resource (rep i32)(dtor (func 0)))) + (type $x (;0;) (resource (rep i32) (dtor (func 0)))) (core func (;1;) (canon resource.new $x)) ) \ No newline at end of file