diff --git a/crates/wasm-compose/src/encoding.rs b/crates/wasm-compose/src/encoding.rs index 70e74073b0..fa9dacde46 100644 --- a/crates/wasm-compose/src/encoding.rs +++ b/crates/wasm-compose/src/encoding.rs @@ -455,7 +455,7 @@ impl<'a> TypeEncoder<'a> { id: ComponentInstanceTypeId, ) -> u32 { let ty = &self.0.types[id]; - let instance = self.instance(state, ty.exports.iter().map(|(n, t)| (n.as_str(), *t))); + let instance = self.instance(state, ty.exports.iter().map(|(n, t)| (n.as_str(), t.ty))); let index = state.cur.encodable.type_count(); state.cur.encodable.ty().instance(&instance); index @@ -466,8 +466,8 @@ impl<'a> TypeEncoder<'a> { let component = self.component( state, - ty.imports.iter().map(|(n, t)| (n.as_str(), *t)), - ty.exports.iter().map(|(n, t)| (n.as_str(), *t)), + ty.imports.iter().map(|(n, t)| (n.as_str(), t.ty)), + ty.exports.iter().map(|(n, t)| (n.as_str(), t.ty)), ); let index = state.cur.encodable.type_count(); @@ -884,7 +884,7 @@ impl ArgumentImport<'_> { let mut map = IndexMap::with_capacity(exports.len()); for (name, ty) in exports { - map.insert(name.as_str(), vec![(*component, *ty)]); + map.insert(name.as_str(), vec![(*component, ty.ty)]); } self.kind = ArgumentImportKind::Instance(map); @@ -907,7 +907,7 @@ impl ArgumentImport<'_> { existing_component, *existing_type, new_component, - *new_type, + new_type.ty, remapping, ) { continue; @@ -923,7 +923,7 @@ impl ArgumentImport<'_> { ecname = existing_component.name, ) } - dst.push((new_component, *new_type)); + dst.push((new_component, new_type.ty)); } } // Otherwise, an attempt to merge an instance with a non-instance is an error @@ -1244,14 +1244,14 @@ impl DependencyRegistrar<'_, '_> { fn component(&mut self, ty: ComponentTypeId) { let ty = &self.types[ty]; - for (_, ty) in ty.imports.iter().chain(&ty.exports) { - self.entity(*ty); + for ty in ty.imports.values().chain(ty.exports.values()) { + self.entity(ty.ty); } } fn instance(&mut self, ty: ComponentInstanceTypeId) { for (_, ty) in self.types[ty].exports.iter() { - self.entity(*ty); + self.entity(ty.ty); } } diff --git a/crates/wasm-compose/src/graph.rs b/crates/wasm-compose/src/graph.rs index 57839e5023..664486f85e 100644 --- a/crates/wasm-compose/src/graph.rs +++ b/crates/wasm-compose/src/graph.rs @@ -144,14 +144,14 @@ impl<'a> Component<'a> { Payload::ComponentImportSection(s) => { for import in s { let import = import?; - let name = import.name.0.to_string(); + let name = import.name.name.to_string(); imports.insert(name, import.ty); } } Payload::ComponentExportSection(s) => { for export in s { let export = export?; - let name = export.name.0.to_string(); + let name = export.name.name.to_string(); exports.insert(name, (export.kind, export.index)); } } @@ -277,7 +277,7 @@ impl<'a> Component<'a> { let (name, _kind, _index) = self.export(index)?; Some(( name, - self.types.as_ref().component_entity_type_of_export(name)?, + self.types.as_ref().component_item_for_export(name)?.ty, )) } @@ -288,7 +288,7 @@ impl<'a> Component<'a> { let (name, _ty) = self.import(index)?; Some(( name, - self.types.as_ref().component_entity_type_of_import(name)?, + self.types.as_ref().component_item_for_import(name)?.ty, )) } @@ -333,7 +333,7 @@ impl<'a> Component<'a> { match self.exports.get_full(k.as_str()) { Some((ai, _, _)) => { let (_, a) = self.export_entity_type(ExportIndex(ai)).unwrap(); - if !ComponentEntityType::is_subtype_of(&a, self.types(), b, types) { + if !ComponentEntityType::is_subtype_of(&a, self.types(), &b.ty, types) { return false; } } @@ -497,7 +497,7 @@ impl ResourceMapping { if let ComponentEntityType::Type { referenced: ComponentAnyTypeId::Resource(resource_id), .. - } = ty + } = ty.ty { exports.insert(export_name, (export_component, resource_id.resource())); } @@ -508,7 +508,7 @@ impl ResourceMapping { if let ComponentEntityType::Type { referenced: ComponentAnyTypeId::Resource(resource_id), .. - } = ty + } = ty.ty { let import_resource = resource_id.resource(); if let Some((export_component, export_resource)) = @@ -591,8 +591,9 @@ impl<'a> CompositionGraph<'a> { let ty = component .types .as_ref() - .component_entity_type_of_import(import_name) - .unwrap(); + .component_item_for_import(import_name) + .unwrap() + .ty; if let ComponentEntityType::Instance(instance_id) = ty { for (export_name, ty) in &component.types[instance_id].exports { @@ -600,7 +601,7 @@ impl<'a> CompositionGraph<'a> { if let ComponentEntityType::Type { referenced: ComponentAnyTypeId::Resource(resource_id), .. - } = ty + } = ty.ty { let set = resource_imports .entry(vec![import_name.to_string(), export_name.to_string()]) diff --git a/crates/wasm-encoder/src/component/builder.rs b/crates/wasm-encoder/src/component/builder.rs index c2065a249a..c971a2bd42 100644 --- a/crates/wasm-encoder/src/component/builder.rs +++ b/crates/wasm-encoder/src/component/builder.rs @@ -327,14 +327,19 @@ impl ComponentBuilder { } /// Imports a new item into this component with the `name` and `ty` specified. - pub fn import(&mut self, name: &str, ty: ComponentTypeRef) -> u32 { + pub fn import<'a>( + &mut self, + name: impl Into>, + ty: ComponentTypeRef, + ) -> u32 { + let name = name.into(); let ret = match &ty { - ComponentTypeRef::Instance(_) => self.instances.add(Some(name)), - ComponentTypeRef::Func(_) => self.funcs.add(Some(name)), - ComponentTypeRef::Type(..) => self.types.add(Some(name)), - ComponentTypeRef::Component(_) => self.components.add(Some(name)), - ComponentTypeRef::Module(_) => self.core_modules.add(Some(name)), - ComponentTypeRef::Value(_) => self.values.add(Some(name)), + ComponentTypeRef::Instance(_) => self.instances.add(Some(&name.name)), + ComponentTypeRef::Func(_) => self.funcs.add(Some(&name.name)), + ComponentTypeRef::Type(..) => self.types.add(Some(&name.name)), + ComponentTypeRef::Component(_) => self.components.add(Some(&name.name)), + ComponentTypeRef::Module(_) => self.core_modules.add(Some(&name.name)), + ComponentTypeRef::Value(_) => self.values.add(Some(&name.name)), }; self.imports().import(name, ty); ret @@ -345,15 +350,16 @@ impl ComponentBuilder { /// /// The `idx` is the item to export and the `ty` is an optional type to /// ascribe to the export. - pub fn export( + pub fn export<'a>( &mut self, - name: &str, + name: impl Into>, kind: ComponentExportKind, idx: u32, ty: Option, ) -> u32 { - self.exports().export(name, kind, idx, ty); - self.inc_kind(Some(name), kind) + let name = name.into(); + self.exports().export(name.clone(), kind, idx, ty); + self.inc_kind(Some(&name.name), kind) } /// Creates a new encoder for the next core type in this component. diff --git a/crates/wasm-encoder/src/component/exports.rs b/crates/wasm-encoder/src/component/exports.rs index ef4745bda7..a1333fa070 100644 --- a/crates/wasm-encoder/src/component/exports.rs +++ b/crates/wasm-encoder/src/component/exports.rs @@ -2,7 +2,10 @@ use super::{ COMPONENT_SORT, CORE_MODULE_SORT, CORE_SORT, FUNCTION_SORT, INSTANCE_SORT, TYPE_SORT, VALUE_SORT, }; -use crate::{ComponentSection, ComponentSectionId, ComponentTypeRef, Encode, encode_section}; +use crate::{ + ComponentExternName, ComponentSection, ComponentSectionId, ComponentTypeRef, Encode, + encode_section, +}; use alloc::vec::Vec; /// Represents the kind of an export from a WebAssembly component. @@ -87,14 +90,14 @@ impl ComponentExportSection { } /// Define an export in the export section. - pub fn export( + pub fn export<'a>( &mut self, - name: &str, + name: impl Into>, kind: ComponentExportKind, index: u32, ty: Option, ) -> &mut Self { - crate::encode_component_export_name(&mut self.bytes, name); + name.into().encode(&mut self.bytes); kind.encode(&mut self.bytes); index.encode(&mut self.bytes); match ty { @@ -122,9 +125,3 @@ impl ComponentSection for ComponentExportSection { ComponentSectionId::Export.into() } } - -/// For more information on this see `encode_component_import_name`. -pub(crate) fn encode_component_export_name(bytes: &mut Vec, name: &str) { - bytes.push(0x00); - name.encode(bytes); -} diff --git a/crates/wasm-encoder/src/component/imports.rs b/crates/wasm-encoder/src/component/imports.rs index 4134ac5433..26adc807a4 100644 --- a/crates/wasm-encoder/src/component/imports.rs +++ b/crates/wasm-encoder/src/component/imports.rs @@ -2,6 +2,8 @@ use crate::{ ComponentExportKind, ComponentSection, ComponentSectionId, ComponentValType, Encode, encode_section, }; +use alloc::borrow::Cow; +use alloc::string::String; use alloc::vec::Vec; /// Represents the possible type bounds for type references. @@ -131,8 +133,12 @@ impl ComponentImportSection { } /// Define an import in the component import section. - pub fn import(&mut self, name: &str, ty: ComponentTypeRef) -> &mut Self { - encode_component_import_name(&mut self.bytes, name); + pub fn import<'a>( + &mut self, + name: impl Into>, + ty: ComponentTypeRef, + ) -> &mut Self { + name.into().encode(&mut self.bytes); ty.encode(&mut self.bytes); self.num_added += 1; self @@ -151,20 +157,91 @@ impl ComponentSection for ComponentImportSection { } } -/// Prior to WebAssembly/component-model#263 import and export names were -/// discriminated with a leading byte indicating what kind of import they are. -/// After that PR though names are always prefixed with a 0x00 byte. -/// -/// On 2023-10-28 in bytecodealliance/wasm-tools#1262 was landed to start -/// transitioning to "always lead with 0x00". That updated the validator/parser -/// to accept either 0x00 or 0x01 but the encoder wasn't updated at the time. -/// -/// On 2024-09-03 in bytecodealliance/wasm-tools#TODO this encoder was updated -/// to always emit 0x00 as a leading byte. -/// -/// This function corresponds with the `importname'` production in the -/// specification. -pub(crate) fn encode_component_import_name(bytes: &mut Vec, name: &str) { - bytes.push(0x00); - name.encode(bytes); +/// Full options for encoding a component name. +#[derive(Debug, Clone)] +pub struct ComponentExternName<'a> { + /// The name to encode. + pub name: Cow<'a, str>, + /// An optional `(implements ...)` directive (See 🏷️ in the component model + /// explainer). + pub implements: Option>, +} + +impl Encode for ComponentExternName<'_> { + fn encode(&self, bytes: &mut Vec) { + let mut options = Vec::new(); + + if let Some(s) = &self.implements { + options.push((0x00, s.as_bytes())); + } + + if options.is_empty() { + // Prior to WebAssembly/component-model#263 import and export names + // were discriminated with a leading byte indicating what kind of + // import they are. After that PR though names are always prefixed + // with a 0x00 byte. + // + // On 2023-10-28 in bytecodealliance/wasm-tools#1262 was landed to + // start transitioning to "always lead with 0x00". That updated the + // validator/parser to accept either 0x00 or 0x01 but the encoder + // wasn't updated at the time. + // + // On 2024-09-03 in bytecodealliance/wasm-tools#TODO this encoder + // was updated to always emit 0x00 as a leading byte. + // + // This corresponds with the `importname'` production in the + // specification. + bytes.push(0x00); + } else { + bytes.push(0x02); + } + + self.name.encode(bytes); + + if !options.is_empty() { + options.len().encode(bytes); + for (kind, val) in options { + bytes.push(kind); + val.encode(bytes); + } + } + } +} + +impl<'a> From<&'a str> for ComponentExternName<'a> { + fn from(name: &'a str) -> Self { + ComponentExternName { + name: Cow::Borrowed(name), + implements: None, + } + } +} + +impl<'a> From<&'a String> for ComponentExternName<'a> { + fn from(name: &'a String) -> Self { + ComponentExternName { + name: Cow::Borrowed(name), + implements: None, + } + } +} + +impl<'a> From for ComponentExternName<'a> { + fn from(name: String) -> Self { + ComponentExternName { + name: Cow::Owned(name), + implements: None, + } + } +} + +#[cfg(feature = "wasmparser")] +impl<'a> From> for ComponentExternName<'a> { + fn from(name: wasmparser::ComponentExternName<'a>) -> Self { + let wasmparser::ComponentExternName { name, implements } = name; + ComponentExternName { + name: name.into(), + implements: implements.map(|s| s.into()), + } + } } diff --git a/crates/wasm-encoder/src/component/instances.rs b/crates/wasm-encoder/src/component/instances.rs index ac3aaf9b19..ced69db459 100644 --- a/crates/wasm-encoder/src/component/instances.rs +++ b/crates/wasm-encoder/src/component/instances.rs @@ -1,6 +1,7 @@ use super::CORE_INSTANCE_SORT; use crate::{ - ComponentExportKind, ComponentSection, ComponentSectionId, Encode, ExportKind, encode_section, + ComponentExportKind, ComponentExternName, ComponentSection, ComponentSectionId, Encode, + ExportKind, encode_section, }; use alloc::vec::Vec; @@ -169,16 +170,17 @@ impl ComponentInstanceSection { } /// Define an instance by exporting items. - pub fn export_items<'a, E>(&mut self, exports: E) -> &mut Self + pub fn export_items<'a, N, E>(&mut self, exports: E) -> &mut Self where - E: IntoIterator, + E: IntoIterator, E::IntoIter: ExactSizeIterator, + N: Into>, { let exports = exports.into_iter(); self.bytes.push(0x01); exports.len().encode(&mut self.bytes); for (name, kind, index) in exports { - crate::encode_component_export_name(&mut self.bytes, name); + name.into().encode(&mut self.bytes); kind.encode(&mut self.bytes); index.encode(&mut self.bytes); } diff --git a/crates/wasm-encoder/src/component/types.rs b/crates/wasm-encoder/src/component/types.rs index cda73d5a31..924336c3c1 100644 --- a/crates/wasm-encoder/src/component/types.rs +++ b/crates/wasm-encoder/src/component/types.rs @@ -1,7 +1,8 @@ use super::CORE_TYPE_SORT; use crate::{ - Alias, ComponentExportKind, ComponentOuterAliasKind, ComponentSection, ComponentSectionId, - ComponentTypeRef, CoreTypeEncoder, Encode, EntityType, ValType, encode_section, + Alias, ComponentExportKind, ComponentExternName, ComponentOuterAliasKind, ComponentSection, + ComponentSectionId, ComponentTypeRef, CoreTypeEncoder, Encode, EntityType, ValType, + encode_section, }; use alloc::vec::Vec; @@ -225,9 +226,13 @@ impl ComponentType { } /// Defines an import in this component type. - pub fn import(&mut self, name: &str, ty: ComponentTypeRef) -> &mut Self { + pub fn import<'a>( + &mut self, + name: impl Into>, + ty: ComponentTypeRef, + ) -> &mut Self { self.bytes.push(0x03); - crate::encode_component_import_name(&mut self.bytes, name); + name.into().encode(&mut self.bytes); ty.encode(&mut self.bytes); self.num_added += 1; match ty { @@ -239,9 +244,13 @@ impl ComponentType { } /// Defines an export in this component type. - pub fn export(&mut self, name: &str, ty: ComponentTypeRef) -> &mut Self { + pub fn export<'a>( + &mut self, + name: impl Into>, + ty: ComponentTypeRef, + ) -> &mut Self { self.bytes.push(0x04); - crate::encode_component_export_name(&mut self.bytes, name); + name.into().encode(&mut self.bytes); ty.encode(&mut self.bytes); self.num_added += 1; match ty { @@ -310,7 +319,11 @@ impl InstanceType { } /// Defines an export in this instance type. - pub fn export(&mut self, name: &str, ty: ComponentTypeRef) -> &mut Self { + pub fn export<'a>( + &mut self, + name: impl Into>, + ty: ComponentTypeRef, + ) -> &mut Self { self.0.export(name, ty); self } diff --git a/crates/wasm-encoder/src/reencode/component.rs b/crates/wasm-encoder/src/reencode/component.rs index acb753713b..48396e2670 100644 --- a/crates/wasm-encoder/src/reencode/component.rs +++ b/crates/wasm-encoder/src/reencode/component.rs @@ -661,7 +661,7 @@ pub mod component_utils { } wasmparser::InstanceTypeDeclaration::Export { name, ty } => { let ty = reencoder.component_type_ref(ty)?; - instance.export(name.0, ty); + instance.export(name, ty); Ok(()) } } @@ -715,12 +715,12 @@ pub mod component_utils { } wasmparser::ComponentTypeDeclaration::Export { name, ty } => { let ty = reencoder.component_type_ref(ty)?; - component.export(name.0, ty); + component.export(name, ty); Ok(()) } wasmparser::ComponentTypeDeclaration::Import(import) => { let ty = reencoder.component_type_ref(import.ty)?; - component.import(import.name.0, ty); + component.import(import.name, ty); Ok(()) } } @@ -903,7 +903,7 @@ pub mod component_utils { ) -> Result<(), Error> { for import in section { let import = import?; - imports.import(import.name.0, reencoder.component_type_ref(import.ty)?); + imports.import(import.name, reencoder.component_type_ref(import.ty)?); } Ok(()) } @@ -1183,7 +1183,7 @@ pub mod component_utils { wasmparser::ComponentInstance::FromExports(exports) => { instances.export_items(exports.iter().map(|export| { ( - export.name.0, + export.name, export.kind.into(), reencoder.component_external_index(export.kind, export.index), ) @@ -1266,7 +1266,7 @@ pub mod component_utils { export: wasmparser::ComponentExport<'_>, ) -> Result<(), Error> { exports.export( - export.name.0, + export.name, export.kind.into(), reencoder.component_external_index(export.kind, export.index), export diff --git a/crates/wasmparser/src/features.rs b/crates/wasmparser/src/features.rs index 2b6a21d001..d7b1044a43 100644 --- a/crates/wasmparser/src/features.rs +++ b/crates/wasmparser/src/features.rs @@ -318,6 +318,12 @@ define_wasm_features! { /// Corresponds to the 🐘 character in /// . pub cm64: CM64(1 << 39) = false; + + /// Support for the `(implements "...")` directive in the component model + /// + /// Corresponds to the 🏷️ character in + /// . + pub cm_implements: CM_IMPLEMENTS(1 << 40) = false; } } diff --git a/crates/wasmparser/src/readers/component/exports.rs b/crates/wasmparser/src/readers/component/exports.rs index e00d0391b4..2b7a6be588 100644 --- a/crates/wasmparser/src/readers/component/exports.rs +++ b/crates/wasmparser/src/readers/component/exports.rs @@ -1,4 +1,6 @@ -use crate::{BinaryReader, ComponentTypeRef, FromReader, Result, SectionLimited}; +use crate::{ + BinaryReader, ComponentExternName, ComponentTypeRef, FromReader, Result, SectionLimited, +}; /// Represents the kind of an external items of a WebAssembly component. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -67,7 +69,7 @@ impl ComponentExternalKind { #[derive(Debug, Clone, Eq, PartialEq)] pub struct ComponentExport<'a> { /// The name of the exported item. - pub name: ComponentExportName<'a>, + pub name: ComponentExternName<'a>, /// The kind of the export. pub kind: ComponentExternalKind, /// The index of the exported item. @@ -113,23 +115,3 @@ impl<'a> FromReader<'a> for ComponentExternalKind { ComponentExternalKind::from_bytes(byte1, byte2, offset) } } - -/// Represents the name of a component export. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[allow(missing_docs)] -pub struct ComponentExportName<'a>(pub &'a str); - -impl<'a> FromReader<'a> for ComponentExportName<'a> { - fn from_reader(reader: &mut BinaryReader<'a>) -> Result { - match reader.read_u8()? { - 0x00 => {} - // Historically export names used a discriminator byte of 0x01 to - // indicate an "interface" of the form `a:b/c` but nowadays that's - // inferred from string syntax. Ignore 0-vs-1 to continue to parse - // older binaries. Eventually this will go away. - 0x01 => {} - x => return reader.invalid_leading_byte(x, "export name"), - } - Ok(ComponentExportName(reader.read_string()?)) - } -} diff --git a/crates/wasmparser/src/readers/component/imports.rs b/crates/wasmparser/src/readers/component/imports.rs index a24f873241..5c45e663e4 100644 --- a/crates/wasmparser/src/readers/component/imports.rs +++ b/crates/wasmparser/src/readers/component/imports.rs @@ -79,7 +79,7 @@ impl<'a> FromReader<'a> for ComponentTypeRef { #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct ComponentImport<'a> { /// The name of the imported item. - pub name: ComponentImportName<'a>, + pub name: ComponentExternName<'a>, /// The type reference for the import. pub ty: ComponentTypeRef, } @@ -112,13 +112,16 @@ pub type ComponentImportSectionReader<'a> = SectionLimited<'a, ComponentImport<' /// Represents the name of a component import. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[allow(missing_docs)] -pub struct ComponentImportName<'a>(pub &'a str); +pub struct ComponentExternName<'a> { + pub name: &'a str, + pub implements: Option<&'a str>, +} -impl<'a> FromReader<'a> for ComponentImportName<'a> { +impl<'a> FromReader<'a> for ComponentExternName<'a> { fn from_reader(reader: &mut BinaryReader<'a>) -> Result { - match reader.read_u8()? { + let has_options = match reader.read_u8()? { // This is the spec-required byte as of this time. - 0x00 => {} + 0x00 => false, // Prior to WebAssembly/component-model#263 export names used a // discriminator byte of 0x01 to indicate an "interface" of the @@ -136,10 +139,51 @@ impl<'a> FromReader<'a> for ComponentImportName<'a> { // time has passed this case may be able to be removed. When // removing this it's probably best to do it with a `WasmFeatures` // flag first to ensure there's an opt-in way of fixing things. - 0x01 => {} + 0x01 => false, + + 0x02 => { + if reader.cm_implements() { + true + } else { + bail!( + reader.original_position() - 1, + "the `cm-implements` feature is not active" + ) + } + } - x => return reader.invalid_leading_byte(x, "import name"), + x => return reader.invalid_leading_byte(x, "component name"), + }; + let mut ret = ComponentExternName { + name: reader.read_string()?, + implements: None, + }; + if has_options { + for _ in 0..reader.read_var_u32()? { + let pos = reader.original_position(); + match reader.read()? { + ComponentNameOpt::Implements(name) => { + if ret.implements.is_some() { + bail!(pos, "duplicate 'implements' option in name"); + } + ret.implements = Some(name); + } + } + } + } + Ok(ret) + } +} + +enum ComponentNameOpt<'a> { + Implements(&'a str), +} + +impl<'a> FromReader<'a> for ComponentNameOpt<'a> { + fn from_reader(reader: &mut BinaryReader<'a>) -> Result { + match reader.read_u8()? { + 0x00 => Ok(ComponentNameOpt::Implements(reader.read()?)), + x => return reader.invalid_leading_byte(x, "name option"), } - Ok(ComponentImportName(reader.read_string()?)) } } diff --git a/crates/wasmparser/src/readers/component/types.rs b/crates/wasmparser/src/readers/component/types.rs index 5dbc2e5544..7e914cb7fc 100644 --- a/crates/wasmparser/src/readers/component/types.rs +++ b/crates/wasmparser/src/readers/component/types.rs @@ -1,7 +1,7 @@ use crate::limits::*; use crate::prelude::*; use crate::{ - BinaryReader, ComponentAlias, ComponentExportName, ComponentImport, ComponentTypeRef, + BinaryReader, ComponentAlias, ComponentExternName, ComponentImport, ComponentTypeRef, FromReader, Import, RecGroup, Result, SectionLimited, TypeRef, ValType, }; use core::fmt; @@ -326,7 +326,7 @@ pub enum ComponentTypeDeclaration<'a> { /// The component type declaration is for an export. Export { /// The name of the export. - name: ComponentExportName<'a>, + name: ComponentExternName<'a>, /// The type reference for the export. ty: ComponentTypeRef, }, @@ -367,7 +367,7 @@ pub enum InstanceTypeDeclaration<'a> { /// The instance type declaration is for an export. Export { /// The name of the export. - name: ComponentExportName<'a>, + name: ComponentExternName<'a>, /// The type reference for the export. ty: ComponentTypeRef, }, diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index dd54e8b391..e1d4ab5bfd 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -6,9 +6,9 @@ use super::{ Abi, AliasableResourceId, ComponentAnyTypeId, ComponentCoreInstanceTypeId, ComponentCoreModuleTypeId, ComponentCoreTypeId, ComponentDefinedType, ComponentDefinedTypeId, ComponentEntityType, ComponentFuncType, ComponentFuncTypeId, - ComponentInstanceType, ComponentInstanceTypeId, ComponentType, ComponentTypeId, - ComponentValType, Context, CoreInstanceTypeKind, InstanceType, ModuleType, RecordType, - Remap, Remapping, ResourceId, SubtypeCx, TupleType, VariantCase, VariantType, + ComponentInstanceType, ComponentInstanceTypeId, ComponentItem, ComponentType, + ComponentTypeId, ComponentValType, Context, CoreInstanceTypeKind, InstanceType, ModuleType, + RecordType, Remap, Remapping, ResourceId, SubtypeCx, TupleType, VariantCase, VariantType, }, core::{InternRecGroup, Module}, types::{CoreTypeId, EntityType, TypeAlloc, TypeInfo, TypeList}, @@ -18,7 +18,7 @@ use crate::limits::*; use crate::prelude::*; use crate::validator::names::{ComponentName, ComponentNameKind, KebabStr, KebabString}; use crate::{ - BinaryReaderError, CanonicalFunction, CanonicalOption, ComponentExportName, + BinaryReaderError, CanonicalFunction, CanonicalOption, ComponentExternName, ComponentExternalKind, ComponentOuterAliasKind, ComponentTypeRef, CompositeInnerType, ExternalKind, FuncType, GlobalType, InstantiationArgKind, MemoryType, PackedIndex, RefType, Result, SubType, TableType, TypeBounds, ValType, WasmFeatures, @@ -61,9 +61,9 @@ pub(crate) struct ComponentState { pub instances: Vec, pub components: Vec, - pub imports: IndexMap, + pub imports: IndexMap, pub import_names: IndexSet, - pub exports: IndexMap, + pub exports: IndexMap, pub export_names: IndexSet, has_start: bool, @@ -653,19 +653,19 @@ impl ComponentState { pub fn add_import( &mut self, - import: crate::ComponentImport, + import: crate::ComponentImport<'_>, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { let mut entity = self.check_type_ref(&import.ty, types, offset)?; self.add_entity( &mut entity, - Some((import.name.0, ExternKind::Import)), + Some((import.name.name, ExternKind::Import)), types, offset, )?; self.toplevel_imported_resources.validate_extern( - import.name.0, + &import.name, ExternKind::Import, &entity, types, @@ -872,10 +872,9 @@ impl ComponentState { // itself are valid to import/export, recursive instances are // captured, and everything is appropriately added to the right // imported/exported set. - ComponentEntityType::Instance(i) => types[*i] - .exports - .iter() - .all(|(_name, ty)| self.validate_and_register_named_types(None, kind, ty, types)), + ComponentEntityType::Instance(i) => types[*i].exports.iter().all(|(_name, ty)| { + self.validate_and_register_named_types(None, kind, &ty.ty, types) + }), // All types referred to by a function must be named. ComponentEntityType::Func(id) => self.all_valtypes_named_in_func(types, *id, set), @@ -921,17 +920,17 @@ impl ComponentState { ) -> bool { // Instances must recursively have all referenced types named. let ty = &types[id]; - ty.exports.values().all(|ty| match ty { + ty.exports.values().all(|ty| match ty.ty { ComponentEntityType::Module(_) => true, - ComponentEntityType::Func(id) => self.all_valtypes_named_in_func(types, *id, set), + ComponentEntityType::Func(id) => self.all_valtypes_named_in_func(types, id, set), ComponentEntityType::Type { created: id, .. } => { - self.all_valtypes_named(types, *id, set) + self.all_valtypes_named(types, id, set) } ComponentEntityType::Value(ComponentValType::Type(id)) => { - self.all_valtypes_named_in_defined(types, *id, set) + self.all_valtypes_named_in_defined(types, id, set) } ComponentEntityType::Instance(id) => { - self.all_valtypes_named_in_instance(types, *id, set) + self.all_valtypes_named_in_instance(types, id, set) } ComponentEntityType::Component(_) | ComponentEntityType::Value(ComponentValType::Primitive(_)) => return true, @@ -1073,7 +1072,7 @@ impl ComponentState { // Using the old-to-new resource mapping perform a substitution on // the `exports` and `explicit_resources` fields of `new_ty` for ty in new_ty.exports.values_mut() { - types.remap_component_entity(ty, &mut mapping); + types.remap_component_entity(&mut ty.ty, &mut mapping); } for (id, path) in mem::take(&mut new_ty.explicit_resources) { let id = *mapping.resources.get(&id).unwrap_or(&id); @@ -1127,7 +1126,7 @@ impl ComponentState { self.defined_resources.insert(new.resource(), None); } for ty in new_ty.exports.values_mut() { - types.remap_component_entity(ty, &mut mapping); + types.remap_component_entity(&mut ty.ty, &mut mapping); } for (id, path) in mem::take(&mut new_ty.explicit_resources) { let id = mapping.resources.get(&id).copied().unwrap_or(id); @@ -1152,7 +1151,7 @@ impl ComponentState { pub fn add_export( &mut self, - name: ComponentExportName<'_>, + name: ComponentExternName<'_>, mut ty: ComponentEntityType, types: &mut TypeAlloc, offset: usize, @@ -1161,9 +1160,14 @@ impl ComponentState { if check_limit { check_max(self.exports.len(), 1, MAX_WASM_EXPORTS, "exports", offset)?; } - self.add_entity(&mut ty, Some((name.0, ExternKind::Export)), types, offset)?; + self.add_entity( + &mut ty, + Some((name.name, ExternKind::Export)), + types, + offset, + )?; self.toplevel_exported_resources.validate_extern( - name.0, + &name, ExternKind::Export, &ty, types, @@ -3427,7 +3431,7 @@ impl ComponentState { let mut exports = component_type.exports.clone(); let mut info = TypeInfo::new(); for (_, ty) in component_type.exports.iter() { - info.combine(ty.info(types), offset)?; + info.combine(ty.ty.info(types), offset)?; } // Perform the subtype check that `args` matches the imports of @@ -3489,7 +3493,7 @@ impl ComponentState { // references to the component's defined resources are rebound to the // fresh ones introduced just above. for entity in exports.values_mut() { - types.remap_component_entity(entity, &mut mapping); + types.remap_component_entity(&mut entity.ty, &mut mapping); } let component_type = &types[component_type_id]; let explicit_resources = component_type @@ -3525,7 +3529,7 @@ impl ComponentState { if cfg!(debug_assertions) { let mut free = IndexSet::default(); for ty in exports.values() { - types.free_variables_component_entity(ty, &mut free); + types.free_variables_component_entity(&ty.ty, &mut free); } assert!(fresh_defined_resources.is_subset(&free)); for resource in fresh_defined_resources.iter() { @@ -3630,7 +3634,7 @@ impl ComponentState { }; names.validate_extern( - export.name.0, + &export.name, ExternKind::Export, &ty, types, @@ -3866,14 +3870,14 @@ impl ComponentState { .exports .get(name) { - Some(ty) => *ty, + Some(ty) => ty.ty, None => bail!( offset, "instance {instance_index} has no export named `{name}`" ), }; - let ok = match (&ty, kind) { + let ok = match (ty, kind) { (ComponentEntityType::Module(_), ComponentExternalKind::Module) => true, (ComponentEntityType::Module(_), _) => false, (ComponentEntityType::Component(_), ComponentExternalKind::Component) => true, @@ -4517,7 +4521,7 @@ impl ComponentState { // of `self.defined_resources` which show up. let mut free = IndexSet::default(); for ty in ty.imports.values() { - types.free_variables_component_entity(ty, &mut free); + types.free_variables_component_entity(&ty.ty, &mut free); } for (resource, _path) in self.defined_resources.iter() { // FIXME: this error message is quite opaque and doesn't indicate @@ -4556,7 +4560,7 @@ impl ComponentState { // abundance of caution. free.clear(); for ty in ty.exports.values() { - types.free_variables_component_entity(ty, &mut free); + types.free_variables_component_entity(&ty.ty, &mut free); } for (id, _rep) in mem::take(&mut self.defined_resources) { if !free.contains(&id) { @@ -4642,20 +4646,26 @@ impl ComponentNameContext { fn validate_extern( &self, - name: &str, + name: &ComponentExternName<'_>, kind: ExternKind, ty: &ComponentEntityType, types: &TypeAlloc, offset: usize, kind_names: &mut IndexSet, - items: &mut IndexMap, + items: &mut IndexMap, info: &mut TypeInfo, features: &WasmFeatures, ) -> Result<()> { // First validate that `name` is even a valid kebab name, meaning it's // in kebab-case, is an ID, etc. - let kebab = ComponentName::new_with_features(name, offset, *features) - .with_context(|| format!("{} name `{name}` is not a valid extern name", kind.desc()))?; + let kebab = + ComponentName::new_with_features(name.name, offset, *features).with_context(|| { + format!( + "{} name `{}` is not a valid extern name", + kind.desc(), + name.name + ) + })?; if let ExternKind::Export = kind { match kebab.kind() { @@ -4668,11 +4678,37 @@ impl ComponentNameContext { ComponentNameKind::Hash(_) | ComponentNameKind::Url(_) | ComponentNameKind::Dependency(_) => { - bail!(offset, "name `{name}` is not a valid export name") + bail!(offset, "name `{}` is not a valid export name", name.name) } } } + if let Some(implements) = name.implements { + if !features.cm_implements() { + bail!(offset, "the `cm-implements` feature is not active"); + } + match kebab.kind() { + ComponentNameKind::Label(_) => {} + _ => bail!( + offset, + "name `{}` is not valid with `implements`", + name.name + ), + } + + match ty { + ComponentEntityType::Instance(_) => {} + _ => bail!(offset, "only instance names can have an `implements`"), + } + + let implements = ComponentName::new_with_features(implements, offset, *features) + .with_context(|| format!("`{implements}` is not a valid name"))?; + match implements.kind() { + ComponentNameKind::Interface(_) => {} + _ => bail!(offset, "name `{implements}` must be an interface"), + } + } + // Validate that the kebab name, if it has structure such as // `[method]a.b`, is indeed valid with respect to known resources. self.validate(&kebab, ty, types, offset) @@ -4692,17 +4728,21 @@ impl ComponentNameContext { // Otherwise all strings must be unique, regardless of their name, so // consult the `items` set to ensure that we're not for example // importing the same interface ID twice. - match items.entry(name.to_string()) { + match items.entry(name.name.to_string()) { Entry::Occupied(e) => { bail!( offset, "{kind} name `{name}` conflicts with previous name `{prev}`", + name = name.name, kind = kind.desc(), prev = e.key(), ); } Entry::Vacant(e) => { - e.insert(*ty); + e.insert(ComponentItem { + ty: *ty, + implements: name.implements.map(|s| s.to_string()), + }); info.combine(ty.info(types), offset)?; } } diff --git a/crates/wasmparser/src/validator/component_types.rs b/crates/wasmparser/src/validator/component_types.rs index 147f10ace0..32deb68c2a 100644 --- a/crates/wasmparser/src/validator/component_types.rs +++ b/crates/wasmparser/src/validator/component_types.rs @@ -969,13 +969,13 @@ pub struct ComponentType { /// /// Each import has its own kebab-name and an optional URL listed. Note that /// the set of import names is disjoint with the set of export names. - pub imports: IndexMap, + pub imports: IndexMap, /// The exports of the component type. /// /// Each export has its own kebab-name and an optional URL listed. Note that /// the set of export names is disjoint with the set of import names. - pub exports: IndexMap, + pub exports: IndexMap, /// Universally quantified resources required to be provided when /// instantiating this component type. @@ -1015,6 +1015,16 @@ pub struct ComponentType { pub explicit_resources: IndexMap>, } +/// Either an import or an export within [`ComponentType`] or +/// [`ComponentInstanceType`]. +#[derive(Debug, Clone)] +pub struct ComponentItem { + /// The type of this item. + pub ty: ComponentEntityType, + /// The optional `(implements "...")` metadata, if specified. + pub implements: Option, +} + impl TypeData for ComponentType { type Id = ComponentTypeId; const IS_CORE_SUB_TYPE: bool = false; @@ -1032,7 +1042,7 @@ pub struct ComponentInstanceType { /// The list of exports, keyed by name, that this instance has. /// /// An optional URL and type of each export is provided as well. - pub exports: IndexMap, + pub exports: IndexMap, /// The list of "defined resources" or those which are closed over in /// this instance type. @@ -2023,18 +2033,18 @@ impl<'a> TypesRef<'a> { } /// Gets the component entity type for the given component import. - pub fn component_entity_type_of_import(&self, name: &str) -> Option { + pub fn component_item_for_import(&self, name: &str) -> Option<&'a ComponentItem> { match &self.kind { TypesRefKind::Module(_) => None, - TypesRefKind::Component(component) => Some(*component.imports.get(name)?), + TypesRefKind::Component(component) => Some(component.imports.get(name)?), } } /// Gets the component entity type for the given component export. - pub fn component_entity_type_of_export(&self, name: &str) -> Option { + pub fn component_item_for_export(&self, name: &str) -> Option<&'a ComponentItem> { match &self.kind { TypesRefKind::Module(_) => None, - TypesRefKind::Component(component) => Some(*component.exports.get(name)?), + TypesRefKind::Component(component) => Some(component.exports.get(name)?), } } @@ -2187,13 +2197,13 @@ impl Types { } /// Gets the component entity type for the given component import name. - pub fn component_entity_type_of_import(&self, name: &str) -> Option { - self.as_ref().component_entity_type_of_import(name) + pub fn component_item_for_import(&self, name: &str) -> Option<&ComponentItem> { + self.as_ref().component_item_for_import(name) } /// Gets the component entity type for the given component export name. - pub fn component_entity_type_of_export(&self, name: &str) -> Option { - self.as_ref().component_entity_type_of_export(name) + pub fn component_item_for_export(&self, name: &str) -> Option<&ComponentItem> { + self.as_ref().component_item_for_export(name) } /// Attempts to lookup the type id that `ty` is an alias of. @@ -2571,7 +2581,7 @@ impl TypeAlloc { // defined resources, so doing this all in one go should be // equivalent. for ty in i.imports.values().chain(i.exports.values()) { - self.free_variables_component_entity(ty, set); + self.free_variables_component_entity(&ty.ty, set); } for (id, _path) in i.imported_resources.iter().chain(&i.defined_resources) { set.swap_remove(id); @@ -2588,7 +2598,7 @@ impl TypeAlloc { // types but then remove those defined by this component instance // itself. for ty in i.exports.values() { - self.free_variables_component_entity(ty, set); + self.free_variables_component_entity(&ty.ty, set); } for id in i.defined_resources.iter() { set.swap_remove(id); @@ -2811,7 +2821,7 @@ where let mut any_changed = false; let mut ty = self[*id].clone(); for ty in ty.imports.values_mut().chain(ty.exports.values_mut()) { - any_changed |= self.remap_component_entity(ty, map); + any_changed |= self.remap_component_entity(&mut ty.ty, map); } for (id, _) in ty .imported_resources @@ -2906,7 +2916,7 @@ where let mut any_changed = false; let mut tmp = self[*id].clone(); for ty in tmp.exports.values_mut() { - any_changed |= self.remap_component_entity(ty, map); + any_changed |= self.remap_component_entity(&mut ty.ty, map); } for id in tmp.defined_resources.iter_mut() { if let Some(new) = map.resources.get(id) { @@ -3208,7 +3218,7 @@ impl<'a> SubtypeCx<'a> { let b_imports = self.b[b] .imports .iter() - .map(|(name, ty)| (name.clone(), *ty)) + .map(|(name, ty)| (name.clone(), ty.ty)) .collect(); self.swap(); let mut import_mapping = @@ -3218,7 +3228,7 @@ impl<'a> SubtypeCx<'a> { let mut a_exports = this.a[a] .exports .iter() - .map(|(name, ty)| (name.clone(), *ty)) + .map(|(name, ty)| (name.clone(), ty.ty)) .collect::>(); for ty in a_exports.values_mut() { this.a.remap_component_entity(ty, &mut import_mapping); @@ -3247,7 +3257,7 @@ impl<'a> SubtypeCx<'a> { let mut exports = Vec::with_capacity(b.exports.len()); for (k, b) in b.exports.iter() { match a.exports.get(k) { - Some(a) => exports.push((*a, *b)), + Some(a) => exports.push((a.ty, b.ty)), None => bail!(offset, "missing expected export `{k}`"), } } @@ -3510,7 +3520,7 @@ impl<'a> SubtypeCx<'a> { // Lookup the first path item in `imports` and the corresponding // entry in `args` by name. let (name, ty) = entities.get_index(path[0]).unwrap(); - let mut ty = *ty; + let mut ty = ty.ty; let mut arg = a.get(name); // Lookup all the subsequent `path` entries, if any, by index in @@ -3522,9 +3532,11 @@ impl<'a> SubtypeCx<'a> { _ => unreachable!(), }; let (name, next_ty) = self.b[id].exports.get_index(i).unwrap(); - ty = *next_ty; + ty = next_ty.ty; arg = match arg { - Some(ComponentEntityType::Instance(id)) => self.a[*id].exports.get(name), + Some(ComponentEntityType::Instance(id)) => { + self.a[*id].exports.get(name).map(|t| &t.ty) + } _ => continue 'outer, }; } @@ -3566,7 +3578,7 @@ impl<'a> SubtypeCx<'a> { let mut to_typecheck = Vec::new(); for (name, expected) in entities.iter() { match a.get(name) { - Some(arg) => to_typecheck.push((*arg, *expected)), + Some(arg) => to_typecheck.push((*arg, expected.ty)), None => bail!(offset, "missing {} named `{name}`", kind.desc()), } } @@ -3920,8 +3932,8 @@ impl<'a> SubtypeCx<'a> { (ComponentEntityType::Instance(expected), ComponentEntityType::Instance(actual)) => { let actual = &self.a[actual]; for (name, expected) in self.b[expected].exports.iter() { - let actual = actual.exports[name]; - self.register_type_renamings(actual, *expected, type_map); + let actual = actual.exports[name].ty; + self.register_type_renamings(actual, expected.ty, type_map); } } _ => {} diff --git a/crates/wasmprinter/src/component.rs b/crates/wasmprinter/src/component.rs index 524760fd7a..4ca4de796a 100644 --- a/crates/wasmprinter/src/component.rs +++ b/crates/wasmprinter/src/component.rs @@ -377,7 +377,7 @@ impl Printer<'_, '_> { self.start_group("export ")?; self.print_component_kind_name(states.last_mut().unwrap(), ty.kind())?; self.result.write_str(" ")?; - self.print_str(name.0)?; + self.print_component_extern_name(&name)?; self.result.write_str(" ")?; self.print_component_import_ty(states.last_mut().unwrap(), &ty, false)?; self.end_group()?; @@ -392,6 +392,17 @@ impl Printer<'_, '_> { Ok(()) } + fn print_component_extern_name(&mut self, name: &ComponentExternName<'_>) -> Result<()> { + self.print_str(name.name)?; + if let Some(implements) = name.implements { + self.result.write_str(" ")?; + self.start_group("implements ")?; + self.print_str(implements)?; + self.end_group()?; + } + Ok(()) + } + pub(crate) fn print_instance_type<'a>( &mut self, states: &mut Vec, @@ -412,7 +423,7 @@ impl Printer<'_, '_> { self.start_group("export ")?; self.print_component_kind_name(states.last_mut().unwrap(), ty.kind())?; self.result.write_str(" ")?; - self.print_str(name.0)?; + self.print_component_extern_name(&name)?; self.result.write_str(" ")?; self.print_component_import_ty(states.last_mut().unwrap(), &ty, false)?; self.end_group()?; @@ -589,7 +600,7 @@ impl Printer<'_, '_> { index: bool, ) -> Result<()> { self.start_group("import ")?; - self.print_str(import.name.0)?; + self.print_component_extern_name(&import.name)?; self.result.write_str(" ")?; self.print_component_import_ty(state, &import.ty, index)?; self.end_group()?; @@ -705,7 +716,7 @@ impl Printer<'_, '_> { self.print_component_kind_name(state, export.kind)?; self.result.write_str(" ")?; } - self.print_str(export.name.0)?; + self.print_component_extern_name(&export.name)?; self.result.write_str(" ")?; self.print_component_external_kind(state, export.kind, export.index)?; if let Some(ty) = &export.ty { diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs index eb90edb6c8..a0c8c2eee1 100644 --- a/crates/wast/src/component/binary.rs +++ b/crates/wast/src/component/binary.rs @@ -279,7 +279,7 @@ impl<'a> Encoder<'a> { InstanceKind::BundleOfExports(exports) => { self.instances.export_items(exports.iter().map(|e| { let (kind, index) = (&e.kind).into(); - (e.name.0, kind, index) + (e.name, kind, index) })); } } @@ -546,8 +546,7 @@ impl<'a> Encoder<'a> { fn encode_import(&mut self, import: &ComponentImport<'a>) { let name = get_name(&import.item.id, &import.item.name); self.names_for_item_kind(&import.item.kind).push(name); - self.imports - .import(import.name.0, (&import.item.kind).into()); + self.imports.import(import.name, (&import.item.kind).into()); self.flush(Some(self.imports.id())); } @@ -555,7 +554,7 @@ impl<'a> Encoder<'a> { let name = get_name(&export.id, &export.debug_name); let (kind, index) = (&export.kind).into(); self.exports.export( - export.name.0, + export.name, kind, index, export.ty.as_ref().map(|ty| (&ty.0.kind).into()), @@ -863,10 +862,10 @@ impl From<&ComponentType<'_>> for wasm_encoder::ComponentType { encoded.alias((&a.target).into()); } ComponentTypeDecl::Import(i) => { - encoded.import(i.name.0, (&i.item.kind).into()); + encoded.import(i.name, (&i.item.kind).into()); } ComponentTypeDecl::Export(e) => { - encoded.export(e.name.0, (&e.item.kind).into()); + encoded.export(e.name, (&e.item.kind).into()); } } } @@ -891,7 +890,7 @@ impl From<&InstanceType<'_>> for wasm_encoder::InstanceType { encoded.alias((&a.target).into()); } InstanceTypeDecl::Export(e) => { - encoded.export(e.name.0, (&e.item.kind).into()); + encoded.export(e.name, (&e.item.kind).into()); } } } @@ -1047,3 +1046,12 @@ impl<'a> From<&AliasTarget<'a>> for wasm_encoder::Alias<'a> { } } } + +impl<'a> From> for wasm_encoder::ComponentExternName<'a> { + fn from(name: ComponentExternName<'a>) -> Self { + wasm_encoder::ComponentExternName { + name: name.name.into(), + implements: name.implements.map(|i| i.into()), + } + } +} diff --git a/crates/wast/src/component/import.rs b/crates/wast/src/component/import.rs index 81c8e5e406..cf2a9f5b1a 100644 --- a/crates/wast/src/component/import.rs +++ b/crates/wast/src/component/import.rs @@ -23,9 +23,14 @@ impl<'a> Parse<'a> for ComponentImport<'a> { } } -/// The different ways an import can be named. +/// Identifiers, and metadata, for component imports and exports. #[derive(Debug, Copy, Clone)] -pub struct ComponentExternName<'a>(pub &'a str); +pub struct ComponentExternName<'a> { + /// The string name this is referring to. + pub name: &'a str, + /// For imports, an optional `(implements "...")` directive. + pub implements: Option<&'a str>, +} impl<'a> Parse<'a> for ComponentExternName<'a> { fn parse(parser: Parser<'a>) -> Result { @@ -43,7 +48,15 @@ impl<'a> Parse<'a> for ComponentExternName<'a> { } else { parser.parse()? }; - Ok(ComponentExternName(name)) + let implements = if parser.peek2::()? { + Some(parser.parens(|p| { + p.parse::()?; + p.parse() + })?) + } else { + None + }; + Ok(ComponentExternName { name, implements }) } } diff --git a/crates/wast/src/lib.rs b/crates/wast/src/lib.rs index be157314f4..ad3cc46e03 100644 --- a/crates/wast/src/lib.rs +++ b/crates/wast/src/lib.rs @@ -454,6 +454,7 @@ pub mod kw { custom_keyword!(i8); custom_keyword!(i8x16); custom_keyword!(import); + custom_keyword!(implements); custom_keyword!(instance); custom_keyword!(instantiate); custom_keyword!(interface); diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 79a1ee1064..8f7a4059f9 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -561,9 +561,13 @@ impl<'a> EncodingState<'a> { let instance_type_idx = self .component .type_instance(Some(&format!("ty-{name}")), &ty); - let instance_idx = self - .component - .import(name, ComponentTypeRef::Instance(instance_type_idx)); + let instance_idx = self.component.import( + wasm_encoder::ComponentExternName { + name: name.into(), + implements: info.implements.as_deref().map(|s| s.into()), + }, + ComponentTypeRef::Instance(instance_type_idx), + ); let prev = self.instances.insert(interface_id, instance_idx); assert!(prev.is_none()); Ok(()) @@ -694,9 +698,9 @@ impl<'a> EncodingState<'a> { let prev = world_func_core_names.insert(name, core_name); assert!(prev.is_none()); } - Export::InterfaceFunc(_, id, name, _) => { + Export::InterfaceFunc(key, _, name, _) => { let prev = interface_func_core_names - .entry(id) + .entry(key) .or_insert(IndexMap::new()) .insert(name.as_str(), core_name); assert!(prev.is_none()); @@ -730,14 +734,15 @@ impl<'a> EncodingState<'a> { let core_name = world_func_core_names[&func.name]; let idx = self.encode_lift(module, &core_name, export_name, func, ty)?; self.component - .export(&export_string, ComponentExportKind::Func, idx, None); + .export(export_string, ComponentExportKind::Func, idx, None); } - WorldItem::Interface { id, .. } => { - let core_names = interface_func_core_names.get(id); + item @ WorldItem::Interface { id, .. } => { + let core_names = interface_func_core_names.get(export_name); self.encode_interface_export( &export_string, module, export_name, + item, *id, core_names, )?; @@ -754,6 +759,7 @@ impl<'a> EncodingState<'a> { export_name: &str, module: CustomModule<'_>, key: &WorldKey, + item: &WorldItem, export: InterfaceId, interface_func_core_names: Option<&IndexMap<&str, &str>>, ) -> Result<()> { @@ -941,7 +947,10 @@ impl<'a> EncodingState<'a> { imports, ); let idx = self.component.export( - export_name, + wasm_encoder::ComponentExternName { + name: export_name.into(), + implements: resolve.implements_value(key, item).map(|s| s.into()), + }, ComponentExportKind::Instance, instance_index, None, diff --git a/crates/wit-component/src/encoding/wit.rs b/crates/wit-component/src/encoding/wit.rs index 93fb729be7..42171ea7dc 100644 --- a/crates/wit-component/src/encoding/wit.rs +++ b/crates/wit-component/src/encoding/wit.rs @@ -72,9 +72,8 @@ pub fn encode_world(resolve: &Resolve, world_id: WorldId) -> Result { component.interface = Some(*id); @@ -94,12 +93,13 @@ pub fn encode_world(resolve: &Resolve, world_id: WorldId) -> Result { component.interface = Some(*id); @@ -113,12 +113,25 @@ pub fn encode_world(resolve: &Resolve, world_id: WorldId) -> Result unreachable!(), }; - component.outer.export(&name, ty); + component + .outer + .export(component_extern_name(resolve, key, export), ty); } Ok(component.outer) } +fn component_extern_name( + resolve: &Resolve, + key: &WorldKey, + item: &WorldItem, +) -> wasm_encoder::ComponentExternName<'static> { + ComponentExternName { + name: resolve.name_world_key(key).into(), + implements: resolve.implements_value(key, item).map(|s| s.into()), + } +} + struct Encoder<'a> { component: ComponentBuilder, resolve: &'a Resolve, @@ -132,7 +145,7 @@ impl Encoder<'_> { let component_ty = self.encode_interface(id)?; let ty = self.component.type_component(Some(name), &component_ty); self.component - .export(name.as_ref(), ComponentExportKind::Type, ty, None); + .export(name, ComponentExportKind::Type, ty, None); } // For each `world` encode it directly as a component and then create a @@ -144,11 +157,11 @@ impl Encoder<'_> { let mut wrapper = ComponentType::new(); wrapper.ty().component(&component_ty); let pkg = &self.resolve.packages[world.package.unwrap()]; - wrapper.export(&pkg.name.interface_id(name), ComponentTypeRef::Component(0)); + wrapper.export(pkg.name.interface_id(name), ComponentTypeRef::Component(0)); let ty = self.component.type_component(Some(name), &wrapper); self.component - .export(name.as_ref(), ComponentExportKind::Type, ty, None); + .export(name, ComponentExportKind::Type, ty, None); } Ok(()) @@ -186,7 +199,7 @@ impl Encoder<'_> { if interface == id { let idx = encoder.encode_instance(interface)?; log::trace!("exporting self as {idx}"); - encoder.outer.export(&name, ComponentTypeRef::Instance(idx)); + encoder.outer.export(name, ComponentTypeRef::Instance(idx)); } else { encoder.push_instance(); for (_, id) in iface.types.iter() { @@ -197,7 +210,7 @@ impl Encoder<'_> { encoder.outer.ty().instance(&instance); encoder.import_map.insert(interface, encoder.instances); encoder.instances += 1; - encoder.outer.import(&name, ComponentTypeRef::Instance(idx)); + encoder.outer.import(name, ComponentTypeRef::Instance(idx)); } } diff --git a/crates/wit-component/src/encoding/world.rs b/crates/wit-component/src/encoding/world.rs index 84cf92dd84..af567bdfdd 100644 --- a/crates/wit-component/src/encoding/world.rs +++ b/crates/wit-component/src/encoding/world.rs @@ -49,6 +49,7 @@ pub struct ComponentWorld<'a> { pub struct ImportedInterface { pub lowerings: IndexMap<(String, AbiVariant), Lowering>, pub interface: Option, + pub implements: Option, } #[derive(Debug)] @@ -244,11 +245,11 @@ impl<'a> ComponentWorld<'a> { fn add_item( import_map: &mut IndexMap, ImportedInterface>, resolve: &Resolve, - name: &WorldKey, + key: &WorldKey, item: &WorldItem, required: &Required<'_>, ) -> Result<()> { - let name = resolve.name_world_key(name); + let name = resolve.name_world_key(key); log::trace!("register import `{name}`"); let import_map_key = match item { WorldItem::Function(_) | WorldItem::Type { .. } => None, @@ -258,13 +259,16 @@ impl<'a> ComponentWorld<'a> { WorldItem::Function(_) | WorldItem::Type { .. } => None, WorldItem::Interface { id, .. } => Some(*id), }; + let implements = resolve.implements_value(key, item); let interface = import_map .entry(import_map_key) .or_insert_with(|| ImportedInterface { interface: interface_id, lowerings: Default::default(), + implements: implements.clone(), }); assert_eq!(interface.interface, interface_id); + assert_eq!(interface.implements, implements); match item { WorldItem::Function(func) => { interface.add_func(required, resolve, func); @@ -447,7 +451,7 @@ impl<'a> ComponentWorld<'a> { let world = self.encoder.metadata.world; let exports = &resolve.worlds[world].exports; - for (_name, item) in exports.iter() { + for (_key, item) in exports.iter() { let id = match item { WorldItem::Function(_) => continue, WorldItem::Interface { id, .. } => *id, @@ -456,9 +460,10 @@ impl<'a> ComponentWorld<'a> { let mut set = HashSet::new(); for other in resolve.interface_direct_deps(id) { + let key = WorldKey::Interface(other); // 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)) { + if !exports.contains_key(&key) { continue; } diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index ac83358582..d6f52df07a 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -280,7 +280,7 @@ pub fn encode( let mut outer_ty = ComponentType::new(); outer_ty.ty().component(&ty); outer_ty.export( - &resolve.id_of_name(world.package.unwrap(), &world.name), + resolve.id_of_name(world.package.unwrap(), &world.name), ComponentTypeRef::Component(0), ); diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 04fd18f15f..9d70140334 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -471,11 +471,17 @@ impl WitPrinter { WorldItem::Interface { id, .. } => { self.print_name_type(name, TypeKind::Other); self.output.str(": "); - assert!(resolve.interfaces[*id].name.is_none()); - self.output.keyword("interface"); - self.output.indent_start(); - self.print_interface(resolve, *id)?; - self.output.indent_end(); + if resolve.interfaces[*id].name.is_none() { + // `import label: interface { .. }` syntax + self.output.keyword("interface"); + self.output.indent_start(); + self.print_interface(resolve, *id)?; + self.output.indent_end(); + } else { + // `import label: use-path;` syntax + self.print_path_to_interface(resolve, *id, cur_pkg)?; + self.output.semicolon(); + } } WorldItem::Function(f) => { self.print_name_type(&f.name, TypeKind::Other); diff --git a/crates/wit-component/tests/components.rs b/crates/wit-component/tests/components.rs index 1a86dc1ffc..43c886c3be 100644 --- a/crates/wit-component/tests/components.rs +++ b/crates/wit-component/tests/components.rs @@ -271,6 +271,11 @@ fn assert_output(contents: &str, path: &Path) -> Result<()> { "\"$CARGO_PKG_VERSION\"", ); if std::env::var_os("BLESS").is_some() { + if let Ok(prev) = fs::read_to_string(path) + && prev == contents + { + return Ok(()); + } fs::write(path, contents)?; } else { match fs::read_to_string(path) { diff --git a/crates/wit-component/tests/components/adapt-export-and-main-export/adapt-old.wat b/crates/wit-component/tests/components/adapt-export-and-main-export/adapt-old.wat new file mode 100644 index 0000000000..0c4debd062 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-and-main-export/adapt-old.wat @@ -0,0 +1,4 @@ +(module + (import "__main_module__" "the_entrypoint" (func $entry)) + (export "foo:foo/new#entrypoint" (func $entry)) +) diff --git a/crates/wit-component/tests/components/adapt-export-and-main-export/adapt-old.wit b/crates/wit-component/tests/components/adapt-export-and-main-export/adapt-old.wit new file mode 100644 index 0000000000..27d1cf9d1b --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-and-main-export/adapt-old.wit @@ -0,0 +1,7 @@ +interface new { + entrypoint: func(); +} + +world adapt-old { + export new; +} diff --git a/crates/wit-component/tests/components/adapt-export-and-main-export/component.wat b/crates/wit-component/tests/components/adapt-export-and-main-export/component.wat new file mode 100644 index 0000000000..28cb10be23 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-and-main-export/component.wat @@ -0,0 +1,57 @@ +(component + (core module $main (;0;) + (type (;0;) (func)) + (export "the_entrypoint" (func 0)) + (export "x#y" (func 1)) + (func (;0;) (type 0)) + (func (;1;) (type 0)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (core module $wit-component:adapter:old (;1;) + (type (;0;) (func)) + (import "__main_module__" "the_entrypoint" (func $entry (;0;) (type 0))) + (export "foo:foo/new#entrypoint" (func $entry)) + ) + (core instance $main (;0;) (instantiate $main)) + (alias core export $main "the_entrypoint" (core func $the_entrypoint (;0;))) + (core instance $__main_module__ (;1;) + (export "the_entrypoint" (func $the_entrypoint)) + ) + (core instance $old (;2;) (instantiate $wit-component:adapter:old + (with "__main_module__" (instance $__main_module__)) + ) + ) + (type (;0;) (func)) + (alias core export $main "x#y" (core func $x#y (;1;))) + (func $y (;0;) (type 0) (canon lift (core func $x#y))) + (component $x-shim-component (;0;) + (type (;0;) (func)) + (import "import-func-y" (func (;0;) (type 0))) + (type (;1;) (func)) + (export (;1;) "y" (func 0) (func (type 1))) + ) + (instance $x-shim-instance (;0;) (instantiate $x-shim-component + (with "import-func-y" (func $y)) + ) + ) + (export $x (;1;) "x" (instance $x-shim-instance)) + (alias core export $old "foo:foo/new#entrypoint" (core func $foo:foo/new#entrypoint (;2;))) + (func $entrypoint (;1;) (type 0) (canon lift (core func $foo:foo/new#entrypoint))) + (component $foo:foo/new-shim-component (;1;) + (type (;0;) (func)) + (import "import-func-entrypoint" (func (;0;) (type 0))) + (type (;1;) (func)) + (export (;1;) "entrypoint" (func 0) (func (type 1))) + ) + (instance $foo:foo/new-shim-instance (;2;) (instantiate $foo:foo/new-shim-component + (with "import-func-entrypoint" (func $entrypoint)) + ) + ) + (export $foo:foo/new (;3;) "foo:foo/new" (instance $foo:foo/new-shim-instance)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/components/adapt-export-and-main-export/component.wit.print b/crates/wit-component/tests/components/adapt-export-and-main-export/component.wit.print new file mode 100644 index 0000000000..0229834db8 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-and-main-export/component.wit.print @@ -0,0 +1,8 @@ +package root:component; + +world root { + export x: interface { + y: func(); + } + export foo:foo/new; +} diff --git a/crates/wit-component/tests/components/adapt-export-and-main-export/module.wat b/crates/wit-component/tests/components/adapt-export-and-main-export/module.wat new file mode 100644 index 0000000000..4135827f45 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-and-main-export/module.wat @@ -0,0 +1,4 @@ +(module + (func (export "the_entrypoint")) + (func (export "x#y")) +) diff --git a/crates/wit-component/tests/components/adapt-export-and-main-export/module.wit b/crates/wit-component/tests/components/adapt-export-and-main-export/module.wit new file mode 100644 index 0000000000..638c6b150d --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-and-main-export/module.wit @@ -0,0 +1,7 @@ +package foo:foo; + +world module { + export x: interface { + y: func(); + } +} diff --git a/crates/wit-component/tests/components/implements/component.wat b/crates/wit-component/tests/components/implements/component.wat new file mode 100644 index 0000000000..412dc2484e --- /dev/null +++ b/crates/wit-component/tests/components/implements/component.wat @@ -0,0 +1,103 @@ +(component + (type $ty-a (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "f" (func (type 0))) + ) + ) + (import "a" (instance $a (;0;) (type $ty-a))) + (type $ty-b (;1;) + (instance + (type (;0;) (func)) + (export (;0;) "f" (func (type 0))) + ) + ) + (import "b" (implements "a:b/i") (instance $b (;1;) (type $ty-b))) + (type $ty-c (;2;) + (instance + (type (;0;) (func)) + (export (;0;) "f" (func (type 0))) + ) + ) + (import "c" (implements "a:b/i") (instance $c (;2;) (type $ty-c))) + (core module $main (;0;) + (type (;0;) (func)) + (import "a" "f" (func (;0;) (type 0))) + (import "b" "f" (func (;1;) (type 0))) + (import "c" "f" (func (;2;) (type 0))) + (export "a#f" (func 3)) + (export "b#f" (func 4)) + (export "c#f" (func 5)) + (func (;3;) (type 0)) + (func (;4;) (type 0)) + (func (;5;) (type 0)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (alias export $a "f" (func $f (;0;))) + (core func $f (;0;) (canon lower (func $f))) + (core instance $a (;0;) + (export "f" (func $f)) + ) + (alias export $b "f" (func $"#func1 f" (@name "f") (;1;))) + (core func $"#core-func1 f" (@name "f") (;1;) (canon lower (func $"#func1 f"))) + (core instance $b (;1;) + (export "f" (func $"#core-func1 f")) + ) + (alias export $c "f" (func $"#func2 f" (@name "f") (;2;))) + (core func $"#core-func2 f" (@name "f") (;2;) (canon lower (func $"#func2 f"))) + (core instance $c (;2;) + (export "f" (func $"#core-func2 f")) + ) + (core instance $main (;3;) (instantiate $main + (with "a" (instance $a)) + (with "b" (instance $b)) + (with "c" (instance $c)) + ) + ) + (type (;3;) (func)) + (alias core export $main "a#f" (core func $a#f (;3;))) + (func $"#func3 f" (@name "f") (;3;) (type 3) (canon lift (core func $a#f))) + (component $a-shim-component (;0;) + (type (;0;) (func)) + (import "import-func-f" (func (;0;) (type 0))) + (type (;1;) (func)) + (export (;1;) "f" (func 0) (func (type 1))) + ) + (instance $a-shim-instance (;3;) (instantiate $a-shim-component + (with "import-func-f" (func $"#func3 f")) + ) + ) + (export $"#instance4 a" (@name "a") (;4;) "a" (instance $a-shim-instance)) + (alias core export $main "b#f" (core func $b#f (;4;))) + (func $"#func4 f" (@name "f") (;4;) (type 3) (canon lift (core func $b#f))) + (component $b-shim-component (;1;) + (type (;0;) (func)) + (import "import-func-f" (func (;0;) (type 0))) + (type (;1;) (func)) + (export (;1;) "f" (func 0) (func (type 1))) + ) + (instance $b-shim-instance (;5;) (instantiate $b-shim-component + (with "import-func-f" (func $"#func4 f")) + ) + ) + (export $"#instance6 b" (@name "b") (;6;) "b" (implements "a:b/i") (instance $b-shim-instance)) + (alias core export $main "c#f" (core func $c#f (;5;))) + (func $"#func5 f" (@name "f") (;5;) (type 3) (canon lift (core func $c#f))) + (component $c-shim-component (;2;) + (type (;0;) (func)) + (import "import-func-f" (func (;0;) (type 0))) + (type (;1;) (func)) + (export (;1;) "f" (func 0) (func (type 1))) + ) + (instance $c-shim-instance (;7;) (instantiate $c-shim-component + (with "import-func-f" (func $"#func5 f")) + ) + ) + (export $"#instance8 c" (@name "c") (;8;) "c" (implements "a:b/i") (instance $c-shim-instance)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/components/implements/component.wit.print b/crates/wit-component/tests/components/implements/component.wit.print new file mode 100644 index 0000000000..e63c2035a2 --- /dev/null +++ b/crates/wit-component/tests/components/implements/component.wit.print @@ -0,0 +1,15 @@ +package root:component; + +world root { + import a: interface { + f: func(); + } + import b: a:b/i; + import c: a:b/i; + + export a: interface { + f: func(); + } + export b: a:b/i; + export c: a:b/i; +} diff --git a/crates/wit-component/tests/components/implements/module.wat b/crates/wit-component/tests/components/implements/module.wat new file mode 100644 index 0000000000..762dadaf61 --- /dev/null +++ b/crates/wit-component/tests/components/implements/module.wat @@ -0,0 +1,9 @@ +(module + (import "a" "f" (func)) + (import "b" "f" (func)) + (import "c" "f" (func)) + + (func (export "a#f")) + (func (export "b#f")) + (func (export "c#f")) +) diff --git a/crates/wit-component/tests/components/implements/module.wit b/crates/wit-component/tests/components/implements/module.wit new file mode 100644 index 0000000000..768723a027 --- /dev/null +++ b/crates/wit-component/tests/components/implements/module.wit @@ -0,0 +1,19 @@ +package a:b; + +world module { + import a: interface { + f: func(); + } + import b: i; + import c: i; + + export a: interface { + f: func(); + } + export b: i; + export c: i; +} + +interface i { + f: func(); +} diff --git a/crates/wit-component/tests/interfaces.rs b/crates/wit-component/tests/interfaces.rs index ad65b4a250..4e5201b593 100644 --- a/crates/wit-component/tests/interfaces.rs +++ b/crates/wit-component/tests/interfaces.rs @@ -108,6 +108,11 @@ fn assert_output(expected: &Path, actual: &str) -> Result<()> { "\"$CARGO_PKG_VERSION\"", ); if std::env::var_os("BLESS").is_some() { + if let Ok(prev) = fs::read_to_string(&expected) + && prev == actual + { + return Ok(()); + } fs::write(expected, actual).with_context(|| format!("failed to write {expected:?}"))?; } else { assert_eq!( diff --git a/crates/wit-component/tests/interfaces/implements.wat b/crates/wit-component/tests/interfaces/implements.wat new file mode 100644 index 0000000000..8d5f242e38 --- /dev/null +++ b/crates/wit-component/tests/interfaces/implements.wat @@ -0,0 +1,130 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + (type (;2;) (func (param "key" string) (param "value" string))) + (export (;1;) "set" (func (type 2))) + ) + ) + (export (;0;) "foo:foo/store" (instance (type 0))) + ) + ) + (export (;1;) "store" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "msg" string))) + (export (;0;) "log" (func (type 0))) + ) + ) + (export (;0;) "foo:foo/logger" (instance (type 0))) + ) + ) + (export (;3;) "logger" (type 2)) + (type (;4;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + (type (;2;) (func (param "key" string) (param "value" string))) + (export (;1;) "set" (func (type 2))) + ) + ) + (import "primary" (implements "foo:foo/store") (instance (;0;) (type 0))) + (type (;1;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + (type (;2;) (func (param "key" string) (param "value" string))) + (export (;1;) "set" (func (type 2))) + ) + ) + (import "backup" (implements "foo:foo/store") (instance (;1;) (type 1))) + (type (;2;) + (instance + (type (;0;) (func (param "msg" string))) + (export (;0;) "log" (func (type 0))) + ) + ) + (import "foo:foo/logger" (instance (;2;) (type 2))) + ) + ) + (export (;0;) "foo:foo/multi-import" (component (type 0))) + ) + ) + (export (;5;) "multi-import" (type 4)) + (type (;6;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + (type (;2;) (func (param "key" string) (param "value" string))) + (export (;1;) "set" (func (type 2))) + ) + ) + (export (;0;) "primary" (implements "foo:foo/store") (instance (type 0))) + (type (;1;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + (type (;2;) (func (param "key" string) (param "value" string))) + (export (;1;) "set" (func (type 2))) + ) + ) + (export (;1;) "backup" (implements "foo:foo/store") (instance (type 1))) + ) + ) + (export (;0;) "foo:foo/multi-export" (component (type 0))) + ) + ) + (export (;7;) "multi-export" (type 6)) + (type (;8;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + (type (;2;) (func (param "key" string) (param "value" string))) + (export (;1;) "set" (func (type 2))) + ) + ) + (import "foo:foo/store" (instance (;0;) (type 0))) + (type (;1;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + (type (;2;) (func (param "key" string) (param "value" string))) + (export (;1;) "set" (func (type 2))) + ) + ) + (import "cache" (implements "foo:foo/store") (instance (;1;) (type 1))) + ) + ) + (export (;0;) "foo:foo/mixed" (component (type 0))) + ) + ) + (export (;9;) "mixed" (type 8)) + (@custom "package-docs" "\01{\22worlds\22:{\22multi-import\22:{\22docs\22:\22A world that imports the same interface multiple times under different\5cnplain names using the `implements` syntax.\22},\22multi-export\22:{\22docs\22:\22A world that exports the same interface multiple times.\22},\22mixed\22:{\22docs\22:\22A world with both implements imports and a normal interface import,\5cntesting that elaboration adds the dependency correctly.\22}}}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/implements.wit b/crates/wit-component/tests/interfaces/implements.wit new file mode 100644 index 0000000000..0aa5578534 --- /dev/null +++ b/crates/wit-component/tests/interfaces/implements.wit @@ -0,0 +1,31 @@ +package foo:foo; + +interface store { + get: func(key: string) -> option; + set: func(key: string, value: string); +} + +interface logger { + log: func(msg: string); +} + +/// A world that imports the same interface multiple times under different +/// plain names using the `implements` syntax. +world multi-import { + import primary: store; + import backup: store; + import logger; +} + +/// A world that exports the same interface multiple times. +world multi-export { + export primary: store; + export backup: store; +} + +/// A world with both implements imports and a normal interface import, +/// testing that elaboration adds the dependency correctly. +world mixed { + import store; + import cache: store; +} diff --git a/crates/wit-component/tests/interfaces/implements.wit.print b/crates/wit-component/tests/interfaces/implements.wit.print new file mode 100644 index 0000000000..86c3044f08 --- /dev/null +++ b/crates/wit-component/tests/interfaces/implements.wit.print @@ -0,0 +1,30 @@ +package foo:foo; + +interface store { + get: func(key: string) -> option; + + set: func(key: string, value: string); +} + +interface logger { + log: func(msg: string); +} + +/// A world that imports the same interface multiple times under different +/// plain names using the `implements` syntax. +world multi-import { + import primary: store; + import backup: store; + import logger; +} +/// A world that exports the same interface multiple times. +world multi-export { + export primary: store; + export backup: store; +} +/// A world with both implements imports and a normal interface import, +/// testing that elaboration adds the dependency correctly. +world mixed { + import store; + import cache: store; +} diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 6321c3ee4c..9d3682021c 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -209,7 +209,7 @@ impl<'a> DeclList<'a> { } Ok(()) } - ExternKind::Path(path) => { + ExternKind::Path(path) | ExternKind::NamedPath(_, path) => { f(None, attrs, path, None, WorldOrInterface::Interface) } ExternKind::Func(..) => Ok(()), @@ -484,6 +484,8 @@ enum ExternKind<'a> { Interface(Id<'a>, Vec>), Path(UsePath<'a>), Func(Id<'a>, Func<'a>), + /// `label: use-path` — a named import/export that implements an interface. + NamedPath(Id<'a>, UsePath<'a>), } impl<'a> ExternKind<'a> { @@ -512,6 +514,26 @@ impl<'a> ExternKind<'a> { *tokens = clone; return Ok(ExternKind::Interface(id, Interface::parse_items(tokens)?)); } + + // import label: use-path + // At this point we consumed `id:` on the clone but the next token + // is not `func`, `async`, or `interface`. This could be either: + // import label: local-iface; (NamedPath) + // import label: pkg:name/iface; (NamedPath with package path) + // import ns:pkg/iface; (regular fully-qualified Path) + // + // Disambiguate: if the next tokens are `id /`, then the colon was + // part of a fully-qualified `namespace:package/interface` name, not + // a label separator. Fall through to the Path parser in that case. + let mut peek = clone.clone(); + let is_qualified_path = + parse_id(&mut peek).is_ok() && peek.clone().eat(Token::Slash).unwrap_or(false); + if !is_qualified_path { + *tokens = clone; + let path = UsePath::parse(tokens)?; + tokens.expect_semicolon()?; + return Ok(ExternKind::NamedPath(id, path)); + } } // import foo @@ -528,6 +550,7 @@ impl<'a> ExternKind<'a> { ExternKind::Path(UsePath::Id(id)) => id.span, ExternKind::Path(UsePath::Package { name, .. }) => name.span, ExternKind::Func(id, _) => id.span, + ExternKind::NamedPath(id, _) => id.span, } } } diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 7c7f617e37..c6b905386d 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -703,8 +703,11 @@ impl<'a> Resolver<'a> { let id = self.extract_iface_from_item(&item, &name, span)?; WorldKey::Interface(id) } + + // Named paths use the label as the key. + ast::ExternKind::NamedPath(name, _) => WorldKey::Name(name.name.to_string()), }; - if let WorldItem::Interface { id, .. } = world_item { + if let WorldKey::Interface(id) = key { if !interfaces.insert(id) { return Err(ParseError::new_syntax( kind.span(), @@ -768,6 +771,16 @@ impl<'a> Resolver<'a> { span: item_span, }) } + ast::ExternKind::NamedPath(name, path) => { + let stability = self.stability(attrs)?; + let (item, iface_name, item_span) = self.resolve_ast_item_path(path)?; + let id = self.extract_iface_from_item(&item, &iface_name, item_span)?; + Ok(WorldItem::Interface { + id, + stability, + span: name.span, + }) + } ast::ExternKind::Func(name, func) => { let func = self.resolve_function( docs, diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index d1d6a8100e..13c8e7aeb0 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -12,7 +12,7 @@ use wasmparser::{ WasmFeatures, component_types::{ ComponentAnyTypeId, ComponentDefinedType, ComponentEntityType, ComponentFuncType, - ComponentInstanceType, ComponentType, ComponentValType, + ComponentInstanceType, ComponentItem, ComponentType, ComponentValType, }, names::{ComponentName, ComponentNameKind}, types, @@ -98,8 +98,8 @@ impl ComponentInfo { for import in s { let import = import?; externs.push(( - import.name.0.to_string(), - Extern::Import(import.name.0.to_string()), + import.name.name.to_string(), + Extern::Import(import.name.name.to_string()), )); } } @@ -107,9 +107,9 @@ impl ComponentInfo { for export in s { let export = export?; externs.push(( - export.name.0.to_string(), + export.name.name.to_string(), Extern::Export(DecodingExport { - name: export.name.0.to_string(), + name: export.name.name.to_string(), kind: export.kind, index: export.index, }), @@ -249,7 +249,7 @@ impl ComponentInfo { let name = component.exports.keys().nth(0).unwrap(); - let name = match component.exports[name] { + let name = match component.exports[name].ty { ComponentEntityType::Component(ty) => { let package_name = decoder.decode_world(name.as_str(), &self.types[ty], &mut fields)?; @@ -334,8 +334,8 @@ impl ComponentInfo { for (_name, item) in self.externs.iter() { match item { - Extern::Import(import) => { - decoder.decode_component_import(import, world, &mut fields)? + Extern::Import(name) => { + decoder.decode_component_import(name, world, &mut fields)? } Extern::Export(export) => { decoder.decode_component_export(export, world, &mut fields)? @@ -477,7 +477,7 @@ pub fn decode_world(wasm: &[u8]) -> Result<(Resolve, WorldId)> { assert_eq!(ty.imports.len(), 0); assert_eq!(ty.exports.len(), 1); let name = ty.exports.keys().nth(0).unwrap(); - let ty = match ty.exports[0] { + let ty = match ty.exports[0].ty { ComponentEntityType::Component(ty) => ty, _ => unreachable!(), }; @@ -543,8 +543,8 @@ impl WitPackageDecoder<'_> { // Process all imports for this package first, where imports are // importing from remote packages. for (name, ty) in ty.imports.iter() { - let ty = match ty { - ComponentEntityType::Instance(idx) => &self.types[*idx], + let ty = match ty.ty { + ComponentEntityType::Instance(idx) => &self.types[idx], _ => bail!("import `{name}` is not an instance"), }; self.register_import(name, ty) @@ -572,14 +572,14 @@ impl WitPackageDecoder<'_> { }; for (name, ty) in ty.exports.iter() { - match ty { + match ty.ty { ComponentEntityType::Instance(idx) => { - let ty = &self.types[*idx]; + let ty = &self.types[idx]; self.register_interface(name.as_str(), ty, &mut fields) .with_context(|| format!("failed to process export `{name}`"))?; } ComponentEntityType::Component(idx) => { - let ty = &self.types[*idx]; + let ty = &self.types[idx]; self.register_world(name.as_str(), ty, &mut fields) .with_context(|| format!("failed to process export `{name}`"))?; } @@ -592,7 +592,7 @@ impl WitPackageDecoder<'_> { fn decode_interface<'a>( &mut self, name: &str, - imports: &wasmparser::collections::IndexMap, + imports: &wasmparser::collections::IndexMap, ty: &ComponentInstanceType, fields: &mut PackageFields<'a>, ) -> Result { @@ -606,8 +606,8 @@ impl WitPackageDecoder<'_> { }; for (name, ty) in imports.iter() { - let ty = match ty { - ComponentEntityType::Instance(idx) => &self.types[*idx], + let ty = match ty.ty { + ComponentEntityType::Instance(idx) => &self.types[idx], _ => bail!("import `{name}` is not an instance"), }; self.register_import(name, ty) @@ -646,30 +646,13 @@ impl WitPackageDecoder<'_> { package: &mut PackageFields<'a>, ) -> Result<()> { log::debug!("decoding component import `{name}`"); - let ty = self - .types - .as_ref() - .component_entity_type_of_import(name) - .unwrap(); + let item = self.types.as_ref().component_item_for_import(name).unwrap(); let owner = TypeOwner::World(world); - let (name, item) = match ty { + let (name, item) = match item.ty { ComponentEntityType::Instance(i) => { let ty = &self.types[i]; - let (name, id) = if name.contains('/') { - let id = self.register_import(name, ty)?; - (WorldKey::Interface(id), id) - } else { - self.register_interface(name, ty, package) - .with_context(|| format!("failed to decode WIT from import `{name}`"))? - }; - ( - name, - WorldItem::Interface { - id, - stability: Default::default(), - span: Default::default(), - }, - ) + self.decode_world_instance(name, item.implements.as_deref(), ty, package) + .with_context(|| format!("failed to decode WIT from import `{name}`"))? } ComponentEntityType::Func(i) => { let ty = &self.types[i]; @@ -709,8 +692,8 @@ impl WitPackageDecoder<'_> { let name = &export.name; log::debug!("decoding component export `{name}`"); let types = self.types.as_ref(); - let ty = types.component_entity_type_of_export(name).unwrap(); - let (name, item) = match ty { + let item = types.component_item_for_export(name).unwrap(); + let (name, item) = match item.ty { ComponentEntityType::Func(i) => { let ty = &types[i]; let func = self @@ -721,21 +704,8 @@ impl WitPackageDecoder<'_> { } ComponentEntityType::Instance(i) => { let ty = &types[i]; - let (name, id) = if name.contains('/') { - let id = self.register_import(name, ty)?; - (WorldKey::Interface(id), id) - } else { - self.register_interface(name, ty, package) - .with_context(|| format!("failed to decode WIT from export `{name}`"))? - }; - ( - name, - WorldItem::Interface { - id, - stability: Default::default(), - span: Default::default(), - }, - ) + self.decode_world_instance(name, item.implements.as_deref(), ty, package) + .with_context(|| format!("failed to decode WIT from export `{name}`"))? } _ => { bail!("component export `{name}` was not a function or instance") @@ -745,6 +715,44 @@ impl WitPackageDecoder<'_> { Ok(()) } + /// Decodes a component instance import/export name into a + /// `(WorldKey, WorldItem)` pair. + /// + /// Handles three name forms: + /// - `ns:pkg/iface` — qualified interface name, keyed by `InterfaceId` + /// - `plain-name` — unqualified name for an inline or local interface + /// - `plain-name (implements "...")` - reference to a preexisting interface + /// elsewhere + fn decode_world_instance<'a>( + &mut self, + name: &str, + implements: Option<&str>, + ty: &ComponentInstanceType, + package: &mut PackageFields<'a>, + ) -> Result<(WorldKey, WorldItem)> { + let (key, id) = match implements { + Some(i) => { + let id = self.register_import(i, ty)?; + (WorldKey::Name(name.to_string()), id) + } + None => match self.parse_component_name(name)?.kind() { + ComponentNameKind::Interface(i) => { + let id = self.register_import(i.as_str(), ty)?; + (WorldKey::Interface(id), id) + } + _ => self.register_interface(name, ty, package)?, + }, + }; + Ok(( + key, + WorldItem::Interface { + id, + stability: Default::default(), + span: Default::default(), + }, + )) + } + /// Registers that the `name` provided is either imported interface from a /// foreign package or referencing a previously defined interface in this /// package. @@ -762,7 +770,7 @@ impl WitPackageDecoder<'_> { let owner = TypeOwner::Interface(interface); for (name, ty) in ty.exports.iter() { log::debug!("decoding import instance export `{name}`"); - match *ty { + match ty.ty { ComponentEntityType::Type { referenced, created, @@ -975,7 +983,7 @@ impl WitPackageDecoder<'_> { let owner = TypeOwner::Interface(self.resolve.interfaces.next_id()); for (name, ty) in ty.exports.iter() { - match *ty { + match ty.ty { ComponentEntityType::Type { referenced, created, @@ -1109,37 +1117,17 @@ impl WitPackageDecoder<'_> { let owner = TypeOwner::World(self.resolve.worlds.next_id()); for (name, ty) in ty.imports.iter() { - let (name, item) = match ty { + let (name, item) = match ty.ty { ComponentEntityType::Instance(idx) => { - let ty = &self.types[*idx]; - let (name, id) = if name.contains('/') { - // If a name is an interface import then it is either to - // a package-local or foreign interface, and both - // situations are handled in `register_import`. - let id = self.register_import(name, ty)?; - (WorldKey::Interface(id), id) - } else { - // A plain kebab-name indicates an inline interface that - // wasn't declared explicitly elsewhere with a name, and - // `register_interface` will create a new `Interface` - // with no name. - self.register_interface(name, ty, package)? - }; - ( - name, - WorldItem::Interface { - id, - stability: Default::default(), - span: Default::default(), - }, - ) + let t = &self.types[idx]; + self.decode_world_instance(name, ty.implements.as_deref(), t, package)? } ComponentEntityType::Type { created, referenced, } => { let ty = - self.register_type_export(name.as_str(), owner, *referenced, *created)?; + self.register_type_export(name.as_str(), owner, referenced, created)?; ( WorldKey::Name(name.to_string()), WorldItem::Type { @@ -1149,7 +1137,7 @@ impl WitPackageDecoder<'_> { ) } ComponentEntityType::Func(idx) => { - let ty = &self.types[*idx]; + let ty = &self.types[idx]; let func = self.convert_function(name.as_str(), ty, owner)?; (WorldKey::Name(name.to_string()), WorldItem::Function(func)) } @@ -1158,34 +1146,15 @@ impl WitPackageDecoder<'_> { world.imports.insert(name, item); } - for (name, ty) in ty.exports.iter() { - let (name, item) = match ty { + for (name, item) in ty.exports.iter() { + let (name, item) = match item.ty { ComponentEntityType::Instance(idx) => { - let ty = &self.types[*idx]; - let (name, id) = if name.contains('/') { - // Note that despite this being an export this is - // calling `register_import`. With a URL this interface - // must have been previously defined so this will - // trigger the logic of either filling in a remotely - // defined interface or connecting items to local - // definitions of our own interface. - let id = self.register_import(name, ty)?; - (WorldKey::Interface(id), id) - } else { - self.register_interface(name, ty, package)? - }; - ( - name, - WorldItem::Interface { - id, - stability: Default::default(), - span: Default::default(), - }, - ) + let ty = &self.types[idx]; + self.decode_world_instance(name, item.implements.as_deref(), ty, package)? } ComponentEntityType::Func(idx) => { - let ty = &self.types[*idx]; + let ty = &self.types[idx]; let func = self.convert_function(name.as_str(), ty, owner)?; (WorldKey::Name(name.to_string()), WorldItem::Function(func)) } @@ -1574,7 +1543,10 @@ impl WitPackageDecoder<'_> { for (name, item) in world.imports.iter().chain(world.exports.iter()) { if let WorldKey::Name(_) = name { if let WorldItem::Interface { id, .. } = item { - self.resolve.interfaces[*id].package = Some(pkg); + let iface = &mut self.resolve.interfaces[*id]; + if iface.name.is_none() { + iface.package = Some(pkg); + } } } } diff --git a/crates/wit-parser/src/metadata.rs b/crates/wit-parser/src/metadata.rs index 0d68bc5ab5..46b6fec1a1 100644 --- a/crates/wit-parser/src/metadata.rs +++ b/crates/wit-parser/src/metadata.rs @@ -275,6 +275,24 @@ impl WorldMetadata { let mut interface_import_stability = StringMap::default(); let mut interface_export_stability = StringMap::default(); + let mut record_interface_stability = |key: &WorldKey, item: &WorldItem, import: bool| { + let stability = match item { + WorldItem::Interface { stability, .. } => stability, + _ => return, + }; + if stability.is_unknown() { + return; + } + + let map = if import { + &mut interface_import_stability + } else { + &mut interface_export_stability + }; + let name = resolve.name_world_key(key); + map.insert(name, stability.clone()); + }; + for ((key, item), import) in world .imports .iter() @@ -286,6 +304,10 @@ impl WorldMetadata { // docs/stability and insert it into one of our maps. WorldKey::Name(name) => match item { WorldItem::Interface { id, .. } => { + if resolve.interfaces[*id].name.is_some() { + record_interface_stability(key, item, import); + continue; + } let data = InterfaceMetadata::extract(resolve, *id); if data.is_empty() { continue; @@ -330,21 +352,7 @@ impl WorldMetadata { // For interface imports/exports extract the stability and // record it if necessary. WorldKey::Interface(_) => { - let stability = match item { - WorldItem::Interface { stability, .. } => stability, - _ => continue, - }; - if stability.is_unknown() { - continue; - } - - let map = if import { - &mut interface_import_stability - } else { - &mut interface_export_stability - }; - let name = resolve.name_world_key(key); - map.insert(name, stability.clone()); + record_interface_stability(key, item, import); } } } diff --git a/crates/wit-parser/src/resolve/clone.rs b/crates/wit-parser/src/resolve/clone.rs index 3dc4da75e3..9e2df3d2fe 100644 --- a/crates/wit-parser/src/resolve/clone.rs +++ b/crates/wit-parser/src/resolve/clone.rs @@ -21,7 +21,7 @@ use crate::*; /// Represents the results of cloning types and/or interfaces as part of a /// `Resolve::merge_worlds` operation. -#[derive(Default)] +#[derive(Default, Clone)] pub struct CloneMaps { pub(super) types: HashMap, pub(super) interfaces: HashMap, @@ -98,6 +98,11 @@ impl<'a> Cloner<'a> { self.function(f); } WorldItem::Interface { id, .. } => { + // Only clone anonymous interfaces, for named interfaces they're + // left as-is for worlds. + if self.resolve.interfaces[*id].name.is_some() { + return; + } self.interface(id); } } diff --git a/crates/wit-parser/src/resolve/mod.rs b/crates/wit-parser/src/resolve/mod.rs index 29ed59323e..b691a704f8 100644 --- a/crates/wit-parser/src/resolve/mod.rs +++ b/crates/wit-parser/src/resolve/mod.rs @@ -652,7 +652,7 @@ impl Resolve { remap.update_function(self, f, Default::default())? } WorldItem::Interface { id, .. } => { - *id = remap.map_interface(*id, Default::default())? + *id = remap.map_interface(*id, Default::default())?; } WorldItem::Type { id, .. } => { *id = remap.map_type(*id, Default::default())? @@ -714,6 +714,9 @@ impl Resolve { if let Some(pkg) = self.interfaces[id].package.as_mut() { *pkg = remap.packages[pkg.index()]; } + if let Some(clone_of) = self.interfaces[id].clone_of.as_mut() { + *clone_of = remap.map_interface(*clone_of, Default::default())?; + } } for id in moved_types { let id = remap.map_type(id, Default::default())?; @@ -1528,6 +1531,21 @@ impl Resolve { } } + /// Returns the component model `implements` value for the world import of + /// `key` and `item`. + /// + /// See the component model explainer and 🏷️ for more information on this feature. + pub fn implements_value(&self, key: &WorldKey, item: &WorldItem) -> Option { + if let WorldKey::Name(_) = key { + if let WorldItem::Interface { id, .. } = item { + if self.interfaces[*id].name.is_some() { + return Some(self.id_of(*id).unwrap().into()); + } + } + } + None + } + /// Returns the interface that `id` uses a type from, if it uses a type from /// a different interface than `id` is defined within. /// @@ -1696,10 +1714,14 @@ impl Resolve { log::debug!("validating world item: {}", self.name_world_key(name)); match item { WorldItem::Interface { id, .. } => { - // anonymous interfaces must belong to the same package - // as the world's package. + // Anonymous interfaces must belong to the same package, + // but interfaces through `implements` can be in any + // package. if matches!(name, WorldKey::Name(_)) { - assert_eq!(self.interfaces[*id].package, world.package); + let iface = &self.interfaces[*id]; + if iface.name.is_none() { + assert_eq!(iface.package, world.package); + } } } WorldItem::Function(f) => { @@ -2098,8 +2120,8 @@ impl Resolve { let mut export_interfaces = IndexMap::default(); for (name, item) in world.exports.iter() { match item { - WorldItem::Interface { id, stability, .. } => { - let prev = export_interfaces.insert(*id, (name.clone(), stability)); + WorldItem::Interface { .. } => { + let prev = export_interfaces.insert(name.clone(), item.clone()); assert!(prev.is_none()); } WorldItem::Function(_) => { @@ -2200,24 +2222,23 @@ impl Resolve { /// operation fails. fn elaborate_world_exports( &self, - export_interfaces: &IndexMap, + export_interfaces: &IndexMap, imports: &mut IndexMap, exports: &mut IndexMap, span: Span, ) -> ResolveResult<()> { let mut required_imports = HashSet::new(); - for (id, (key, stability)) in export_interfaces.iter() { + for (key, item) in export_interfaces.iter() { let name = self.name_world_key(&key); let ok = add_world_export( self, imports, exports, - export_interfaces, + &export_interfaces, &mut required_imports, - *id, - key, + key.clone(), + item.clone(), true, - stability, ); if !ok { // FIXME: this is not a great error message and basically no @@ -2248,56 +2269,61 @@ impl Resolve { resolve: &Resolve, imports: &mut IndexMap, exports: &mut IndexMap, - export_interfaces: &IndexMap, + export_interfaces: &IndexMap, required_imports: &mut HashSet, - id: InterfaceId, - key: &WorldKey, + key: WorldKey, + item: WorldItem, add_export: bool, - stability: &Stability, ) -> bool { - if exports.contains_key(key) { + if exports.contains_key(&key) { if add_export { return true; } else { return false; } } - // If this is an import and it's already in the `required_imports` - // set then we can skip it as we've already visited this interface. + let (id, stability) = match &item { + WorldItem::Interface { id, stability, .. } => (*id, stability), + _ => unreachable!(), + }; + // If this is an import and it's already in the `imports` set then + // we can skip it as we've already visited this interface. if !add_export && required_imports.contains(&id) { return true; } let ok = resolve.interface_direct_deps(id).all(|dep| { + let item = WorldItem::Interface { + id: dep, + stability: stability.clone(), + span: Default::default(), + }; let key = WorldKey::Interface(dep); - let add_export = add_export && export_interfaces.contains_key(&dep); + let add_export = add_export && export_interfaces.contains_key(&key); add_world_export( resolve, imports, exports, export_interfaces, required_imports, - dep, - &key, + key, + item, add_export, - stability, ) }); if !ok { return false; } - let item = WorldItem::Interface { - id, - stability: stability.clone(), - span: Default::default(), - }; if add_export { if required_imports.contains(&id) { return false; } - exports.insert(key.clone(), item); + let prev = exports.insert(key.clone(), item); + assert!(prev.is_none()); } else { required_imports.insert(id); - imports.insert(key.clone(), item); + if !imports.contains_key(&key) { + imports.insert(key.clone(), item); + } } true } @@ -2838,92 +2864,293 @@ impl Resolve { /// jobs much easier because now Id-uniqueness matches the semantic meaning /// of the world as well. /// - /// This function will rewrite exported interfaces, as appropriate, to all - /// have unique ids if they would otherwise overlap with the imports. - pub fn generate_nominal_type_ids(&mut self, world: WorldId) { - let mut imports = HashSet::new(); - - // Build up a list of all imported interfaces, they're not changing and - // this is used to test for overlap between imports/exports. - for import in self.worlds[world].imports.values() { - if let WorldItem::Interface { id, .. } = import { - imports.insert(*id); - } - } + /// This function will rewrite imported/exported interfaces, as appropriate, + /// to all have unique ids if they would otherwise overlap with the imports. + pub fn generate_nominal_type_ids(&mut self, world_id: WorldId) { + let mut seen = HashSet::new(); + let mut interface_keys_rewritten = HashSet::new(); + let mut maps = CloneMaps::default(); - let mut to_clone = IndexMap::default(); - for (i, export) in self.worlds[world].exports.values().enumerate() { - let id = match export { - WorldItem::Interface { id, .. } => *id, + // Pull out the imports/exports from `self` to have simultaneous + // borrows. + let world = &mut self.worlds[world_id]; + let mut imports = mem::take(&mut world.imports); + let mut exports = mem::take(&mut world.exports); + + // Notably visit `imports` first as they always get priority in the + // order of having things imported from them. After `imports` are + // visited then process all `exports`. + log::trace!("nominalizing world imports"); + self.nominalize_world_items( + &mut maps, + world_id, + &mut imports, + &mut seen, + &mut interface_keys_rewritten, + ); + log::trace!("nominalizing world exports"); + self.nominalize_world_items( + &mut maps, + world_id, + &mut exports, + &mut seen, + &mut interface_keys_rewritten, + ); - // Functions can only refer to imported types so there's no need - // to rewrite anything as imports always stay as-is. - WorldItem::Function(_) => continue, + // Put that thing back where it came from, or so help me! + let world = &mut self.worlds[world_id]; + assert!(world.imports.is_empty()); + assert!(world.exports.is_empty()); + world.imports = imports; + world.exports = exports; - WorldItem::Type { .. } => unreachable!(), + #[cfg(debug_assertions)] + self.assert_valid(); + } + + /// Implementation of `generate_nominal_type_ids` which processes either a + /// world's imports or exports as specified in `items`. + /// + /// The parameters here are: + /// + /// * `maps` - the set of types that have previously been cloned by the + /// previous stage, if any. This is used to update any dependencies of an + /// interface that is cloned. + /// + /// * `world` - the world that's being nominalized. + /// + /// * `items` - either `imports` or `exports` for the `world`. This is + /// mutated in-place to have keys/items rewritten as-needed. + /// + /// * `seen` - the set of interfaces that have been seen previously when + /// iterating over this world. All interfaces processed are added to this + /// set. + /// + /// * `interface_keys_rewritten` - a distinct set from `seen` which only + /// tracks rewritten interfaces which have `WorldKey::Interface`. This is + /// the set of interfaces which dependencies can pull types from, which + /// needs to be tracked separately to know when to rewrite. + fn nominalize_world_items( + &mut self, + maps: &mut CloneMaps, + world: WorldId, + items: &mut IndexMap, + seen: &mut HashSet, + interface_keys_rewritten: &mut HashSet, + ) { + // Overall the problem that this function is trying to solve is not an + // easy one. The input is an AST-like structure and the goal of this + // function is to effectively perform a name resolution pass. The end + // result is that if a thing points to another thing (e.g. type-use or + // interface id) then that represents the actual name resolution of what + // it points to. + // + // Currently, though, this isn't really a full-blown name resolution + // pass. This is a pretty simple "do things in the right order" and name + // resolution pops out. The conventions of WIT and how it translates to + // components is what falls out of this loop below. + // + // The first rule of WIT is that interfaces can only use types from + // other interface imports/exports. This notably excludes named imports. + // For example: + // + // interface a { + // type t = u32; + // } + // + // interface b { + // use a.{t}; + // } + // + // world w { + // import a; + // import b; // uses `import a` + // import c: b; // also uses `import a` + // import d: interface { + // use a.{t}; // uses `import a` + // } + // } + // + // The next rule is that exported interfaces will use types from + // imports, unless the interface is also exported. For example: + // + // interface a { + // type t = u32; + // } + // + // interface b { + // use a.{t}; + // } + // + // world w1 { + // import a; + // + // export b; // uses `import a` + // export c: b; // uses `import a` + // export d: interface { + // use a.{t}; // uses `import a` + // } + // } + // + // world w2 { + // export a; + // export b; // uses `export a` + // export c: b; // uses `export a` + // export d: interface { + // use a.{t}; // uses `export a` + // } + // } + // + // Finally, named imports, such as `import a: b` and `import a: + // interface { ... }` cannot be used by anything. They can only + // reference types in other interface imports/exports. + // + // Overall this is a pretty simplistic system. It's "good enough" for + // now but will almost certainly be expanded over time. The hope is that + // expanding this involves making this function more complicated but + // ideally nowhere else. + + // Given all that intro, the first thing we need to prioritize is that + // `WorldKey::Name`'d interfaces are visited after `WorldKey::Interface` + // interfaces. This is a bit of a weird result of how this function is + // implemented right now. This visit order is a bit of a hack and + // probably won't live beyond making WIT more powerful. + // + // Anyway, the reason for this has to do with the `CloneMaps` down + // below. Basically what we're doing here is cloning interfaces, but + // when doing so we need to be able to rewrite references to + // previously-cloned interfaces if need be. `CloneMaps` represents the + // aggregate results of all previous clones. Due to named interfaces + // never being importable-from it means that mutations to `CloneMaps` + // are discarded when named interfaces are cloned. The trick here + // happens where this unconditionally preserves all modifications to + // `CloneMaps` for `WorldKey::Interface` clones. Behaivor then "falls + // out" where references to cloned interfaces are naturally rewritten. + // + // This all falls down, however, if an import is cloned and recorded. + // Interfaces can be both exported and imported, which would mean that + // the import and export are both cloned, and both need to be preserved + // in `CloneMaps`. Right now `CloneMaps` requires uniqueness when + // cloning (e.g. can't clone the same thing twice). + // + // Long story short: it's a hack that sort order here is the way it is. + // Sorry. Be prepared to delete this should WIT get more powerful. + let mut order = items.iter().enumerate().collect::>(); + order.sort_by_key(|(_, (key, item))| match (key, item) { + (WorldKey::Name(_), WorldItem::Interface { .. }) => 1, + _ => 0, + }); + let mut to_rewrite = IndexMap::default(); + for (i, (key, item)) in order { + let id = match item { + WorldItem::Interface { id, .. } => *id, + + // Functions/types aren't rewritten, they're all nominal at the + // world-level anyway. + WorldItem::Function(_) | WorldItem::Type { .. } => continue, }; - // If this interface itself is both imported and exported, or if any - // dependency of this interface is rewritten, then the interface - // itself needs to be rewritten. Otherwise continue onwards. - let imported_and_exported = imports.contains(&id); + // If this interface itself is being visited for the second time + // (e.g. imported & exported, imported twice, exported twice, etc), + // or if any dependency of this interface is rewritten, then the + // interface itself needs to be rewritten. Otherwise continue + // onwards. + let duplicated = !seen.insert(id); let any_dep_rewritten = self .interface_direct_deps(id) - .any(|dep| to_clone.contains_key(&dep)); - if !(imported_and_exported || any_dep_rewritten) { + .any(|dep| interface_keys_rewritten.contains(&dep)); + if !(duplicated || any_dep_rewritten) { + log::trace!("{} already nominal", self.name_world_key(key)); continue; } + log::trace!("{} getting rewritten nominal", self.name_world_key(key)); + + // If this is `WorldKey::Interface` then register this in the + // `interface_keys_rewritten` map, and also plumb this through to + // the rewriting stage to know whether `maps` needs to be reset or + // not. + let is_name = matches!(key, WorldKey::Name(_)); + if !is_name { + assert!(interface_keys_rewritten.insert(id)); + } - to_clone.insert(id, i); + to_rewrite + .entry(id) + .or_insert(Vec::new()) + .push((i, is_name)); } - let mut maps = CloneMaps::default(); - let mut cloner = clone::Cloner::new( - self, - &mut maps, - TypeOwner::World(world), - TypeOwner::World(world), - ); - for (id, i) in to_clone { - // First, clone the interface. This'll make a `new_id`, and then we - // need to update the world to point to this new id. Note that the - // clones happen topologically here (due to iterating in-order - // above) and the `CloneMaps` are shared amongst interfaces. This - // means that future clones will use the types produced here too. - let mut new_id = id; - cloner.new_package = cloner.resolve.interfaces[new_id].package; - cloner.interface(&mut new_id); - - // Load up the previous `key` and go ahead and mutate the - // `WorldItem` in place which is guaranteed to be an `Interface` - // because of the loop above. - let exports = &mut cloner.resolve.worlds[world].exports; - let (key, prev) = exports.get_index_mut(i).unwrap(); - match prev { - WorldItem::Interface { id, .. } => *id = new_id, - _ => unreachable!(), - } + // Now that we know what to rewrite, rewrite everything. + // + // The trickiest part here is deciding what to do with `maps`. As + // interfaces are cloned they'll record all remappings of + // types/interfaces/etc within `maps`. We don't want to persist + // everything because if an interface is cloned twice then everything + // will get overwritten/corrupted within the map. In theory what we want + // is for `maps` to track, for any one interface, just the transitive + // set of dependencies for that interface and how they've been cloned. + // What's implemented here is an approximation of this that should work + // for now. + // + // Notably `to_rewrite` is an ordered list keyed by `InterfaceId`. This + // means that if we walk `to_rewrite` in order we're walking this in + // topological order. Second we then additionally sort the `list` for + // each `to_rewrite` entry to ensure that all `WorldKey::Name` items are + // visited first. In doing so we also discard all mutations to `maps` + // after visiting is done. In effect what this does is it discards all + // modifications due to `WorldKey::Name`, because nothing can depend on + // those interfaces, and then it preserves modifications for + // `WorldKey::Interface`, which other interfaces can indeed depend on. + // In the end this basically does a very careful walk over a very + // careful organization of `to_rewrite`. + // + // This'll need massive refactoring if WIT gets the ability to express + // arbitrary edges between interfaces. It's WIT-level restriction right + // now of sorts. There's no current way to model a `import a: i;` where + // `i`'s dependencies are pulled from `import b: dep`, for example. + // Right now if `i` depends on `dep` then that just always results in + // `import dep`. + for (id, mut list) in to_rewrite { + list.sort_by_key(|(_, is_name)| if *is_name { 0 } else { 1 }); + for (i, is_name) in list { + let prev_maps = if is_name { Some(maps.clone()) } else { None }; + + let mut cloner = clone::Cloner::new( + self, + maps, + TypeOwner::World(world), + TypeOwner::World(world), + ); + + let mut new_id = id; + cloner.new_package = cloner.resolve.interfaces[id].package; + cloner.interface(&mut new_id); + let (key, prev) = items.get_index_mut(i).unwrap(); + match prev { + WorldItem::Interface { id, .. } => *id = new_id, + _ => unreachable!(), + } + + match key { + // If the key for this is an `Interface` then that means we + // need to update the key as well. Here that's replaced by-index + // in the `IndexMap` to preserve the same ordering as before, + // and this operation should always succeed since `new_id` is + // fresh, hence the `unwrap()`. + WorldKey::Interface(_) => { + items.replace_index(i, WorldKey::Interface(new_id)).unwrap(); + } - match key { - // If the key for this is an `Interface` then that means we - // need to update the key as well. Here that's replaced by-index - // in the `IndexMap` to preserve the same ordering as before, - // and this operation should always succeed since `new_id` is - // fresh, hence the `unwrap()`. - WorldKey::Interface(_) => { - exports - .replace_index(i, WorldKey::Interface(new_id)) - .unwrap(); + // Name-based keys don't need updating as they only contain a + // string, no ids. + WorldKey::Name(_) => {} } - // Name-based keys don't need updating as they only contain a - // string, no ids. - WorldKey::Name(_) => {} + if let Some(prev) = prev_maps { + *maps = prev; + } } } - - #[cfg(debug_assertions)] - self.assert_valid(); } } diff --git a/crates/wit-parser/tests/ui/parse-fail/implements-conflicts-func.wit b/crates/wit-parser/tests/ui/parse-fail/implements-conflicts-func.wit new file mode 100644 index 0000000000..64f4fc1757 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/implements-conflicts-func.wit @@ -0,0 +1,11 @@ +// parse-fail +package foo:foo; + +interface store { + get: func() -> option; +} + +world test { + import primary: func(); + import primary: store; +} diff --git a/crates/wit-parser/tests/ui/parse-fail/implements-conflicts-func.wit.result b/crates/wit-parser/tests/ui/parse-fail/implements-conflicts-func.wit.result new file mode 100644 index 0000000000..dbfa3bb0d2 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/implements-conflicts-func.wit.result @@ -0,0 +1,5 @@ +import `primary` conflicts with prior func of same name + --> tests/ui/parse-fail/implements-conflicts-func.wit:10:10 + | + 10 | import primary: store; + | ^------ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/implements-duplicate-label.wit b/crates/wit-parser/tests/ui/parse-fail/implements-duplicate-label.wit new file mode 100644 index 0000000000..68f4e45742 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/implements-duplicate-label.wit @@ -0,0 +1,11 @@ +// parse-fail +package foo:foo; + +interface store { + get: func() -> option; +} + +world test { + import primary: store; + import primary: store; +} diff --git a/crates/wit-parser/tests/ui/parse-fail/implements-duplicate-label.wit.result b/crates/wit-parser/tests/ui/parse-fail/implements-duplicate-label.wit.result new file mode 100644 index 0000000000..72af579bb3 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/implements-duplicate-label.wit.result @@ -0,0 +1,5 @@ +import `primary` conflicts with prior interface of same name + --> tests/ui/parse-fail/implements-duplicate-label.wit:10:10 + | + 10 | import primary: store; + | ^------ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/implements-nonexistent.wit b/crates/wit-parser/tests/ui/parse-fail/implements-nonexistent.wit new file mode 100644 index 0000000000..30cbfce02c --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/implements-nonexistent.wit @@ -0,0 +1,10 @@ +// parse-fail +package foo:foo; + +interface store { + get: func() -> option; +} + +world test { + import primary: nonexistent; +} diff --git a/crates/wit-parser/tests/ui/parse-fail/implements-nonexistent.wit.result b/crates/wit-parser/tests/ui/parse-fail/implements-nonexistent.wit.result new file mode 100644 index 0000000000..3a332c6883 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/implements-nonexistent.wit.result @@ -0,0 +1,5 @@ +interface or world `nonexistent` does not exist + --> tests/ui/parse-fail/implements-nonexistent.wit:9:19 + | + 9 | import primary: nonexistent; + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/implements-not-interface.wit b/crates/wit-parser/tests/ui/parse-fail/implements-not-interface.wit new file mode 100644 index 0000000000..c0a070c7fb --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/implements-not-interface.wit @@ -0,0 +1,6 @@ +// parse-fail +package foo:foo; + +world test { + import primary: test; +} diff --git a/crates/wit-parser/tests/ui/parse-fail/implements-not-interface.wit.result b/crates/wit-parser/tests/ui/parse-fail/implements-not-interface.wit.result new file mode 100644 index 0000000000..95b62fabf9 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/implements-not-interface.wit.result @@ -0,0 +1,5 @@ +name `test` is defined as a world, not an interface + --> tests/ui/parse-fail/implements-not-interface.wit:5:19 + | + 5 | import primary: test; + | ^--- \ No newline at end of file diff --git a/crates/wit-smith/src/config.rs b/crates/wit-smith/src/config.rs index e5d0849845..ba0ec2074f 100644 --- a/crates/wit-smith/src/config.rs +++ b/crates/wit-smith/src/config.rs @@ -31,6 +31,8 @@ pub struct Config { pub fixed_length_lists: bool, #[cfg_attr(feature = "clap", clap(long, default_value_t = Config::default().world_include))] pub world_include: bool, + #[cfg_attr(feature = "clap", clap(long, default_value_t = Config::default().implements))] + pub implements: bool, } impl Default for Config { @@ -50,6 +52,7 @@ impl Default for Config { error_context: false, fixed_length_lists: false, world_include: false, + implements: false, } } } @@ -71,6 +74,7 @@ impl Arbitrary<'_> for Config { error_context: u.arbitrary()?, fixed_length_lists: u.arbitrary()?, world_include: false, + implements: u.arbitrary()?, }) } } diff --git a/crates/wit-smith/src/generate.rs b/crates/wit-smith/src/generate.rs index eddc657303..461c8672c7 100644 --- a/crates/wit-smith/src/generate.rs +++ b/crates/wit-smith/src/generate.rs @@ -647,6 +647,7 @@ impl<'a> InterfaceGenerator<'a> { Func(Direction), Interface(Direction), AnonInterface(Direction), + ImplementsInterface(Direction), Type, Use, Include, @@ -666,9 +667,18 @@ impl<'a> InterfaceGenerator<'a> { && u.arbitrary()? { let kind = u.arbitrary::()?; + + // Gate config-disabled features early, before consuming any + // more random bytes, to keep byte consumption deterministic + // when a feature is toggled off. + if matches!(kind, ItemKind::ImplementsInterface(_)) && !self.generator.config.implements + { + continue; + } + let (direction, named) = match kind { ItemKind::Func(dir) | ItemKind::AnonInterface(dir) => (Some(dir), true), - ItemKind::Interface(dir) => (Some(dir), false), + ItemKind::Interface(dir) | ItemKind::ImplementsInterface(dir) => (Some(dir), false), ItemKind::Type => (None, true), ItemKind::Use => (None, false), ItemKind::Include => (None, false), @@ -750,6 +760,34 @@ impl<'a> InterfaceGenerator<'a> { } part.push_str(";"); } + ItemKind::ImplementsInterface(dir) => { + let names = match dir { + Direction::Import => &mut self.unique_names, + Direction::Export => &mut export_names, + }; + let label = gen_unique_name(u, names)?; + + let mut path_str = String::new(); + if self + .generator + .gen_interface_path(u, self.file, &mut path_str)? + .is_none() + { + continue; + } + + self.generator.packages.add_name( + self.package_name.to_string(), + world_name.to_string(), + label.to_string(), + ); + + part.push_str("%"); + part.push_str(&label); + part.push_str(": "); + part.push_str(&path_str); + part.push_str(";"); + } ItemKind::AnonInterface(_) => { let iface = InterfaceGenerator::new(self.generator, self.file, self.package_name, None) diff --git a/tests/cli/component-model/implements.wast b/tests/cli/component-model/implements.wast new file mode 100644 index 0000000000..3bf689ed6e --- /dev/null +++ b/tests/cli/component-model/implements.wast @@ -0,0 +1,99 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-implements + +(component + (component + (import "a" (implements "a:b/c") (instance)) + (import "b" (implements "a:b/c") (instance)) + (import "c" (implements "a:b/c@1.0.0") (instance)) + (import "my-label" (implements "ns:pkg/iface") (instance)) + (import "a:b/c" (instance)) + (import "a:b/c@1.0.0" (instance)) + + (instance $a) + + (export "a" (implements "a:b/c") (instance $a)) + (export "b" (implements "a:b/c") (instance $a)) + (export "c" (implements "a:b/c@1.0.0") (instance $a)) + (export "my-label" (implements "ns:pkg/iface") (instance $a)) + (export "a:b/c" (instance $a)) + (export "a:b/c@1.0.0" (instance $a)) + ) + + (type (instance + (export "a" (implements "a:b/c") (instance)) + )) + (type (component + (import "a" (implements "a:b/c") (instance)) + (export "a" (implements "a:b/c") (instance)) + )) + + (instance $a) + (instance + (export "a" (implements "a:b/c") (instance $a)) + ) +) + +(assert_invalid + (component (import "a" (implements "not-valid") (instance))) + "must be an interface") +(assert_invalid + (component (import "a" (implements "") (instance))) + "not a valid name") + +(assert_invalid + (component + (import "a" (implements "a:b/c") (instance)) + (import "a" (implements "a:b/c") (instance)) + ) + "conflicts with previous name") + +(assert_invalid + (component + (import "a" (implements "a1:b/c") (instance)) + (import "a" (implements "a2:b/c") (instance)) + ) + "conflicts with previous name") + +(assert_invalid + (component + (import "a" (implements "a:b/c") (instance)) + (import "a" (implements "a:b/c@1.0.0") (instance)) + ) + "conflicts with previous name") + +(assert_invalid + (component + (import "a" (instance)) + (import "a" (implements "a:b/c") (instance)) + ) + "conflicts with previous name") + +(assert_invalid + (component + (import "a" (implements "a:b/c") (func)) + ) + "only instance names can have an `implements`") + +(assert_invalid + (component + (import "a1:b/c" (implements "a2:b/c") (instance)) + ) + "name `a1:b/c` is not valid with `implements`") + +;; validity checks apply to other locations of `implements`, such as +;; component/instance types and bag-of-exports. +(assert_invalid + (component (type (component (import "a" (implements "not-valid") (instance))))) + "must be an interface") +(assert_invalid + (component (type (component (export "a" (implements "") (instance))))) + "not a valid name") +(assert_invalid + (component (type (instance (export "a" (implements "a:b/c") (func))))) + "only instance names") +(assert_invalid + (component + (instance) + (instance (export "x" (implements "a") (instance 0))) + ) + "must be an interface") diff --git a/tests/cli/dummy-canonical-and-implements-export.wit b/tests/cli/dummy-canonical-and-implements-export.wit new file mode 100644 index 0000000000..209286d38c --- /dev/null +++ b/tests/cli/dummy-canonical-and-implements-export.wit @@ -0,0 +1,15 @@ +// RUN: component embed % --dummy-names legacy | \ +// component new | \ +// validate -f cm-implements + +package x:name; + +interface name { + resource r; +} + +world w { + export a: name; + export name; + export b: name; +} diff --git a/tests/cli/dummy-implements-export-twice-only.wit b/tests/cli/dummy-implements-export-twice-only.wit new file mode 100644 index 0000000000..4ed64eb877 --- /dev/null +++ b/tests/cli/dummy-implements-export-twice-only.wit @@ -0,0 +1,19 @@ +// RUN: component embed % --dummy-names legacy | \ +// component new | \ +// validate -f cm-implements + +package x:name; + +interface name { + resource r { + m: func() -> bool; + } +} + +world w { + import a: name; + import name; + + export a: name; + export b: name; +} diff --git a/tests/cli/dummy-implements-export-twice.wit b/tests/cli/dummy-implements-export-twice.wit new file mode 100644 index 0000000000..09ea1093e6 --- /dev/null +++ b/tests/cli/dummy-implements-export-twice.wit @@ -0,0 +1,14 @@ +// RUN: component embed % --dummy-names legacy | \ +// component new | \ +// validate -f cm-implements + +package x:name; + +interface name { + resource r; +} + +world w { + export name; + export n: name; +} diff --git a/tests/cli/dummy-implements-import-only.wit b/tests/cli/dummy-implements-import-only.wit new file mode 100644 index 0000000000..5f8260c9ba --- /dev/null +++ b/tests/cli/dummy-implements-import-only.wit @@ -0,0 +1,15 @@ +// RUN: component embed % --dummy-names legacy | \ +// component new | \ +// validate -f cm-implements + +package x:name; + +interface name { + resource r; +} + +world w { + import a: name; + + export name; +} diff --git a/tests/cli/dummy-inline-interface-resource.wit b/tests/cli/dummy-inline-interface-resource.wit new file mode 100644 index 0000000000..277f776caa --- /dev/null +++ b/tests/cli/dummy-inline-interface-resource.wit @@ -0,0 +1,11 @@ +// RUN: component embed % --dummy-names legacy | \ +// component new | \ +// validate + +package a:b; + +world w { + import xaxxx: interface { + resource r; + } +} diff --git a/tests/cli/dummy-world-type-and-func.wit b/tests/cli/dummy-world-type-and-func.wit new file mode 100644 index 0000000000..2d58f4e9f3 --- /dev/null +++ b/tests/cli/dummy-world-type-and-func.wit @@ -0,0 +1,12 @@ +// RUN: component embed % --dummy-names legacy | \ +// component new | \ +// validate + +package a:b; + +world w { + flags my-flags { + a, + } + import f: func(); +} diff --git a/tests/cli/dump/alias.wat.stdout b/tests/cli/dump/alias.wat.stdout index 29256bb68c..a26f4f7100 100644 --- a/tests/cli/dump/alias.wat.stdout +++ b/tests/cli/dump/alias.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 1f | component type section 0xa | 01 | 1 count - 0xb | 42 04 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExportName("f1"), ty: Func(0) }, Type(Func(ComponentFuncType { async_: false, params: [("p1", Primitive(String))], result: None })), Export { name: ComponentExportName("f2"), ty: Func(1) }]) + 0xb | 42 04 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "f1", implements: None }, ty: Func(0) }, Type(Func(ComponentFuncType { async_: false, params: [("p1", Primitive(String))], result: None })), Export { name: ComponentExternName { name: "f2", implements: None }, ty: Func(1) }]) | 00 01 00 04 | 00 02 66 31 | 01 00 01 40 @@ -12,7 +12,7 @@ | 01 01 0x29 | 0a 06 | component import section 0x2b | 01 | 1 count - 0x2c | 00 01 69 05 | [instance 0] ComponentImport { name: ComponentImportName("i"), ty: Instance(0) } + 0x2c | 00 01 69 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "i", implements: None }, ty: Instance(0) } | 00 0x31 | 06 13 | component alias section 0x33 | 03 | 3 count diff --git a/tests/cli/dump/alias2.wat.stdout b/tests/cli/dump/alias2.wat.stdout index c3a5388fe6..c5ff04c981 100644 --- a/tests/cli/dump/alias2.wat.stdout +++ b/tests/cli/dump/alias2.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 30 | component type section 0xa | 01 | 1 count - 0xb | 42 09 00 50 | [type 0] Instance([CoreType(Module([])), Export { name: ComponentExportName("a"), ty: Module(0) }, Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExportName("b"), ty: Func(0) }, Export { name: ComponentExportName("c"), ty: Value(Primitive(String)) }, Type(Instance([])), Export { name: ComponentExportName("d"), ty: Instance(1) }, Type(Component([])), Export { name: ComponentExportName("e"), ty: Component(2) }]) + 0xb | 42 09 00 50 | [type 0] Instance([CoreType(Module([])), Export { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) }, Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "b", implements: None }, ty: Func(0) }, Export { name: ComponentExternName { name: "c", implements: None }, ty: Value(Primitive(String)) }, Type(Instance([])), Export { name: ComponentExternName { name: "d", implements: None }, ty: Instance(1) }, Type(Component([])), Export { name: ComponentExternName { name: "e", implements: None }, ty: Component(2) }]) | 00 04 00 01 | 61 00 11 00 | 01 40 00 01 @@ -16,7 +16,7 @@ | 65 04 02 0x3a | 0a 06 | component import section 0x3c | 01 | 1 count - 0x3d | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentImportName("a"), ty: Instance(0) } + 0x3d | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Instance(0) } | 00 0x42 | 04 59 | [component 0] inline size 0x44 | 00 61 73 6d | version 13 (Component) @@ -26,30 +26,30 @@ 0x4f | 50 00 | [core type 0] Module([]) 0x51 | 0a 07 | component import section 0x53 | 01 | 1 count - 0x54 | 00 01 61 00 | [module 0] ComponentImport { name: ComponentImportName("a"), ty: Module(0) } + 0x54 | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) } | 11 00 0x5a | 07 05 | component type section 0x5c | 01 | 1 count 0x5d | 40 00 01 00 | [type 0] Func(ComponentFuncType { async_: false, params: [], result: None }) 0x61 | 0a 0b | component import section 0x63 | 02 | 2 count - 0x64 | 00 01 62 01 | [func 0] ComponentImport { name: ComponentImportName("b"), ty: Func(0) } + 0x64 | 00 01 62 01 | [func 0] ComponentImport { name: ComponentExternName { name: "b", implements: None }, ty: Func(0) } | 00 - 0x69 | 00 01 63 02 | [value 0] ComponentImport { name: ComponentImportName("c"), ty: Value(Primitive(String)) } + 0x69 | 00 01 63 02 | [value 0] ComponentImport { name: ComponentExternName { name: "c", implements: None }, ty: Value(Primitive(String)) } | 73 0x6e | 07 03 | component type section 0x70 | 01 | 1 count 0x71 | 42 00 | [type 1] Instance([]) 0x73 | 0a 06 | component import section 0x75 | 01 | 1 count - 0x76 | 00 01 64 05 | [instance 0] ComponentImport { name: ComponentImportName("d"), ty: Instance(1) } + 0x76 | 00 01 64 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "d", implements: None }, ty: Instance(1) } | 01 0x7b | 07 03 | component type section 0x7d | 01 | 1 count 0x7e | 41 00 | [type 2] Component([]) 0x80 | 0a 06 | component import section 0x82 | 01 | 1 count - 0x83 | 00 01 65 04 | [component 0] ComponentImport { name: ComponentImportName("e"), ty: Component(2) } + 0x83 | 00 01 65 04 | [component 0] ComponentImport { name: ComponentExternName { name: "e", implements: None }, ty: Component(2) } | 02 0x88 | 00 13 | custom section 0x8a | 0e 63 6f 6d | name: "component-name" @@ -86,7 +86,7 @@ 0xe2 | 03 02 01 00 | alias [type 0] Outer { kind: Type, count: 1, index: 0 } 0xe6 | 0a 06 | component import section 0xe8 | 01 | 1 count - 0xe9 | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentImportName("a"), ty: Instance(0) } + 0xe9 | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Instance(0) } | 00 0xee | 00 1b | custom section 0xf0 | 0e 63 6f 6d | name: "component-name" @@ -112,7 +112,7 @@ | 65 0x128 | 05 24 | component instance section 0x12a | 02 | 2 count - 0x12b | 01 05 00 01 | [instance 4] FromExports([ComponentExport { name: ComponentExportName("a"), kind: Module, index: 1, ty: None }, ComponentExport { name: ComponentExportName("b"), kind: Func, index: 1, ty: None }, ComponentExport { name: ComponentExportName("c"), kind: Value, index: 1, ty: None }, ComponentExport { name: ComponentExportName("d"), kind: Instance, index: 3, ty: None }, ComponentExport { name: ComponentExportName("e"), kind: Component, index: 3, ty: None }]) + 0x12b | 01 05 00 01 | [instance 4] FromExports([ComponentExport { name: ComponentExternName { name: "a", implements: None }, kind: Module, index: 1, ty: None }, ComponentExport { name: ComponentExternName { name: "b", implements: None }, kind: Func, index: 1, ty: None }, ComponentExport { name: ComponentExternName { name: "c", implements: None }, kind: Value, index: 1, ty: None }, ComponentExport { name: ComponentExternName { name: "d", implements: None }, kind: Instance, index: 3, ty: None }, ComponentExport { name: ComponentExternName { name: "e", implements: None }, kind: Component, index: 3, ty: None }]) | 61 00 11 01 | 00 01 62 01 | 01 00 01 63 diff --git a/tests/cli/dump/bundled.wat.stdout b/tests/cli/dump/bundled.wat.stdout index 02a9535068..461507aa10 100644 --- a/tests/cli/dump/bundled.wat.stdout +++ b/tests/cli/dump/bundled.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 30 | component type section 0xa | 01 | 1 count - 0xb | 42 06 01 70 | [type 0] Instance([Type(Defined(List(Primitive(U8)))), Type(Func(ComponentFuncType { async_: false, params: [("len", Primitive(U32))], result: Some(Type(0)) })), Export { name: ComponentExportName("read"), ty: Func(1) }, Type(Defined(List(Primitive(U8)))), Type(Func(ComponentFuncType { async_: false, params: [("buf", Type(2))], result: Some(Primitive(U32)) })), Export { name: ComponentExportName("write"), ty: Func(3) }]) + 0xb | 42 06 01 70 | [type 0] Instance([Type(Defined(List(Primitive(U8)))), Type(Func(ComponentFuncType { async_: false, params: [("len", Primitive(U32))], result: Some(Type(0)) })), Export { name: ComponentExternName { name: "read", implements: None }, ty: Func(1) }, Type(Defined(List(Primitive(U8)))), Type(Func(ComponentFuncType { async_: false, params: [("buf", Type(2))], result: Some(Primitive(U32)) })), Export { name: ComponentExternName { name: "write", implements: None }, ty: Func(3) }]) | 7d 01 40 01 | 03 6c 65 6e | 79 00 00 04 @@ -16,7 +16,7 @@ | 65 01 03 0x3a | 0a 0e | component import section 0x3c | 01 | 1 count - 0x3d | 00 09 77 61 | [instance 0] ComponentImport { name: ComponentImportName("wasi-file"), ty: Instance(0) } + 0x3d | 00 09 77 61 | [instance 0] ComponentImport { name: ComponentExternName { name: "wasi-file", implements: None }, ty: Instance(0) } | 73 69 2d 66 | 69 6c 65 05 | 00 @@ -194,7 +194,7 @@ | 01 0x1e0 | 0b 0a | component export section 0x1e2 | 01 | 1 count - 0x1e3 | 00 04 77 6f | export ComponentExport { name: ComponentExportName("work"), kind: Func, index: 1, ty: None } + 0x1e3 | 00 04 77 6f | export ComponentExport { name: ComponentExternName { name: "work", implements: None }, kind: Func, index: 1, ty: None } | 72 6b 01 01 | 00 0x1ec | 00 7c | custom section diff --git a/tests/cli/dump/component-expand-bundle2.wat.stdout b/tests/cli/dump/component-expand-bundle2.wat.stdout index b525afcd98..8ad680abe9 100644 --- a/tests/cli/dump/component-expand-bundle2.wat.stdout +++ b/tests/cli/dump/component-expand-bundle2.wat.stdout @@ -8,7 +8,7 @@ | 01 00 00 00 0x1c | 0b 08 | component export section 0x1e | 01 | 1 count - 0x1f | 00 01 65 00 | export ComponentExport { name: ComponentExportName("e"), kind: Module, index: 0, ty: None } + 0x1f | 00 01 65 00 | export ComponentExport { name: ComponentExternName { name: "e", implements: None }, kind: Module, index: 0, ty: None } | 11 00 00 0x26 | 00 13 | custom section 0x28 | 0e 63 6f 6d | name: "component-name" @@ -22,12 +22,12 @@ | 0d 00 01 00 0x45 | 07 0d | component type section 0x47 | 01 | 1 count - 0x48 | 41 02 00 50 | [type 0] Component([CoreType(Module([])), Import(ComponentImport { name: ComponentImportName("i"), ty: Module(0) })]) + 0x48 | 41 02 00 50 | [type 0] Component([CoreType(Module([])), Import(ComponentImport { name: ComponentExternName { name: "i", implements: None }, ty: Module(0) })]) | 00 03 00 01 | 69 00 11 00 0x54 | 0a 06 | component import section 0x56 | 01 | 1 count - 0x57 | 00 01 69 04 | [component 0] ComponentImport { name: ComponentImportName("i"), ty: Component(0) } + 0x57 | 00 01 69 04 | [component 0] ComponentImport { name: ComponentExternName { name: "i", implements: None }, ty: Component(0) } | 00 0x5c | 00 14 | custom section 0x5e | 0e 63 6f 6d | name: "component-name" @@ -45,7 +45,7 @@ | 01 65 0x81 | 05 10 | component instance section 0x83 | 02 | 2 count - 0x84 | 01 01 00 01 | [instance 1] FromExports([ComponentExport { name: ComponentExportName("e"), kind: Module, index: 0, ty: None }]) + 0x84 | 01 01 00 01 | [instance 1] FromExports([ComponentExport { name: ComponentExternName { name: "e", implements: None }, kind: Module, index: 0, ty: None }]) | 65 00 11 00 0x8c | 00 01 01 01 | [instance 2] Instantiate { component_index: 1, args: [ComponentInstantiationArg { name: "i", kind: Instance, index: 1 }] } | 69 05 01 diff --git a/tests/cli/dump/component-inline-export-import.wat.stdout b/tests/cli/dump/component-inline-export-import.wat.stdout index f29289865b..99b4e14aba 100644 --- a/tests/cli/dump/component-inline-export-import.wat.stdout +++ b/tests/cli/dump/component-inline-export-import.wat.stdout @@ -5,17 +5,17 @@ 0xb | 41 00 | [type 0] Component([]) 0xd | 0a 05 | component import section 0xf | 01 | 1 count - 0x10 | 00 00 04 00 | [component 0] ComponentImport { name: ComponentImportName(""), ty: Component(0) } + 0x10 | 00 00 04 00 | [component 0] ComponentImport { name: ComponentExternName { name: "", implements: None }, ty: Component(0) } 0x14 | 03 03 | core type section 0x16 | 01 | 1 count 0x17 | 50 00 | [core type 0] Module([]) 0x19 | 0a 07 | component import section 0x1b | 01 | 1 count - 0x1c | 00 01 61 00 | [module 0] ComponentImport { name: ComponentImportName("a"), ty: Module(0) } + 0x1c | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) } | 11 00 0x22 | 0b 0d | component export section 0x24 | 02 | 2 count - 0x25 | 00 00 04 00 | export ComponentExport { name: ComponentExportName(""), kind: Component, index: 0, ty: None } + 0x25 | 00 00 04 00 | export ComponentExport { name: ComponentExternName { name: "", implements: None }, kind: Component, index: 0, ty: None } | 00 - 0x2a | 00 01 61 00 | export ComponentExport { name: ComponentExportName("a"), kind: Module, index: 0, ty: None } + 0x2a | 00 01 61 00 | export ComponentExport { name: ComponentExternName { name: "a", implements: None }, kind: Module, index: 0, ty: None } | 11 00 00 diff --git a/tests/cli/dump/component-inline-type.wat.stdout b/tests/cli/dump/component-inline-type.wat.stdout index 4d0d178807..492bd15fe1 100644 --- a/tests/cli/dump/component-inline-type.wat.stdout +++ b/tests/cli/dump/component-inline-type.wat.stdout @@ -5,26 +5,26 @@ 0xb | 41 00 | [type 0] Component([]) 0xd | 0a 06 | component import section 0xf | 01 | 1 count - 0x10 | 00 01 61 04 | [component 0] ComponentImport { name: ComponentImportName("a"), ty: Component(0) } + 0x10 | 00 01 61 04 | [component 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Component(0) } | 00 0x15 | 03 03 | core type section 0x17 | 01 | 1 count 0x18 | 50 00 | [core type 0] Module([]) 0x1a | 0a 07 | component import section 0x1c | 01 | 1 count - 0x1d | 00 01 62 00 | [module 0] ComponentImport { name: ComponentImportName("b"), ty: Module(0) } + 0x1d | 00 01 62 00 | [module 0] ComponentImport { name: ComponentExternName { name: "b", implements: None }, ty: Module(0) } | 11 00 0x23 | 07 03 | component type section 0x25 | 01 | 1 count 0x26 | 42 00 | [type 1] Instance([]) 0x28 | 0a 06 | component import section 0x2a | 01 | 1 count - 0x2b | 00 01 63 05 | [instance 0] ComponentImport { name: ComponentImportName("c"), ty: Instance(1) } + 0x2b | 00 01 63 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "c", implements: None }, ty: Instance(1) } | 01 0x30 | 07 05 | component type section 0x32 | 01 | 1 count 0x33 | 40 00 01 00 | [type 2] Func(ComponentFuncType { async_: false, params: [], result: None }) 0x37 | 0a 06 | component import section 0x39 | 01 | 1 count - 0x3a | 00 01 64 01 | [func 0] ComponentImport { name: ComponentImportName("d"), ty: Func(2) } + 0x3a | 00 01 64 01 | [func 0] ComponentImport { name: ComponentExternName { name: "d", implements: None }, ty: Func(2) } | 02 diff --git a/tests/cli/dump/component-instance-type.wat.stdout b/tests/cli/dump/component-instance-type.wat.stdout index 8b98d2b96e..a661a28422 100644 --- a/tests/cli/dump/component-instance-type.wat.stdout +++ b/tests/cli/dump/component-instance-type.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 28 | component type section 0xa | 01 | 1 count - 0xb | 42 03 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Type(Component([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Alias(Outer { kind: Type, count: 1, index: 0 }), Import(ComponentImport { name: ComponentImportName("1"), ty: Func(0) }), Export { name: ComponentExportName("1"), ty: Func(1) }])), Export { name: ComponentExportName("c5"), ty: Component(1) }]) + 0xb | 42 03 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Type(Component([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Alias(Outer { kind: Type, count: 1, index: 0 }), Import(ComponentImport { name: ComponentExternName { name: "1", implements: None }, ty: Func(0) }), Export { name: ComponentExternName { name: "1", implements: None }, ty: Func(1) }])), Export { name: ComponentExternName { name: "c5", implements: None }, ty: Component(1) }]) | 00 01 00 01 | 41 04 01 40 | 00 01 00 02 diff --git a/tests/cli/dump/component-linking.wat.stdout b/tests/cli/dump/component-linking.wat.stdout index 1e5e5cc3d9..91eed3ce30 100644 --- a/tests/cli/dump/component-linking.wat.stdout +++ b/tests/cli/dump/component-linking.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 30 | component type section 0xa | 01 | 1 count - 0xb | 42 09 00 50 | [type 0] Instance([CoreType(Module([])), Export { name: ComponentExportName("a"), ty: Module(0) }, Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExportName("b"), ty: Func(0) }, Export { name: ComponentExportName("c"), ty: Value(Primitive(String)) }, Type(Instance([])), Export { name: ComponentExportName("d"), ty: Instance(1) }, Type(Component([])), Export { name: ComponentExportName("e"), ty: Component(2) }]) + 0xb | 42 09 00 50 | [type 0] Instance([CoreType(Module([])), Export { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) }, Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "b", implements: None }, ty: Func(0) }, Export { name: ComponentExternName { name: "c", implements: None }, ty: Value(Primitive(String)) }, Type(Instance([])), Export { name: ComponentExternName { name: "d", implements: None }, ty: Instance(1) }, Type(Component([])), Export { name: ComponentExternName { name: "e", implements: None }, ty: Component(2) }]) | 00 04 00 01 | 61 00 11 00 | 01 40 00 01 @@ -16,7 +16,7 @@ | 65 04 02 0x3a | 0a 06 | component import section 0x3c | 01 | 1 count - 0x3d | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentImportName("a"), ty: Instance(0) } + 0x3d | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Instance(0) } | 00 0x42 | 04 59 | [component 0] inline size 0x44 | 00 61 73 6d | version 13 (Component) @@ -26,30 +26,30 @@ 0x4f | 50 00 | [core type 0] Module([]) 0x51 | 0a 07 | component import section 0x53 | 01 | 1 count - 0x54 | 00 01 61 00 | [module 0] ComponentImport { name: ComponentImportName("a"), ty: Module(0) } + 0x54 | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) } | 11 00 0x5a | 07 05 | component type section 0x5c | 01 | 1 count 0x5d | 40 00 01 00 | [type 0] Func(ComponentFuncType { async_: false, params: [], result: None }) 0x61 | 0a 0b | component import section 0x63 | 02 | 2 count - 0x64 | 00 01 62 01 | [func 0] ComponentImport { name: ComponentImportName("b"), ty: Func(0) } + 0x64 | 00 01 62 01 | [func 0] ComponentImport { name: ComponentExternName { name: "b", implements: None }, ty: Func(0) } | 00 - 0x69 | 00 01 63 02 | [value 0] ComponentImport { name: ComponentImportName("c"), ty: Value(Primitive(String)) } + 0x69 | 00 01 63 02 | [value 0] ComponentImport { name: ComponentExternName { name: "c", implements: None }, ty: Value(Primitive(String)) } | 73 0x6e | 07 03 | component type section 0x70 | 01 | 1 count 0x71 | 42 00 | [type 1] Instance([]) 0x73 | 0a 06 | component import section 0x75 | 01 | 1 count - 0x76 | 00 01 64 05 | [instance 0] ComponentImport { name: ComponentImportName("d"), ty: Instance(1) } + 0x76 | 00 01 64 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "d", implements: None }, ty: Instance(1) } | 01 0x7b | 07 03 | component type section 0x7d | 01 | 1 count 0x7e | 41 00 | [type 2] Component([]) 0x80 | 0a 06 | component import section 0x82 | 01 | 1 count - 0x83 | 00 01 65 04 | [component 0] ComponentImport { name: ComponentImportName("e"), ty: Component(2) } + 0x83 | 00 01 65 04 | [component 0] ComponentImport { name: ComponentExternName { name: "e", implements: None }, ty: Component(2) } | 02 0x88 | 00 13 | custom section 0x8a | 0e 63 6f 6d | name: "component-name" diff --git a/tests/cli/dump/import-modules.wat.stdout b/tests/cli/dump/import-modules.wat.stdout index 2b50be09ce..2ff8fde16b 100644 --- a/tests/cli/dump/import-modules.wat.stdout +++ b/tests/cli/dump/import-modules.wat.stdout @@ -7,7 +7,7 @@ | 01 66 00 00 0x17 | 0a 07 | component import section 0x19 | 01 | 1 count - 0x1a | 00 01 61 00 | [module 0] ComponentImport { name: ComponentImportName("a"), ty: Module(0) } + 0x1a | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) } | 11 00 0x20 | 01 2b | [core module 1] inline size 0x22 | 00 61 73 6d | version 1 (Module) diff --git a/tests/cli/dump/instance-expand.wat.stdout b/tests/cli/dump/instance-expand.wat.stdout index 48cf6e3d5c..7178765803 100644 --- a/tests/cli/dump/instance-expand.wat.stdout +++ b/tests/cli/dump/instance-expand.wat.stdout @@ -2,12 +2,12 @@ | 0d 00 01 00 0x8 | 07 0d | component type section 0xa | 01 | 1 count - 0xb | 42 02 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExportName(""), ty: Func(0) }]) + 0xb | 42 02 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "", implements: None }, ty: Func(0) }]) | 00 01 00 04 | 00 00 01 00 0x17 | 0a 06 | component import section 0x19 | 01 | 1 count - 0x1a | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentImportName("a"), ty: Instance(0) } + 0x1a | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Instance(0) } | 00 0x1f | 00 16 | custom section 0x21 | 0e 63 6f 6d | name: "component-name" diff --git a/tests/cli/dump/instance-type.wat.stdout b/tests/cli/dump/instance-type.wat.stdout index 9d1e20356a..cb29c01998 100644 --- a/tests/cli/dump/instance-type.wat.stdout +++ b/tests/cli/dump/instance-type.wat.stdout @@ -2,9 +2,9 @@ | 0d 00 01 00 0x8 | 07 17 | component type section 0xa | 02 | 2 count - 0xb | 42 02 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExportName(""), ty: Func(0) }]) + 0xb | 42 02 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "", implements: None }, ty: Func(0) }]) | 00 01 00 04 | 00 00 01 00 - 0x17 | 42 02 01 42 | [type 1] Instance([Type(Instance([])), Export { name: ComponentExportName(""), ty: Instance(0) }]) + 0x17 | 42 02 01 42 | [type 1] Instance([Type(Instance([])), Export { name: ComponentExternName { name: "", implements: None }, ty: Instance(0) }]) | 00 04 00 00 | 05 00 diff --git a/tests/cli/dump/instance-type2.wat.stdout b/tests/cli/dump/instance-type2.wat.stdout index cbe5db9ff0..4189197cab 100644 --- a/tests/cli/dump/instance-type2.wat.stdout +++ b/tests/cli/dump/instance-type2.wat.stdout @@ -3,10 +3,10 @@ 0x8 | 07 18 | component type section 0xa | 04 | 4 count 0xb | 42 00 | [type 0] Instance([]) - 0xd | 42 01 04 00 | [type 1] Instance([Export { name: ComponentExportName(""), ty: Instance(0) }]) + 0xd | 42 01 04 00 | [type 1] Instance([Export { name: ComponentExternName { name: "", implements: None }, ty: Instance(0) }]) | 00 05 00 0x14 | 42 00 | [type 2] Instance([]) - 0x16 | 42 02 02 03 | [type 3] Instance([Alias(Outer { kind: Type, count: 1, index: 2 }), Export { name: ComponentExportName(""), ty: Instance(0) }]) + 0x16 | 42 02 02 03 | [type 3] Instance([Alias(Outer { kind: Type, count: 1, index: 2 }), Export { name: ComponentExternName { name: "", implements: None }, ty: Instance(0) }]) | 02 01 02 04 | 00 00 05 00 0x22 | 00 16 | custom section diff --git a/tests/cli/dump/instantiate.wat.stdout b/tests/cli/dump/instantiate.wat.stdout index 84883c42aa..846be32015 100644 --- a/tests/cli/dump/instantiate.wat.stdout +++ b/tests/cli/dump/instantiate.wat.stdout @@ -5,14 +5,14 @@ 0xb | 41 00 | [type 0] Component([]) 0xd | 0a 06 | component import section 0xf | 01 | 1 count - 0x10 | 00 01 61 04 | [component 0] ComponentImport { name: ComponentImportName("a"), ty: Component(0) } + 0x10 | 00 01 61 04 | [component 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Component(0) } | 00 0x15 | 07 05 | component type section 0x17 | 01 | 1 count 0x18 | 40 00 01 00 | [type 1] Func(ComponentFuncType { async_: false, params: [], result: None }) 0x1c | 0a 06 | component import section 0x1e | 01 | 1 count - 0x1f | 00 01 66 01 | [func 0] ComponentImport { name: ComponentImportName("f"), ty: Func(1) } + 0x1f | 00 01 66 01 | [func 0] ComponentImport { name: ComponentExternName { name: "f", implements: None }, ty: Func(1) } | 01 0x24 | 05 08 | component instance section 0x26 | 01 | 1 count diff --git a/tests/cli/dump/instantiate2.wat.stdout b/tests/cli/dump/instantiate2.wat.stdout index 194a87c901..c3f80961d9 100644 --- a/tests/cli/dump/instantiate2.wat.stdout +++ b/tests/cli/dump/instantiate2.wat.stdout @@ -2,13 +2,13 @@ | 0d 00 01 00 0x8 | 07 0e | component type section 0xa | 01 | 1 count - 0xb | 41 02 01 40 | [type 0] Component([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Import(ComponentImport { name: ComponentImportName("a"), ty: Func(0) })]) + 0xb | 41 02 01 40 | [type 0] Component([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Import(ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Func(0) })]) | 00 01 00 03 | 00 01 61 01 | 00 0x18 | 0a 06 | component import section 0x1a | 01 | 1 count - 0x1b | 00 01 61 04 | [component 0] ComponentImport { name: ComponentImportName("a"), ty: Component(0) } + 0x1b | 00 01 61 04 | [component 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Component(0) } | 00 0x20 | 05 04 | component instance section 0x22 | 01 | 1 count diff --git a/tests/cli/dump/module-types.wat.stdout b/tests/cli/dump/module-types.wat.stdout index 52e1b1badf..a4ce4f1b53 100644 --- a/tests/cli/dump/module-types.wat.stdout +++ b/tests/cli/dump/module-types.wat.stdout @@ -13,7 +13,7 @@ | 00 01 0x2d | 0a 07 | component import section 0x2f | 01 | 1 count - 0x30 | 00 01 61 00 | [module 0] ComponentImport { name: ComponentImportName("a"), ty: Module(0) } + 0x30 | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) } | 11 00 0x36 | 07 03 | component type section 0x38 | 01 | 1 count diff --git a/tests/cli/dump/nested-component.wat.stdout b/tests/cli/dump/nested-component.wat.stdout index 5c44753924..b690270821 100644 --- a/tests/cli/dump/nested-component.wat.stdout +++ b/tests/cli/dump/nested-component.wat.stdout @@ -5,7 +5,7 @@ 0xb | 41 00 | [type 0] Component([]) 0xd | 0a 06 | component import section 0xf | 01 | 1 count - 0x10 | 00 01 61 04 | [component 0] ComponentImport { name: ComponentImportName("a"), ty: Component(0) } + 0x10 | 00 01 61 04 | [component 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Component(0) } | 00 0x15 | 04 08 | [component 1] inline size 0x17 | 00 61 73 6d | version 13 (Component) @@ -39,25 +39,25 @@ | 73 01 00 0x70 | 0a 06 | component import section 0x72 | 01 | 1 count - 0x73 | 00 01 61 01 | [func 0] ComponentImport { name: ComponentImportName("a"), ty: Func(0) } + 0x73 | 00 01 61 01 | [func 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Func(0) } | 00 0x78 | 0b 08 | component export section 0x7a | 01 | 1 count - 0x7b | 00 01 61 00 | export ComponentExport { name: ComponentExportName("a"), kind: Module, index: 0, ty: None } + 0x7b | 00 01 61 00 | export ComponentExport { name: ComponentExternName { name: "a", implements: None }, kind: Module, index: 0, ty: None } | 11 00 00 0x82 | 07 0e | component type section 0x84 | 01 | 1 count - 0x85 | 42 02 01 40 | [type 1] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExportName("a"), ty: Func(0) }]) + 0x85 | 42 02 01 40 | [type 1] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "a", implements: None }, ty: Func(0) }]) | 00 01 00 04 | 00 01 61 01 | 00 0x92 | 0a 06 | component import section 0x94 | 01 | 1 count - 0x95 | 00 01 62 05 | [instance 0] ComponentImport { name: ComponentImportName("b"), ty: Instance(1) } + 0x95 | 00 01 62 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "b", implements: None }, ty: Instance(1) } | 01 0x9a | 0b 07 | component export section 0x9c | 01 | 1 count - 0x9d | 00 01 62 05 | export ComponentExport { name: ComponentExportName("b"), kind: Instance, index: 0, ty: None } + 0x9d | 00 01 62 05 | export ComponentExport { name: ComponentExternName { name: "b", implements: None }, kind: Instance, index: 0, ty: None } | 00 00 0xa3 | 00 17 | custom section 0xa5 | 0e 63 6f 6d | name: "component-name" @@ -69,5 +69,5 @@ 0xb9 | 00 01 6d | Naming { index: 0, name: "m" } 0xbc | 0b 07 | component export section 0xbe | 01 | 1 count - 0xbf | 00 01 61 04 | export ComponentExport { name: ComponentExportName("a"), kind: Component, index: 3, ty: None } + 0xbf | 00 01 61 04 | export ComponentExport { name: ComponentExternName { name: "a", implements: None }, kind: Component, index: 3, ty: None } | 03 00 diff --git a/tests/cli/missing-features/component-model/implements.wast b/tests/cli/missing-features/component-model/implements.wast new file mode 100644 index 0000000000..cb133d712d --- /dev/null +++ b/tests/cli/missing-features/component-model/implements.wast @@ -0,0 +1,21 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f=-cm-implements + +;; textual usage disallowed +(assert_malformed + (component + (import "a" (implements "a:b/x") (instance)) + ) + "the `cm-implements` feature is not active") + +;; binary usage, even if it's not used, is disallowed without the feature +(assert_malformed + (component binary + "\00asm" ;; header + "\0d\00\01\00" ;; version + "\0a\05" ;; import section, 14 bytes + "\01" ;; 1 import + "\02" ;; "import with flags" + "\01a" ;; name = "a" + "\00" ;; 0 options + ) + "the `cm-implements` feature is not active") diff --git a/tests/cli/nominal10.wit b/tests/cli/nominal10.wit new file mode 100644 index 0000000000..45ae27cb3b --- /dev/null +++ b/tests/cli/nominal10.wit @@ -0,0 +1,15 @@ +// RUN: component wit --generate-nominal-type-ids foo % --json + +package a:b; + +interface dep { + type t = u32; +} +interface i { + use dep.{t}; +} + +world foo { + import a: i; + import b: i; +} diff --git a/tests/cli/nominal10.wit.stdout b/tests/cli/nominal10.wit.stdout new file mode 100644 index 0000000000..6aa93710b1 --- /dev/null +++ b/tests/cli/nominal10.wit.stdout @@ -0,0 +1,97 @@ +{ + "worlds": [ + { + "name": "foo", + "imports": { + "interface-0": { + "interface": { + "id": 0 + } + }, + "a": { + "interface": { + "id": 1 + } + }, + "b": { + "interface": { + "id": 2 + } + } + }, + "exports": {}, + "package": 0 + } + ], + "interfaces": [ + { + "name": "dep", + "types": { + "t": 0 + }, + "functions": {}, + "package": 0 + }, + { + "name": "i", + "types": { + "t": 1 + }, + "functions": {}, + "package": 0 + }, + { + "name": "i", + "types": { + "t": 2 + }, + "functions": {}, + "package": 0, + "clone_of": 1 + } + ], + "types": [ + { + "name": "t", + "kind": { + "type": "u32" + }, + "owner": { + "interface": 0 + } + }, + { + "name": "t", + "kind": { + "type": 0 + }, + "owner": { + "interface": 1 + } + }, + { + "name": "t", + "kind": { + "type": 0 + }, + "owner": { + "interface": 2 + } + } + ], + "packages": [ + { + "name": "a:b", + "docs": { + "contents": "RUN: component wit --generate-nominal-type-ids foo % --json" + }, + "interfaces": { + "dep": 0, + "i": 1 + }, + "worlds": { + "foo": 0 + } + } + ] +} diff --git a/tests/cli/nominal11.wit b/tests/cli/nominal11.wit new file mode 100644 index 0000000000..29c03fbe69 --- /dev/null +++ b/tests/cli/nominal11.wit @@ -0,0 +1,17 @@ +// RUN: component wit --generate-nominal-type-ids foo % --json + +package a:b; + +interface dep { + type t = u32; +} +interface i { + use dep.{t}; +} + +world foo { + import a: i; + import b: i; + export a: i; + export b: i; +} diff --git a/tests/cli/nominal11.wit.stdout b/tests/cli/nominal11.wit.stdout new file mode 100644 index 0000000000..da7aeb39ad --- /dev/null +++ b/tests/cli/nominal11.wit.stdout @@ -0,0 +1,144 @@ +{ + "worlds": [ + { + "name": "foo", + "imports": { + "interface-0": { + "interface": { + "id": 0 + } + }, + "a": { + "interface": { + "id": 1 + } + }, + "b": { + "interface": { + "id": 2 + } + } + }, + "exports": { + "a": { + "interface": { + "id": 3 + } + }, + "b": { + "interface": { + "id": 4 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "dep", + "types": { + "t": 0 + }, + "functions": {}, + "package": 0 + }, + { + "name": "i", + "types": { + "t": 1 + }, + "functions": {}, + "package": 0 + }, + { + "name": "i", + "types": { + "t": 2 + }, + "functions": {}, + "package": 0, + "clone_of": 1 + }, + { + "name": "i", + "types": { + "t": 3 + }, + "functions": {}, + "package": 0, + "clone_of": 1 + }, + { + "name": "i", + "types": { + "t": 4 + }, + "functions": {}, + "package": 0, + "clone_of": 1 + } + ], + "types": [ + { + "name": "t", + "kind": { + "type": "u32" + }, + "owner": { + "interface": 0 + } + }, + { + "name": "t", + "kind": { + "type": 0 + }, + "owner": { + "interface": 1 + } + }, + { + "name": "t", + "kind": { + "type": 0 + }, + "owner": { + "interface": 2 + } + }, + { + "name": "t", + "kind": { + "type": 0 + }, + "owner": { + "interface": 3 + } + }, + { + "name": "t", + "kind": { + "type": 0 + }, + "owner": { + "interface": 4 + } + } + ], + "packages": [ + { + "name": "a:b", + "docs": { + "contents": "RUN: component wit --generate-nominal-type-ids foo % --json" + }, + "interfaces": { + "dep": 0, + "i": 1 + }, + "worlds": { + "foo": 0 + } + } + ] +} diff --git a/tests/cli/nominal12.wit b/tests/cli/nominal12.wit new file mode 100644 index 0000000000..3aaad2c40d --- /dev/null +++ b/tests/cli/nominal12.wit @@ -0,0 +1,18 @@ +// RUN: component wit --generate-nominal-type-ids foo % --json + +package a:b; + +interface dep { + type t = u32; +} +interface i { + use dep.{t}; +} + +world foo { + import a: i; + import b: i; + export dep; + export a: i; + export b: i; +} diff --git a/tests/cli/nominal12.wit.stdout b/tests/cli/nominal12.wit.stdout new file mode 100644 index 0000000000..7e2717a5f5 --- /dev/null +++ b/tests/cli/nominal12.wit.stdout @@ -0,0 +1,167 @@ +{ + "worlds": [ + { + "name": "foo", + "imports": { + "interface-0": { + "interface": { + "id": 0 + } + }, + "a": { + "interface": { + "id": 1 + } + }, + "b": { + "interface": { + "id": 2 + } + } + }, + "exports": { + "interface-3": { + "interface": { + "id": 3 + } + }, + "a": { + "interface": { + "id": 4 + } + }, + "b": { + "interface": { + "id": 5 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "dep", + "types": { + "t": 0 + }, + "functions": {}, + "package": 0 + }, + { + "name": "i", + "types": { + "t": 1 + }, + "functions": {}, + "package": 0 + }, + { + "name": "i", + "types": { + "t": 2 + }, + "functions": {}, + "package": 0, + "clone_of": 1 + }, + { + "name": "dep", + "types": { + "t": 3 + }, + "functions": {}, + "package": 0, + "clone_of": 0 + }, + { + "name": "i", + "types": { + "t": 4 + }, + "functions": {}, + "package": 0, + "clone_of": 1 + }, + { + "name": "i", + "types": { + "t": 5 + }, + "functions": {}, + "package": 0, + "clone_of": 1 + } + ], + "types": [ + { + "name": "t", + "kind": { + "type": "u32" + }, + "owner": { + "interface": 0 + } + }, + { + "name": "t", + "kind": { + "type": 0 + }, + "owner": { + "interface": 1 + } + }, + { + "name": "t", + "kind": { + "type": 0 + }, + "owner": { + "interface": 2 + } + }, + { + "name": "t", + "kind": { + "type": "u32" + }, + "owner": { + "interface": 3 + } + }, + { + "name": "t", + "kind": { + "type": 3 + }, + "owner": { + "interface": 4 + } + }, + { + "name": "t", + "kind": { + "type": 3 + }, + "owner": { + "interface": 5 + } + } + ], + "packages": [ + { + "name": "a:b", + "docs": { + "contents": "RUN: component wit --generate-nominal-type-ids foo % --json" + }, + "interfaces": { + "dep": 0, + "i": 1 + }, + "worlds": { + "foo": 0 + } + } + ] +} diff --git a/tests/cli/nominal13.wit b/tests/cli/nominal13.wit new file mode 100644 index 0000000000..01ffbf18d5 --- /dev/null +++ b/tests/cli/nominal13.wit @@ -0,0 +1,11 @@ +// RUN: component wit --generate-nominal-type-ids w % --json + +package a:b; + +interface i {} + +world w { + import i; + export i; + export n: i; +} diff --git a/tests/cli/nominal13.wit.stdout b/tests/cli/nominal13.wit.stdout new file mode 100644 index 0000000000..600535dc1e --- /dev/null +++ b/tests/cli/nominal13.wit.stdout @@ -0,0 +1,64 @@ +{ + "worlds": [ + { + "name": "w", + "imports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "exports": { + "interface-2": { + "interface": { + "id": 2 + } + }, + "n": { + "interface": { + "id": 1 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "i", + "types": {}, + "functions": {}, + "package": 0 + }, + { + "name": "i", + "types": {}, + "functions": {}, + "package": 0, + "clone_of": 0 + }, + { + "name": "i", + "types": {}, + "functions": {}, + "package": 0, + "clone_of": 0 + } + ], + "types": [], + "packages": [ + { + "name": "a:b", + "docs": { + "contents": "RUN: component wit --generate-nominal-type-ids w % --json" + }, + "interfaces": { + "i": 0 + }, + "worlds": { + "w": 0 + } + } + ] +} diff --git a/tests/cli/nominal5.wit b/tests/cli/nominal5.wit new file mode 100644 index 0000000000..fc41f8c67f --- /dev/null +++ b/tests/cli/nominal5.wit @@ -0,0 +1,12 @@ +// RUN: component wit --generate-nominal-type-ids foo % --json + +package a:b; + +interface i { + x: func(); +} + +world foo { + import a: i; + import b: i; +} diff --git a/tests/cli/nominal5.wit.stdout b/tests/cli/nominal5.wit.stdout new file mode 100644 index 0000000000..fc7f0bd6cc --- /dev/null +++ b/tests/cli/nominal5.wit.stdout @@ -0,0 +1,63 @@ +{ + "worlds": [ + { + "name": "foo", + "imports": { + "a": { + "interface": { + "id": 0 + } + }, + "b": { + "interface": { + "id": 1 + } + } + }, + "exports": {}, + "package": 0 + } + ], + "interfaces": [ + { + "name": "i", + "types": {}, + "functions": { + "x": { + "name": "x", + "kind": "freestanding", + "params": [] + } + }, + "package": 0 + }, + { + "name": "i", + "types": {}, + "functions": { + "x": { + "name": "x", + "kind": "freestanding", + "params": [] + } + }, + "package": 0, + "clone_of": 0 + } + ], + "types": [], + "packages": [ + { + "name": "a:b", + "docs": { + "contents": "RUN: component wit --generate-nominal-type-ids foo % --json" + }, + "interfaces": { + "i": 0 + }, + "worlds": { + "foo": 0 + } + } + ] +} diff --git a/tests/cli/nominal6.wit b/tests/cli/nominal6.wit new file mode 100644 index 0000000000..26492a108b --- /dev/null +++ b/tests/cli/nominal6.wit @@ -0,0 +1,12 @@ +// RUN: component wit --generate-nominal-type-ids foo % --json + +package a:b; + +interface i { + x: func(); +} + +world foo { + import a: i; + export i; +} diff --git a/tests/cli/nominal6.wit.stdout b/tests/cli/nominal6.wit.stdout new file mode 100644 index 0000000000..82c375a627 --- /dev/null +++ b/tests/cli/nominal6.wit.stdout @@ -0,0 +1,64 @@ +{ + "worlds": [ + { + "name": "foo", + "imports": { + "a": { + "interface": { + "id": 0 + } + } + }, + "exports": { + "interface-1": { + "interface": { + "id": 1 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "i", + "types": {}, + "functions": { + "x": { + "name": "x", + "kind": "freestanding", + "params": [] + } + }, + "package": 0 + }, + { + "name": "i", + "types": {}, + "functions": { + "x": { + "name": "x", + "kind": "freestanding", + "params": [] + } + }, + "package": 0, + "clone_of": 0 + } + ], + "types": [], + "packages": [ + { + "name": "a:b", + "docs": { + "contents": "RUN: component wit --generate-nominal-type-ids foo % --json" + }, + "interfaces": { + "i": 0 + }, + "worlds": { + "foo": 0 + } + } + ] +} diff --git a/tests/cli/nominal7.wit b/tests/cli/nominal7.wit new file mode 100644 index 0000000000..76ac6a6786 --- /dev/null +++ b/tests/cli/nominal7.wit @@ -0,0 +1,12 @@ +// RUN: component wit --generate-nominal-type-ids foo % --json + +package a:b; + +interface i { + x: func(); +} + +world foo { + import i; + export a: i; +} diff --git a/tests/cli/nominal7.wit.stdout b/tests/cli/nominal7.wit.stdout new file mode 100644 index 0000000000..6ab64be37f --- /dev/null +++ b/tests/cli/nominal7.wit.stdout @@ -0,0 +1,64 @@ +{ + "worlds": [ + { + "name": "foo", + "imports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "exports": { + "a": { + "interface": { + "id": 1 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "i", + "types": {}, + "functions": { + "x": { + "name": "x", + "kind": "freestanding", + "params": [] + } + }, + "package": 0 + }, + { + "name": "i", + "types": {}, + "functions": { + "x": { + "name": "x", + "kind": "freestanding", + "params": [] + } + }, + "package": 0, + "clone_of": 0 + } + ], + "types": [], + "packages": [ + { + "name": "a:b", + "docs": { + "contents": "RUN: component wit --generate-nominal-type-ids foo % --json" + }, + "interfaces": { + "i": 0 + }, + "worlds": { + "foo": 0 + } + } + ] +} diff --git a/tests/cli/nominal8.wit b/tests/cli/nominal8.wit new file mode 100644 index 0000000000..165e90781b --- /dev/null +++ b/tests/cli/nominal8.wit @@ -0,0 +1,12 @@ +// RUN: component wit --generate-nominal-type-ids foo % --json + +package a:b; + +interface i { + x: func(); +} + +world foo { + import a: i; + export a: i; +} diff --git a/tests/cli/nominal8.wit.stdout b/tests/cli/nominal8.wit.stdout new file mode 100644 index 0000000000..e20f038f9d --- /dev/null +++ b/tests/cli/nominal8.wit.stdout @@ -0,0 +1,64 @@ +{ + "worlds": [ + { + "name": "foo", + "imports": { + "a": { + "interface": { + "id": 0 + } + } + }, + "exports": { + "a": { + "interface": { + "id": 1 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "i", + "types": {}, + "functions": { + "x": { + "name": "x", + "kind": "freestanding", + "params": [] + } + }, + "package": 0 + }, + { + "name": "i", + "types": {}, + "functions": { + "x": { + "name": "x", + "kind": "freestanding", + "params": [] + } + }, + "package": 0, + "clone_of": 0 + } + ], + "types": [], + "packages": [ + { + "name": "a:b", + "docs": { + "contents": "RUN: component wit --generate-nominal-type-ids foo % --json" + }, + "interfaces": { + "i": 0 + }, + "worlds": { + "foo": 0 + } + } + ] +} diff --git a/tests/cli/nominal9.wit b/tests/cli/nominal9.wit new file mode 100644 index 0000000000..47bf3e131b --- /dev/null +++ b/tests/cli/nominal9.wit @@ -0,0 +1,12 @@ +// RUN: component wit --generate-nominal-type-ids foo % --json + +package a:b; + +interface i { + x: func(); +} + +world foo { + export a: i; + export b: i; +} diff --git a/tests/cli/nominal9.wit.stdout b/tests/cli/nominal9.wit.stdout new file mode 100644 index 0000000000..2a84af6a9d --- /dev/null +++ b/tests/cli/nominal9.wit.stdout @@ -0,0 +1,63 @@ +{ + "worlds": [ + { + "name": "foo", + "imports": {}, + "exports": { + "a": { + "interface": { + "id": 0 + } + }, + "b": { + "interface": { + "id": 1 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "i", + "types": {}, + "functions": { + "x": { + "name": "x", + "kind": "freestanding", + "params": [] + } + }, + "package": 0 + }, + { + "name": "i", + "types": {}, + "functions": { + "x": { + "name": "x", + "kind": "freestanding", + "params": [] + } + }, + "package": 0, + "clone_of": 0 + } + ], + "types": [], + "packages": [ + { + "name": "a:b", + "docs": { + "contents": "RUN: component wit --generate-nominal-type-ids foo % --json" + }, + "interfaces": { + "i": 0 + }, + "worlds": { + "foo": 0 + } + } + ] +} diff --git a/tests/cli/validate-unknown-features.wat.stderr b/tests/cli/validate-unknown-features.wat.stderr index 8be24bf0b1..169c535ae0 100644 --- a/tests/cli/validate-unknown-features.wat.stderr +++ b/tests/cli/validate-unknown-features.wat.stderr @@ -1,4 +1,4 @@ error: invalid value 'unknown' for '--features ': unknown feature `unknown` -Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, legacy-exceptions, gc-types, stack-switching, wide-arithmetic, cm-values, cm-nested-names, cm-async, cm-async-stackful, cm-more-async-builtins, cm-threading, cm-error-context, cm-fixed-length-lists, cm-gc, call-indirect-overlong, bulk-memory-opt, custom-descriptors, compact-imports, cm-map, cm64, mvp, wasm1, wasm2, wasm3, lime1, all +Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, legacy-exceptions, gc-types, stack-switching, wide-arithmetic, cm-values, cm-nested-names, cm-async, cm-async-stackful, cm-more-async-builtins, cm-threading, cm-error-context, cm-fixed-length-lists, cm-gc, call-indirect-overlong, bulk-memory-opt, custom-descriptors, compact-imports, cm-map, cm64, cm-implements, mvp, wasm1, wasm2, wasm3, lime1, all For more information, try '--help'. diff --git a/tests/cli/world-export-elaborated-import-stability.wit b/tests/cli/world-export-elaborated-import-stability.wit new file mode 100644 index 0000000000..452921cc80 --- /dev/null +++ b/tests/cli/world-export-elaborated-import-stability.wit @@ -0,0 +1,22 @@ +// RUN[round-trip]: component wit % --wasm | component wit + +package a:b@9.0.0; + +interface name { + enum xxx { + a, + } +} + +interface x { + use name.{xxx}; +} + +world w { + @since(version = 2.0.0) + @deprecated(version = 5.0.0) + import name; + import x; + + export y: x; +} diff --git a/tests/cli/world-export-elaborated-import-stability.wit.round-trip.stdout b/tests/cli/world-export-elaborated-import-stability.wit.round-trip.stdout new file mode 100644 index 0000000000..01054e5b3c --- /dev/null +++ b/tests/cli/world-export-elaborated-import-stability.wit.round-trip.stdout @@ -0,0 +1,21 @@ +/// RUN[round-trip]: component wit % --wasm | component wit +package a:b@9.0.0; + +interface name { + enum xxx { + a, + } +} + +interface x { + use name.{xxx}; +} + +world w { + @since(version = 2.0.0) + @deprecated(version = 5.0.0) + import name; + import x; + + export y: x; +} diff --git a/tests/snapshots/cli/component-model/implements.wast.json b/tests/snapshots/cli/component-model/implements.wast.json new file mode 100644 index 0000000000..4fb762c050 --- /dev/null +++ b/tests/snapshots/cli/component-model/implements.wast.json @@ -0,0 +1,95 @@ +{ + "source_filename": "tests/cli/component-model/implements.wast", + "commands": [ + { + "type": "module", + "line": 3, + "filename": "implements.0.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 37, + "filename": "implements.1.wasm", + "module_type": "binary", + "text": "must be an interface" + }, + { + "type": "assert_invalid", + "line": 40, + "filename": "implements.2.wasm", + "module_type": "binary", + "text": "not a valid name" + }, + { + "type": "assert_invalid", + "line": 44, + "filename": "implements.3.wasm", + "module_type": "binary", + "text": "conflicts with previous name" + }, + { + "type": "assert_invalid", + "line": 51, + "filename": "implements.4.wasm", + "module_type": "binary", + "text": "conflicts with previous name" + }, + { + "type": "assert_invalid", + "line": 58, + "filename": "implements.5.wasm", + "module_type": "binary", + "text": "conflicts with previous name" + }, + { + "type": "assert_invalid", + "line": 65, + "filename": "implements.6.wasm", + "module_type": "binary", + "text": "conflicts with previous name" + }, + { + "type": "assert_invalid", + "line": 72, + "filename": "implements.7.wasm", + "module_type": "binary", + "text": "only instance names can have an `implements`" + }, + { + "type": "assert_invalid", + "line": 78, + "filename": "implements.8.wasm", + "module_type": "binary", + "text": "name `a1:b/c` is not valid with `implements`" + }, + { + "type": "assert_invalid", + "line": 86, + "filename": "implements.9.wasm", + "module_type": "binary", + "text": "must be an interface" + }, + { + "type": "assert_invalid", + "line": 89, + "filename": "implements.10.wasm", + "module_type": "binary", + "text": "not a valid name" + }, + { + "type": "assert_invalid", + "line": 92, + "filename": "implements.11.wasm", + "module_type": "binary", + "text": "only instance names" + }, + { + "type": "assert_invalid", + "line": 95, + "filename": "implements.12.wasm", + "module_type": "binary", + "text": "must be an interface" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/cli/component-model/implements.wast/0.print b/tests/snapshots/cli/component-model/implements.wast/0.print new file mode 100644 index 0000000000..31a6e7dbf2 --- /dev/null +++ b/tests/snapshots/cli/component-model/implements.wast/0.print @@ -0,0 +1,59 @@ +(component + (component (;0;) + (type (;0;) + (instance) + ) + (import "a" (implements "a:b/c") (instance (;0;) (type 0))) + (type (;1;) + (instance) + ) + (import "b" (implements "a:b/c") (instance (;1;) (type 1))) + (type (;2;) + (instance) + ) + (import "c" (implements "a:b/c@1.0.0") (instance (;2;) (type 2))) + (type (;3;) + (instance) + ) + (import "my-label" (implements "ns:pkg/iface") (instance (;3;) (type 3))) + (type (;4;) + (instance) + ) + (import "a:b/c" (instance (;4;) (type 4))) + (type (;5;) + (instance) + ) + (import "a:b/c@1.0.0" (instance (;5;) (type 5))) + (instance $a (;6;)) + (export (;7;) "a" (implements "a:b/c") (instance $a)) + (export (;8;) "b" (implements "a:b/c") (instance $a)) + (export (;9;) "c" (implements "a:b/c@1.0.0") (instance $a)) + (export (;10;) "my-label" (implements "ns:pkg/iface") (instance $a)) + (export (;11;) "a:b/c" (instance $a)) + (export (;12;) "a:b/c@1.0.0" (instance $a)) + ) + (type (;0;) + (instance + (type (;0;) + (instance) + ) + (export (;0;) "a" (implements "a:b/c") (instance (type 0))) + ) + ) + (type (;1;) + (component + (type (;0;) + (instance) + ) + (import "a" (implements "a:b/c") (instance (;0;) (type 0))) + (type (;1;) + (instance) + ) + (export (;1;) "a" (implements "a:b/c") (instance (type 1))) + ) + ) + (instance $a (;0;)) + (instance (;1;) + (export "a" (implements "a:b/c") (instance $a)) + ) +) diff --git a/tests/snapshots/cli/missing-features/component-model/implements.wast.json b/tests/snapshots/cli/missing-features/component-model/implements.wast.json new file mode 100644 index 0000000000..9ec6d00b3e --- /dev/null +++ b/tests/snapshots/cli/missing-features/component-model/implements.wast.json @@ -0,0 +1,19 @@ +{ + "source_filename": "tests/cli/missing-features/component-model/implements.wast", + "commands": [ + { + "type": "assert_malformed", + "line": 5, + "filename": "implements.0.wasm", + "module_type": "binary", + "text": "the `cm-implements` feature is not active" + }, + { + "type": "assert_malformed", + "line": 12, + "filename": "implements.1.wasm", + "module_type": "binary", + "text": "the `cm-implements` feature is not active" + } + ] +} \ No newline at end of file