diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 95d4508a7d..36dfbc9d85 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -87,6 +87,7 @@ jobs: - run: cargo check --no-default-features --features strip - run: cargo check --no-default-features --features compose - run: cargo check --no-default-features --features demangle + - run: cargo check --no-default-features --features component doc: runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index ec771f9ccd..dcc797b182 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ 'crates/fuzz-stats', 'crates/wasm-mutate-stats', 'fuzz', - 'crates/wit-component/fuzz', + 'crates/wit-parser/fuzz', ] [workspace.package] @@ -39,6 +39,7 @@ rand = { version = "0.8.4", features = ["small_rng"] } rayon = "1.3" serde = { version = "1.0.137", features = ["derive"] } wasmtime = { version = "3.0.0", default-features = false, features = ['cranelift'] } +url = "2.3.1" wasm-encoder = { version = "0.20.0", path = "crates/wasm-encoder"} wasm-compose = { version = "0.2.1", path = "crates/wasm-compose"} @@ -97,8 +98,9 @@ rustc-demangle = { version = "0.1.21", optional = true } cpp_demangle = { version = "0.4.0", optional = true } # Dependencies of `component` -wit-component = { workspace = true, optional = true } +wit-component = { workspace = true, optional = true, features = ['dummy-module'] } wit-parser = { workspace = true, optional = true } +wast = { workspace = true, optional = true } [dev-dependencies] serde_json = "1.0" @@ -144,4 +146,4 @@ objdump = ['wasmparser'] strip = ['wasm-encoder', 'wasmparser', 'regex'] compose = ['wasm-compose'] demangle = ['rustc-demangle', 'cpp_demangle', 'wasmparser', 'wasm-encoder'] -component = ['wit-component', 'wit-parser'] +component = ['wit-component', 'wit-parser', 'wast', 'wasm-encoder', 'wasmparser'] diff --git a/crates/wasm-compose/src/encoding.rs b/crates/wasm-compose/src/encoding.rs index 5313e40a6c..2b2cbe185a 100644 --- a/crates/wasm-compose/src/encoding.rs +++ b/crates/wasm-compose/src/encoding.rs @@ -21,7 +21,7 @@ fn type_ref_to_export_kind(ty: wasmparser::ComponentTypeRef) -> ComponentExportK wasmparser::ComponentTypeRef::Module(_) => ComponentExportKind::Module, wasmparser::ComponentTypeRef::Func(_) => ComponentExportKind::Func, wasmparser::ComponentTypeRef::Value(_) => ComponentExportKind::Value, - wasmparser::ComponentTypeRef::Type(_, _) => ComponentExportKind::Type, + wasmparser::ComponentTypeRef::Type { .. } => ComponentExportKind::Type, wasmparser::ComponentTypeRef::Instance(_) => ComponentExportKind::Instance, wasmparser::ComponentTypeRef::Component(_) => ComponentExportKind::Component, } @@ -255,8 +255,8 @@ impl<'a> TypeEncoder<'a> { wasmparser::types::ComponentEntityType::Value(ty) => { ComponentTypeRef::Value(self.component_val_type(encodable, types, ty)) } - wasmparser::types::ComponentEntityType::Type(id) => { - ComponentTypeRef::Type(TypeBounds::Eq, self.ty(encodable, types, id)) + wasmparser::types::ComponentEntityType::Type { created, .. } => { + ComponentTypeRef::Type(TypeBounds::Eq, self.ty(encodable, types, created)) } wasmparser::types::ComponentEntityType::Instance(id) => { ComponentTypeRef::Instance(self.component_instance_type(encodable, types, id)) diff --git a/crates/wasm-compose/src/graph.rs b/crates/wasm-compose/src/graph.rs index 462a70d173..bcf2c5d3b0 100644 --- a/crates/wasm-compose/src/graph.rs +++ b/crates/wasm-compose/src/graph.rs @@ -21,7 +21,7 @@ pub(crate) fn type_desc(item: ComponentEntityType) -> &'static str { ComponentEntityType::Module(_) => "module", ComponentEntityType::Func(_) => "function", ComponentEntityType::Value(_) => "value", - ComponentEntityType::Type(_) => "type", + ComponentEntityType::Type { .. } => "type", ComponentEntityType::Component(_) => "component", } } diff --git a/crates/wasmparser/Cargo.toml b/crates/wasmparser/Cargo.toml index 6be2467b76..734a3d22ac 100644 --- a/crates/wasmparser/Cargo.toml +++ b/crates/wasmparser/Cargo.toml @@ -14,7 +14,7 @@ exclude = ["benches/*.wasm"] [dependencies] indexmap = { workspace = true } -url = "2.3.1" +url = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs index 95cd5b40e4..cb4aca61f2 100644 --- a/crates/wasmparser/src/validator.rs +++ b/crates/wasmparser/src/validator.rs @@ -1169,9 +1169,9 @@ impl Validator { current.exports.reserve(count as usize); Ok(()) }, - |components, _, _, export, offset| { + |components, types, _, export, offset| { let current = components.last_mut().unwrap(); - let ty = current.export_to_entity_type(&export, offset)?; + let ty = current.export_to_entity_type(&export, types, offset)?; current.add_export( export.name, export.url, diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index f3fcd23fe6..79087b28ec 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -211,7 +211,7 @@ impl ComponentState { pub fn add_import( &mut self, import: crate::ComponentImport, - types: &TypeList, + types: &mut TypeAlloc, offset: usize, ) -> Result<()> { let entity = self.check_type_ref(&import.ty, types, offset)?; @@ -270,8 +270,8 @@ impl ComponentState { self.values.push((ty, value_used)); (self.values.len(), MAX_WASM_VALUES, "values") } - ComponentEntityType::Type(id) => { - self.types.push(id); + ComponentEntityType::Type { created, .. } => { + self.types.push(created); (self.types.len(), MAX_WASM_TYPES, "types") } }; @@ -654,10 +654,10 @@ impl ComponentState { Ok(()) } - pub fn check_type_ref( + fn check_type_ref( &self, ty: &ComponentTypeRef, - types: &TypeList, + types: &mut TypeAlloc, offset: usize, ) -> Result { Ok(match ty { @@ -685,7 +685,12 @@ impl ComponentState { ComponentEntityType::Value(ty) } ComponentTypeRef::Type(TypeBounds::Eq, index) => { - ComponentEntityType::Type(self.type_at(*index, false, offset)?) + let referenced = self.type_at(*index, false, offset)?; + let created = types.with_unique(referenced); + ComponentEntityType::Type { + referenced, + created, + } } ComponentTypeRef::Instance(index) => { let id = self.type_at(*index, false, offset)?; @@ -707,6 +712,7 @@ impl ComponentState { pub fn export_to_entity_type( &mut self, export: &crate::ComponentExport, + types: &mut TypeAlloc, offset: usize, ) -> Result { Ok(match export.kind { @@ -720,7 +726,12 @@ impl ComponentState { ComponentEntityType::Value(*self.value_at(export.index, offset)?) } ComponentExternalKind::Type => { - ComponentEntityType::Type(self.type_at(export.index, false, offset)?) + let referenced = self.type_at(export.index, false, offset)?; + let created = types.with_unique(referenced); + ComponentEntityType::Type { + referenced, + created, + } } ComponentExternalKind::Instance => { ComponentEntityType::Instance(self.instance_at(export.index, offset)?) @@ -1242,9 +1253,18 @@ impl ComponentState { )?; } ComponentExternalKind::Type => { + let ty = self.type_at(export.index, false, offset)?; insert_export( export.name, - ComponentEntityType::Type(self.type_at(export.index, false, offset)?), + ComponentEntityType::Type { + referenced: ty, + // The created type index here isn't used anywhere + // in index spaces because a "bag of exports" + // doesn't build up its own index spaces. Just fill + // in the same index here in this case as what's + // referenced. + created: ty, + }, &mut inst_exports, &mut type_size, offset, @@ -1493,8 +1513,8 @@ impl ComponentState { ComponentExternalKind::Type => { check_max(self.type_count(), 1, MAX_WASM_TYPES, "types", offset)?; match self.instance_export(instance_index, name, types, offset)? { - ComponentEntityType::Type(ty) => { - let id = types.with_unique(*ty); + ComponentEntityType::Type { referenced, .. } => { + let id = types.with_unique(*referenced); self.types.push(id); Ok(()) } diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs index 1cb6179625..945cb758c8 100644 --- a/crates/wasmparser/src/validator/types.rs +++ b/crates/wasmparser/src/validator/types.rs @@ -2,9 +2,8 @@ use super::{component::ComponentState, core::Module}; use crate::{ - ComponentExport, ComponentExternalKind, ComponentImport, ComponentTypeRef, Export, - ExternalKind, FuncType, GlobalType, Import, MemoryType, PrimitiveValType, TableType, TypeRef, - ValType, + ComponentExport, ComponentImport, Export, ExternalKind, FuncType, GlobalType, Import, + MemoryType, PrimitiveValType, TableType, TypeRef, ValType, }; use indexmap::{IndexMap, IndexSet}; use std::{ @@ -218,6 +217,12 @@ impl fmt::Display for KebabString { } } +impl From for String { + fn from(s: KebabString) -> String { + s.0 + } +} + /// A simple alloc-free list of types used for calculating lowered function signatures. pub(crate) struct LoweredTypes { types: [ValType; MAX_LOWERED_TYPES], @@ -734,7 +739,18 @@ pub enum ComponentEntityType { /// The entity is a value. Value(ComponentValType), /// The entity is a type. - Type(TypeId), + Type { + /// This is the identifier of the type that was referenced when this + /// entity was created. + referenced: TypeId, + /// This is the identifier of the type that was created when this type + /// was imported or exported from the component. + /// + /// Note that the underlying type information for the `referenced` + /// field and for this `created` field is the same, but these two types + /// will hash to different values. + created: TypeId, + }, /// The entity is a component instance. Instance(TypeId), /// The entity is a component. @@ -764,12 +780,14 @@ impl ComponentEntityType { (Self::Value(a), Self::Value(b)) => { ComponentValType::internal_is_subtype_of(a, at, b, bt) } - (Self::Type(a), Self::Type(b)) => ComponentDefinedType::internal_is_subtype_of( - at[*a].as_defined_type().unwrap(), - at, - bt[*b].as_defined_type().unwrap(), - bt, - ), + (Self::Type { referenced: a, .. }, Self::Type { referenced: b, .. }) => { + ComponentDefinedType::internal_is_subtype_of( + at[*a].as_defined_type().unwrap(), + at, + bt[*b].as_defined_type().unwrap(), + bt, + ) + } (Self::Instance(a), Self::Instance(b)) => { ComponentInstanceType::internal_is_subtype_of( at[*a].as_component_instance_type().unwrap(), @@ -793,7 +811,7 @@ impl ComponentEntityType { Self::Module(_) => "module", Self::Func(_) => "function", Self::Value(_) => "value", - Self::Type(_) => "type", + Self::Type { .. } => "type", Self::Instance(_) => "instance", Self::Component(_) => "component", } @@ -803,7 +821,7 @@ impl ComponentEntityType { match self { Self::Module(ty) | Self::Func(ty) - | Self::Type(ty) + | Self::Type { referenced: ty, .. } | Self::Instance(ty) | Self::Component(ty) => ty.type_size, Self::Value(ty) => ty.type_size(), @@ -1632,29 +1650,10 @@ impl<'a> TypesRef<'a> { ) -> Option { match &self.kind { TypesRefKind::Module(_) => None, - TypesRefKind::Component(component) => Some(match import.ty { - ComponentTypeRef::Module(idx) => { - ComponentEntityType::Module(*component.core_types.get(idx as usize)?) - } - ComponentTypeRef::Func(idx) => { - ComponentEntityType::Func(*component.types.get(idx as usize)?) - } - ComponentTypeRef::Value(ty) => ComponentEntityType::Value(match ty { - crate::ComponentValType::Primitive(ty) => ComponentValType::Primitive(ty), - crate::ComponentValType::Type(idx) => { - ComponentValType::Type(*component.types.get(idx as usize)?) - } - }), - ComponentTypeRef::Type(_, idx) => { - ComponentEntityType::Type(*component.types.get(idx as usize)?) - } - ComponentTypeRef::Instance(idx) => { - ComponentEntityType::Instance(*component.types.get(idx as usize)?) - } - ComponentTypeRef::Component(idx) => { - ComponentEntityType::Component(*component.types.get(idx as usize)?) - } - }), + TypesRefKind::Component(component) => { + let key = KebabStr::new(import.name)?; + Some(component.imports.get(key)?.1) + } } } @@ -1665,29 +1664,10 @@ impl<'a> TypesRef<'a> { ) -> Option { match &self.kind { TypesRefKind::Module(_) => None, - TypesRefKind::Component(component) => Some(match export.kind { - ComponentExternalKind::Module => { - ComponentEntityType::Module(*component.core_modules.get(export.index as usize)?) - } - ComponentExternalKind::Func => { - ComponentEntityType::Func(*component.funcs.get(export.index as usize)?) - } - ComponentExternalKind::Value => ComponentEntityType::Value( - component - .values - .get(export.index as usize) - .map(|(r, _)| *r)?, - ), - ComponentExternalKind::Type => { - ComponentEntityType::Type(*component.types.get(export.index as usize)?) - } - ComponentExternalKind::Instance => { - ComponentEntityType::Instance(*component.instances.get(export.index as usize)?) - } - ComponentExternalKind::Component => ComponentEntityType::Component( - *component.components.get(export.index as usize)?, - ), - }), + TypesRefKind::Component(component) => { + let key = KebabStr::new(export.name)?; + Some(component.exports.get(key)?.1) + } } } } diff --git a/crates/wit-component/Cargo.toml b/crates/wit-component/Cargo.toml index eae40abf81..6e8994bf09 100644 --- a/crates/wit-component/Cargo.toml +++ b/crates/wit-component/Cargo.toml @@ -20,10 +20,15 @@ anyhow = { workspace = true } log = "0.4.17" bitflags = "1.3.2" indexmap = { workspace = true } -wat = { workspace = true } +url = { workspace = true } +wat = { workspace = true, optional = true } [dev-dependencies] wasmprinter = { workspace = true } glob = "0.3.0" pretty_assertions = "1.3.0" env_logger = { workspace = true } +wat = { workspace = true } + +[features] +dummy-module = ['dep:wat'] diff --git a/crates/wit-component/fuzz/fuzz_targets/roundtrip-wit.rs b/crates/wit-component/fuzz/fuzz_targets/roundtrip-wit.rs deleted file mode 100644 index 46619c910d..0000000000 --- a/crates/wit-component/fuzz/fuzz_targets/roundtrip-wit.rs +++ /dev/null @@ -1,502 +0,0 @@ -#![no_main] - -use libfuzzer_sys::fuzz_target; -use std::path::Path; -use wit_component::*; - -fuzz_target!(|data: &[u8]| { - drop(env_logger::try_init()); - - let mut u = arbitrary::Unstructured::new(data); - let doc = match generate::document(&mut u) { - Ok(doc) => doc, - Err(_) => return, - }; - let text = DocumentPrinter::default().print(&doc).unwrap(); - write_file("doc.wit", &text); - let world = match doc.default_world() { - Ok(world) => world, - Err(_) => return, - }; - - let types_only_binary = ComponentEncoder::default() - .validate(true) - .types_only(true) - .document(doc.clone(), StringEncoding::UTF8) - .unwrap() - .encode() - .unwrap(); - write_file("doc.types-only.wasm", &types_only_binary); - let (types_only_doc, _) = decode_world(&doc.worlds[world].name, &types_only_binary).unwrap(); - let text_from_types_only = DocumentPrinter::default().print(&types_only_doc).unwrap(); - write_file("doc.types-only.wit", &text_from_types_only); - - let dummy = dummy::dummy_module(&doc); - write_file("doc.dummy.wasm", &dummy); - let normal_binary = ComponentEncoder::default() - .validate(true) - .module(&dummy) - .unwrap() - .document(doc.clone(), StringEncoding::UTF8) - .unwrap() - .encode() - .unwrap(); - write_file("doc.normal.wasm", &normal_binary); - let (normal_doc, _) = decode_world(&doc.worlds[world].name, &normal_binary).unwrap(); - let text_from_normal = DocumentPrinter::default().print(&normal_doc).unwrap(); - write_file("doc.normal.wit", &text_from_normal); - - if text_from_normal != text_from_types_only { - panic!("text not equal"); - } -}); - -fn write_file(path: &str, contents: impl AsRef<[u8]>) { - if !log::log_enabled!(log::Level::Debug) { - return; - } - log::debug!("writing file {path}"); - let contents = contents.as_ref(); - let path = Path::new(path); - std::fs::write(path, contents).unwrap(); - if path.extension().and_then(|s| s.to_str()) == Some("wasm") { - let path = path.with_extension("wat"); - log::debug!("writing file {}", path.display()); - std::fs::write(path, wasmprinter::print_bytes(&contents).unwrap()).unwrap(); - } -} - -mod generate { - use arbitrary::{Arbitrary, Result, Unstructured}; - use std::collections::HashSet; - use std::str; - use wit_parser::*; - - #[derive(Default)] - struct Generator { - doc: Document, - type_sizes: Vec, - unique_names: HashSet, - types_in_interface: Vec, - named_types: Vec, - } - - pub fn document(u: &mut Unstructured<'_>) -> Result { - let mut gen = Generator::default(); - gen.gen(u)?; - Ok(gen.doc) - } - - impl Generator { - fn gen(&mut self, u: &mut Unstructured<'_>) -> Result<()> { - #[derive(Arbitrary)] - enum Generate { - World, - Interface, - Done, - } - - while !u.is_empty() { - match u.arbitrary()? { - Generate::World => { - self.gen_world(u)?; - } - Generate::Interface => { - self.gen_interface(u)?; - } - Generate::Done => break, - } - } - Ok(()) - } - - fn gen_world(&mut self, u: &mut Unstructured<'_>) -> Result { - let mut world = World::default(); - world.name = self.gen_name(u)?; - - let mut interfaces = self - .doc - .interfaces - .iter() - .map(|(id, _)| id) - .collect::>(); - - #[derive(Arbitrary)] - enum Generate { - Import, - // TODO: this is buggy right now due to use-through-export not - // being implemented. Should fix and re-enable this. - // Export, - } - - let mut imports = Vec::new(); - let mut exports = Vec::new(); - while interfaces.len() > 0 && u.arbitrary()? { - let dst = match u.arbitrary()? { - Generate::Import => &mut imports, - // Generate::Export => &mut exports, - }; - if dst.len() > 10 { - continue; - } - let i = u.int_in_range(0..=interfaces.len() - 1)?; - let interface = interfaces.swap_remove(i); - dst.push(interface); - } - if interfaces.len() > 0 && u.arbitrary()? { - world.default = Some(*u.choose(&interfaces)?); - } - - // TODO: most of this probably doesn't make sense. One hope is that - // this all goes away and can be replaced with the text-based - // resolution pass by generating a text file instead of an AST - // directly. Unsure how to best clean this up. - let mut visited = HashSet::new(); - let mut import_order = Vec::new(); - for id in imports { - self.topo_visit(id, &mut visited, &mut import_order); - } - for id in exports.iter() { - self.topo_visit(*id, &mut visited, &mut import_order); - import_order.pop(); - } - if let Some(default) = world.default { - self.topo_visit(default, &mut visited, &mut import_order); - import_order.pop(); - } - - for i in import_order { - world.imports.insert(self.gen_unique_name(u)?, i); - } - for e in exports { - world.exports.insert(self.gen_unique_name(u)?, e); - } - - self.unique_names.clear(); - return Ok(self.doc.worlds.alloc(world)); - } - - fn topo_visit( - &self, - id: InterfaceId, - visited: &mut HashSet, - order: &mut Vec, - ) { - if !visited.insert(id) { - return; - } - let interface = &self.doc.interfaces[id]; - for ty in interface.types.iter() { - let other_id = match self.doc.types[*ty].kind { - TypeDefKind::Type(Type::Id(id)) => id, - _ => continue, - }; - let other_interface = match self.doc.types[other_id].interface { - Some(i) => i, - None => continue, - }; - if other_interface == id { - continue; - } - self.topo_visit(other_interface, visited, order); - } - order.push(id); - } - - fn gen_interface(&mut self, u: &mut Unstructured<'_>) -> Result { - let mut iface = Interface::default(); - iface.name = self.gen_name(u)?; - - #[derive(Arbitrary)] - enum Generate { - Type, - Function, - } - - while u.arbitrary()? { - match u.arbitrary()? { - Generate::Type => { - let (size, typedef) = self.gen_typedef(u)?; - let id = self.doc.types.alloc(typedef); - self.type_sizes.push(size); - if self.doc.types[id].name.is_some() { - iface.types.push(id); - self.named_types.push(id); - } - self.types_in_interface.push(id); - } - Generate::Function => { - let f = self.gen_func(u)?; - iface.functions.push(f); - } - } - } - - self.types_in_interface.clear(); - Ok(self.doc.interfaces.alloc(iface)) - } - - fn gen_typedef(&mut self, u: &mut Unstructured<'_>) -> Result<(usize, TypeDef)> { - const MAX_PARTS: usize = 5; - - #[derive(Arbitrary)] - pub enum Kind { - Record, - Tuple, - Flags, - Variant, - Enum, - Option, - Result, - Union, - List, - Type, - } - - let mut size = 1; - let kind = match u.arbitrary()? { - Kind::Record => TypeDefKind::Record(Record { - fields: (0..u.int_in_range(0..=MAX_PARTS)?) - .map(|_| { - Ok(Field { - docs: Docs::default(), - name: self.gen_unique_name(u)?, - ty: self.gen_type(u, &mut size)?, - }) - }) - .collect::>()?, - }), - Kind::Variant => TypeDefKind::Variant(Variant { - cases: (0..u.int_in_range(1..=MAX_PARTS)?) - .map(|_| { - Ok(Case { - docs: Docs::default(), - name: self.gen_unique_name(u)?, - ty: if u.arbitrary()? { - Some(self.gen_type(u, &mut size)?) - } else { - None - }, - }) - }) - .collect::>()?, - }), - Kind::Tuple => TypeDefKind::Tuple(Tuple { - types: (0..u.int_in_range(0..=MAX_PARTS)?) - .map(|_| self.gen_type(u, &mut size)) - .collect::>()?, - }), - Kind::Union => TypeDefKind::Union(Union { - cases: (0..u.int_in_range(1..=MAX_PARTS)?) - .map(|_| { - Ok(UnionCase { - docs: Docs::default(), - ty: self.gen_type(u, &mut size)?, - }) - }) - .collect::>()?, - }), - Kind::List => TypeDefKind::List(self.gen_type(u, &mut size)?), - Kind::Type => TypeDefKind::Type(self.gen_type_allow_use(u, true, &mut size)?), - Kind::Option => TypeDefKind::Option(self.gen_type(u, &mut size)?), - Kind::Result => TypeDefKind::Result(Result_ { - ok: if u.arbitrary()? { - Some(self.gen_type(u, &mut size)?) - } else { - None - }, - err: if u.arbitrary()? { - Some(self.gen_type(u, &mut size)?) - } else { - None - }, - }), - Kind::Flags => TypeDefKind::Flags(Flags { - flags: (0..u.int_in_range(0..=MAX_PARTS)?) - .map(|_| { - Ok(Flag { - name: self.gen_unique_name(u)?, - docs: Docs::default(), - }) - }) - .collect::>()?, - }), - Kind::Enum => TypeDefKind::Enum(Enum { - cases: (0..u.int_in_range(1..=MAX_PARTS)?) - .map(|_| { - Ok(EnumCase { - name: self.gen_unique_name(u)?, - docs: Docs::default(), - }) - }) - .collect::>()?, - }), - }; - - // Determine if this kind is allowed to be anonymous or not, and if - // it can be optionally annotate it with an interface and a name. - // Note that non-anonymous types must have an interface and a name. - let can_be_anonymous = match kind { - TypeDefKind::Type(_) - | TypeDefKind::Record(_) - | TypeDefKind::Enum(_) - | TypeDefKind::Flags(_) - | TypeDefKind::Variant(_) - | TypeDefKind::Future(_) - | TypeDefKind::Stream(_) - | TypeDefKind::Union(_) => false, - TypeDefKind::Tuple(_) - | TypeDefKind::Option(_) - | TypeDefKind::List(_) - | TypeDefKind::Result(_) => true, - }; - - // Choose a name, either because we are forced to or because the - // fuzz input says we should name this. - let name = if !can_be_anonymous || u.arbitrary()? { - Some(self.gen_unique_name(u)?) - } else { - None - }; - - // Determine if the `interface` field will be set. This is required - // for named or non-anonymous types since they're attached to an - // interface. Otherwise let the fuzz input determine that. - let interface = if name.is_some() || !can_be_anonymous || u.arbitrary()? { - Some(self.doc.interfaces.next_id()) - } else { - None - }; - - Ok(( - size, - TypeDef { - docs: Docs::default(), - kind, - name, - interface, - }, - )) - } - - fn gen_type(&mut self, u: &mut Unstructured<'_>, size: &mut usize) -> Result { - self.gen_type_allow_use(u, false, size) - } - - fn gen_type_allow_use( - &mut self, - u: &mut Unstructured<'_>, - allow_use: bool, - size: &mut usize, - ) -> Result { - const MAX_SIZE: usize = 100; - - #[derive(Arbitrary)] - enum Kind { - Bool, - U8, - U16, - U32, - U64, - S8, - S16, - S32, - S64, - Float32, - Float64, - Char, - String, - Id, - } - - *size += 1; - loop { - break match u.arbitrary()? { - Kind::Bool => Ok(Type::Bool), - Kind::U8 => Ok(Type::U8), - Kind::S8 => Ok(Type::S8), - Kind::U16 => Ok(Type::U16), - Kind::S16 => Ok(Type::S16), - Kind::U32 => Ok(Type::U32), - Kind::S32 => Ok(Type::S32), - Kind::U64 => Ok(Type::U64), - Kind::S64 => Ok(Type::S64), - Kind::Float32 => Ok(Type::Float32), - Kind::Float64 => Ok(Type::Float64), - Kind::Char => Ok(Type::Char), - Kind::String => Ok(Type::String), - Kind::Id => { - let types = if allow_use { - &self.named_types - } else { - &self.types_in_interface - }; - if types.is_empty() { - continue; - } - let id = *u.choose(&types)?; - if *size + self.type_sizes[id.index()] > MAX_SIZE { - continue; - } - *size += self.type_sizes[id.index()]; - Ok(Type::Id(id)) - } - }; - } - } - - fn gen_func(&mut self, u: &mut Unstructured<'_>) -> Result { - Ok(Function { - docs: Docs::default(), - name: self.gen_unique_name(u)?, - kind: FunctionKind::Freestanding, - params: self.gen_params(u)?, - results: if u.arbitrary()? { - Results::Anon(self.gen_type(u, &mut 1)?) - } else { - Results::Named(self.gen_params(u)?) - }, - }) - } - - fn gen_params(&mut self, u: &mut Unstructured<'_>) -> Result> { - (0..u.int_in_range(0..=5)?) - .map(|_| Ok((self.gen_unique_name(u)?, self.gen_type(u, &mut 1)?))) - .collect() - } - - fn gen_name(&self, u: &mut Unstructured<'_>) -> Result { - let size = u.arbitrary_len::()?; - let size = std::cmp::min(size, 20); - let name = match str::from_utf8(u.peek_bytes(size).unwrap()) { - Ok(s) => { - u.bytes(size).unwrap(); - s.to_string() - } - Err(e) => { - let i = e.valid_up_to(); - let valid = u.bytes(i).unwrap(); - str::from_utf8(valid).unwrap().to_string() - } - }; - let name = name - .chars() - .map(|x| if x.is_ascii_lowercase() { x } else { 'x' }) - .collect::(); - Ok(if name.is_empty() { - "name".to_string() - } else { - name - }) - } - - fn gen_unique_name(&mut self, u: &mut Unstructured<'_>) -> Result { - use std::fmt::Write; - let mut name = self.gen_name(u)?; - while !self.unique_names.insert(name.clone()) { - write!(&mut name, "{}", self.unique_names.len()).unwrap(); - } - Ok(name) - } - } -} diff --git a/crates/wit-component/src/builder.rs b/crates/wit-component/src/builder.rs index 7956f19f95..d7465a8251 100644 --- a/crates/wit-component/src/builder.rs +++ b/crates/wit-component/src/builder.rs @@ -147,6 +147,12 @@ impl ComponentBuilder { ret } + pub fn component_type(&mut self, ty: &ComponentType) -> u32 { + let ret = inc(&mut self.types); + self.types().component(ty); + ret + } + pub fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { (inc(&mut self.types), self.types().defined_type()) } @@ -163,15 +169,6 @@ impl ComponentBuilder { }); inc(&mut self.types) } - - pub fn alias_outer_type(&mut self, count: u32, index: u32) -> u32 { - self.aliases().alias(Alias::Outer { - count, - kind: ComponentOuterAliasKind::Type, - index, - }); - inc(&mut self.types) - } } // Helper macro to generate methods on `ComponentBuilder` to get specific diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index 914fb5de5d..5baefea088 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -1,10 +1,11 @@ -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use indexmap::IndexMap; +use std::collections::HashMap; use std::hash::{Hash, Hasher}; +use url::Url; use wasmparser::{ - types::{self, KebabString}, - ComponentExport, ComponentImport, ComponentTypeRef, Parser, Payload, PrimitiveValType, - ValidPayload, Validator, WasmFeatures, + types, ComponentExport, ComponentExternalKind, ComponentImport, ComponentTypeRef, Parser, + Payload, PrimitiveValType, ValidPayload, Validator, WasmFeatures, }; use wit_parser::*; @@ -69,117 +70,221 @@ impl<'a> ComponentInfo<'a> { exports, }) } -} -/// Decode the world described by the given component bytes. -/// -/// This function takes a binary component as input and will infer the -/// `World` representation of its imports and exports. The binary component at -/// this time is either a "types only" component produced by `wit-component` or -/// an actual output of `wit-component`. -/// -/// The returned world represents the description of imports and exports -/// from the component. -/// -/// This can fail if the input component is invalid or otherwise isn't of the -/// expected shape. At this time not all component shapes are supported here. -pub fn decode_world(name: &str, bytes: &[u8]) -> Result<(Document, WorldId)> { - let info = ComponentInfo::new(bytes)?; - let mut imports = IndexMap::new(); - let mut exports = IndexMap::new(); - let mut ret = Document::default(); - let mut decoder = InterfaceDecoder::new(&info, &mut ret); - - for (name, import) in info.imports.iter() { - // Imports right now are only supported if they're an import of an - // instance. The instance is expected to export only functions and types - // where types are named types used in functions. - let ty = match import.ty { - ComponentTypeRef::Instance(i) => match info.types.type_at(i, false).unwrap() { - types::Type::ComponentInstance(i) => i, - _ => unreachable!(), + fn is_wit_package(&self) -> bool { + // wit packages only export component types and must export at least one + if !self.imports.is_empty() || self.exports.is_empty() { + return false; + } + + // all wit package exports must be component types + self.exports.iter().all(|(_, export)| match export.kind { + ComponentExternalKind::Type => match self.types.type_at(export.index, false) { + Some(types::Type::Component(_)) => true, + _ => false, }, - _ => bail!("unsupported non-instance import `{name}`"), + _ => false, + }) + } + + fn decode_wit_package(&self, name: &str) -> Result<(Resolve, PackageId)> { + assert!(self.is_wit_package()); + let resolve = Resolve::default(); + let mut decoder = WitPackageDecoder { + resolve, + info: self, + url_to_package: HashMap::default(), + type_map: HashMap::new(), + type_src_map: HashMap::new(), + url_to_interface: HashMap::new(), }; - let id = decoder.decode( - Some(*name), - Some(import.url), - ty.exports(info.types.as_ref()) - .map(|(n, _, ty)| (n.as_str(), ty)), - )?; - imports.insert(name.to_string(), id); + + let mut docs = Vec::new(); + for (doc, export) in self.exports.iter() { + let ty = match self.types.type_at(export.index, false) { + Some(types::Type::Component(ty)) => ty, + _ => unreachable!(), + }; + let id = decoder + .decode_document(doc, ty) + .with_context(|| format!("failed to decode document `{doc}`"))?; + docs.push((doc, id)); + } + + let mut resolve = decoder.resolve; + let package = resolve.packages.alloc(Package { + name: name.to_string(), + documents: docs + .iter() + .map(|(name, d)| (name.to_string(), *d)) + .collect(), + url: None, + }); + for (_, doc) in docs.iter() { + resolve.documents[*doc].package = Some(package); + } + + Ok((resolve, package)) } - let mut default = IndexMap::new(); - for (name, export) in info.exports.iter() { - // Get a `ComponentEntityType` which describes the type of the item - // being exported here. If a type itself is being exported then "peel" - // it to feign an actual entity being exported here to handle both - // type-only and normal components produced by `wit-component`. - let mut ty = info - .types - .component_entity_type_from_export(export) - .unwrap(); - if let types::ComponentEntityType::Type(id) = ty { - match info.types.type_from_id(id).unwrap() { - types::Type::ComponentInstance(_) => ty = types::ComponentEntityType::Instance(id), - types::Type::ComponentFunc(_) => ty = types::ComponentEntityType::Func(id), - _ => {} - } + fn decode_component(&self, name: &str) -> Result<(Resolve, WorldId)> { + assert!(!self.is_wit_package()); + let mut resolve = Resolve::default(); + let package = resolve.packages.alloc(Package { + name: name.to_string(), + documents: Default::default(), + url: None, + }); + let doc = resolve.documents.alloc(Document { + name: "root".to_string(), + interfaces: Default::default(), + worlds: Default::default(), + default_interface: None, + default_world: None, + package: Some(package), + }); + let world = resolve.worlds.alloc(World { + name: name.to_string(), + docs: Default::default(), + imports: Default::default(), + exports: Default::default(), + document: doc, + }); + resolve.documents[doc] + .worlds + .insert(name.to_string(), world); + resolve.documents[doc].default_world = Some(world); + let mut decoder = WitPackageDecoder { + resolve, + info: self, + url_to_package: HashMap::default(), + type_map: HashMap::new(), + type_src_map: HashMap::new(), + url_to_interface: HashMap::new(), + }; + + for (name, import) in self.imports.iter() { + let item = match import.ty { + ComponentTypeRef::Instance(i) => { + let ty = match self.types.type_at(i, false) { + Some(types::Type::ComponentInstance(ty)) => ty, + _ => unreachable!(), + }; + let id = decoder + .register_interface(doc, Some(name), ty) + .with_context(|| format!("failed to decode WIT from import `{name}`"))?; + decoder.resolve.documents[doc] + .interfaces + .insert(name.to_string(), id); + WorldItem::Interface(id) + } + ComponentTypeRef::Func(i) => { + let ty = match self.types.type_at(i, false) { + Some(types::Type::ComponentFunc(ty)) => ty, + _ => unreachable!(), + }; + let func = decoder.convert_function(name, ty).with_context(|| { + format!("failed to decode function from import `{name}`") + })?; + WorldItem::Function(func) + } + _ => bail!("component import `{name}` was neither a function nor instance"), + }; + decoder.resolve.worlds[world] + .imports + .insert(name.to_string(), item); } + for (name, export) in self.exports.iter() { + let item = match export.kind { + ComponentExternalKind::Func => { + let ty = self.types.component_function_at(export.index).unwrap(); + let func = decoder.convert_function(name, ty).with_context(|| { + format!("failed to decode function from export `{name}`") + })?; + + WorldItem::Function(func) + } + ComponentExternalKind::Instance => { + let ty = self.types.component_instance_at(export.index).unwrap(); + let id = decoder + .register_interface(doc, Some(name), ty) + .with_context(|| format!("failed to decode WIT from export `{name}`"))?; + decoder.resolve.documents[doc] + .interfaces + .insert(name.to_string(), id); + WorldItem::Interface(id) + } + _ => bail!("component export `{name}` was neither a function nor instance"), + }; + decoder.resolve.worlds[world] + .exports + .insert(name.to_string(), item); + } + Ok((decoder.resolve, world)) + } +} - match ty { - // If an instance is being exported then that means this is an - // interface being exported, so decode the interface here and - // register an export. - types::ComponentEntityType::Instance(ty) => { - let ty = info - .types - .type_from_id(ty) - .unwrap() - .as_component_instance_type() - .unwrap(); - let id = decoder.decode( - Some(*name), - Some(export.url), - ty.exports(info.types.as_ref()) - .map(|(n, _, t)| (n.as_str(), t)), - )?; - exports.insert(name.to_string(), id); - } +/// Result of the [`decode`] function. +pub enum DecodedWasm { + /// The input to [`decode`] was a binary-encoded WIT package. + /// + /// The full resolve graph is here plus the identifier of the package that + /// was encoded. Note that other packages may be within the resolve if this + /// package refers to foreign packages. + WitPackage(Resolve, PackageId), + + /// The input to [`decode`] was a component and its interface is specified + /// by the world here. + Component(Resolve, WorldId), +} - // Otherwise assume everything else is part of the "default" export. - ty => { - default.insert(*name, ty); +impl DecodedWasm { + /// Returns the [`Resolve`] for WIT types contained. + pub fn resolve(&self) -> &Resolve { + match self { + DecodedWasm::WitPackage(resolve, _) => resolve, + DecodedWasm::Component(resolve, _) => resolve, + } + } + + /// Returns the main package of what was decoded. + pub fn package(&self) -> PackageId { + match self { + DecodedWasm::WitPackage(_, id) => *id, + DecodedWasm::Component(resolve, world) => { + let doc = resolve.worlds[*world].document; + resolve.documents[doc].package.unwrap() } } } +} - let default = if default.is_empty() { - None +/// Decodes an in-memory WebAssembly binary into a WIT [`Resolve`] and +/// associated metadata. +/// +/// The WebAssembly binary provided here can either be a +/// WIT-package-encoded-as-binary or an actual component itself. A [`Resolve`] +/// is always created and the return value indicates which was detected. +pub fn decode(name: &str, bytes: &[u8]) -> Result { + let info = ComponentInfo::new(bytes)?; + + if info.is_wit_package() { + let (resolve, pkg) = info.decode_wit_package(name)?; + Ok(DecodedWasm::WitPackage(resolve, pkg)) } else { - Some(decoder.decode(None, None, default.iter().map(|(n, t)| (*n, *t)))?) - }; - let world = ret.worlds.alloc(World { - name: name.to_string(), - docs: Default::default(), - imports, - exports, - default, - }); - Ok((ret, world)) + let (resolve, world) = info.decode_component(name)?; + Ok(DecodedWasm::Component(resolve, world)) + } } -/// Represents an interface decoder for WebAssembly components. -struct InterfaceDecoder<'a, 'doc> { +struct WitPackageDecoder<'a> { + resolve: Resolve, info: &'a ComponentInfo<'a>, - doc: &'doc mut Document, - - /// Names learned prior about each type id, if it was exported. - name_map: IndexMap, + url_to_package: HashMap, + url_to_interface: HashMap, /// A map from a type id to what it's been translated to. - type_map: IndexMap, + type_map: HashMap, /// A second map, similar to `type_map`, which is keyed off a pointer hash /// instead of `TypeId`. @@ -189,420 +294,654 @@ struct InterfaceDecoder<'a, 'doc> { /// structure, so the second layer of map here ensures that types are /// only defined once and the second `TypeId` referring to a type will end /// up as an alias and/or import. - type_src_map: IndexMap, Type>, + type_src_map: HashMap, TypeId>, } -impl<'a, 'doc> InterfaceDecoder<'a, 'doc> { - /// Creates a new interface decoder for the given component information. - fn new(info: &'a ComponentInfo<'a>, doc: &'doc mut Document) -> Self { - Self { - info, - doc, - name_map: IndexMap::new(), - type_map: IndexMap::new(), - type_src_map: IndexMap::new(), - } - } - - /// Consumes the decoder and returns the interface representation assuming - /// that the interface is made of the specified exports. - pub fn decode( - &mut self, - name: Option<&str>, - url: Option<&str>, - exports: impl ExactSizeIterator + Clone, - ) -> Result { - let mut interface = Interface::default(); - if let Some(name) = name { - interface.name = name.to_string(); - } - if let Some(url) = url { - interface.url = Some(url.to_string()); - } - // Populate names in the name map first - let mut types = Vec::new(); - let mut funcs = Vec::new(); - for (name, ty) in exports { - let id = match ty { - types::ComponentEntityType::Type(id) => { - types.push(id); - id - } - types::ComponentEntityType::Func(ty) => { - funcs.push((name, ty)); - continue; +impl WitPackageDecoder<'_> { + fn decode_document(&mut self, name: &str, ty: &types::ComponentType) -> Result { + // Process all imports for this document first, where imports are either + // importing interfaces from previously defined documents or from remote + // packages. Note that the URL must be specified here for these + // reconstruction purposes. + for (name, (url, ty)) in ty.imports.iter() { + let url = match url { + Some(url) => url, + None => bail!("no url specified for import `{name}`"), + }; + let ty = match ty { + types::ComponentEntityType::Instance(idx) => { + match self.info.types.type_from_id(*idx) { + Some(types::Type::ComponentInstance(ty)) => ty, + _ => unreachable!(), + } } - _ => bail!("expected function or type export"), + _ => bail!("import `{name}` is not an instance"), }; - - let prev = self.name_map.insert(id, name); - assert!(prev.is_none()); + self.register_import(url, ty) + .with_context(|| format!("failed to process import `{name}`"))?; } - // Process all types first which should show show up in topological - // order of the types as defined in the original component. This will - // ensure that type aliases are resolved correctly for now. - for id in types { - assert!(matches!( - self.info.types.type_from_id(id).unwrap(), - types::Type::Defined(_) - )); - let ty = self.decode_type(&types::ComponentValType::Type(id))?; + let doc = self.resolve.documents.alloc(Document { + name: name.to_string(), + interfaces: IndexMap::new(), + worlds: IndexMap::new(), + default_interface: None, + default_world: None, + package: None, + }); + + for (name, (url, ty)) in ty.exports.iter() { match ty { - Type::Id(id) => { - interface.types.push(id); + types::ComponentEntityType::Instance(idx) => { + let ty = match self.info.types.type_from_id(*idx) { + Some(types::Type::ComponentInstance(ty)) => ty, + _ => unreachable!(), + }; + let id = self + .register_interface(doc, Some(name), ty) + .with_context(|| format!("failed to process export `{name}`"))?; + let prev = self.resolve.documents[doc] + .interfaces + .insert(name.to_string(), id); + assert!(prev.is_none()); + if let Some(url) = url { + let prev = self.url_to_interface.insert(url.clone(), id); + assert!(prev.is_none()); + } } - _ => unreachable!(), - } - } - - // Afterwards process all functions which should mostly use the types - // previously decoded. - for (name, ty) in funcs { - match self.info.types.type_from_id(ty).unwrap() { - types::Type::ComponentFunc(ty) => { - let func = self.function(name, ty)?; - interface.functions.push(func); + types::ComponentEntityType::Component(idx) => { + let ty = match self.info.types.type_from_id(*idx) { + Some(types::Type::Component(ty)) => ty, + _ => unreachable!(), + }; + let id = self + .register_world(doc, name, ty) + .with_context(|| format!("failed to process export `{name}`"))?; + let prev = self.resolve.documents[doc] + .worlds + .insert(name.to_string(), id); + assert!(prev.is_none()); } - _ => unimplemented!(), + _ => bail!("component export `{name}` is not an instance or component"), } } + Ok(doc) + } - // Reset the `name_map` for the next interface, but notably persist the - // `type_map` which is required to get `use` of types across interfaces - // working since in the component it will be an alias to a type defined - // in a previous interface. - self.name_map.clear(); + fn register_import( + &mut self, + url: &Url, + ty: &types::ComponentInstanceType, + ) -> Result { + let interface = self.extract_url_interface(url)?; - Ok(self.doc.interfaces.alloc(interface)) - } + for (name, export_url, ty) in ty.exports(self.info.types.as_ref()) { + if export_url.is_some() { + bail!("instance type export `{name}` should not have a url") + } - fn decode_params(&mut self, ps: &[(KebabString, types::ComponentValType)]) -> Result { - ps.iter() - .map(|(n, t)| Ok((n.to_string(), self.decode_type(t)?))) - .collect::>() - } + match ty { + types::ComponentEntityType::Type { + referenced, + created, + } => { + let def = match self.info.types.type_from_id(referenced) { + Some(types::Type::Defined(ty)) => ty, + _ => unreachable!(), + }; + + let id = match self.resolve.interfaces[interface].types.get(name.as_str()) { + // If this name is already defined as a type in the + // specified interface then that's ok. For package-local + // interfaces that's expected since the interface was + // fully defined. For remote interfaces it means we're + // using something that was already used elsewhere. In + // both cases continue along. + // + // TODO: ideally this would verify that `def` matches + // the structure of `id`. + Some(id) => *id, + + // If the name is not defined, however, then there's two + // possibilities: + // + // * For package-local interfaces this is an error + // because the package-local interface defined + // everything already and this is referencing + // something that isn't defined. + // + // * For remote interfaces they're never fully declared + // so it's lazily filled in here. This means that the + // view of remote interfaces ends up being the minimal + // slice needed for this resolve, which is what's + // intended. + None => { + if url.scheme() == "pkg" { + bail!("instance type export `{name}` not defined in interface"); + } + let kind = match self.type_map.get(&referenced).copied() { + Some(id) => TypeDefKind::Type(Type::Id(id)), + None => self.convert_defined(def)?, + }; + let id = self.resolve.types.alloc(TypeDef { + name: Some(name.to_string()), + kind, + docs: Default::default(), + owner: TypeOwner::Interface(interface), + }); + let prev = self.resolve.interfaces[interface] + .types + .insert(name.to_string(), id); + assert!(prev.is_none()); + id + } + }; + + // Register the `types::TypeId` with our resolve `TypeId` + // for ensuring type information remains correct throughout + // decoding. + let prev = self.type_map.insert(created, id); + assert!(prev.is_none()); + self.type_src_map.entry(PtrHash(def)).or_insert(id); + } - fn decode_results( - &mut self, - ps: &[(Option, types::ComponentValType)], - ) -> Result { - let results: Vec<(Option, Type)> = ps - .iter() - .map(|(n, t)| Ok((n.as_ref().map(KebabString::to_string), self.decode_type(t)?))) - .collect::>()?; - - // Results must be either - // - A single anonymous type - // - Any number of named types - match results.len() { - 1 => { - // We either have a single anonymous type or a single - // named type. Either is valid. - let (name, ty) = results.into_iter().next().unwrap(); - match name { - Some(name) => Ok(Results::Named(vec![(name, ty)])), - None => Ok(Results::Anon(ty)), + // This has similar logic to types above where we lazily fill in + // functions for remote dependencies and otherwise assert + // they're already defined for local dependencies. + types::ComponentEntityType::Func(ty) => { + let def = match self.info.types.type_from_id(ty) { + Some(types::Type::ComponentFunc(ty)) => ty, + _ => unreachable!(), + }; + if self.resolve.interfaces[interface] + .functions + .contains_key(name.as_str()) + { + // TODO: should ideally verify that function signatures + // match. + continue; + } + if url.scheme() == "pkg" { + bail!("instance function export `{name}` not defined in interface"); + } + let func = self.convert_function(name, def)?; + let prev = self.resolve.interfaces[interface] + .functions + .insert(name.to_string(), func); + assert!(prev.is_none()); } - } - _ => { - // Otherwise, all types must be named; unwrap the names. - Ok(Results::Named( - results.into_iter().map(|(n, t)| (n.unwrap(), t)).collect(), - )) + + _ => bail!("instance type export `{name}` is not a type"), } } - } - fn function(&mut self, func_name: &str, ty: &types::ComponentFuncType) -> Result { - let params = self.decode_params(&ty.params)?; - let results = self.decode_results(&ty.results)?; + Ok(interface) + } - Ok(Function { - docs: Docs::default(), - name: func_name.to_string(), - kind: FunctionKind::Freestanding, - params, - results, + fn extract_url_interface(&mut self, url: &Url) -> Result { + Ok(if url.scheme() == "pkg" { + self.url_to_interface + .get(url) + .copied() + .ok_or_else(|| anyhow!("no previously defined interface with url: {url}"))? + } else { + self.extract_dep_interface(url) + .with_context(|| format!("failed to parse url: {url}"))? }) } - fn decode_type(&mut self, ty: &types::ComponentValType) -> Result { - Ok(match ty { - types::ComponentValType::Primitive(ty) => self.decode_primitive(*ty)?, - types::ComponentValType::Type(id) => { - // If this precise `TypeId` has already been decoded, then - // return that same result. - if let Some(ty) = self.type_map.get(id) { - return Ok(*ty); - } + /// TODO: Ideally this function should not need to exist. + /// + /// This function parses the `url` provided and requires it to have a + /// particular structure. That's not really great, however, since otherwise + /// there's no need to impose structure on the url field of imports/exports. + /// + /// Note that this is only used for foreign dependencies of which the binary + /// encoding does not currently reflect the package/document/interface + /// organization. Instead foreign dependencies simply have their interfaces + /// imported, and from this interface import we need to somehow translate + /// back into a package/document structure as well. + /// + /// Resolving this may require changing the binary format for components, or + /// otherwise encoding more pieces into the binary encoding of a WIT + /// document. In any case this is "good enough" for now hopefully. + fn extract_dep_interface(&mut self, url: &Url) -> Result { + // Extract the interface and the document from the url + let mut segments = url.path_segments().ok_or_else(|| anyhow!("invalid url"))?; + let interface = segments.next_back().ok_or_else(|| anyhow!("invalid url"))?; + let document = segments.next_back().ok_or_else(|| anyhow!("invalid url"))?; + let package_name = segments.next_back().ok_or_else(|| anyhow!("invalid url"))?; + + // Then drop the two path segments from the url as a key to lookup the + // dependency package by url. + let mut url = url.clone(); + url.path_segments_mut().unwrap().pop().pop(); + + // Lazily create a `Package` as necessary, along with the document and + // interface. + let package = *self.url_to_package.entry(url.clone()).or_insert_with(|| { + self.resolve.packages.alloc(Package { + name: package_name.to_string(), + documents: Default::default(), + url: Some(url.to_string()), + }) + }); + let doc = *self.resolve.packages[package] + .documents + .entry(document.to_string()) + .or_insert_with(|| { + self.resolve.documents.alloc(Document { + name: document.to_string(), + interfaces: IndexMap::new(), + worlds: IndexMap::new(), + default_interface: None, + default_world: None, + package: Some(package), + }) + }); + let interface = *self.resolve.documents[doc] + .interfaces + .entry(interface.to_string()) + .or_insert_with(|| { + self.resolve.interfaces.alloc(Interface { + name: Some(interface.to_string()), + docs: Default::default(), + types: IndexMap::default(), + functions: IndexMap::new(), + document: doc, + }) + }); + Ok(interface) + } - let name = self.name_map.get(id).map(ToString::to_string); - let ty = self.info.types.type_from_id(*id).unwrap(); - let key = PtrHash(ty); - let ty = match self.type_src_map.get(&key) { - // If this `TypeId` points to a type which has previously - // been defined then a second `TypeId` pointing at it is - // indicative of an alias. Inject the alias here. - Some(prev) => { - let id = self.doc.types.alloc(TypeDef { - docs: Default::default(), - kind: TypeDefKind::Type(*prev), - name, - interface: Some(self.doc.interfaces.next_id()), - }); - Type::Id(id) - } + fn register_interface( + &mut self, + doc: DocumentId, + name: Option<&str>, + ty: &types::ComponentInstanceType, + ) -> Result { + let mut interface = Interface { + name: name.map(|n| n.to_string()), + docs: Default::default(), + types: IndexMap::default(), + functions: IndexMap::new(), + document: doc, + }; + + for (name, export_url, ty) in ty.exports(self.info.types.as_ref()) { + if export_url.is_some() { + bail!("instance type export `{name}` should not have a url") + } - // ... or this `TypeId`'s source definition has never been - // seen before, so declare the full type. - None => { - let ty = self.decode_defined_type(name, ty)?; + match ty { + types::ComponentEntityType::Type { + referenced, + created, + } => { + let ty = match self.info.types.type_from_id(referenced) { + Some(types::Type::Defined(ty)) => ty, + _ => unreachable!(), + }; + let key = PtrHash(ty); + + // Note that first the `type_map` is consulted for the + // referenced type id here, meaning if this is a reexport of + // another type in this interface then we're guaranteed to + // get that precise link. + // + // Failing that, though, the `type_src_map` is consulted to + // find the item, if present, from an alias of an import or + // other export, representing a cross-interface `use`. + let (kind, insert_src) = match self + .type_map + .get(&referenced) + .or_else(|| self.type_src_map.get(&key)) + { + // If this `TypeId` points to a type which has + // previously been defined, meaning we're aliasing a + // prior definition. + Some(prev) => (TypeDefKind::Type(Type::Id(*prev)), false), + + // ... or this `TypeId`'s source definition has never + // been seen before, so declare the full type. + None => { + let ty = self + .convert_defined(ty) + .with_context(|| format!("failed to decode type `{name}`"))?; + (ty, true) + } + }; + let ty = self.resolve.types.alloc(TypeDef { + docs: Default::default(), + kind, + name: Some(name.to_string()), + owner: TypeOwner::Interface(self.resolve.interfaces.next_id()), + }); + + if insert_src { let prev = self.type_src_map.insert(key, ty); assert!(prev.is_none()); - ty } - }; - - // Record the result of translation with the `TypeId` we have. - let prev = self.type_map.insert(*id, ty); - assert!(prev.is_none()); - ty - } - }) - } - - fn decode_defined_type(&mut self, name: Option, ty: &'a types::Type) -> Result { - match ty { - types::Type::Defined(ty) => match ty { - types::ComponentDefinedType::Primitive(ty) => self.decode_named_primitive(name, ty), - types::ComponentDefinedType::Record(r) => self.decode_record(name, r.fields.iter()), - types::ComponentDefinedType::Variant(v) => { - self.decode_variant(name, v.cases.iter()) - } - types::ComponentDefinedType::List(ty) => { - let inner = self.decode_type(ty)?; - Ok(Type::Id(self.alloc_type(name, TypeDefKind::List(inner)))) + let prev = self.type_map.insert(created, ty); + assert!(prev.is_none()); + let prev = interface.types.insert(name.to_string(), ty); + assert!(prev.is_none()); } - types::ComponentDefinedType::Tuple(t) => self.decode_tuple(name, &t.types), - types::ComponentDefinedType::Flags(names) => self.decode_flags(name, names.iter()), - types::ComponentDefinedType::Enum(names) => self.decode_enum(name, names.iter()), - types::ComponentDefinedType::Union(u) => self.decode_union(name, &u.types), - types::ComponentDefinedType::Option(ty) => self.decode_option(name, ty), - types::ComponentDefinedType::Result { ok, err } => { - self.decode_result(name, ok.as_ref(), err.as_ref()) + + types::ComponentEntityType::Func(ty) => { + let ty = match self.info.types.type_from_id(ty) { + Some(types::Type::ComponentFunc(ty)) => ty, + _ => unreachable!(), + }; + let func = self.convert_function(&name, ty)?; + let prev = interface.functions.insert(name.to_string(), func); + assert!(prev.is_none()); } - }, - _ => unreachable!(), + _ => bail!("instance type export `{name}` is not a type or function"), + }; } + Ok(self.resolve.interfaces.alloc(interface)) } - fn decode_optional_type( + fn register_world( &mut self, - ty: Option<&types::ComponentValType>, - ) -> Result> { - match ty { - Some(ty) => self.decode_type(ty).map(Some), - None => Ok(None), - } - } + document: DocumentId, + name: &str, + ty: &types::ComponentType, + ) -> Result { + let mut world = World { + name: name.to_string(), + docs: Default::default(), + imports: Default::default(), + exports: Default::default(), + document, + }; - fn decode_named_primitive( - &mut self, - name: Option, - ty: &PrimitiveValType, - ) -> Result { - let mut ty = self.decode_primitive(*ty)?; - if let Some(name) = name { - ty = Type::Id(self.alloc_type(Some(name), TypeDefKind::Type(ty))); + // Imports in this component type represent all of the imported items + // into the world itself, so all imports get registered. + for (name, (url, ty)) in ty.imports.iter() { + let item = match ty { + types::ComponentEntityType::Instance(idx) => { + let ty = match self.info.types.type_from_id(*idx) { + Some(types::Type::ComponentInstance(ty)) => ty, + _ => unreachable!(), + }; + let id = match url { + // If a URL is specified then the import is either to a + // package-local or foreign interface, and both + // situations are handled in `register_import`. + Some(url) => self.register_import(url, ty)?, + + // Without a URL this indicates an inline interface that + // wasn't declared explicitly elsewhere with a name, and + // `register_interface` will create a new `Interface` + // with no name. + None => self.register_interface(document, None, ty)?, + }; + WorldItem::Interface(id) + } + + types::ComponentEntityType::Func(idx) => { + let ty = match self.info.types.type_from_id(*idx) { + Some(types::Type::ComponentFunc(ty)) => ty, + _ => unreachable!(), + }; + let func = self.convert_function(name, ty)?; + WorldItem::Function(func) + } + + _ => bail!("component import `{name}` is not an instance or function"), + }; + world.imports.insert(name.to_string(), item); } - Ok(ty) - } + for (name, (url, ty)) in ty.exports.iter() { + let item = match ty { + types::ComponentEntityType::Instance(idx) => { + let ty = match self.info.types.type_from_id(*idx) { + Some(types::Type::ComponentInstance(ty)) => ty, + _ => unreachable!(), + }; + let id = match url { + // 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. + Some(url) => self.register_import(url, ty)?, + None => self.register_interface(document, None, ty)?, + }; + WorldItem::Interface(id) + } - fn decode_primitive(&mut self, ty: PrimitiveValType) -> Result { - Ok(match ty { - PrimitiveValType::Bool => Type::Bool, - PrimitiveValType::S8 => Type::S8, - PrimitiveValType::U8 => Type::U8, - PrimitiveValType::S16 => Type::S16, - PrimitiveValType::U16 => Type::U16, - PrimitiveValType::S32 => Type::S32, - PrimitiveValType::U32 => Type::U32, - PrimitiveValType::S64 => Type::S64, - PrimitiveValType::U64 => Type::U64, - PrimitiveValType::Float32 => Type::Float32, - PrimitiveValType::Float64 => Type::Float64, - PrimitiveValType::Char => Type::Char, - PrimitiveValType::String => Type::String, - }) + types::ComponentEntityType::Func(idx) => { + let ty = match self.info.types.type_from_id(*idx) { + Some(types::Type::ComponentFunc(ty)) => ty, + _ => unreachable!(), + }; + let func = self.convert_function(name, ty)?; + WorldItem::Function(func) + } + + _ => bail!("component export `{name}` is not an instance or function"), + }; + world.exports.insert(name.to_string(), item); + } + Ok(self.resolve.worlds.alloc(world)) } - fn decode_record( - &mut self, - record_name: Option, - fields: impl ExactSizeIterator, - ) -> Result { - let record_name = - record_name.ok_or_else(|| anyhow!("interface has an unnamed record type"))?; - - let record = Record { - fields: fields - .map(|(name, ty)| { - Ok(Field { - docs: Docs::default(), - name: name.to_string(), - ty: self.decode_type(ty)?, + fn convert_function(&mut self, name: &str, ty: &types::ComponentFuncType) -> Result { + let params = ty + .params + .iter() + .map(|(name, ty)| Ok((name.to_string(), self.convert_valtype(ty)?))) + .collect::>>()?; + let results = if ty.results.len() == 1 && ty.results[0].0.is_none() { + Results::Anon(self.convert_valtype(&ty.results[0].1)?) + } else { + Results::Named( + ty.results + .iter() + .map(|(name, ty)| { + Ok(( + name.as_ref().unwrap().to_string(), + self.convert_valtype(ty)?, + )) }) - }) - .collect::>()?, + .collect::>>()?, + ) }; - - Ok(Type::Id(self.alloc_type( - Some(record_name), - TypeDefKind::Record(record), - ))) + Ok(Function { + docs: Default::default(), + kind: FunctionKind::Freestanding, + name: name.to_string(), + params, + results, + }) } - fn decode_variant( - &mut self, - variant_name: Option, - cases: impl ExactSizeIterator, - ) -> Result { - let variant_name = - variant_name.ok_or_else(|| anyhow!("interface has an unnamed variant type"))?; - - let variant = Variant { - cases: cases - .map(|(name, case)| { - Ok(Case { - docs: Docs::default(), - name: name.to_string(), - ty: self.decode_optional_type(case.ty.as_ref())?, - }) - }) - .collect::>()?, + fn convert_valtype(&mut self, ty: &types::ComponentValType) -> Result { + let id = match ty { + types::ComponentValType::Primitive(ty) => return Ok(self.convert_primitive(*ty)), + types::ComponentValType::Type(id) => *id, }; - Ok(Type::Id(self.alloc_type( - Some(variant_name), - TypeDefKind::Variant(variant), - ))) - } + // Don't create duplicate types for anything previously created. + if let Some(ret) = self.type_map.get(&id) { + return Ok(Type::Id(*ret)); + } - fn decode_tuple( - &mut self, - name: Option, - tys: &[types::ComponentValType], - ) -> Result { - let tuple = Tuple { - types: tys - .iter() - .map(|ty| self.decode_type(ty)) - .collect::>()?, + // Otherwise create a new `TypeDef` without a name since this is an + // anonymous valtype. Note that this is invalid for some types so return + // errors on those types, but eventually the `bail!` here is + // more-or-less unreachable due to expected validation to be added to + // the component model binary format itself. + let ty = match self.info.types.type_from_id(id) { + Some(types::Type::Defined(ty)) => ty, + _ => unreachable!(), }; - - Ok(Type::Id(self.alloc_type(name, TypeDefKind::Tuple(tuple)))) + let kind = self.convert_defined(ty)?; + match &kind { + TypeDefKind::Type(_) + | TypeDefKind::List(_) + | TypeDefKind::Tuple(_) + | TypeDefKind::Option(_) + | TypeDefKind::Result(_) => {} + + TypeDefKind::Record(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Variant(_) + | TypeDefKind::Union(_) + | TypeDefKind::Flags(_) + | TypeDefKind::Future(_) + | TypeDefKind::Stream(_) => { + bail!("unexpected unnamed type"); + } + TypeDefKind::Unknown => unreachable!(), + } + let ty = self.resolve.types.alloc(TypeDef { + name: None, + docs: Default::default(), + owner: TypeOwner::None, + kind, + }); + let prev = self.type_map.insert(id, ty); + assert!(prev.is_none()); + Ok(Type::Id(ty)) } - fn decode_flags( - &mut self, - flags_name: Option, - names: impl ExactSizeIterator, - ) -> Result { - let flags_name = - flags_name.ok_or_else(|| anyhow!("interface has an unnamed flags type"))?; - - let flags = Flags { - flags: names - .map(|name| { - Ok(Flag { - docs: Docs::default(), - name: name.to_string(), - }) - }) - .collect::>()?, - }; + /// Converts a wasmparser `ComponentDefinedType`, the definition of a type + /// in the component model, to a WIT `TypeDefKind` to get inserted into the + /// types arena by the caller. + fn convert_defined(&mut self, ty: &types::ComponentDefinedType) -> Result { + match ty { + types::ComponentDefinedType::Primitive(t) => { + Ok(TypeDefKind::Type(self.convert_primitive(*t))) + } - Ok(Type::Id( - self.alloc_type(Some(flags_name), TypeDefKind::Flags(flags)), - )) - } + types::ComponentDefinedType::List(t) => { + let t = self.convert_valtype(t)?; + Ok(TypeDefKind::List(t)) + } - fn decode_enum( - &mut self, - enum_name: Option, - names: impl ExactSizeIterator, - ) -> Result { - let enum_name = enum_name.ok_or_else(|| anyhow!("interface has an unnamed enum type"))?; - let enum_ = Enum { - cases: names - .map(|name| { - Ok(EnumCase { - docs: Docs::default(), - name: name.to_string(), - }) - }) - .collect::>()?, - }; + types::ComponentDefinedType::Tuple(t) => { + let types = t + .types + .iter() + .map(|t| self.convert_valtype(t)) + .collect::>()?; + Ok(TypeDefKind::Tuple(Tuple { types })) + } - Ok(Type::Id( - self.alloc_type(Some(enum_name), TypeDefKind::Enum(enum_)), - )) - } + types::ComponentDefinedType::Option(t) => { + let t = self.convert_valtype(t)?; + Ok(TypeDefKind::Option(t)) + } - fn decode_union( - &mut self, - name: Option, - tys: &[types::ComponentValType], - ) -> Result { - let union = Union { - cases: tys - .iter() - .map(|ty| { - Ok(UnionCase { - docs: Docs::default(), - ty: self.decode_type(ty)?, + types::ComponentDefinedType::Result { ok, err } => { + let ok = match ok { + Some(t) => Some(self.convert_valtype(t)?), + None => None, + }; + let err = match err { + Some(t) => Some(self.convert_valtype(t)?), + None => None, + }; + Ok(TypeDefKind::Result(Result_ { ok, err })) + } + + types::ComponentDefinedType::Record(r) => { + let fields = r + .fields + .iter() + .map(|(name, ty)| { + Ok(Field { + name: name.to_string(), + ty: self.convert_valtype(ty)?, + docs: Default::default(), + }) }) - }) - .collect::>()?, - }; + .collect::>()?; + Ok(TypeDefKind::Record(Record { fields })) + } - Ok(Type::Id(self.alloc_type(name, TypeDefKind::Union(union)))) - } + types::ComponentDefinedType::Variant(v) => { + let cases = v + .cases + .iter() + .map(|(name, case)| { + if case.refines.is_some() { + bail!("unimplemented support for `refines`"); + } + Ok(Case { + name: name.to_string(), + ty: match &case.ty { + Some(ty) => Some(self.convert_valtype(ty)?), + None => None, + }, + docs: Default::default(), + }) + }) + .collect::>()?; + Ok(TypeDefKind::Variant(Variant { cases })) + } - fn decode_option( - &mut self, - name: Option, - payload: &types::ComponentValType, - ) -> Result { - let payload = self.decode_type(payload)?; - Ok(Type::Id( - self.alloc_type(name, TypeDefKind::Option(payload)), - )) - } + types::ComponentDefinedType::Flags(f) => { + let flags = f + .iter() + .map(|name| Flag { + name: name.to_string(), + docs: Default::default(), + }) + .collect(); + Ok(TypeDefKind::Flags(Flags { flags })) + } - fn decode_result( - &mut self, - name: Option, - ok: Option<&types::ComponentValType>, - err: Option<&types::ComponentValType>, - ) -> Result { - let ok = self.decode_optional_type(ok)?; - let err = self.decode_optional_type(err)?; - Ok(Type::Id(self.alloc_type( - name, - TypeDefKind::Result(Result_ { ok, err }), - ))) + types::ComponentDefinedType::Union(u) => { + let cases = u + .types + .iter() + .map(|ty| { + Ok(UnionCase { + ty: self.convert_valtype(ty)?, + docs: Default::default(), + }) + }) + .collect::>()?; + Ok(TypeDefKind::Union(Union { cases })) + } + + types::ComponentDefinedType::Enum(e) => { + let cases = e + .iter() + .cloned() + .map(|name| EnumCase { + name: name.into(), + docs: Default::default(), + }) + .collect(); + Ok(TypeDefKind::Enum(Enum { cases })) + } + } } - fn alloc_type(&mut self, name: Option, kind: TypeDefKind) -> TypeId { - self.doc.types.alloc(TypeDef { - docs: Docs::default(), - kind, - name, - interface: Some(self.doc.interfaces.next_id()), - }) + fn convert_primitive(&self, ty: PrimitiveValType) -> Type { + match ty { + PrimitiveValType::U8 => Type::U8, + PrimitiveValType::S8 => Type::S8, + PrimitiveValType::U16 => Type::U16, + PrimitiveValType::S16 => Type::S16, + PrimitiveValType::U32 => Type::U32, + PrimitiveValType::S32 => Type::S32, + PrimitiveValType::U64 => Type::U64, + PrimitiveValType::S64 => Type::S64, + PrimitiveValType::Bool => Type::Bool, + PrimitiveValType::Char => Type::Char, + PrimitiveValType::String => Type::String, + PrimitiveValType::Float32 => Type::Float32, + PrimitiveValType::Float64 => Type::Float64, + } } } diff --git a/crates/wit-component/src/dummy.rs b/crates/wit-component/src/dummy.rs index e52aec98a8..32eef075d2 100644 --- a/crates/wit-component/src/dummy.rs +++ b/crates/wit-component/src/dummy.rs @@ -1,33 +1,45 @@ use wit_parser::abi::{AbiVariant, WasmType}; -use wit_parser::{Document, Function}; +use wit_parser::{Function, Resolve, WorldId, WorldItem}; /// Generate a dummy implementation core Wasm module for a given WIT document -pub fn dummy_module(doc: &Document) -> Vec { - let world = doc.default_world().unwrap(); - let world = &doc.worlds[world]; +pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec { + let world = &resolve.worlds[world]; let mut wat = String::new(); wat.push_str("(module\n"); for (name, import) in world.imports.iter() { - for func in doc.interfaces[*import].functions.iter() { - let sig = doc.wasm_signature(AbiVariant::GuestImport, func); + match import { + WorldItem::Function(func) => { + let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); - wat.push_str(&format!("(import \"{name}\" \"{}\" (func", func.name)); - push_tys(&mut wat, "param", &sig.params); - push_tys(&mut wat, "result", &sig.results); - wat.push_str("))\n"); - } - } + wat.push_str(&format!("(import \"\" \"{}\" (func", func.name)); + push_tys(&mut wat, "param", &sig.params); + push_tys(&mut wat, "result", &sig.results); + wat.push_str("))\n"); + } + WorldItem::Interface(import) => { + for (_, func) in resolve.interfaces[*import].functions.iter() { + let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); - for (name, export) in world.exports.iter() { - for func in doc.interfaces[*export].functions.iter() { - let name = func.core_export_name(Some(name)); - push_func(&mut wat, &name, doc, func); + wat.push_str(&format!("(import \"{name}\" \"{}\" (func", func.name)); + push_tys(&mut wat, "param", &sig.params); + push_tys(&mut wat, "result", &sig.results); + wat.push_str("))\n"); + } + } } } - if let Some(default) = world.default { - for func in doc.interfaces[default].functions.iter() { - push_func(&mut wat, &func.name, doc, func); + for (name, export) in world.exports.iter() { + match export { + WorldItem::Function(func) => { + push_func(&mut wat, &func.name, resolve, func); + } + WorldItem::Interface(export) => { + for (_, func) in resolve.interfaces[*export].functions.iter() { + let name = func.core_export_name(Some(name)); + push_func(&mut wat, &name, resolve, func); + } + } } } @@ -39,14 +51,14 @@ pub fn dummy_module(doc: &Document) -> Vec { return wat::parse_str(&wat).unwrap(); - fn push_func(wat: &mut String, name: &str, doc: &Document, func: &Function) { - let sig = doc.wasm_signature(AbiVariant::GuestExport, func); + fn push_func(wat: &mut String, name: &str, resolve: &Resolve, func: &Function) { + let sig = resolve.wasm_signature(AbiVariant::GuestExport, func); wat.push_str(&format!("(func (export \"{name}\")")); push_tys(wat, "param", &sig.params); push_tys(wat, "result", &sig.results); wat.push_str(" unreachable)\n"); - if doc.guest_export_needs_post_return(func) { + if resolve.guest_export_needs_post_return(func) { wat.push_str(&format!("(func (export \"cabi_post_{name}\")")); push_tys(wat, "param", &sig.results); wat.push_str(")\n"); diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 3350c60735..1e4ec83f2f 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -53,12 +53,9 @@ //! component model. use crate::builder::ComponentBuilder; -use crate::dummy; use crate::metadata::{self, Bindgen, ModuleMetadata}; -use crate::{ - validation::{ValidatedModule, MAIN_MODULE_IMPORT_NAME}, - StringEncoding, -}; +use crate::validation::{ValidatedModule, MAIN_MODULE_IMPORT_NAME}; +use crate::StringEncoding; use anyhow::{anyhow, bail, Context, Result}; use indexmap::IndexMap; use std::collections::HashMap; @@ -67,11 +64,14 @@ use wasm_encoder::*; use wasmparser::{Validator, WasmFeatures}; use wit_parser::{ abi::{AbiVariant, WasmSignature, WasmType}, - Document, Function, InterfaceId, Type, TypeDefKind, TypeId, WorldId, + Function, InterfaceId, Resolve, Type, TypeDefKind, TypeId, TypeOwner, WorldId, WorldItem, }; const INDIRECT_TABLE_NAME: &str = "$imports"; +mod wit; +pub use wit::encode; + mod types; use types::{InstanceTypeEncoder, RootTypeEncoder, ValtypeEncoder}; mod world; @@ -103,15 +103,15 @@ bitflags::bitflags! { } impl RequiredOptions { - fn for_import(doc: &Document, func: &Function) -> RequiredOptions { - let sig = doc.wasm_signature(AbiVariant::GuestImport, func); + fn for_import(resolve: &Resolve, func: &Function) -> RequiredOptions { + let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); let mut ret = RequiredOptions::empty(); // Lift the params and lower the results for imports ret.add_lift(TypeContents::for_types( - doc, + resolve, func.params.iter().map(|(_, t)| t), )); - ret.add_lower(TypeContents::for_types(doc, func.results.iter_types())); + ret.add_lower(TypeContents::for_types(resolve, func.results.iter_types())); // If anything is indirect then `memory` will be required to read the // indirect values. @@ -121,15 +121,15 @@ impl RequiredOptions { ret } - fn for_export(doc: &Document, func: &Function) -> RequiredOptions { - let sig = doc.wasm_signature(AbiVariant::GuestExport, func); + fn for_export(resolve: &Resolve, func: &Function) -> RequiredOptions { + let sig = resolve.wasm_signature(AbiVariant::GuestExport, func); let mut ret = RequiredOptions::empty(); // Lower the params and lift the results for exports ret.add_lower(TypeContents::for_types( - doc, + resolve, func.params.iter().map(|(_, t)| t), )); - ret.add_lift(TypeContents::for_types(doc, func.results.iter_types())); + ret.add_lift(TypeContents::for_types(resolve, func.results.iter_types())); // If anything is indirect then `memory` will be required to read the // indirect values, but if the arguments are indirect then `realloc` is @@ -241,48 +241,49 @@ bitflags::bitflags! { } impl TypeContents { - fn for_types<'a>(doc: &Document, types: impl Iterator) -> Self { + fn for_types<'a>(resolve: &Resolve, types: impl Iterator) -> Self { let mut cur = TypeContents::empty(); for ty in types { - cur |= Self::for_type(doc, ty); + cur |= Self::for_type(resolve, ty); } cur } fn for_optional_types<'a>( - doc: &Document, + resolve: &Resolve, types: impl Iterator>, ) -> Self { - Self::for_types(doc, types.flatten()) + Self::for_types(resolve, types.flatten()) } - fn for_optional_type(doc: &Document, ty: Option<&Type>) -> Self { + fn for_optional_type(resolve: &Resolve, ty: Option<&Type>) -> Self { match ty { - Some(ty) => Self::for_type(doc, ty), + Some(ty) => Self::for_type(resolve, ty), None => Self::empty(), } } - fn for_type(doc: &Document, ty: &Type) -> Self { + fn for_type(resolve: &Resolve, ty: &Type) -> Self { match ty { - Type::Id(id) => match &doc.types[*id].kind { - TypeDefKind::Record(r) => Self::for_types(doc, r.fields.iter().map(|f| &f.ty)), - TypeDefKind::Tuple(t) => Self::for_types(doc, t.types.iter()), + Type::Id(id) => match &resolve.types[*id].kind { + TypeDefKind::Record(r) => Self::for_types(resolve, r.fields.iter().map(|f| &f.ty)), + TypeDefKind::Tuple(t) => Self::for_types(resolve, t.types.iter()), TypeDefKind::Flags(_) => Self::empty(), - TypeDefKind::Option(t) => Self::for_type(doc, t), + TypeDefKind::Option(t) => Self::for_type(resolve, t), TypeDefKind::Result(r) => { - Self::for_optional_type(doc, r.ok.as_ref()) - | Self::for_optional_type(doc, r.err.as_ref()) + Self::for_optional_type(resolve, r.ok.as_ref()) + | Self::for_optional_type(resolve, r.err.as_ref()) } TypeDefKind::Variant(v) => { - Self::for_optional_types(doc, v.cases.iter().map(|c| c.ty.as_ref())) + Self::for_optional_types(resolve, v.cases.iter().map(|c| c.ty.as_ref())) } - TypeDefKind::Union(v) => Self::for_types(doc, v.cases.iter().map(|c| &c.ty)), + TypeDefKind::Union(v) => Self::for_types(resolve, v.cases.iter().map(|c| &c.ty)), TypeDefKind::Enum(_) => Self::empty(), - TypeDefKind::List(t) => Self::for_type(doc, t) | Self::LIST, - TypeDefKind::Type(t) => Self::for_type(doc, t), + TypeDefKind::List(t) => Self::for_type(resolve, t) | Self::LIST, + TypeDefKind::Type(t) => Self::for_type(resolve, t), TypeDefKind::Future(_) => todo!("encoding for future"), TypeDefKind::Stream(_) => todo!("encoding for stream"), + TypeDefKind::Unknown => unreachable!(), }, Type::String => Self::STRING, _ => Self::empty(), @@ -355,7 +356,7 @@ impl<'a> EncodingState<'a> { } } - fn root_type_encoder(&mut self, interface: InterfaceId) -> RootTypeEncoder<'_, 'a> { + fn root_type_encoder(&mut self, interface: Option) -> RootTypeEncoder<'_, 'a> { RootTypeEncoder { state: self, type_exports: Vec::new(), @@ -374,21 +375,27 @@ impl<'a> EncodingState<'a> { } fn encode_imports(&mut self) -> Result<()> { - let doc = &self.info.encoder.metadata.doc; + let resolve = &self.info.encoder.metadata.resolve; for (name, info) in self.info.import_map.iter() { - let interface = &doc.interfaces[info.interface]; - log::trace!("encoding imports for `{name}` as {:?}", info.interface); + let name = match name { + Some(name) => name, + None => unimplemented!("top-level imports"), + }; + let (interface_id, url) = info.interface.as_ref().unwrap(); + let interface_id = *interface_id; + let interface = &resolve.interfaces[interface_id]; + log::trace!("encoding imports for `{name}` as {:?}", interface_id); let ty = { - let mut encoder = self.instance_type_encoder(info.interface); + let mut encoder = self.instance_type_encoder(interface_id); // Encode all required functions from this imported interface // into the instance type. - for func in interface.functions.iter() { + for (_, func) in interface.functions.iter() { if !info.required.contains(func.name.as_str()) { continue; } log::trace!("encoding function type for `{}`", func.name); - let idx = encoder.encode_func_type(doc, func)?; + let idx = encoder.encode_func_type(resolve, func)?; encoder .ty @@ -398,10 +405,10 @@ impl<'a> EncodingState<'a> { // If there were any live types from this instance which weren't // otherwise reached through the above function types then this // will forward them through. - if let Some(live) = encoder.state.info.live_types.get(&info.interface) { + if let Some(live) = encoder.state.info.live_types.get(&interface_id) { for ty in live { log::trace!("encoding extra type {ty:?}"); - encoder.encode_valtype(doc, &Type::Id(*ty))?; + encoder.encode_valtype(resolve, &Type::Id(*ty))?; } } @@ -414,12 +421,10 @@ impl<'a> EncodingState<'a> { continue; } let instance_type_idx = self.component.instance_type(&ty); - let instance_idx = self.component.import( - name, - &info.url, - ComponentTypeRef::Instance(instance_type_idx), - ); - let prev = self.imported_instances.insert(info.interface, instance_idx); + let instance_idx = + self.component + .import(name, url, ComponentTypeRef::Instance(instance_type_idx)); + let prev = self.imported_instances.insert(interface_id, instance_idx); assert!(prev.is_none()); } Ok(()) @@ -429,10 +434,11 @@ impl<'a> EncodingState<'a> { // Using the original `interface` definition of `id` and its name create // an alias which refers to the type export of that instance which must // have previously been imported. - let ty = &self.info.encoder.metadata.doc.types[id]; - let interface = ty - .interface - .expect("cannot import anonymous type across interfaces"); + let ty = &self.info.encoder.metadata.resolve.types[id]; + let interface = match ty.owner { + TypeOwner::Interface(id) => id, + _ => panic!("cannot import anonymous type across interfaces"), + }; let name = ty .name .as_ref() @@ -442,7 +448,7 @@ impl<'a> EncodingState<'a> { } fn encode_core_instantiation(&mut self) -> Result<()> { - let info = self.info.info.as_ref().unwrap(); + let info = &self.info.info; // Encode a shim instantiation if needed let shims = self.encode_shim_instantiation(); @@ -509,8 +515,9 @@ impl<'a> EncodingState<'a> { shims: &Shims<'_>, metadata: &ModuleMetadata, ) -> u32 { - let import = &self.info.import_map[name]; - let instance_index = self.imported_instances[&import.interface]; + let import = &self.info.import_map[&Some(name)]; + let (interface, _url) = import.interface.as_ref().unwrap(); + let instance_index = self.imported_instances[interface]; let mut exports = Vec::with_capacity(import.direct.len() + import.indirect.len()); // Add an entry for all indirect lowerings which come as an export of @@ -544,113 +551,133 @@ impl<'a> EncodingState<'a> { } fn encode_exports(&mut self, opts: &'a ComponentEncoder, module: CustomModule) -> Result<()> { - let doc = &opts.metadata.doc; - let metadata = match module { - CustomModule::Main => &opts.metadata.metadata, - CustomModule::Adapter(name) => &opts.adapters[name].1, - }; + let resolve = &opts.metadata.resolve; let world = match module { CustomModule::Main => opts.metadata.world, CustomModule::Adapter(name) => opts.adapters[name].2, }; - let world = &doc.worlds[world]; - for (export, export_name) in world.exports() { - let mut interface_exports = Vec::new(); - - // All types are encoded into the root component here using this - // encoder which keeps track of type exports to determine how to - // export them later as well. - let mut enc = self.root_type_encoder(export); - for func in &doc.interfaces[export].functions { - let name = func.core_export_name(export_name); - let instance_index = match module { - CustomModule::Main => enc.state.instance_index.expect("instantiated by now"), - CustomModule::Adapter(name) => enc.state.adapter_instances[name], - }; - let core_func_index = enc.state.component.alias_core_item( - instance_index, - ExportKind::Func, - name.as_ref(), - ); - - let ty = enc.encode_func_type(doc, func)?; - let options = RequiredOptions::for_export(doc, func); - - let encoding = metadata.export_encodings[&name[..]]; - // TODO: This realloc detection should probably be improved with - // some sort of scheme to have per-function reallocs like - // `cabi_realloc_{name}` or something like that. - let realloc_index = match module { - CustomModule::Main => enc.state.realloc_index, - CustomModule::Adapter(name) => enc.state.adapter_export_reallocs[name], - }; - let mut options = options - .into_iter(encoding, enc.state.memory_index, realloc_index)? - .collect::>(); - - // TODO: This should probe for the existence of - // `cabi_post_{name}` but not require its existence. - if doc.guest_export_needs_post_return(func) { - let post_return = enc.state.component.alias_core_item( - instance_index, - ExportKind::Func, - &format!("cabi_post_{name}"), - ); - options.push(CanonicalOption::PostReturn(post_return)); + let world = &resolve.worlds[world]; + for (export_name, export) in world.exports.iter() { + match export { + WorldItem::Function(func) => { + let mut enc = self.root_type_encoder(None); + let ty = enc.encode_func_type(resolve, func)?; + for (idx, name) in enc.type_exports { + self.component + .export(name, "", ComponentExportKind::Type, idx); + } + let core_name = func.core_export_name(None); + let idx = self.encode_lift(opts, module, &core_name, func, ty)?; + self.component + .export(export_name, "", ComponentExportKind::Func, idx); } - let func_index = enc.state.component.lift_func(core_func_index, ty, options); - interface_exports.push((func.name.as_str(), ComponentExportKind::Func, func_index)); - } - - // Extend the interface exports to be created with type exports - // found during encoding of function types. - interface_exports.extend( - enc.type_exports - .into_iter() - .map(|(idx, name)| (name, ComponentExportKind::Type, idx)), - ); + WorldItem::Interface(export) => { + let mut interface_exports = Vec::new(); + + // All types are encoded into the root component here using this + // encoder which keeps track of type exports to determine how to + // export them later as well. + let mut enc = self.root_type_encoder(Some(*export)); + for (_, func) in &resolve.interfaces[*export].functions { + let core_name = func.core_export_name(Some(export_name)); + let ty = enc.encode_func_type(resolve, func)?; + let func_index = + enc.state.encode_lift(opts, module, &core_name, func, ty)?; + interface_exports.push(( + func.name.as_str(), + ComponentExportKind::Func, + func_index, + )); + } - if interface_exports.is_empty() { - continue; - } + // Place type imports first for now. The lifted function + // types in theory should reference the indices of these + // exports but that's not possible in the binary encoding + // right now. + interface_exports.splice( + 0..0, + enc.type_exports + .into_iter() + .map(|(idx, name)| (name, ComponentExportKind::Type, idx)), + ); - // The default exported interface has all of its items exported - // directly but otherwise an instance type is created and then - // exported. - match export_name { - Some(export_name) => { if export_name.is_empty() { bail!("cannot export an unnamed interface"); } let instance_index = self.component.instantiate_exports(interface_exports); + let url = resolve.url_of(*export).unwrap_or(String::new()); self.component.export( export_name, - doc.interfaces[export].url.as_deref().unwrap_or(""), + &url, ComponentExportKind::Instance, instance_index, ); } - None => { - for (name, kind, idx) in interface_exports { - self.component.export(name, "", kind, idx); - } - } } } Ok(()) } + fn encode_lift( + &mut self, + opts: &'a ComponentEncoder, + module: CustomModule<'_>, + core_name: &str, + func: &Function, + ty: u32, + ) -> Result { + let resolve = &opts.metadata.resolve; + let metadata = match module { + CustomModule::Main => &opts.metadata.metadata, + CustomModule::Adapter(name) => &opts.adapters[name].1, + }; + let instance_index = match module { + CustomModule::Main => self.instance_index.expect("instantiated by now"), + CustomModule::Adapter(name) => self.adapter_instances[name], + }; + let core_func_index = + self.component + .alias_core_item(instance_index, ExportKind::Func, core_name); + + let options = RequiredOptions::for_export(resolve, func); + + let encoding = metadata.export_encodings[core_name]; + // TODO: This realloc detection should probably be improved with + // some sort of scheme to have per-function reallocs like + // `cabi_realloc_{name}` or something like that. + let realloc_index = match module { + CustomModule::Main => self.realloc_index, + CustomModule::Adapter(name) => self.adapter_export_reallocs[name], + }; + let mut options = options + .into_iter(encoding, self.memory_index, realloc_index)? + .collect::>(); + + // TODO: This should probe for the existence of + // `cabi_post_{name}` but not require its existence. + if resolve.guest_export_needs_post_return(func) { + let post_return = self.component.alias_core_item( + instance_index, + ExportKind::Func, + &format!("cabi_post_{core_name}"), + ); + options.push(CanonicalOption::PostReturn(post_return)); + } + let func_index = self.component.lift_func(core_func_index, ty, options); + Ok(func_index) + } + fn encode_shim_instantiation(&mut self) -> Shims<'a> { let mut signatures = Vec::new(); let mut ret = Shims::default(); - let info = self.info.info.as_ref().unwrap(); + let info = &self.info.info; // For all interfaces imported into the main module record all of their // indirect lowerings into `Shims`. for name in info.required_imports.keys() { - let import = &self.info.import_map[name]; + let import = &self.info.import_map[&Some(*name)]; ret.append_indirect( name, CustomModule::Main, @@ -666,7 +693,7 @@ impl<'a> EncodingState<'a> { for (adapter, funcs) in info.adapters_required.iter() { let (info, _wasm) = &self.info.adapters[adapter]; for (name, _) in info.required_imports.iter() { - let import = &self.info.import_map[name]; + let import = &self.info.import_map[&Some(*name)]; ret.append_indirect( name, CustomModule::Adapter(adapter), @@ -846,8 +873,9 @@ impl<'a> EncodingState<'a> { realloc, encoding, } => { - let interface = &self.info.import_map[interface]; - let instance_index = self.imported_instances[&interface.interface]; + let interface = &self.info.import_map[&Some(*interface)]; + let (interface_id, _url) = interface.interface.as_ref().unwrap(); + let instance_index = self.imported_instances[interface_id]; let func_index = self .component .alias_func(instance_index, interface.indirect[*indirect_index].name); @@ -1111,7 +1139,6 @@ pub struct ComponentEncoder { module: Vec, metadata: Bindgen, validate: bool, - types_only: bool, // This is a map from the name of the adapter to a pair of: // @@ -1140,33 +1167,6 @@ impl ComponentEncoder { self } - /// Add a document to this encoder as a manual specification of what's being - /// imported/exported. - /// - /// The string encoding of the specified world is supplied here as - /// well. - /// - /// Note that this can also be inferred from the `module` input if the - /// module embeds its own metadata. This is otherwise required to describe - /// imports/exports that aren't otherwise self-descried in `module`. - pub fn document(mut self, doc: Document, encoding: StringEncoding) -> Result { - let world = doc.default_world()?; - self.metadata.merge(Bindgen { - metadata: ModuleMetadata::new(&doc, world, encoding), - doc, - world, - })?; - Ok(self) - } - - /// Add a document & synthesize a dummy module for this encoder. - pub fn document_dummy(mut self, doc: Document, encoding: StringEncoding) -> Result { - let module = dummy::dummy_module(&doc); - let (wasm, _) = metadata::decode(&module)?; - self.module = wasm; - self.document(doc, encoding) - } - /// Specifies a new adapter which is used to translate from a historical /// wasm ABI to the canonical ABI and the `interface` provided. /// @@ -1189,22 +1189,18 @@ impl ComponentEncoder { // Merge the adapter's document into our own document to have one large // document, but the adapter's world isn't merged in to our world so // retain it separately. - let world = self.metadata.doc.merge(metadata.doc).world_map[metadata.world.index()]; + let world = self.metadata.resolve.merge(metadata.resolve).worlds[metadata.world.index()]; self.adapters .insert(name.to_string(), (wasm, metadata.metadata, world)); Ok(self) } - /// Indicates whether this encoder is only encoding types and does not - /// require a `module` as input. - pub fn types_only(mut self, only: bool) -> Self { - self.types_only = only; - self - } - /// Encode the component and return the bytes. pub fn encode(&self) -> Result> { - let doc = &self.metadata.doc; + if self.module.is_empty() { + bail!("a module is required when encoding a component"); + } + let world = ComponentWorld::new(self)?; let mut state = EncodingState { component: ComponentBuilder::default(), @@ -1223,69 +1219,13 @@ impl ComponentEncoder { imported_instances: Default::default(), info: &world, }; - let world = &doc.worlds[self.metadata.world]; state.encode_imports()?; - - if self.types_only { - if !self.module.is_empty() { - bail!("a module cannot be specified for a types-only encoding"); - } - - // In types-only mode there's no actual items to export so skip - // shims/adapters etc. Instead instance types are exported to - // represent exported instances and exported types represent the - // default export. - for (id, name) in world.exports() { - let interface = &doc.interfaces[id]; - let url = interface.url.as_deref().unwrap_or(""); - match name { - Some(name) => { - let mut ty = state.instance_type_encoder(id); - for func in interface.functions.iter() { - let idx = ty.encode_func_type(doc, func)?; - ty.ty.export(&func.name, "", ComponentTypeRef::Func(idx)); - } - let ty = ty.ty; - if ty.is_empty() { - continue; - } - let index = state.component.instance_type(&ty); - state - .component - .export(name, url, ComponentExportKind::Type, index); - } - None => { - let mut ty = state.root_type_encoder(id); - for func in interface.functions.iter() { - let idx = ty.encode_func_type(doc, func)?; - ty.state.component.export( - &func.name, - "", - ComponentExportKind::Type, - idx, - ); - } - for (idx, name) in ty.type_exports { - ty.state - .component - .export(name, "", ComponentExportKind::Type, idx); - } - } - } - } - } else { - if self.module.is_empty() { - bail!("a module is required when encoding a component"); - } - - state.encode_core_modules(); - state.encode_core_instantiation()?; - state.encode_exports(self, CustomModule::Main)?; - for name in self.adapters.keys() { - state.encode_exports(self, CustomModule::Adapter(name))?; - } + state.encode_core_modules(); + state.encode_core_instantiation()?; + state.encode_exports(self, CustomModule::Main)?; + for name in self.adapters.keys() { + state.encode_exports(self, CustomModule::Adapter(name))?; } - let bytes = state.component.finish(); if self.validate { diff --git a/crates/wit-component/src/encoding/types.rs b/crates/wit-component/src/encoding/types.rs index 454b7a75f8..89e9af35d4 100644 --- a/crates/wit-component/src/encoding/types.rs +++ b/crates/wit-component/src/encoding/types.rs @@ -3,8 +3,8 @@ use anyhow::Result; use std::collections::HashMap; use wasm_encoder::*; use wit_parser::{ - Document, Enum, Flags, Function, InterfaceId, Params, Record, Result_, Results, Tuple, Type, - TypeDefKind, TypeId, Union, Variant, + Enum, Flags, Function, InterfaceId, Params, Record, Resolve, Result_, Results, Tuple, Type, + TypeDefKind, TypeId, TypeOwner, Union, Variant, }; /// Represents a key type for interface function definitions. @@ -33,11 +33,8 @@ pub trait ValtypeEncoder<'a> { /// place its results. fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>); - /// Aliases the provided type as a new type using an outer depth of 0. - fn define_type_alias_self(&mut self, ty: u32) -> u32; - /// Creates an export item for the specified type index. - fn export_type(&mut self, index: u32, name: &'a str); + fn export_type(&mut self, index: u32, name: &'a str) -> Option; /// Returns a map of all types previously defined in this type index space. fn type_map(&mut self) -> &mut HashMap; @@ -54,7 +51,7 @@ pub trait ValtypeEncoder<'a> { /// Encodes a new function type which is defined within the provided /// document. - fn encode_func_type(&mut self, doc: &'a Document, func: &'a Function) -> Result { + fn encode_func_type(&mut self, resolve: &'a Resolve, func: &'a Function) -> Result { let key = FunctionKey { params: &func.params, results: &func.results, @@ -64,7 +61,7 @@ pub trait ValtypeEncoder<'a> { } // Encode all referenced parameter types from this function. - let params: Vec<_> = self.encode_params(doc, &func.params)?; + let params: Vec<_> = self.encode_params(resolve, &func.params)?; enum EncodedResults<'a> { Named(Vec<(&'a str, ComponentValType)>), @@ -72,8 +69,8 @@ pub trait ValtypeEncoder<'a> { } let results = match &func.results { - Results::Named(rs) => EncodedResults::Named(self.encode_params(doc, rs)?), - Results::Anon(ty) => EncodedResults::Anon(self.encode_valtype(doc, ty)?), + Results::Named(rs) => EncodedResults::Named(self.encode_params(resolve, rs)?), + Results::Anon(ty) => EncodedResults::Anon(self.encode_valtype(resolve, ty)?), }; // Encode the function type @@ -90,21 +87,21 @@ pub trait ValtypeEncoder<'a> { fn encode_params( &mut self, - doc: &'a Document, + resolve: &'a Resolve, params: &'a Params, ) -> Result> { params .iter() - .map(|(name, ty)| Ok((name.as_str(), self.encode_valtype(doc, ty)?))) + .map(|(name, ty)| Ok((name.as_str(), self.encode_valtype(resolve, ty)?))) .collect::>() } - /// Encodes the `ty`, defined within `doc`, into this encoder and returns + /// Encodes the `ty`, defined within `resolve`, into this encoder and returns /// the corresponding `ComponentValType` that it represents. /// /// This will recursively define the entire structure of `ty` within `self` /// if necessary. - fn encode_valtype(&mut self, doc: &'a Document, ty: &Type) -> Result { + fn encode_valtype(&mut self, resolve: &'a Resolve, ty: &Type) -> Result { Ok(match *ty { Type::Bool => ComponentValType::Primitive(PrimitiveValType::Bool), Type::U8 => ComponentValType::Primitive(PrimitiveValType::U8), @@ -134,34 +131,26 @@ pub trait ValtypeEncoder<'a> { } // ... and failing all that insert the type export. - let ty = &doc.types[id]; + let ty = &resolve.types[id]; let mut encoded = match &ty.kind { - TypeDefKind::Record(r) => self.encode_record(doc, r)?, - TypeDefKind::Tuple(t) => self.encode_tuple(doc, t)?, + TypeDefKind::Record(r) => self.encode_record(resolve, r)?, + TypeDefKind::Tuple(t) => self.encode_tuple(resolve, t)?, TypeDefKind::Flags(r) => self.encode_flags(r)?, - TypeDefKind::Variant(v) => self.encode_variant(doc, v)?, - TypeDefKind::Union(u) => self.encode_union(doc, u)?, - TypeDefKind::Option(t) => self.encode_option(doc, t)?, - TypeDefKind::Result(r) => self.encode_result(doc, r)?, + TypeDefKind::Variant(v) => self.encode_variant(resolve, v)?, + TypeDefKind::Union(u) => self.encode_union(resolve, u)?, + TypeDefKind::Option(t) => self.encode_option(resolve, t)?, + TypeDefKind::Result(r) => self.encode_result(resolve, r)?, TypeDefKind::Enum(e) => self.encode_enum(e)?, TypeDefKind::List(ty) => { - let ty = self.encode_valtype(doc, ty)?; + let ty = self.encode_valtype(resolve, ty)?; let (index, encoder) = self.defined_type(); encoder.list(ty); ComponentValType::Type(index) } - TypeDefKind::Type(ty) => { - match self.encode_valtype(doc, ty)? { - // This is `type a = b` which is encoded as an - // `outer` alias of depth 0 - ComponentValType::Type(index) => { - ComponentValType::Type(self.define_type_alias_self(index)) - } - t @ ComponentValType::Primitive(_) => t, - } - } + TypeDefKind::Type(ty) => self.encode_valtype(resolve, ty)?, TypeDefKind::Future(_) => todo!("encoding for future type"), TypeDefKind::Stream(_) => todo!("encoding for stream type"), + TypeDefKind::Unknown => unreachable!(), }; if let Some(name) = &ty.name { @@ -175,7 +164,7 @@ pub trait ValtypeEncoder<'a> { index } }; - self.export_type(index, name); + let index = self.export_type(index, name).unwrap_or(index); encoded = ComponentValType::Type(index); } @@ -191,20 +180,20 @@ pub trait ValtypeEncoder<'a> { fn encode_optional_valtype( &mut self, - doc: &'a Document, + resolve: &'a Resolve, ty: Option<&Type>, ) -> Result> { match ty { - Some(ty) => self.encode_valtype(doc, ty).map(Some), + Some(ty) => self.encode_valtype(resolve, ty).map(Some), None => Ok(None), } } - fn encode_record(&mut self, doc: &'a Document, record: &Record) -> Result { + fn encode_record(&mut self, resolve: &'a Resolve, record: &Record) -> Result { let fields = record .fields .iter() - .map(|f| Ok((f.name.as_str(), self.encode_valtype(doc, &f.ty)?))) + .map(|f| Ok((f.name.as_str(), self.encode_valtype(resolve, &f.ty)?))) .collect::>>()?; let (index, encoder) = self.defined_type(); @@ -212,11 +201,11 @@ pub trait ValtypeEncoder<'a> { Ok(ComponentValType::Type(index)) } - fn encode_tuple(&mut self, doc: &'a Document, tuple: &Tuple) -> Result { + fn encode_tuple(&mut self, resolve: &'a Resolve, tuple: &Tuple) -> Result { let tys = tuple .types .iter() - .map(|ty| self.encode_valtype(doc, ty)) + .map(|ty| self.encode_valtype(resolve, ty)) .collect::>>()?; let (index, encoder) = self.defined_type(); encoder.tuple(tys); @@ -229,14 +218,18 @@ pub trait ValtypeEncoder<'a> { Ok(ComponentValType::Type(index)) } - fn encode_variant(&mut self, doc: &'a Document, variant: &Variant) -> Result { + fn encode_variant( + &mut self, + resolve: &'a Resolve, + variant: &Variant, + ) -> Result { let cases = variant .cases .iter() .map(|c| { Ok(( c.name.as_str(), - self.encode_optional_valtype(doc, c.ty.as_ref())?, + self.encode_optional_valtype(resolve, c.ty.as_ref())?, None, // TODO: support defaulting case values in the future )) }) @@ -247,11 +240,11 @@ pub trait ValtypeEncoder<'a> { Ok(ComponentValType::Type(index)) } - fn encode_union(&mut self, doc: &'a Document, union: &Union) -> Result { + fn encode_union(&mut self, resolve: &'a Resolve, union: &Union) -> Result { let tys = union .cases .iter() - .map(|c| self.encode_valtype(doc, &c.ty)) + .map(|c| self.encode_valtype(resolve, &c.ty)) .collect::>>()?; let (index, encoder) = self.defined_type(); @@ -259,16 +252,20 @@ pub trait ValtypeEncoder<'a> { Ok(ComponentValType::Type(index)) } - fn encode_option(&mut self, doc: &'a Document, payload: &Type) -> Result { - let ty = self.encode_valtype(doc, payload)?; + fn encode_option(&mut self, resolve: &'a Resolve, payload: &Type) -> Result { + let ty = self.encode_valtype(resolve, payload)?; let (index, encoder) = self.defined_type(); encoder.option(ty); Ok(ComponentValType::Type(index)) } - fn encode_result(&mut self, doc: &'a Document, result: &Result_) -> Result { - let ok = self.encode_optional_valtype(doc, result.ok.as_ref())?; - let error = self.encode_optional_valtype(doc, result.err.as_ref())?; + fn encode_result( + &mut self, + resolve: &'a Resolve, + result: &Result_, + ) -> Result { + let ok = self.encode_optional_valtype(resolve, result.ok.as_ref())?; + let error = self.encode_optional_valtype(resolve, result.err.as_ref())?; let (index, encoder) = self.defined_type(); encoder.result(ok, error); Ok(ComponentValType::Type(index)) @@ -284,7 +281,7 @@ pub trait ValtypeEncoder<'a> { pub struct RootTypeEncoder<'state, 'a> { pub state: &'state mut EncodingState<'a>, pub type_exports: Vec<(u32, &'a str)>, - pub interface: InterfaceId, + pub interface: Option, } impl<'a> ValtypeEncoder<'a> for RootTypeEncoder<'_, 'a> { @@ -294,11 +291,9 @@ impl<'a> ValtypeEncoder<'a> for RootTypeEncoder<'_, 'a> { fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { self.state.component.function_type() } - fn define_type_alias_self(&mut self, idx: u32) -> u32 { - self.state.component.alias_outer_type(0, idx) - } - fn export_type(&mut self, idx: u32, name: &'a str) { + fn export_type(&mut self, idx: u32, name: &'a str) -> Option { self.type_exports.push((idx, name)); + None } fn type_map(&mut self) -> &mut HashMap { &mut self.state.type_map @@ -307,8 +302,11 @@ impl<'a> ValtypeEncoder<'a> for RootTypeEncoder<'_, 'a> { // If this `id` is anonymous or belongs to this interface there's // nothing to import, it needs defining. Otherwise alias the type from // an import into this component's root namespace. - let other = self.state.info.encoder.metadata.doc.types[id].interface?; - if other == self.interface { + let other = match self.state.info.encoder.metadata.resolve.types[id].owner { + TypeOwner::Interface(id) => id, + _ => return None, + }; + if Some(other) == self.interface { return None; } // TODO: this doesn't work for importing types from other exports. That @@ -335,18 +333,11 @@ impl<'a> ValtypeEncoder<'a> for InstanceTypeEncoder<'_, 'a> { fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { (self.ty.type_count(), self.ty.ty().function()) } - fn define_type_alias_self(&mut self, idx: u32) -> u32 { + fn export_type(&mut self, idx: u32, name: &str) -> Option { let ret = self.ty.type_count(); - self.ty.alias(Alias::Outer { - count: 0, - index: idx, - kind: ComponentOuterAliasKind::Type, - }); - ret - } - fn export_type(&mut self, idx: u32, name: &str) { self.ty .export(name, "", ComponentTypeRef::Type(TypeBounds::Eq, idx)); + Some(ret) } fn type_map(&mut self) -> &mut HashMap { &mut self.type_map @@ -356,7 +347,10 @@ impl<'a> ValtypeEncoder<'a> for InstanceTypeEncoder<'_, 'a> { // there's nothing to import, it needs defining. Otherwise // perform the importing process with an outer alias to the // parent component. - let other = self.state.info.encoder.metadata.doc.types[id].interface?; + let other = match self.state.info.encoder.metadata.resolve.types[id].owner { + TypeOwner::Interface(id) => id, + _ => return None, + }; if other == self.interface { return None; } diff --git a/crates/wit-component/src/encoding/wit.rs b/crates/wit-component/src/encoding/wit.rs new file mode 100644 index 0000000000..8f6ae89929 --- /dev/null +++ b/crates/wit-component/src/encoding/wit.rs @@ -0,0 +1,295 @@ +use crate::builder::ComponentBuilder; +use crate::encoding::types::{FunctionKey, ValtypeEncoder}; +use anyhow::Result; +use indexmap::IndexMap; +use std::collections::HashMap; +use std::mem; +use url::Url; +use wasm_encoder::*; +use wit_parser::*; + +/// Encodes the given `package` within `resolve` to a binary WebAssembly +/// representation. +/// +/// This function is the root of the implementation of serializing a WIT package +/// into a WebAssembly representation. The wasm representation serves two +/// purposes: +/// +/// * One is to be a binary encoding of a WIT document which is ideally more +/// stable than the WIT textual format itself. +/// * Another is to provide a clear mapping of all WIT features into the +/// component model through use of its binary representation. +/// +/// The `resolve` provided is a "world" of packages and types and such and the +/// `package` argument is an ID within the world provided. The documents within +/// `package` will all be encoded into the binary returned. +/// +/// The binary returned can be [`decode`d](crate::decode) to recover the WIT +/// package provided. +pub fn encode(resolve: &Resolve, package: PackageId) -> Result> { + let mut encoder = Encoder { + component: ComponentBuilder::default(), + resolve, + package, + }; + encoder.run()?; + Ok(encoder.component.finish()) +} + +struct Encoder<'a> { + component: ComponentBuilder, + resolve: &'a Resolve, + package: PackageId, +} + +impl Encoder<'_> { + fn run(&mut self) -> Result<()> { + for (name, doc) in self.resolve.packages[self.package].documents.iter() { + let ty = self.encode_document(*doc)?; + let url = format!("pkg:/{name}"); + self.component + .export(name, &url, ComponentExportKind::Type, ty); + } + Ok(()) + } + + fn encode_document(&mut self, doc: DocumentId) -> Result { + let mut set = LiveTypes::default(); + + // Note that worlds within `doc` are explicitly excluded here since + // worlds have their own scope and import everything themselves, this + // transitive import set is only required for the interfaces defined in + // the document. + for (_, id) in self.resolve.documents[doc].interfaces.iter() { + set.add_interface(self.resolve, *id); + } + + let mut imported_interfaces = IndexMap::new(); + for ty in set.iter() { + let owner = match &self.resolve.types[ty].owner { + TypeOwner::Interface(i) => *i, + _ => continue, + }; + if self.resolve.interfaces[owner].document == doc { + continue; + } + imported_interfaces + .entry(owner) + .or_insert(Vec::new()) + .push(ty); + } + + let mut encoder = InterfaceEncoder::new(self.resolve); + for (owner, ids) in imported_interfaces { + let owner_iface = &self.resolve.interfaces[owner]; + encoder.push_instance(); + for id in ids { + encoder.encode_valtype(self.resolve, &Type::Id(id))?; + } + let instance = encoder.pop_instance(); + let idx = encoder.outer.type_count(); + encoder.outer.ty().instance(&instance); + encoder.import_map.insert(owner, encoder.instances); + encoder.instances += 1; + + let import_name = owner_iface.name.as_ref().unwrap(); + let url = self.url_of(owner); + encoder + .outer + .import(import_name, &url, ComponentTypeRef::Instance(idx)); + } + + let doc = &self.resolve.documents[doc]; + for (name, interface) in doc.interfaces.iter() { + let idx = encoder.encode_instance(*interface)?; + let url = format!("pkg:/{}/{name}", doc.name); + encoder + .outer + .export(name, &url, ComponentTypeRef::Instance(idx)); + } + + for (name, world) in doc.worlds.iter() { + let world = &self.resolve.worlds[*world]; + let mut component = InterfaceEncoder::new(self.resolve); + for (name, import) in world.imports.iter() { + let (url, ty) = match import { + WorldItem::Interface(i) => { + let idx = component.encode_instance(*i)?; + (self.url_of(*i), ComponentTypeRef::Instance(idx)) + } + WorldItem::Function(f) => { + let idx = component.encode_func_type(self.resolve, f)?; + (String::new(), ComponentTypeRef::Func(idx)) + } + }; + component.outer.import(name, &url, ty); + } + for (name, export) in world.exports.iter() { + let (url, ty) = match export { + WorldItem::Interface(i) => { + let idx = component.encode_instance(*i)?; + (self.url_of(*i), ComponentTypeRef::Instance(idx)) + } + WorldItem::Function(f) => { + let idx = component.encode_func_type(self.resolve, f)?; + (String::new(), ComponentTypeRef::Func(idx)) + } + }; + component.outer.export(name, &url, ty); + } + let idx = encoder.outer.type_count(); + encoder.outer.ty().component(&component.outer); + let url = format!("pkg:/{}/{name}", doc.name); + encoder + .outer + .export(&name, &url, ComponentTypeRef::Component(idx)); + } + + Ok(self.component.component_type(&encoder.outer)) + } + + fn url_of(&self, interface: InterfaceId) -> String { + let iface = &self.resolve.interfaces[interface]; + let iface_name = match &iface.name { + Some(name) => name, + None => return String::new(), + }; + let doc = &self.resolve.documents[iface.document]; + let pkg = doc.package.unwrap(); + let mut base = if pkg == self.package { + Url::parse("pkg:/").unwrap() + } else { + let pkg = &self.resolve.packages[pkg]; + Url::parse(pkg.url.as_ref().unwrap()).unwrap() + }; + let mut segments = base.path_segments_mut().unwrap(); + segments.push(&doc.name); + segments.push(iface_name); + drop(segments); + base.to_string() + } +} + +struct InterfaceEncoder<'a> { + resolve: &'a Resolve, + outer: ComponentType, + ty: Option, + func_type_map: HashMap, u32>, + type_map: HashMap, + import_map: HashMap, + outer_type_map: HashMap, + instances: u32, +} + +impl InterfaceEncoder<'_> { + fn new(resolve: &Resolve) -> InterfaceEncoder<'_> { + InterfaceEncoder { + resolve, + outer: ComponentType::new(), + ty: None, + type_map: Default::default(), + func_type_map: Default::default(), + import_map: Default::default(), + outer_type_map: Default::default(), + instances: 0, + } + } + + fn encode_instance(&mut self, interface: InterfaceId) -> Result { + self.push_instance(); + let iface = &self.resolve.interfaces[interface]; + for (_, id) in iface.types.iter() { + self.encode_valtype(self.resolve, &Type::Id(*id))?; + } + for (name, func) in iface.functions.iter() { + let ty = self.encode_func_type(self.resolve, func)?; + self.ty + .as_mut() + .unwrap() + .export(name, "", ComponentTypeRef::Func(ty)); + } + let instance = self.pop_instance(); + let idx = self.outer.type_count(); + self.outer.ty().instance(&instance); + self.import_map.insert(interface, self.instances); + self.instances += 1; + Ok(idx) + } + + fn push_instance(&mut self) { + assert!(self.ty.is_none()); + self.func_type_map.clear(); + self.type_map.clear(); + self.ty = Some(InstanceType::default()); + } + + fn pop_instance(&mut self) -> InstanceType { + self.func_type_map.clear(); + self.type_map.clear(); + mem::take(&mut self.ty).unwrap() + } +} + +impl<'a> ValtypeEncoder<'a> for InterfaceEncoder<'a> { + fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { + match &mut self.ty { + Some(ty) => (ty.type_count(), ty.ty().defined_type()), + None => (self.outer.type_count(), self.outer.ty().defined_type()), + } + } + fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { + match &mut self.ty { + Some(ty) => (ty.type_count(), ty.ty().function()), + None => (self.outer.type_count(), self.outer.ty().function()), + } + } + fn export_type(&mut self, index: u32, name: &'a str) -> Option { + match &mut self.ty { + Some(ty) => { + let ret = ty.type_count(); + ty.export(name, "", ComponentTypeRef::Type(TypeBounds::Eq, index)); + Some(ret) + } + None => { + let ret = self.outer.type_count(); + self.outer + .export(name, "", ComponentTypeRef::Type(TypeBounds::Eq, index)); + Some(ret) + } + } + } + fn type_map(&mut self) -> &mut HashMap { + &mut self.type_map + } + fn maybe_import_type(&mut self, id: TypeId) -> Option { + let ty = &self.resolve.types[id]; + let owner = match ty.owner { + TypeOwner::Interface(i) => i, + _ => return None, + }; + let instance = *self.import_map.get(&owner)?; + let outer_idx = *self.outer_type_map.entry(id).or_insert_with(|| { + let ret = self.outer.type_count(); + self.outer.alias(Alias::InstanceExport { + instance, + name: ty.name.as_ref().unwrap(), + kind: ComponentExportKind::Type, + }); + ret + }); + let ty = match &mut self.ty { + Some(ty) => ty, + None => unimplemented!(), + }; + let ret = ty.type_count(); + ty.alias(Alias::Outer { + count: 1, + index: outer_idx, + kind: ComponentOuterAliasKind::Type, + }); + Some(ret) + } + fn func_type_map(&mut self) -> &mut HashMap, u32> { + &mut self.func_type_map + } +} diff --git a/crates/wit-component/src/encoding/world.rs b/crates/wit-component/src/encoding/world.rs index 63513ae6f9..9123ea4660 100644 --- a/crates/wit-component/src/encoding/world.rs +++ b/crates/wit-component/src/encoding/world.rs @@ -5,11 +5,10 @@ use crate::validation::{ use anyhow::{Context, Result}; use indexmap::{IndexMap, IndexSet}; use std::collections::HashSet; -use std::mem; use wasmparser::FuncType; use wit_parser::{ abi::{AbiVariant, WasmSignature, WasmType}, - Document, Function, InterfaceId, Type, TypeDefKind, TypeId, WorldId, + Function, InterfaceId, LiveTypes, Resolve, TypeId, TypeOwner, WorldId, WorldItem, }; /// Metadata discovered from the state configured in a `ComponentEncoder`. @@ -22,12 +21,12 @@ pub struct ComponentWorld<'a> { pub encoder: &'a ComponentEncoder, /// Validation information of the input module, or `None` in `--types-only` /// mode. - pub info: Option>, + pub info: ValidatedModule<'a>, /// Validation information about adapters populated only for required /// adapters. Additionally stores the gc'd wasm for each adapter. pub adapters: IndexMap<&'a str, (ValidatedAdapter<'a>, Vec)>, /// Map of all imports and descriptions of what they're importing. - pub import_map: IndexMap<&'a str, ImportedInterface<'a>>, + pub import_map: IndexMap, ImportedInterface<'a>>, /// Set of all live types which must be exported either because they're /// directly used or because they're transitively used. pub live_types: IndexMap>, @@ -35,13 +34,12 @@ pub struct ComponentWorld<'a> { #[derive(Debug)] pub struct ImportedInterface<'a> { - pub url: &'a str, pub direct: Vec>, pub indirect: Vec>, /// Required functions on the interface, or the filter on the functions list /// in `interface`. pub required: HashSet<&'a str>, - pub interface: InterfaceId, + pub interface: Option<(InterfaceId, String)>, } #[derive(Debug)] @@ -58,20 +56,12 @@ pub struct IndirectLowering<'a> { impl<'a> ComponentWorld<'a> { pub fn new(encoder: &'a ComponentEncoder) -> Result { - let info = if !encoder.module.is_empty() { - let adapters = encoder - .adapters - .keys() - .map(|s| s.as_str()) - .collect::>(); - Some(validate_module( - &encoder.module, - &encoder.metadata, - &adapters, - )?) - } else { - None - }; + let adapters = encoder + .adapters + .keys() + .map(|s| s.as_str()) + .collect::>(); + let info = validate_module(&encoder.module, &encoder.metadata, &adapters)?; let mut ret = ComponentWorld { encoder, @@ -93,19 +83,16 @@ impl<'a> ComponentWorld<'a> { /// adapter itself, either because the functions are imported by the /// main module or they're part of the adapter's exports. fn process_adapters(&mut self) -> Result<()> { - let doc = &self.encoder.metadata.doc; + let resolve = &self.encoder.metadata.resolve; for (name, (wasm, metadata, world)) in self.encoder.adapters.iter() { - let required_by_import = self - .info - .as_ref() - .and_then(|info| info.adapters_required.get(name.as_str())); - let required = self.required_adapter_exports(doc, *world, required_by_import); + let required_by_import = self.info.adapters_required.get(name.as_str()); + let required = self.required_adapter_exports(resolve, *world, required_by_import); if required.is_empty() { continue; } let wasm = crate::gc::run(wasm, &required) .context("failed to reduce input adapter module to its minimal size")?; - let info = validate_adapter_module(&wasm, doc, *world, metadata, &required) + let info = validate_adapter_module(&wasm, resolve, *world, metadata, &required) .context("failed to validate the imports of the minimized adapter module")?; self.adapters.insert(name, (info, wasm)); } @@ -117,7 +104,7 @@ impl<'a> ComponentWorld<'a> { /// they're required as an import to the main module. fn required_adapter_exports( &self, - doc: &Document, + resolve: &Resolve, world: WorldId, required_by_import: Option<&IndexMap<&str, FuncType>>, ) -> IndexMap { @@ -129,18 +116,26 @@ impl<'a> ComponentWorld<'a> { required.insert(name.to_string(), ty.clone()); } } - for (interface, name) in doc.worlds[world].exports() { - for func in doc.interfaces[interface].functions.iter() { - let name = func.core_export_name(name); - let ty = doc.wasm_signature(AbiVariant::GuestExport, func); - let prev = required.insert( - name.into_owned(), - wasmparser::FuncType::new( - ty.params.iter().map(to_valty), - ty.results.iter().map(to_valty), - ), - ); - assert!(prev.is_none()); + let mut add_func = |func: &Function, name: Option<&str>| { + let name = func.core_export_name(name); + let ty = resolve.wasm_signature(AbiVariant::GuestExport, func); + let prev = required.insert( + name.into_owned(), + wasmparser::FuncType::new( + ty.params.iter().map(to_valty), + ty.results.iter().map(to_valty), + ), + ); + assert!(prev.is_none()); + }; + for (name, item) in resolve.worlds[world].exports.iter() { + match item { + WorldItem::Function(func) => add_func(func, None), + WorldItem::Interface(id) => { + for (_, func) in resolve.interfaces[*id].functions.iter() { + add_func(func, Some(name)); + } + } } } return required; @@ -159,68 +154,90 @@ impl<'a> ComponentWorld<'a> { /// functions from all imports. This additionally classifies imported /// functions into direct or indirect lowerings for managing shims. fn process_imports(&mut self) -> Result<()> { - let doc = &self.encoder.metadata.doc; + let resolve = &self.encoder.metadata.resolve; let world = self.encoder.metadata.world; - let empty = IndexSet::new(); - for (name, interface) in doc.worlds[world].imports.iter() { - let required = match &self.info { - Some(info) => Some(info.required_imports.get(name.as_str()).unwrap_or(&empty)), - None => None, - }; - add_interface(&mut self.import_map, doc, name, *interface, required)?; + for (name, item) in resolve.worlds[world].imports.iter() { + add_item( + &mut self.import_map, + resolve, + name, + item, + &self.info.required_imports, + )?; } for (adapter_name, (info, _wasm)) in self.adapters.iter() { - for (name, required) in info.required_imports.iter() { - let (_, _, world) = self.encoder.adapters[*adapter_name]; - let interface = doc.worlds[world].imports[*name]; - add_interface(&mut self.import_map, doc, name, interface, Some(required))?; + let (_, _, world) = self.encoder.adapters[*adapter_name]; + for (name, item) in resolve.worlds[world].imports.iter() { + add_item( + &mut self.import_map, + resolve, + name, + item, + &info.required_imports, + )?; } } return Ok(()); - fn add_interface<'a>( - import_map: &mut IndexMap<&'a str, ImportedInterface<'a>>, - doc: &'a Document, + fn add_item<'a>( + import_map: &mut IndexMap, ImportedInterface<'a>>, + resolve: &'a Resolve, name: &'a str, - id: InterfaceId, - required: Option<&IndexSet<&str>>, + item: &'a WorldItem, + required: &IndexMap<&str, IndexSet<&str>>, ) -> Result<()> { - import_map.entry(name).or_insert_with(|| ImportedInterface { - interface: id, - url: doc.interfaces[id].url.as_deref().unwrap_or(""), - direct: Default::default(), - indirect: Default::default(), - required: Default::default(), - }); - for func in doc.interfaces[id].functions.iter() { - // If this function isn't actually required then skip it - if let Some(required) = required { - if !required.contains(func.name.as_str()) { - continue; + let empty = IndexSet::new(); + match item { + WorldItem::Function(func) => { + let required = required.get("").unwrap_or(&empty); + // If this function isn't actually required then skip it + if !required.contains(name) { + return Ok(()); } + let interface = import_map.entry(None).or_insert_with(|| ImportedInterface { + interface: None, + direct: Default::default(), + indirect: Default::default(), + required: Default::default(), + }); + add_import(interface, resolve, func) + } + WorldItem::Interface(id) => { + let required = required.get(name).unwrap_or(&empty); + let url = resolve.url_of(*id).unwrap_or(String::new()); + let interface = + import_map + .entry(Some(name)) + .or_insert_with(|| ImportedInterface { + interface: Some((*id, url)), + direct: Default::default(), + indirect: Default::default(), + required: Default::default(), + }); + for (_name, func) in resolve.interfaces[*id].functions.iter() { + // If this function isn't actually required then skip it + if required.contains(func.name.as_str()) { + add_import(interface, resolve, func)?; + } + } + Ok(()) } - add_import(import_map, doc, name, id, func)?; } - Ok(()) } fn add_import<'a>( - import_map: &mut IndexMap<&'a str, ImportedInterface<'a>>, - doc: &'a Document, - name: &'a str, - id: InterfaceId, + interface: &mut ImportedInterface<'a>, + resolve: &'a Resolve, func: &'a Function, ) -> Result<()> { - let interface = import_map.get_mut(name).unwrap(); - assert_eq!(interface.interface, id); if !interface.required.insert(func.name.as_str()) { return Ok(()); } - let options = RequiredOptions::for_import(doc, func); + let options = RequiredOptions::for_import(resolve, func); if options.is_empty() { interface.direct.push(DirectLowering { name: &func.name }); } else { - let sig = doc.wasm_signature(AbiVariant::GuestImport, func); + let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); interface.indirect.push(IndirectLowering { name: &func.name, sig, @@ -236,102 +253,68 @@ impl<'a> ComponentWorld<'a> { /// individual interface by walking over the set of live functions in /// imports and recursively walking types. fn process_live_types(&mut self) { - let mut live_types = mem::take(&mut self.live_types); - let doc = &self.encoder.metadata.doc; - for (_name, info) in self.import_map.iter() { - let interface = &doc.interfaces[info.interface]; - for func in interface.functions.iter() { - if !info.required.contains(func.name.as_str()) { - continue; - } - self.add_live_func(func, &mut live_types); - } - } - for (id, _) in doc.worlds[self.encoder.metadata.world].exports() { - let interface = &doc.interfaces[id]; - for func in interface.functions.iter() { - self.add_live_func(func, &mut live_types); - } + let mut live = LiveTypes::default(); + let resolve = &self.encoder.metadata.resolve; + let world = &resolve.worlds[self.encoder.metadata.world]; + self.add_live_imports( + self.encoder.metadata.world, + &self.info.required_imports, + &mut live, + ); + for (_, item) in world.exports.iter() { + live.add_world_item(resolve, item); } - for (_, (_, _, world)) in self.encoder.adapters.iter() { - for (id, _) in doc.worlds[*world].exports() { - let interface = &doc.interfaces[id]; - for func in interface.functions.iter() { - self.add_live_func(func, &mut live_types); - } + for (adapter_name, (info, _wasm)) in self.adapters.iter() { + let (_, _, world) = self.encoder.adapters[*adapter_name]; + self.add_live_imports(world, &info.required_imports, &mut live); + for (_, item) in resolve.worlds[world].exports.iter() { + live.add_world_item(resolve, item); } } - self.live_types = live_types; + for live in live.iter() { + let owner = match resolve.types[live].owner { + TypeOwner::Interface(id) => id, + _ => continue, + }; + self.live_types + .entry(owner) + .or_insert(Default::default()) + .insert(live); + } } - fn add_live_func( + fn add_live_imports( &self, - func: &Function, - live_types: &mut IndexMap>, + world: WorldId, + required: &IndexMap<&str, IndexSet<&str>>, + live: &mut LiveTypes, ) { - for ty in func - .params - .iter() - .map(|(_, t)| t) - .chain(func.results.iter_types()) - { - self.add_live(ty, live_types); - } - } - - fn add_live(&self, ty: &Type, live_types: &mut IndexMap>) { - let id = match *ty { - Type::Id(id) => id, - _ => return, - }; - let ty = &self.encoder.metadata.doc.types[id]; - let interface = match ty.interface { - Some(id) => id, - None => return, - }; - let set = live_types.entry(interface).or_insert_with(Default::default); - if !set.insert(id) { - return; - } - log::trace!("live {id:?} in {interface:?}"); - match &ty.kind { - TypeDefKind::Record(t) => { - for f in t.fields.iter() { - self.add_live(&f.ty, live_types); - } - } - TypeDefKind::Tuple(t) => { - for ty in t.types.iter() { - self.add_live(ty, live_types); - } - } - TypeDefKind::Union(t) => { - for c in t.cases.iter() { - self.add_live(&c.ty, live_types); - } - } - TypeDefKind::Variant(t) => { - for c in t.cases.iter() { - if let Some(ty) = &c.ty { - self.add_live(ty, live_types); + let resolve = &self.encoder.metadata.resolve; + for (name, item) in resolve.worlds[world].imports.iter() { + match item { + WorldItem::Function(func) => { + let required = match required.get("") { + Some(set) => set, + None => return, + }; + if !required.contains(name.as_str()) { + return; } + live.add_func(resolve, func); } - } - TypeDefKind::Result(t) => { - if let Some(t) = &t.ok { - self.add_live(t, live_types); - } - if let Some(t) = &t.err { - self.add_live(t, live_types); + WorldItem::Interface(id) => { + let required = match required.get(name.as_str()) { + Some(set) => set, + None => return, + }; + for (name, func) in resolve.interfaces[*id].functions.iter() { + if required.contains(name.as_str()) { + live.add_func(resolve, func); + } + } } } - TypeDefKind::Option(t) | TypeDefKind::Type(t) | TypeDefKind::List(t) => { - self.add_live(t, live_types); - } - TypeDefKind::Enum(_) | TypeDefKind::Flags(_) => {} - TypeDefKind::Stream(_) => todo!(), - TypeDefKind::Future(_) => todo!(), } } } diff --git a/crates/wit-component/src/lib.rs b/crates/wit-component/src/lib.rs index 182831fce0..466d15db28 100644 --- a/crates/wit-component/src/lib.rs +++ b/crates/wit-component/src/lib.rs @@ -9,19 +9,22 @@ use wasm_encoder::CanonicalOption; mod builder; mod decoding; -mod dummy; mod encoding; mod gc; mod printing; mod validation; -pub use decoding::decode_world; -pub use encoding::ComponentEncoder; +pub use decoding::{decode, DecodedWasm}; +pub use encoding::{encode, ComponentEncoder}; pub use printing::*; -pub use dummy::dummy_module; pub mod metadata; +#[cfg(feature = "dummy-module")] +pub use dummy::dummy_module; +#[cfg(feature = "dummy-module")] +mod dummy; + /// Supported string encoding formats. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum StringEncoding { diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index d1790099cd..4a1e824c67 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -16,9 +16,7 @@ //! per-language-binding-generation and consumed by slurping up all the //! sections during the component creation process. //! -//! The custom section here contains `World`, the interpretation of a "world" -//! of a component, along with how strings are encoded for all the specified -//! interfaces. Currently the encoding is: +//! Currently the encoding of this custom section is: //! //! * First, a version byte (`CURRENT_VERSION`). This is intended to detect //! mismatches between different versions of the binding generator and @@ -26,17 +24,22 @@ //! //! * Next a string encoding byte. //! -//! * Afterwards a "types only" component encoding of a `World` -//! package through the `ComponentEncoder::types_only` configuration. +//! * Next, three strings are encoded. These are the names of the root package, +//! document, and world that the bindings were generated for. These strings +//! are used as lookups into the next field. +//! +//! * Finally the Wasm-encoded representation of a `Resolve` is included in its +//! binary form. This is the encoding of a package into wasm, and the bound +//! world for the bindings is specified from the prior strings. -use crate::{decode_world, ComponentEncoder, StringEncoding}; +use crate::{DecodedWasm, StringEncoding}; use anyhow::{bail, Context, Result}; use indexmap::IndexMap; use wasm_encoder::Encode; use wasmparser::BinaryReader; -use wit_parser::{Document, World, WorldId}; +use wit_parser::{Document, Package, Resolve, World, WorldId, WorldItem}; -const CURRENT_VERSION: u8 = 0x01; +const CURRENT_VERSION: u8 = 0x02; /// The result of decoding binding information from a WebAssembly binary. /// @@ -44,7 +47,7 @@ const CURRENT_VERSION: u8 = 0x01; /// WebAssembly binary. pub struct Bindgen { /// Interface and type information for this binary. - pub doc: Document, + pub resolve: Resolve, /// The world that was bound. pub world: WorldId, /// Metadata about this specific module that was bound. @@ -53,10 +56,29 @@ pub struct Bindgen { impl Default for Bindgen { fn default() -> Bindgen { - let mut doc = Document::default(); - let world = doc.worlds.alloc(World::default()); + let mut resolve = Resolve::default(); + let package = resolve.packages.alloc(Package { + name: "root".to_string(), + url: None, + documents: Default::default(), + }); + let document = resolve.documents.alloc(Document { + name: "root".to_string(), + interfaces: Default::default(), + worlds: Default::default(), + default_world: None, + default_interface: None, + package: Some(package), + }); + let world = resolve.worlds.alloc(World { + name: "root".to_string(), + docs: Default::default(), + imports: Default::default(), + exports: Default::default(), + document, + }); Bindgen { - doc, + resolve, world, metadata: ModuleMetadata::default(), } @@ -120,13 +142,19 @@ pub fn decode(wasm: &[u8]) -> Result<(Vec, Bindgen)> { /// into the final core wasm binary. The core wasm binary is later fed /// through `wit-component` to produce the actual component where this returned /// section will be decoded. -pub fn encode(doc: &Document, world: WorldId, encoding: StringEncoding) -> Vec { - let component = ComponentEncoder::default() - .types_only(true) - .document(doc.clone(), encoding) - .unwrap() - .encode() - .unwrap(); +pub fn encode(resolve: &Resolve, world: WorldId, encoding: StringEncoding) -> Result> { + let world = &resolve.worlds[world]; + let doc = &resolve.documents[world.document]; + let pkg = &resolve.packages[doc.package.unwrap()]; + + assert!( + resolve + .packages + .iter() + .filter(|(_, p)| p.name == pkg.name) + .count() + == 1 + ); let mut ret = Vec::new(); ret.push(CURRENT_VERSION); @@ -135,9 +163,11 @@ pub fn encode(doc: &Document, world: WorldId, encoding: StringEncoding) -> Vec 0x01, StringEncoding::CompactUTF16 => 0x02, }); - doc.worlds[world].name.encode(&mut ret); - ret.extend(component); - ret + pkg.name.encode(&mut ret); + doc.name.encode(&mut ret); + world.name.encode(&mut ret); + ret.extend(crate::encoding::encode(resolve, doc.package.unwrap())?); + Ok(ret) } impl Bindgen { @@ -153,12 +183,19 @@ impl Bindgen { 0x02 => StringEncoding::CompactUTF16, byte => bail!("invalid string encoding {byte:#x}"), }; - let name = reader.read_string()?; + let pkg_name = reader.read_string()?; + let doc_name = reader.read_string()?; + let world_name = reader.read_string()?; - let (doc, world) = decode_world(name, &data[reader.original_position()..])?; - let metadata = ModuleMetadata::new(&doc, world, encoding); + let (resolve, pkg) = match crate::decode(pkg_name, &data[reader.original_position()..])? { + DecodedWasm::WitPackage(resolve, pkg) => (resolve, pkg), + DecodedWasm::Component(..) => bail!("expected an encoded wit package"), + }; + let doc = resolve.packages[pkg].documents[doc_name]; + let world = resolve.documents[doc].worlds[world_name]; + let metadata = ModuleMetadata::new(&resolve, world, encoding); Ok(Bindgen { - doc, + resolve, world, metadata, }) @@ -175,7 +212,7 @@ impl Bindgen { /// between metadata. pub fn merge(&mut self, other: Bindgen) -> Result<()> { let Bindgen { - doc, + resolve, world, metadata: ModuleMetadata { @@ -184,8 +221,8 @@ impl Bindgen { }, } = other; - let world = self.doc.merge(doc).world_map[world.index()]; - self.doc + let world = self.resolve.merge(resolve).worlds[world.index()]; + self.resolve .merge_worlds(world, self.world) .context("failed to merge worlds from two documents")?; @@ -219,31 +256,46 @@ impl Bindgen { impl ModuleMetadata { /// Creates a new `ModuleMetadata` instance holding the given set of /// interfaces which are expected to all use the `encoding` specified. - pub fn new(doc: &Document, world: WorldId, encoding: StringEncoding) -> ModuleMetadata { + pub fn new(resolve: &Resolve, world: WorldId, encoding: StringEncoding) -> ModuleMetadata { let mut ret = ModuleMetadata::default(); - if let Some(iface) = doc.worlds[world].default { - for func in doc.interfaces[iface].functions.iter() { - let name = func.core_export_name(None); - let prev = ret.export_encodings.insert(name.to_string(), encoding); - assert!(prev.is_none()); + let world = &resolve.worlds[world]; + for (name, item) in world.imports.iter() { + match item { + WorldItem::Function(_) => { + let prev = ret + .import_encodings + .insert((String::new(), name.clone()), encoding); + assert!(prev.is_none()); + } + WorldItem::Interface(i) => { + for (func, _) in resolve.interfaces[*i].functions.iter() { + let prev = ret + .import_encodings + .insert((name.clone(), func.clone()), encoding); + assert!(prev.is_none()); + } + } } } - for (name, import) in doc.worlds[world].imports.iter() { - for func in doc.interfaces[*import].functions.iter() { - let key = (name.clone(), func.name.clone()); - let prev = ret.import_encodings.insert(key, encoding); - assert!(prev.is_none()); - } - } - for (name, export) in doc.worlds[world].exports.iter() { - for func in doc.interfaces[*export].functions.iter() { - let name = func.core_export_name(Some(name)); - let prev = ret.export_encodings.insert(name.to_string(), encoding); - assert!(prev.is_none()); + for (name, item) in world.exports.iter() { + match item { + WorldItem::Function(func) => { + let name = func.core_export_name(None).into_owned(); + let prev = ret.export_encodings.insert(name, encoding); + assert!(prev.is_none()); + } + WorldItem::Interface(i) => { + for (_, func) in resolve.interfaces[*i].functions.iter() { + let name = func.core_export_name(Some(name)).into_owned(); + let prev = ret.export_encodings.insert(name, encoding); + assert!(prev.is_none()); + } + } } } + ret } } diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 15954afcff..3fdd035eeb 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -1,6 +1,5 @@ use anyhow::{anyhow, bail, Result}; use indexmap::{IndexMap, IndexSet}; -use std::collections::HashSet; use std::fmt::{self, Write}; use wit_parser::*; @@ -9,79 +8,57 @@ use wit_parser::*; pub struct DocumentPrinter { output: Output, declared: IndexSet, - iface_names: Vec, } impl DocumentPrinter { /// Print the given `*.wit` document to a string. - pub fn print(&mut self, doc: &Document) -> Result { - let mut all_names = HashSet::new(); - for (id, iface) in doc.interfaces.iter() { - let name = name(&iface.name, &mut all_names); - writeln!(&mut self.output, "interface {name} {{")?; - self.print_interface(doc, id)?; + pub fn print(&mut self, resolve: &Resolve, docid: DocumentId) -> Result { + let doc = &resolve.documents[docid]; + for (name, id) in doc.interfaces.iter() { + if Some(*id) == doc.default_interface { + self.output.push_str("default "); + } + self.output.push_str("interface "); + self.print_name(name); + self.output.push_str(" {\n"); + self.print_interface(resolve, *id)?; writeln!(&mut self.output, "}}\n")?; - self.iface_names.push(name); } - for (_id, world) in doc.worlds.iter() { - let name = name(&world.name, &mut all_names); - writeln!(&mut self.output, "world {name} {{")?; + for (name, id) in doc.worlds.iter() { + if Some(*id) == doc.default_world { + self.output.push_str("default "); + } + let world = &resolve.worlds[*id]; + self.output.push_str("world "); + self.print_name(name); + self.output.push_str(" {\n"); for (name, import) in world.imports.iter() { - let iface_name = &self.iface_names[import.index()]; - writeln!(&mut self.output, "import {name}: {iface_name}")?; + self.print_world_item(resolve, name, import, docid, "import")?; } for (name, export) in world.exports.iter() { - let iface_name = &self.iface_names[export.index()]; - writeln!(&mut self.output, "export {name}: {iface_name}")?; + self.print_world_item(resolve, name, export, docid, "export")?; } - if let Some(default) = &world.default { - let iface_name = &self.iface_names[default.index()]; - writeln!(&mut self.output, "default export {iface_name}")?; - } - writeln!(&mut self.output, "}}")?; } self.declared.clear(); - self.iface_names.clear(); - return Ok(std::mem::take(&mut self.output).into()); - - fn name(name: &str, all_names: &mut HashSet) -> String { - if !name.is_empty() && all_names.insert(name.to_string()) { - return name.to_string(); - } - for i in 0.. { - let name = if name.is_empty() { - format!("interface{i}") - } else { - format!("{name}{i}") - }; - if all_names.insert(name.clone()) { - return name; - } - } - unreachable!() - } + Ok(std::mem::take(&mut self.output).into()) } /// Print the given WebAssembly interface to a string. - fn print_interface(&mut self, doc: &Document, id: InterfaceId) -> Result<()> { - let interface = &doc.interfaces[id]; + fn print_interface(&mut self, resolve: &Resolve, id: InterfaceId) -> Result<()> { + let interface = &resolve.interfaces[id]; // Partition types defined in this interface into either those imported // from foreign interfaces or those defined locally. let mut types_to_declare = Vec::new(); let mut types_to_import = IndexMap::new(); - for ty_id in &interface.types { - let ty = &doc.types[*ty_id]; - let name = ty - .name - .as_ref() - .ok_or_else(|| anyhow!("unnamed type exported from interface"))?; + for (name, ty_id) in &interface.types { + let ty = &resolve.types[*ty_id]; if let TypeDefKind::Type(Type::Id(other)) = ty.kind { - let other = &doc.types[other]; - if let Some(other_iface) = other.interface { + let other = &resolve.types[other]; + if let TypeOwner::Interface(other_iface) = other.owner { if other_iface != id { let other_name = other .name @@ -100,73 +77,137 @@ impl DocumentPrinter { } // Generate a `use` statement for all imported types. + let my_doc = resolve.interfaces[id].document; for (id, tys) in types_to_import { - write!(&mut self.output, "use {{ ")?; + write!(&mut self.output, "use ")?; + self.print_path_to_interface(resolve, id, my_doc)?; + write!(&mut self.output, ".{{")?; for (i, (my_name, other_name)) in tys.into_iter().enumerate() { if i > 0 { write!(&mut self.output, ", ")?; } if my_name == other_name { - write!(&mut self.output, "{my_name}")?; + self.print_name(my_name); } else { - write!(&mut self.output, "{other_name} as {my_name}")?; + self.print_name(other_name); + self.output.push_str(" as "); + self.print_name(my_name); } } - let iface_name = &self.iface_names[id.index()]; - writeln!(&mut self.output, " }} from {iface_name}")?; + writeln!(&mut self.output, "}}")?; } // Declare all local types for id in types_to_declare { - self.declare_type(doc, &Type::Id(id))?; + self.declare_type(resolve, &Type::Id(id))?; } - for (i, func) in interface.functions.iter().enumerate() { + for (i, (name, func)) in interface.functions.iter().enumerate() { if i > 0 { self.output.push_str("\n"); } - write!(&mut self.output, "{}: func(", func.name)?; - for (i, (name, ty)) in func.params.iter().enumerate() { - if i > 0 { - self.output.push_str(", "); - } - write!(&mut self.output, "{}: ", name)?; - self.print_type_name(doc, ty)?; + self.print_name(name); + self.output.push_str(": "); + self.print_function(resolve, func)?; + self.output.push_str("\n"); + } + + Ok(()) + } + + fn print_function(&mut self, resolve: &Resolve, func: &Function) -> Result<()> { + self.output.push_str("func("); + for (i, (name, ty)) in func.params.iter().enumerate() { + if i > 0 { + self.output.push_str(", "); } - self.output.push_str(")"); - - match &func.results { - Results::Named(rs) => match rs.len() { - 0 => (), - 1 => { - self.output.push_str(" -> "); - self.print_type_name(doc, &rs[0].1)?; - } - _ => { - self.output.push_str(" -> ("); - for (i, (name, ty)) in rs.iter().enumerate() { - if i > 0 { - self.output.push_str(", "); - } - write!(&mut self.output, "{name}: ")?; - self.print_type_name(doc, ty)?; + self.print_name(name); + self.output.push_str(": "); + self.print_type_name(resolve, ty)?; + } + self.output.push_str(")"); + + match &func.results { + Results::Named(rs) => match rs.len() { + 0 => (), + _ => { + self.output.push_str(" -> ("); + for (i, (name, ty)) in rs.iter().enumerate() { + if i > 0 { + self.output.push_str(", "); } - self.output.push_str(")"); + self.print_name(name); + self.output.push_str(": "); + self.print_type_name(resolve, ty)?; } - }, - Results::Anon(ty) => { - self.output.push_str(" -> "); - self.print_type_name(doc, ty)?; + self.output.push_str(")"); } + }, + Results::Anon(ty) => { + self.output.push_str(" -> "); + self.print_type_name(resolve, ty)?; } + } + Ok(()) + } - self.output.push_str("\n"); + fn print_world_item( + &mut self, + resolve: &Resolve, + name: &str, + item: &WorldItem, + cur_doc: DocumentId, + desc: &str, + ) -> Result<()> { + self.output.push_str(desc); + self.output.push_str(" "); + self.print_name(name); + self.output.push_str(": "); + match item { + WorldItem::Interface(id) => { + if resolve.interfaces[*id].name.is_some() { + self.print_path_to_interface(resolve, *id, cur_doc)?; + self.output.push_str("\n"); + } else { + writeln!(self.output, "interface {{")?; + self.print_interface(resolve, *id)?; + writeln!(self.output, "}}")?; + } + } + WorldItem::Function(f) => { + self.print_function(resolve, f)?; + self.output.push_str("\n"); + } } + Ok(()) + } + fn print_path_to_interface( + &mut self, + resolve: &Resolve, + interface: InterfaceId, + cur_doc: DocumentId, + ) -> Result<()> { + let cur_pkg = resolve.documents[cur_doc].package; + let iface = &resolve.interfaces[interface]; + let iface_doc = &resolve.documents[iface.document]; + if iface.document == cur_doc { + self.output.push_str("self"); + } else if cur_pkg == iface_doc.package { + self.output.push_str("pkg."); + self.print_name(&iface_doc.name); + } else { + let iface_pkg = &resolve.packages[iface_doc.package.unwrap()]; + self.print_name(&iface_pkg.name); + self.output.push_str("."); + self.print_name(&iface_doc.name); + } + self.output.push_str("."); + self.print_name(iface.name.as_ref().unwrap()); Ok(()) } - fn print_type_name(&mut self, doc: &Document, ty: &Type) -> Result<()> { + fn print_type_name(&mut self, resolve: &Resolve, ty: &Type) -> Result<()> { match ty { Type::Bool => self.output.push_str("bool"), Type::U8 => self.output.push_str("u8"), @@ -183,49 +224,50 @@ impl DocumentPrinter { Type::String => self.output.push_str("string"), Type::Id(id) => { - let ty = &doc.types[*id]; + let ty = &resolve.types[*id]; if let Some(name) = &ty.name { - self.output.push_str(name); + self.print_name(name); return Ok(()); } match &ty.kind { TypeDefKind::Tuple(t) => { - self.print_tuple_type(doc, t)?; + self.print_tuple_type(resolve, t)?; } TypeDefKind::Option(t) => { - self.print_option_type(doc, t)?; + self.print_option_type(resolve, t)?; } TypeDefKind::Result(t) => { - self.print_result_type(doc, t)?; + self.print_result_type(resolve, t)?; } TypeDefKind::Record(_) => { - bail!("doc has an unnamed record type"); + bail!("resolve has an unnamed record type"); } TypeDefKind::Flags(_) => { - bail!("doc has unnamed flags type") + bail!("resolve has unnamed flags type") } TypeDefKind::Enum(_) => { - bail!("doc has unnamed enum type") + bail!("resolve has unnamed enum type") } TypeDefKind::Variant(_) => { - bail!("doc has unnamed variant type") + bail!("resolve has unnamed variant type") } TypeDefKind::Union(_) => { bail!("document has unnamed union type") } TypeDefKind::List(ty) => { self.output.push_str("list<"); - self.print_type_name(doc, ty)?; + self.print_type_name(resolve, ty)?; self.output.push_str(">"); } - TypeDefKind::Type(ty) => self.print_type_name(doc, ty)?, + TypeDefKind::Type(ty) => self.print_type_name(resolve, ty)?, TypeDefKind::Future(_) => { todo!("document has an unnamed future type") } TypeDefKind::Stream(_) => { todo!("document has an unnamed stream type") } + TypeDefKind::Unknown => unreachable!(), } } } @@ -233,36 +275,36 @@ impl DocumentPrinter { Ok(()) } - fn print_tuple_type(&mut self, doc: &Document, tuple: &Tuple) -> Result<()> { + fn print_tuple_type(&mut self, resolve: &Resolve, tuple: &Tuple) -> Result<()> { self.output.push_str("tuple<"); for (i, ty) in tuple.types.iter().enumerate() { if i > 0 { self.output.push_str(", "); } - self.print_type_name(doc, ty)?; + self.print_type_name(resolve, ty)?; } self.output.push_str(">"); Ok(()) } - fn print_option_type(&mut self, doc: &Document, payload: &Type) -> Result<()> { + fn print_option_type(&mut self, resolve: &Resolve, payload: &Type) -> Result<()> { self.output.push_str("option<"); - self.print_type_name(doc, payload)?; + self.print_type_name(resolve, payload)?; self.output.push_str(">"); Ok(()) } - fn print_result_type(&mut self, doc: &Document, result: &Result_) -> Result<()> { + fn print_result_type(&mut self, resolve: &Resolve, result: &Result_) -> Result<()> { match result { Result_ { ok: Some(ok), err: Some(err), } => { self.output.push_str("result<"); - self.print_type_name(doc, ok)?; + self.print_type_name(resolve, ok)?; self.output.push_str(", "); - self.print_type_name(doc, err)?; + self.print_type_name(resolve, err)?; self.output.push_str(">"); } Result_ { @@ -270,7 +312,7 @@ impl DocumentPrinter { err: Some(err), } => { self.output.push_str("result<_, "); - self.print_type_name(doc, err)?; + self.print_type_name(resolve, err)?; self.output.push_str(">"); } Result_ { @@ -278,7 +320,7 @@ impl DocumentPrinter { err: None, } => { self.output.push_str("result<"); - self.print_type_name(doc, ok)?; + self.print_type_name(resolve, ok)?; self.output.push_str(">"); } Result_ { @@ -291,7 +333,7 @@ impl DocumentPrinter { Ok(()) } - fn declare_type(&mut self, doc: &Document, ty: &Type) -> Result<()> { + fn declare_type(&mut self, resolve: &Resolve, ty: &Type) -> Result<()> { match ty { Type::Bool | Type::U8 @@ -312,29 +354,40 @@ impl DocumentPrinter { return Ok(()); } - let ty = &doc.types[*id]; + let ty = &resolve.types[*id]; match &ty.kind { - TypeDefKind::Record(r) => self.declare_record(doc, ty.name.as_deref(), r)?, - TypeDefKind::Tuple(t) => self.declare_tuple(doc, ty.name.as_deref(), t)?, + TypeDefKind::Record(r) => { + self.declare_record(resolve, ty.name.as_deref(), r)? + } + TypeDefKind::Tuple(t) => self.declare_tuple(resolve, ty.name.as_deref(), t)?, TypeDefKind::Flags(f) => self.declare_flags(ty.name.as_deref(), f)?, - TypeDefKind::Variant(v) => self.declare_variant(doc, ty.name.as_deref(), v)?, - TypeDefKind::Union(u) => self.declare_union(doc, ty.name.as_deref(), u)?, - TypeDefKind::Option(t) => self.declare_option(doc, ty.name.as_deref(), t)?, - TypeDefKind::Result(r) => self.declare_result(doc, ty.name.as_deref(), r)?, + TypeDefKind::Variant(v) => { + self.declare_variant(resolve, ty.name.as_deref(), v)? + } + TypeDefKind::Union(u) => self.declare_union(resolve, ty.name.as_deref(), u)?, + TypeDefKind::Option(t) => { + self.declare_option(resolve, ty.name.as_deref(), t)? + } + TypeDefKind::Result(r) => { + self.declare_result(resolve, ty.name.as_deref(), r)? + } TypeDefKind::Enum(e) => self.declare_enum(ty.name.as_deref(), e)?, TypeDefKind::List(inner) => { - self.declare_list(doc, ty.name.as_deref(), inner)? + self.declare_list(resolve, ty.name.as_deref(), inner)? } TypeDefKind::Type(inner) => match ty.name.as_deref() { Some(name) => { - write!(&mut self.output, "type {} = ", name)?; - self.print_type_name(doc, inner)?; + self.output.push_str("type "); + self.print_name(name); + self.output.push_str(" = "); + self.print_type_name(resolve, inner)?; self.output.push_str("\n\n"); } None => bail!("unnamed type in document"), }, TypeDefKind::Future(_) => todo!("declare future"), TypeDefKind::Stream(_) => todo!("declare stream"), + TypeDefKind::Unknown => unreachable!(), } } } @@ -343,21 +396,24 @@ impl DocumentPrinter { fn declare_record( &mut self, - doc: &Document, + resolve: &Resolve, name: Option<&str>, record: &Record, ) -> Result<()> { for field in record.fields.iter() { - self.declare_type(doc, &field.ty)?; + self.declare_type(resolve, &field.ty)?; } match name { Some(name) => { - writeln!(&mut self.output, "record {} {{", name)?; + self.output.push_str("record "); + self.print_name(name); + self.output.push_str(" {\n"); for field in &record.fields { - write!(&mut self.output, "{}: ", field.name)?; - self.declare_type(doc, &field.ty)?; - self.print_type_name(doc, &field.ty)?; + self.print_name(&field.name); + self.output.push_str(": "); + self.declare_type(resolve, &field.ty)?; + self.print_type_name(resolve, &field.ty)?; self.output.push_str(",\n"); } self.output.push_str("}\n\n"); @@ -367,14 +423,21 @@ impl DocumentPrinter { } } - fn declare_tuple(&mut self, doc: &Document, name: Option<&str>, tuple: &Tuple) -> Result<()> { + fn declare_tuple( + &mut self, + resolve: &Resolve, + name: Option<&str>, + tuple: &Tuple, + ) -> Result<()> { for ty in tuple.types.iter() { - self.declare_type(doc, ty)?; + self.declare_type(resolve, ty)?; } if let Some(name) = name { - write!(&mut self.output, "type {} = ", name)?; - self.print_tuple_type(doc, tuple)?; + self.output.push_str("type "); + self.print_name(name); + self.output.push_str(" = "); + self.print_tuple_type(resolve, tuple)?; self.output.push_str("\n\n"); } Ok(()) @@ -383,9 +446,12 @@ impl DocumentPrinter { fn declare_flags(&mut self, name: Option<&str>, flags: &Flags) -> Result<()> { match name { Some(name) => { - writeln!(&mut self.output, "flags {} {{", name)?; + self.output.push_str("flags "); + self.print_name(name); + self.output.push_str(" {\n"); for flag in &flags.flags { - writeln!(&mut self.output, "{},", flag.name)?; + self.print_name(&flag.name); + self.output.push_str(",\n"); } self.output.push_str("}\n\n"); } @@ -396,13 +462,13 @@ impl DocumentPrinter { fn declare_variant( &mut self, - doc: &Document, + resolve: &Resolve, name: Option<&str>, variant: &Variant, ) -> Result<()> { for case in variant.cases.iter() { if let Some(ty) = case.ty { - self.declare_type(doc, &ty)?; + self.declare_type(resolve, &ty)?; } } @@ -410,12 +476,14 @@ impl DocumentPrinter { Some(name) => name, None => bail!("document has unnamed union type"), }; - writeln!(&mut self.output, "variant {} {{", name)?; + self.output.push_str("variant "); + self.print_name(name); + self.output.push_str(" {\n"); for case in &variant.cases { - write!(&mut self.output, "{}", case.name)?; + self.print_name(&case.name); if let Some(ty) = case.ty { self.output.push_str("("); - self.print_type_name(doc, &ty)?; + self.print_type_name(resolve, &ty)?; self.output.push_str(")"); } self.output.push_str(",\n"); @@ -424,31 +492,45 @@ impl DocumentPrinter { Ok(()) } - fn declare_union(&mut self, doc: &Document, name: Option<&str>, union: &Union) -> Result<()> { + fn declare_union( + &mut self, + resolve: &Resolve, + name: Option<&str>, + union: &Union, + ) -> Result<()> { for case in union.cases.iter() { - self.declare_type(doc, &case.ty)?; + self.declare_type(resolve, &case.ty)?; } let name = match name { Some(name) => name, None => bail!("document has unnamed union type"), }; - writeln!(&mut self.output, "union {} {{", name)?; + self.output.push_str("union "); + self.print_name(name); + self.output.push_str(" {\n"); for case in &union.cases { self.output.push_str(""); - self.print_type_name(doc, &case.ty)?; + self.print_type_name(resolve, &case.ty)?; self.output.push_str(",\n"); } self.output.push_str("}\n\n"); Ok(()) } - fn declare_option(&mut self, doc: &Document, name: Option<&str>, payload: &Type) -> Result<()> { - self.declare_type(doc, payload)?; + fn declare_option( + &mut self, + resolve: &Resolve, + name: Option<&str>, + payload: &Type, + ) -> Result<()> { + self.declare_type(resolve, payload)?; if let Some(name) = name { - write!(&mut self.output, "type {} = ", name)?; - self.print_option_type(doc, payload)?; + self.output.push_str("type "); + self.print_name(name); + self.output.push_str(" = "); + self.print_option_type(resolve, payload)?; self.output.push_str("\n\n"); } Ok(()) @@ -456,20 +538,22 @@ impl DocumentPrinter { fn declare_result( &mut self, - doc: &Document, + resolve: &Resolve, name: Option<&str>, result: &Result_, ) -> Result<()> { if let Some(ok) = result.ok { - self.declare_type(doc, &ok)?; + self.declare_type(resolve, &ok)?; } if let Some(err) = result.err { - self.declare_type(doc, &err)?; + self.declare_type(resolve, &err)?; } if let Some(name) = name { - write!(&mut self.output, "type {} = ", name)?; - self.print_result_type(doc, result)?; + self.output.push_str("type "); + self.print_name(name); + self.output.push_str(" = "); + self.print_result_type(resolve, result)?; self.output.push_str("\n\n"); } Ok(()) @@ -480,26 +564,49 @@ impl DocumentPrinter { Some(name) => name, None => bail!("document has unnamed enum type"), }; - writeln!(&mut self.output, "enum {} {{", name)?; + self.output.push_str("enum "); + self.print_name(name); + self.output.push_str(" {\n"); for case in &enum_.cases { - writeln!(&mut self.output, "{},", case.name)?; + self.print_name(&case.name); + self.output.push_str(",\n"); } self.output.push_str("}\n\n"); Ok(()) } - fn declare_list(&mut self, doc: &Document, name: Option<&str>, ty: &Type) -> Result<()> { - self.declare_type(doc, ty)?; + fn declare_list(&mut self, resolve: &Resolve, name: Option<&str>, ty: &Type) -> Result<()> { + self.declare_type(resolve, ty)?; if let Some(name) = name { - write!(&mut self.output, "type {} = list<", name)?; - self.print_type_name(doc, ty)?; + self.output.push_str("type "); + self.print_name(name); + self.output.push_str(" = list<"); + self.print_type_name(resolve, ty)?; self.output.push_str(">\n\n"); return Ok(()); } Ok(()) } + + fn print_name(&mut self, name: &str) { + if is_keyword(name) { + self.output.push_str("%"); + } + self.output.push_str(name); + } +} + +fn is_keyword(name: &str) -> bool { + match name { + "use" | "type" | "func" | "u8" | "u16" | "u32" | "u64" | "s8" | "s16" | "s32" | "s64" + | "float32" | "float64" | "char" | "record" | "flags" | "variant" | "enum" | "union" + | "bool" | "string" | "option" | "result" | "future" | "stream" | "list" | "_" | "as" + | "from" | "static" | "interface" | "tuple" | "implements" | "world" | "import" + | "export" | "default" | "pkg" | "self" => true, + _ => false, + } } /// Helper structure to help maintain an indentation level when printing source, diff --git a/crates/wit-component/src/validation.rs b/crates/wit-component/src/validation.rs index a66a93ae01..2e8c72f5f9 100644 --- a/crates/wit-component/src/validation.rs +++ b/crates/wit-component/src/validation.rs @@ -1,5 +1,5 @@ use crate::metadata::{Bindgen, ModuleMetadata}; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use indexmap::{map::Entry, IndexMap, IndexSet}; use wasmparser::{ types::Types, Encoding, ExternalKind, FuncType, Parser, Payload, TypeRef, ValType, @@ -7,7 +7,7 @@ use wasmparser::{ }; use wit_parser::{ abi::{AbiVariant, WasmSignature, WasmType}, - Document, InterfaceId, WorldId, + Function, InterfaceId, Resolve, WorldId, WorldItem, }; fn is_canonical_function(name: &str) -> bool { @@ -154,17 +154,24 @@ pub fn validate_module<'a>( } let types = types.unwrap(); - let world = &metadata.doc.worlds[metadata.world]; + let world = &metadata.resolve.worlds[metadata.world]; for (name, funcs) in &import_funcs { + // An empty module name is indicative of the top-level import namespace, + // so look for top-level functions here. if name.is_empty() { - bail!("module imports from an empty module name"); + validate_imports_top_level(&metadata.resolve, metadata.world, funcs, &types)?; + let funcs = funcs.keys().cloned().collect(); + let prev = ret.required_imports.insert("", funcs); + assert!(prev.is_none()); + continue; } match world.imports.get(*name) { - Some(interface) => { - validate_imported_interface(&metadata.doc, *interface, name, funcs, &types)?; - let funcs = funcs.into_iter().map(|(f, _ty)| *f).collect(); + Some(WorldItem::Interface(interface)) => { + let funcs = + validate_imported_interface(&metadata.resolve, *interface, name, funcs, &types) + .with_context(|| format!("failed to validate import interface `{name}`"))?; let prev = ret.required_imports.insert(name, funcs); assert!(prev.is_none()); } @@ -175,20 +182,14 @@ pub fn validate_module<'a>( map.insert(func, ty.clone()); } } - None => bail!("module requires an import interface named `{}`", name), + None | Some(WorldItem::Function(_)) => { + bail!("module requires an import interface named `{}`", name) + } } } - if let Some(interface) = world.default { - validate_exported_interface(&metadata.doc, interface, None, &export_funcs, &types)?; - } - - for (name, interface) in world.exports.iter() { - if name.is_empty() { - bail!("cannot export an interface with an empty name"); - } - - validate_exported_interface(&metadata.doc, *interface, Some(name), &export_funcs, &types)?; + for (name, item) in world.exports.iter() { + validate_exported_item(&metadata.resolve, item, name, &export_funcs, &types)?; } Ok(ret) @@ -242,7 +243,7 @@ pub struct ValidatedAdapter<'a> { /// didn't accidentally break the wasm module. pub fn validate_adapter_module<'a>( bytes: &[u8], - doc: &'a Document, + resolve: &'a Resolve, world: WorldId, metadata: &'a ModuleMetadata, required: &IndexMap, @@ -339,15 +340,46 @@ pub fn validate_adapter_module<'a>( if name == MAIN_MODULE_IMPORT_NAME { ret.needs_core_exports .extend(funcs.iter().map(|(name, _ty)| name.to_string())); - } else { - let interface = *doc.worlds[world] + continue; + } + + // An empty module name is indicative of the top-level import namespace, + // so look for top-level functions here. + if name.is_empty() { + validate_imports_top_level(&resolve, world, &funcs, &types)?; + let funcs = resolve.worlds[world] .imports - .get(name) - .ok_or_else(|| anyhow!("adapter module imports unknown module `{name}`"))?; - let required_funcs = validate_imported_interface(doc, interface, name, &funcs, &types)?; - let iface_name = &doc.interfaces[interface].name; - assert_eq!(iface_name, name); - ret.required_imports.insert(iface_name, required_funcs); + .iter() + .filter_map(|(name, item)| match item { + WorldItem::Function(_) if funcs.contains_key(name.as_str()) => { + Some(name.as_str()) + } + _ => None, + }) + .collect(); + ret.required_imports.insert("", funcs); + continue; + } + + match resolve.worlds[world].imports.get_full(name) { + Some((_, name, WorldItem::Interface(interface))) => { + validate_imported_interface(resolve, *interface, name, &funcs, &types) + .with_context(|| format!("failed to validate import interface `{name}`"))?; + let funcs = resolve.interfaces[*interface] + .functions + .keys() + .map(|s| s.as_str()) + .filter(|s| funcs.contains_key(s)) + .collect(); + let prev = ret.required_imports.insert(name, funcs); + assert!(prev.is_none()); + } + None | Some((_, _, WorldItem::Function(_))) => { + bail!( + "adapter module requires an import interface named `{}`", + name + ) + } } } @@ -375,8 +407,26 @@ pub fn validate_adapter_module<'a>( Ok(ret) } +fn validate_imports_top_level<'a>( + resolve: &Resolve, + world: WorldId, + funcs: &IndexMap<&'a str, u32>, + types: &Types, +) -> Result<()> { + for (name, ty) in funcs { + let func = match resolve.worlds[world].imports.get(*name) { + Some(WorldItem::Function(func)) => func, + Some(_) => bail!("expected world top-level import `{name}` to be a function"), + None => bail!("no top-level imported function `{name}` specified"), + }; + let ty = types.func_type_at(*ty).unwrap(); + validate_func(resolve, ty, func, AbiVariant::GuestImport)?; + } + Ok(()) +} + fn validate_imported_interface<'a>( - doc: &'a Document, + resolve: &'a Resolve, interface: InterfaceId, name: &str, imports: &IndexMap<&str, u32>, @@ -384,10 +434,9 @@ fn validate_imported_interface<'a>( ) -> Result> { let mut funcs = IndexSet::new(); for (func_name, ty) in imports { - let f = doc.interfaces[interface] + let f = resolve.interfaces[interface] .functions - .iter() - .find(|f| f.name == *func_name) + .get(*func_name) .ok_or_else(|| { anyhow!( "import interface `{}` is missing function `{}` that is required by the module", @@ -396,19 +445,8 @@ fn validate_imported_interface<'a>( ) })?; - let expected = wasm_sig_to_func_type(doc.wasm_signature(AbiVariant::GuestImport, f)); let ty = types.func_type_at(*ty).unwrap(); - if ty != &expected { - bail!( - "type mismatch for function `{}` on imported interface `{}`: expected `{:?} -> {:?}` but found `{:?} -> {:?}`", - f.name, - name, - expected.params(), - expected.results(), - ty.params(), - ty.results() - ); - } + validate_func(resolve, ty, f, AbiVariant::GuestImport)?; funcs.insert(f.name.as_str()); } @@ -416,53 +454,56 @@ fn validate_imported_interface<'a>( Ok(funcs) } -fn validate_exported_interface( - doc: &Document, - interface: InterfaceId, - export_name: Option<&str>, +fn validate_func( + resolve: &Resolve, + ty: &wasmparser::FuncType, + func: &Function, + abi: AbiVariant, +) -> Result<()> { + let expected = wasm_sig_to_func_type(resolve.wasm_signature(abi, func)); + if ty != &expected { + bail!( + "type mismatch for function `{}`: expected `{:?} -> {:?}` but found `{:?} -> {:?}`", + func.name, + expected.params(), + expected.results(), + ty.params(), + ty.results() + ); + } + + Ok(()) +} + +fn validate_exported_item( + resolve: &Resolve, + item: &WorldItem, + export_name: &str, exports: &IndexMap<&str, u32>, types: &Types, ) -> Result<()> { - for f in &doc.interfaces[interface].functions { - let expected_export_name = f.core_export_name(export_name); + let validate = |func: &Function, name: Option<&str>| { + let expected_export_name = func.core_export_name(name); match exports.get(expected_export_name.as_ref()) { Some(func_index) => { - let expected_ty = - wasm_sig_to_func_type(doc.wasm_signature(AbiVariant::GuestExport, f)); let ty = types.function_at(*func_index).unwrap(); - if ty == &expected_ty { - continue; - } - match export_name { - Some(name) => { - bail!( - "type mismatch for function `{}` from exported interface `{name}`: \ - expected `{:?} -> {:?}` but found `{:?} -> {:?}`", - f.name, - expected_ty.params(), - expected_ty.results(), - ty.params(), - ty.results() - ); - } - None => { - bail!( - "type mismatch for default interface function `{}`: \ - expected `{:?} -> {:?}` but found `{:?} -> {:?}`", - f.name, - expected_ty.params(), - expected_ty.results(), - ty.params(), - ty.results() - ); - } - } + validate_func(resolve, ty, func, AbiVariant::GuestExport) } None => bail!( "module does not export required function `{}`", expected_export_name ), } + }; + match item { + WorldItem::Function(func) => validate(func, None)?, + WorldItem::Interface(interface) => { + for (_, f) in &resolve.interfaces[*interface].functions { + validate(f, Some(export_name)).with_context(|| { + format!("failed to validate exported interface `{export_name}`") + })?; + } + } } Ok(()) diff --git a/crates/wit-component/tests/components.rs b/crates/wit-component/tests/components.rs index 754ef901eb..98e2b7b7a0 100644 --- a/crates/wit-component/tests/components.rs +++ b/crates/wit-component/tests/components.rs @@ -1,22 +1,9 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, Result}; use pretty_assertions::assert_eq; use std::{fs, path::Path}; use wasm_encoder::{Encode, Section}; -use wit_component::{ComponentEncoder, StringEncoding}; -use wit_parser::Document; - -fn read_adapters(dir: &Path) -> Result, Document)>> { - glob::glob(dir.join("adapt-*.wat").to_str().unwrap())? - .map(|p| { - let p = p?; - let adapter = - wat::parse_file(&p).with_context(|| format!("expected file `{}`", p.display()))?; - let stem = p.file_stem().unwrap().to_str().unwrap(); - let doc = read_document(dir, &format!("{stem}-"))?; - Ok((stem.trim_start_matches("adapt-").to_string(), adapter, doc)) - }) - .collect::>() -} +use wit_component::{ComponentEncoder, DecodedWasm, DocumentPrinter, StringEncoding}; +use wit_parser::{Resolve, UnresolvedPackage}; /// Tests the encoding of components. /// @@ -24,21 +11,27 @@ fn read_adapters(dir: &Path) -> Result, Document)>> { /// /// The expected input files for a test case are: /// -/// * [required] `module.wat` - contains the core module definition to be encoded -/// as a component. -/// * [optional] `default.wit` - represents the component's default interface. -/// * [optional] `export-.wit` - represents an interface exported by the component. -/// * [optional] `import-.wit` - represents an interface imported by the component. +/// * [required] `module.wat` - contains the core module definition to be +/// encoded as a component. +/// * [required] `module.wit` - WIT package describing the interface of +/// `module.wat`. Must have a `default world` +/// * [optional] `adapt-$name.wat` - optional adapter for the module name +/// `$name`, can be specified for multiple `$name`s +/// * [optional] `adapt-$name.wit` - required for each `*.wat` adapter to +/// describe imports/exports of the adapter. /// /// And the output files are one of the following: /// -/// * `component.wat` - the expected encoded component in text format if the encoding -/// is expected to succeed. -/// * `error.txt` - the expected error message if the encoding is expected to fail. +/// * `component.wat` - the expected encoded component in text format if the +/// encoding is expected to succeed. +/// * `component.wit` - if `component.wat` exists this is the inferred interface +/// of the component. +/// * `error.txt` - the expected error message if the encoding is expected to +/// fail. /// -/// The test encodes a component based on the input files. If the encoding succeeds, -/// it expects the output to match `component.wat`. If the encoding fails, it expects -/// the output to match `error.txt`. +/// The test encodes a component based on the input files. If the encoding +/// succeeds, it expects the output to match `component.wat`. If the encoding +/// fails, it expects the output to match `error.txt`. /// /// Run the test with the environment variable `BLESS` set to update /// either `component.wat` or `error.txt` depending on the outcome of the encoding. @@ -56,126 +49,82 @@ fn component_encoding_via_flags() -> Result<()> { println!("testing {test_case}"); let module_path = path.join("module.wat"); + let module = read_core_module(&module_path)?; + let mut encoder = ComponentEncoder::default().module(&module)?.validate(true); + encoder = add_adapters(encoder, &path)?; let component_path = path.join("component.wat"); + let component_wit_path = path.join("component.wit"); let error_path = path.join("error.txt"); - let module = wat::parse_file(&module_path) - .with_context(|| format!("expected file `{}`", module_path.display()))?; - let document = read_document(&path, "")?; - - // Test the generated component using the `.document(...)` method - { - println!("test using `.document(...)`"); - let mut encoder = ComponentEncoder::default() - .module(&module)? - .validate(true) - .document(document.clone(), StringEncoding::UTF8)?; - encoder = add_adapters(encoder, &path)?; - assert_output(test_case, &encoder, &component_path, &error_path)?; - } - - // Test the generated component by embedding the component type - // information in a custom section. - { - println!("test using custom section"); - let mut module = module.clone(); - let contents = wit_component::metadata::encode( - &document, - document.default_world()?, - StringEncoding::UTF8, - ); - let section = wasm_encoder::CustomSection { - name: "component-type", - data: &contents, - }; - module.push(section.id()); - section.encode(&mut module); - // Now parse run the `module` alone through the encoder without extra - // information about interfaces to ensure it still works as before. - let mut encoder = ComponentEncoder::default().module(&module)?.validate(true); - encoder = add_adapters(encoder, &path)?; - assert_output(test_case, &encoder, &component_path, &error_path)?; - } - - // Test the `--types-only` component is valid - { - println!("test using --types-only"); - let mut encoder = ComponentEncoder::default() - .validate(true) - .types_only(true) - .document(document.clone(), StringEncoding::UTF8)?; - encoder = add_adapters(encoder, &path)?; - encoder.encode()?; - } + let bytes = match encoder.encode() { + Ok(bytes) => bytes, + Err(err) => { + assert_output(&format!("{err:?}"), &error_path)?; + continue; + } + }; + let wat = wasmprinter::print_bytes(&bytes)?; + assert_output(&wat, &component_path)?; + let (doc, resolve) = match wit_component::decode("component", &bytes)? { + DecodedWasm::WitPackage(..) => unreachable!(), + DecodedWasm::Component(resolve, world) => (resolve.worlds[world].document, resolve), + }; + let wit = DocumentPrinter::default().print(&resolve, doc)?; + assert_output(&wit, &component_wit_path)?; } Ok(()) } -fn read_document(path: &Path, prefix: &str) -> Result { - let wit_path = path.join(&format!("{prefix}world.wit")); - Document::parse_file(&wit_path) -} - fn add_adapters(mut encoder: ComponentEncoder, path: &Path) -> Result { - for (name, mut wasm, doc) in read_adapters(path)? { - // Create a `component-type` custom section by slurping up `imports` as - // a "world" and encoding it. - let contents = - wit_component::metadata::encode(&doc, doc.default_world()?, StringEncoding::UTF8); - let section = wasm_encoder::CustomSection { - name: "component-type", - data: &contents, - }; - wasm.push(section.id()); - section.encode(&mut wasm); - - // Then register our new wasm blob which has the necessary custom - // section. + for adapter in glob::glob(path.join("adapt-*.wat").to_str().unwrap())? { + let adapter = adapter?; + let wasm = read_core_module(&adapter)?; + let stem = adapter.file_stem().unwrap().to_str().unwrap(); + let name = stem.trim_start_matches("adapt-"); encoder = encoder.adapter(&name, &wasm)?; } Ok(encoder) } -fn assert_output( - test_case: &str, - encoder: &ComponentEncoder, - component_path: &Path, - error_path: &Path, -) -> Result<()> { - let r = encoder.encode(); - - let (output, baseline_path) = if error_path.is_file() { - match r { - Ok(_) => bail!("encoding should fail for test case `{}`", test_case), - Err(e) => (e.to_string(), &error_path), - } - } else { - ( - wasmprinter::print_bytes( - &r.with_context(|| format!("failed to encode for test case `{}`", test_case))?, - ) - .with_context(|| { - format!( - "failed to print component bytes for test case `{}`", - test_case - ) - })?, - &component_path, - ) +/// Parses the core wasm module at `path`, expected as a `*.wat` file. +/// +/// Additionally expects a sibling `*.wit` file which will be used to encode +/// metadata into the binary returned here. +fn read_core_module(path: &Path) -> Result> { + let mut wasm = wat::parse_file(path)?; + let interface = path.with_extension("wit"); + let mut resolve = Resolve::default(); + let pkg = resolve.push( + UnresolvedPackage::parse_file(&interface)?, + &Default::default(), + )?; + let doc = *resolve.packages[pkg].documents.iter().next().unwrap().1; + let doc = &resolve.documents[doc]; + let world = doc + .default_world + .ok_or_else(|| anyhow!("no default world specified"))?; + let encoded = wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8)?; + + let section = wasm_encoder::CustomSection { + name: "component-type", + data: &encoded, }; + wasm.push(section.id()); + section.encode(&mut wasm); + Ok(wasm) +} +fn assert_output(contents: &str, path: &Path) -> Result<()> { + let contents = contents.replace("\r\n", "\n"); if std::env::var_os("BLESS").is_some() { - fs::write(&baseline_path, output)?; + fs::write(path, contents)?; } else { assert_eq!( - fs::read_to_string(&baseline_path)? - .replace("\r\n", "\n") - .trim(), - output.trim(), - "failed baseline comparison for test case `{}` ({})", - test_case, - baseline_path.display(), + fs::read_to_string(path)?.replace("\r\n", "\n").trim(), + contents.trim(), + "failed baseline comparison ({})", + path.display(), ); } Ok(()) diff --git a/crates/wit-component/tests/components/adapt-empty-interface/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-empty-interface/adapt-old.wit similarity index 77% rename from crates/wit-component/tests/components/adapt-empty-interface/adapt-old-world.wit rename to crates/wit-component/tests/components/adapt-empty-interface/adapt-old.wit index 462f31119c..e38000c63e 100644 --- a/crates/wit-component/tests/components/adapt-empty-interface/adapt-old-world.wit +++ b/crates/wit-component/tests/components/adapt-empty-interface/adapt-old.wit @@ -1,4 +1,4 @@ -world new { +default world new { import new: interface { thunk-that-is-not-called: func() } diff --git a/crates/wit-component/tests/components/adapt-empty-interface/component.wit b/crates/wit-component/tests/components/adapt-empty-interface/component.wit new file mode 100644 index 0000000000..da09ea6eeb --- /dev/null +++ b/crates/wit-component/tests/components/adapt-empty-interface/component.wit @@ -0,0 +1,2 @@ +default world component { +} diff --git a/crates/wit-component/tests/components/adapt-empty-interface/module.wit b/crates/wit-component/tests/components/adapt-empty-interface/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-empty-interface/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-empty-interface/world.wit b/crates/wit-component/tests/components/adapt-empty-interface/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-empty-interface/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-export-default/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-export-default/adapt-old-world.wit deleted file mode 100644 index e1b55d8198..0000000000 --- a/crates/wit-component/tests/components/adapt-export-default/adapt-old-world.wit +++ /dev/null @@ -1,5 +0,0 @@ -world new { - default export interface { - entrypoint: func() - } -} diff --git a/crates/wit-component/tests/components/adapt-export-default/adapt-old.wit b/crates/wit-component/tests/components/adapt-export-default/adapt-old.wit new file mode 100644 index 0000000000..ad8e637513 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-default/adapt-old.wit @@ -0,0 +1,3 @@ +default world new { + export entrypoint: func() +} diff --git a/crates/wit-component/tests/components/adapt-export-default/component.wat b/crates/wit-component/tests/components/adapt-export-default/component.wat index 443cf11ba8..833335c7e2 100644 --- a/crates/wit-component/tests/components/adapt-export-default/component.wat +++ b/crates/wit-component/tests/components/adapt-export-default/component.wat @@ -18,8 +18,8 @@ (with "__main_module__" (instance 1)) ) ) - (alias core export 2 "entrypoint" (core func (;1;))) (type (;0;) (func)) + (alias core export 2 "entrypoint" (core func (;1;))) (func (;0;) (type 0) (canon lift (core func 1))) (export (;1;) "entrypoint" (func 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/adapt-export-default/component.wit b/crates/wit-component/tests/components/adapt-export-default/component.wit new file mode 100644 index 0000000000..9368d1d606 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-default/component.wit @@ -0,0 +1,3 @@ +default world component { + export entrypoint: func() +} diff --git a/crates/wit-component/tests/components/adapt-export-default/module.wit b/crates/wit-component/tests/components/adapt-export-default/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-default/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-export-default/world.wit b/crates/wit-component/tests/components/adapt-export-default/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-export-default/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-export-namespaced/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-export-namespaced/adapt-old-world.wit deleted file mode 100644 index 179ed9b1bb..0000000000 --- a/crates/wit-component/tests/components/adapt-export-namespaced/adapt-old-world.wit +++ /dev/null @@ -1,7 +0,0 @@ -interface new { - entrypoint: func() -} - -world brave-new-world { - export new: new -} diff --git a/crates/wit-component/tests/components/adapt-export-namespaced/adapt-old.wit b/crates/wit-component/tests/components/adapt-export-namespaced/adapt-old.wit new file mode 100644 index 0000000000..4e4ff9c6bf --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-namespaced/adapt-old.wit @@ -0,0 +1,7 @@ +interface new { + entrypoint: func() +} + +default world brave-new-world { + export new: self.new +} diff --git a/crates/wit-component/tests/components/adapt-export-namespaced/component.wat b/crates/wit-component/tests/components/adapt-export-namespaced/component.wat index 0871a2bc80..20cd105de8 100644 --- a/crates/wit-component/tests/components/adapt-export-namespaced/component.wat +++ b/crates/wit-component/tests/components/adapt-export-namespaced/component.wat @@ -18,8 +18,8 @@ (with "__main_module__" (instance 1)) ) ) - (alias core export 2 "new#entrypoint" (core func (;1;))) (type (;0;) (func)) + (alias core export 2 "new#entrypoint" (core func (;1;))) (func (;0;) (type 0) (canon lift (core func 1))) (instance (;0;) (export "entrypoint" (func 0)) diff --git a/crates/wit-component/tests/components/adapt-export-namespaced/component.wit b/crates/wit-component/tests/components/adapt-export-namespaced/component.wit new file mode 100644 index 0000000000..90160cd85d --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-namespaced/component.wit @@ -0,0 +1,7 @@ +interface new { + entrypoint: func() +} + +default world component { + export new: self.new +} diff --git a/crates/wit-component/tests/components/adapt-export-namespaced/module.wit b/crates/wit-component/tests/components/adapt-export-namespaced/module.wit new file mode 100644 index 0000000000..234a91911c --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-namespaced/module.wit @@ -0,0 +1 @@ +default world foo {} diff --git a/crates/wit-component/tests/components/adapt-export-namespaced/world.wit b/crates/wit-component/tests/components/adapt-export-namespaced/world.wit deleted file mode 100644 index 911d40a020..0000000000 --- a/crates/wit-component/tests/components/adapt-export-namespaced/world.wit +++ /dev/null @@ -1 +0,0 @@ -world foo {} diff --git a/crates/wit-component/tests/components/adapt-export-reallocs/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-export-reallocs/adapt-old-world.wit deleted file mode 100644 index c1703a3cbe..0000000000 --- a/crates/wit-component/tests/components/adapt-export-reallocs/adapt-old-world.wit +++ /dev/null @@ -1,8 +0,0 @@ -world brave-new-world { - import new: interface { - read: func(amt: u32) -> list - } - default export interface { - entrypoint: func(args: list) - } -} diff --git a/crates/wit-component/tests/components/adapt-export-reallocs/adapt-old.wit b/crates/wit-component/tests/components/adapt-export-reallocs/adapt-old.wit new file mode 100644 index 0000000000..d342fe79d2 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-reallocs/adapt-old.wit @@ -0,0 +1,6 @@ +default world brave-new-world { + import new: interface { + read: func(amt: u32) -> list + } + export entrypoint: func(args: list) +} diff --git a/crates/wit-component/tests/components/adapt-export-reallocs/component.wat b/crates/wit-component/tests/components/adapt-export-reallocs/component.wat index d5ad819db4..aaa69c4899 100644 --- a/crates/wit-component/tests/components/adapt-export-reallocs/component.wat +++ b/crates/wit-component/tests/components/adapt-export-reallocs/component.wat @@ -110,9 +110,9 @@ (with "" (instance 5)) ) ) - (alias core export 4 "entrypoint" (core func (;6;))) (type (;1;) (list string)) (type (;2;) (func (param "args" 1))) + (alias core export 4 "entrypoint" (core func (;6;))) (func (;1;) (type 2) (canon lift (core func 6) (memory 0) (realloc 2) string-encoding=utf8)) (export (;2;) "entrypoint" (func 1)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/adapt-export-reallocs/component.wit b/crates/wit-component/tests/components/adapt-export-reallocs/component.wit new file mode 100644 index 0000000000..49897fff21 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-reallocs/component.wit @@ -0,0 +1,8 @@ +interface new { + read: func(amt: u32) -> list +} + +default world component { + import new: self.new + export entrypoint: func(args: list) +} diff --git a/crates/wit-component/tests/components/adapt-export-reallocs/module.wit b/crates/wit-component/tests/components/adapt-export-reallocs/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-reallocs/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-export-reallocs/world.wit b/crates/wit-component/tests/components/adapt-export-reallocs/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-export-reallocs/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-export-save-args/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-export-save-args/adapt-old-world.wit deleted file mode 100644 index ae5567533a..0000000000 --- a/crates/wit-component/tests/components/adapt-export-save-args/adapt-old-world.wit +++ /dev/null @@ -1,5 +0,0 @@ -world brave-new-world { - default export interface { - entrypoint: func(nargs: u32) - } -} diff --git a/crates/wit-component/tests/components/adapt-export-save-args/adapt-old.wit b/crates/wit-component/tests/components/adapt-export-save-args/adapt-old.wit new file mode 100644 index 0000000000..3f6bd6a293 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-save-args/adapt-old.wit @@ -0,0 +1,3 @@ +default world brave-new-world { + export entrypoint: func(nargs: u32) +} diff --git a/crates/wit-component/tests/components/adapt-export-save-args/component.wat b/crates/wit-component/tests/components/adapt-export-save-args/component.wat index 233601f396..c2f2da94f6 100644 --- a/crates/wit-component/tests/components/adapt-export-save-args/component.wat +++ b/crates/wit-component/tests/components/adapt-export-save-args/component.wat @@ -66,8 +66,8 @@ (with "" (instance 5)) ) ) - (alias core export 4 "entrypoint" (core func (;3;))) (type (;0;) (func (param "nargs" u32))) + (alias core export 4 "entrypoint" (core func (;3;))) (func (;0;) (type 0) (canon lift (core func 3))) (export (;1;) "entrypoint" (func 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/adapt-export-save-args/component.wit b/crates/wit-component/tests/components/adapt-export-save-args/component.wit new file mode 100644 index 0000000000..e5a72119ea --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-save-args/component.wit @@ -0,0 +1,3 @@ +default world component { + export entrypoint: func(nargs: u32) +} diff --git a/crates/wit-component/tests/components/adapt-export-save-args/module.wit b/crates/wit-component/tests/components/adapt-export-save-args/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-save-args/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-export-save-args/world.wit b/crates/wit-component/tests/components/adapt-export-save-args/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-export-save-args/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-inject-stack/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-inject-stack/adapt-old.wit similarity index 69% rename from crates/wit-component/tests/components/adapt-inject-stack/adapt-old-world.wit rename to crates/wit-component/tests/components/adapt-inject-stack/adapt-old.wit index 45a9913be5..7a56eb164a 100644 --- a/crates/wit-component/tests/components/adapt-inject-stack/adapt-old-world.wit +++ b/crates/wit-component/tests/components/adapt-inject-stack/adapt-old.wit @@ -1,4 +1,4 @@ -world brave-new-world { +default world brave-new-world { import new: interface { get-two: func() -> (a: u32, b: u32) } diff --git a/crates/wit-component/tests/components/adapt-inject-stack/component.wit b/crates/wit-component/tests/components/adapt-inject-stack/component.wit new file mode 100644 index 0000000000..d570b2929b --- /dev/null +++ b/crates/wit-component/tests/components/adapt-inject-stack/component.wit @@ -0,0 +1,7 @@ +interface new { + get-two: func() -> (a: u32, b: u32) +} + +default world component { + import new: self.new +} diff --git a/crates/wit-component/tests/components/adapt-inject-stack/module.wit b/crates/wit-component/tests/components/adapt-inject-stack/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-inject-stack/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-inject-stack/world.wit b/crates/wit-component/tests/components/adapt-inject-stack/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-inject-stack/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-list-return/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-list-return/adapt-old.wit similarity index 65% rename from crates/wit-component/tests/components/adapt-list-return/adapt-old-world.wit rename to crates/wit-component/tests/components/adapt-list-return/adapt-old.wit index 658b39af1d..d321768c2f 100644 --- a/crates/wit-component/tests/components/adapt-list-return/adapt-old-world.wit +++ b/crates/wit-component/tests/components/adapt-list-return/adapt-old.wit @@ -1,4 +1,4 @@ -world brave-new-world { +default world brave-new-world { import new: interface { read: func() -> list } diff --git a/crates/wit-component/tests/components/adapt-list-return/component.wit b/crates/wit-component/tests/components/adapt-list-return/component.wit new file mode 100644 index 0000000000..cdcc76911c --- /dev/null +++ b/crates/wit-component/tests/components/adapt-list-return/component.wit @@ -0,0 +1,7 @@ +interface new { + read: func() -> list +} + +default world component { + import new: self.new +} diff --git a/crates/wit-component/tests/components/adapt-list-return/module.wit b/crates/wit-component/tests/components/adapt-list-return/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-list-return/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-list-return/world.wit b/crates/wit-component/tests/components/adapt-list-return/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-list-return/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-unused/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-memory-simple/adapt-old.wit similarity index 64% rename from crates/wit-component/tests/components/adapt-unused/adapt-old-world.wit rename to crates/wit-component/tests/components/adapt-memory-simple/adapt-old.wit index b16de0ffd0..5a86aec124 100644 --- a/crates/wit-component/tests/components/adapt-unused/adapt-old-world.wit +++ b/crates/wit-component/tests/components/adapt-memory-simple/adapt-old.wit @@ -1,4 +1,4 @@ -world brave-new-world { +default world brave-new-world { import new: interface { log: func(s: string) } diff --git a/crates/wit-component/tests/components/adapt-memory-simple/component.wit b/crates/wit-component/tests/components/adapt-memory-simple/component.wit new file mode 100644 index 0000000000..dc408de3cb --- /dev/null +++ b/crates/wit-component/tests/components/adapt-memory-simple/component.wit @@ -0,0 +1,7 @@ +interface new { + log: func(s: string) +} + +default world component { + import new: self.new +} diff --git a/crates/wit-component/tests/components/adapt-memory-simple/module.wit b/crates/wit-component/tests/components/adapt-memory-simple/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-memory-simple/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-memory-simple/world.wit b/crates/wit-component/tests/components/adapt-memory-simple/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-memory-simple/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-missing-memory/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-missing-memory/adapt-old.wit similarity index 64% rename from crates/wit-component/tests/components/adapt-missing-memory/adapt-old-world.wit rename to crates/wit-component/tests/components/adapt-missing-memory/adapt-old.wit index b16de0ffd0..5a86aec124 100644 --- a/crates/wit-component/tests/components/adapt-missing-memory/adapt-old-world.wit +++ b/crates/wit-component/tests/components/adapt-missing-memory/adapt-old.wit @@ -1,4 +1,4 @@ -world brave-new-world { +default world brave-new-world { import new: interface { log: func(s: string) } diff --git a/crates/wit-component/tests/components/adapt-missing-memory/module.wit b/crates/wit-component/tests/components/adapt-missing-memory/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-missing-memory/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-missing-memory/world.wit b/crates/wit-component/tests/components/adapt-missing-memory/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-missing-memory/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-multiple/adapt-foo-world.wit b/crates/wit-component/tests/components/adapt-multiple/adapt-foo.wit similarity index 76% rename from crates/wit-component/tests/components/adapt-multiple/adapt-foo-world.wit rename to crates/wit-component/tests/components/adapt-multiple/adapt-foo.wit index bd18fe09fa..bf449c18ec 100644 --- a/crates/wit-component/tests/components/adapt-multiple/adapt-foo-world.wit +++ b/crates/wit-component/tests/components/adapt-multiple/adapt-foo.wit @@ -1,4 +1,4 @@ -world new-foo-world { +default world new-foo-world { import other1: interface { foo: func() } diff --git a/crates/wit-component/tests/components/adapt-multiple/component.wit b/crates/wit-component/tests/components/adapt-multiple/component.wit new file mode 100644 index 0000000000..5b111ba21f --- /dev/null +++ b/crates/wit-component/tests/components/adapt-multiple/component.wit @@ -0,0 +1,12 @@ +interface other1 { + foo: func() +} + +interface other2 { + bar: func() +} + +default world component { + import other1: self.other1 + import other2: self.other2 +} diff --git a/crates/wit-component/tests/components/adapt-multiple/module.wit b/crates/wit-component/tests/components/adapt-multiple/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-multiple/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-multiple/world.wit b/crates/wit-component/tests/components/adapt-multiple/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-multiple/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-preview1/adapt-wasi_snapshot_preview1.wat b/crates/wit-component/tests/components/adapt-preview1/adapt-wasi-snapshot-preview1.wat similarity index 100% rename from crates/wit-component/tests/components/adapt-preview1/adapt-wasi_snapshot_preview1.wat rename to crates/wit-component/tests/components/adapt-preview1/adapt-wasi-snapshot-preview1.wat diff --git a/crates/wit-component/tests/components/adapt-preview1/adapt-wasi_snapshot_preview1-world.wit b/crates/wit-component/tests/components/adapt-preview1/adapt-wasi-snapshot-preview1.wit similarity index 82% rename from crates/wit-component/tests/components/adapt-preview1/adapt-wasi_snapshot_preview1-world.wit rename to crates/wit-component/tests/components/adapt-preview1/adapt-wasi-snapshot-preview1.wit index 7a7ea2370b..65035733e0 100644 --- a/crates/wit-component/tests/components/adapt-preview1/adapt-wasi_snapshot_preview1-world.wit +++ b/crates/wit-component/tests/components/adapt-preview1/adapt-wasi-snapshot-preview1.wit @@ -7,6 +7,6 @@ interface my-wasi { something-not-used: func() } -world adapter { - import my-wasi: my-wasi +default world adapter { + import my-wasi: self.my-wasi } diff --git a/crates/wit-component/tests/components/adapt-preview1/component.wat b/crates/wit-component/tests/components/adapt-preview1/component.wat index 6ec2d54179..8b961de798 100644 --- a/crates/wit-component/tests/components/adapt-preview1/component.wat +++ b/crates/wit-component/tests/components/adapt-preview1/component.wat @@ -18,8 +18,8 @@ (type (;1;) (func (param i32))) (type (;2;) (func (param i32 i32) (result i32))) (import "foo" "foo" (func (;0;) (type 0))) - (import "wasi_snapshot_preview1" "proc_exit" (func (;1;) (type 1))) - (import "wasi_snapshot_preview1" "random_get" (func (;2;) (type 2))) + (import "wasi-snapshot-preview1" "proc_exit" (func (;1;) (type 1))) + (import "wasi-snapshot-preview1" "random_get" (func (;2;) (type 2))) (memory (;0;) 1) (export "memory" (memory 0)) ) @@ -40,20 +40,20 @@ (core module (;2;) (type (;0;) (func (param i32))) (type (;1;) (func (param i32 i32) (result i32))) - (func $adapt-wasi_snapshot_preview1-proc_exit (;0;) (type 0) (param i32) + (func $adapt-wasi-snapshot-preview1-proc_exit (;0;) (type 0) (param i32) local.get 0 i32.const 0 call_indirect (type 0) ) - (func $adapt-wasi_snapshot_preview1-random_get (;1;) (type 1) (param i32 i32) (result i32) + (func $adapt-wasi-snapshot-preview1-random_get (;1;) (type 1) (param i32 i32) (result i32) local.get 0 local.get 1 i32.const 1 call_indirect (type 1) ) (table (;0;) 2 2 funcref) - (export "0" (func $adapt-wasi_snapshot_preview1-proc_exit)) - (export "1" (func $adapt-wasi_snapshot_preview1-random_get)) + (export "0" (func $adapt-wasi-snapshot-preview1-proc_exit)) + (export "1" (func $adapt-wasi-snapshot-preview1-random_get)) (export "$imports" (table 0)) ) (core module (;3;) @@ -78,7 +78,7 @@ ) (core instance (;3;) (instantiate 0 (with "foo" (instance 1)) - (with "wasi_snapshot_preview1" (instance 2)) + (with "wasi-snapshot-preview1" (instance 2)) ) ) (alias core export 3 "memory" (core memory (;0;))) diff --git a/crates/wit-component/tests/components/adapt-preview1/component.wit b/crates/wit-component/tests/components/adapt-preview1/component.wit new file mode 100644 index 0000000000..25d767a1d2 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-preview1/component.wit @@ -0,0 +1,12 @@ +interface foo { + foo: func() +} + +interface my-wasi { + proc-exit: func(code: u32) +} + +default world component { + import foo: self.foo + import my-wasi: self.my-wasi +} diff --git a/crates/wit-component/tests/components/adapt-preview1/module.wat b/crates/wit-component/tests/components/adapt-preview1/module.wat index 370ede67ac..70449b7e35 100644 --- a/crates/wit-component/tests/components/adapt-preview1/module.wat +++ b/crates/wit-component/tests/components/adapt-preview1/module.wat @@ -3,8 +3,8 @@ (import "foo" "foo" (func)) ;; import some wasi functions - (import "wasi_snapshot_preview1" "proc_exit" (func (param i32))) - (import "wasi_snapshot_preview1" "random_get" (func (param i32 i32) (result i32))) + (import "wasi-snapshot-preview1" "proc_exit" (func (param i32))) + (import "wasi-snapshot-preview1" "random_get" (func (param i32 i32) (result i32))) ;; required by wasi (memory (export "memory") 1) diff --git a/crates/wit-component/tests/components/adapt-preview1/world.wit b/crates/wit-component/tests/components/adapt-preview1/module.wit similarity index 65% rename from crates/wit-component/tests/components/adapt-preview1/world.wit rename to crates/wit-component/tests/components/adapt-preview1/module.wit index 126383780e..b35cf9a1ec 100644 --- a/crates/wit-component/tests/components/adapt-preview1/world.wit +++ b/crates/wit-component/tests/components/adapt-preview1/module.wit @@ -1,4 +1,4 @@ -world my-world { +default world my-world { import foo: interface { foo: func() } diff --git a/crates/wit-component/tests/components/adapt-memory-simple/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-unused/adapt-old.wit similarity index 64% rename from crates/wit-component/tests/components/adapt-memory-simple/adapt-old-world.wit rename to crates/wit-component/tests/components/adapt-unused/adapt-old.wit index b16de0ffd0..5a86aec124 100644 --- a/crates/wit-component/tests/components/adapt-memory-simple/adapt-old-world.wit +++ b/crates/wit-component/tests/components/adapt-unused/adapt-old.wit @@ -1,4 +1,4 @@ -world brave-new-world { +default world brave-new-world { import new: interface { log: func(s: string) } diff --git a/crates/wit-component/tests/components/adapt-unused/component.wit b/crates/wit-component/tests/components/adapt-unused/component.wit new file mode 100644 index 0000000000..da09ea6eeb --- /dev/null +++ b/crates/wit-component/tests/components/adapt-unused/component.wit @@ -0,0 +1,2 @@ +default world component { +} diff --git a/crates/wit-component/tests/components/adapt-unused/module.wit b/crates/wit-component/tests/components/adapt-unused/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-unused/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-unused/world.wit b/crates/wit-component/tests/components/adapt-unused/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-unused/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/default-export-sig-mismatch/error.txt b/crates/wit-component/tests/components/default-export-sig-mismatch/error.txt index fdb207a3b3..b7a8e003cd 100644 --- a/crates/wit-component/tests/components/default-export-sig-mismatch/error.txt +++ b/crates/wit-component/tests/components/default-export-sig-mismatch/error.txt @@ -1 +1 @@ -type mismatch for default interface function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file +type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file diff --git a/crates/wit-component/tests/components/default-export-sig-mismatch/module.wat b/crates/wit-component/tests/components/default-export-sig-mismatch/module.wat index 420a370fa9..38ea217899 100644 --- a/crates/wit-component/tests/components/default-export-sig-mismatch/module.wat +++ b/crates/wit-component/tests/components/default-export-sig-mismatch/module.wat @@ -1,3 +1,3 @@ (module (func (export "a") unreachable) -) \ No newline at end of file +) diff --git a/crates/wit-component/tests/components/default-export-sig-mismatch/module.wit b/crates/wit-component/tests/components/default-export-sig-mismatch/module.wit new file mode 100644 index 0000000000..b8fa3e1312 --- /dev/null +++ b/crates/wit-component/tests/components/default-export-sig-mismatch/module.wit @@ -0,0 +1,3 @@ +default world my-world { + export a: func(x: string) -> string +} diff --git a/crates/wit-component/tests/components/default-export-sig-mismatch/world.wit b/crates/wit-component/tests/components/default-export-sig-mismatch/world.wit deleted file mode 100644 index 6099af4fce..0000000000 --- a/crates/wit-component/tests/components/default-export-sig-mismatch/world.wit +++ /dev/null @@ -1,5 +0,0 @@ -world my-world { - default export interface { - a: func(x: string) -> string - } -} diff --git a/crates/wit-component/tests/components/empty-module-import/error.txt b/crates/wit-component/tests/components/empty-module-import/error.txt index fd0bd9faf8..3fc2443a8a 100644 --- a/crates/wit-component/tests/components/empty-module-import/error.txt +++ b/crates/wit-component/tests/components/empty-module-import/error.txt @@ -1 +1 @@ -module imports from an empty module name \ No newline at end of file +no top-level imported function `foo` specified \ No newline at end of file diff --git a/crates/wit-component/tests/components/empty-module-import/module.wit b/crates/wit-component/tests/components/empty-module-import/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/empty-module-import/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/empty-module-import/world.wit b/crates/wit-component/tests/components/empty-module-import/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/empty-module-import/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/empty/component.wit b/crates/wit-component/tests/components/empty/component.wit new file mode 100644 index 0000000000..da09ea6eeb --- /dev/null +++ b/crates/wit-component/tests/components/empty/component.wit @@ -0,0 +1,2 @@ +default world component { +} diff --git a/crates/wit-component/tests/components/empty/module.wit b/crates/wit-component/tests/components/empty/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/empty/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/empty/world.wit b/crates/wit-component/tests/components/empty/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/empty/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/ensure-default-type-exports/component.wat b/crates/wit-component/tests/components/ensure-default-type-exports/component.wat index 4b236cbc1d..f603758a01 100644 --- a/crates/wit-component/tests/components/ensure-default-type-exports/component.wat +++ b/crates/wit-component/tests/components/ensure-default-type-exports/component.wat @@ -3,9 +3,9 @@ (instance (type (;0;) u8) (export (;1;) "foo" (type (eq 0))) - (type (;2;) (record (field "x" 0))) + (type (;2;) (record (field "x" 1))) (export (;3;) "bar" (type (eq 2))) - (type (;4;) (func (param "b" 2))) + (type (;4;) (func (param "b" 3))) (export (;0;) "a" (func (type 4))) ) ) @@ -36,12 +36,8 @@ ) (alias core export 1 "memory" (core memory (;0;))) (alias core export 1 "cabi_realloc" (core func (;1;))) + (type (;1;) (func (param "b" u8))) (alias core export 1 "a" (core func (;2;))) - (type (;1;) u8) - (type (;2;) (record (field "x" 1))) - (type (;3;) (func (param "b" 2))) - (func (;1;) (type 3) (canon lift (core func 2))) + (func (;1;) (type 1) (canon lift (core func 2))) (export (;2;) "a" (func 1)) - (export (;4;) "foo" (type 1)) - (export (;5;) "bar" (type 2)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/ensure-default-type-exports/component.wit b/crates/wit-component/tests/components/ensure-default-type-exports/component.wit new file mode 100644 index 0000000000..a7b39a50c9 --- /dev/null +++ b/crates/wit-component/tests/components/ensure-default-type-exports/component.wit @@ -0,0 +1,14 @@ +interface foo { + type foo = u8 + + record bar { + x: foo, + } + + a: func(b: bar) +} + +default world component { + import foo: self.foo + export a: func(b: u8) +} diff --git a/crates/wit-component/tests/components/ensure-default-type-exports/module.wit b/crates/wit-component/tests/components/ensure-default-type-exports/module.wit new file mode 100644 index 0000000000..c66cd9c4df --- /dev/null +++ b/crates/wit-component/tests/components/ensure-default-type-exports/module.wit @@ -0,0 +1,15 @@ +interface foo { + type foo = u8 + + record bar { + x: foo + } + + a: func(b: bar) +} + +default world my-world { + import foo: self.foo + + export a: func(b: u8) +} diff --git a/crates/wit-component/tests/components/ensure-default-type-exports/world.wit b/crates/wit-component/tests/components/ensure-default-type-exports/world.wit deleted file mode 100644 index 9d9f7c29a6..0000000000 --- a/crates/wit-component/tests/components/ensure-default-type-exports/world.wit +++ /dev/null @@ -1,22 +0,0 @@ -interface foo { - type foo = u8 - - record bar { - x: foo - } - - a: func(b: bar) -} - -world my-world { - import foo: foo - default export interface { - type foo = u8 - - record bar { - x: foo - } - - a: func(b: bar) - } -} diff --git a/crates/wit-component/tests/components/export-sig-mismatch/error.txt b/crates/wit-component/tests/components/export-sig-mismatch/error.txt index 724247f7c8..d2226ac3d9 100644 --- a/crates/wit-component/tests/components/export-sig-mismatch/error.txt +++ b/crates/wit-component/tests/components/export-sig-mismatch/error.txt @@ -1 +1,4 @@ -type mismatch for function `a` from exported interface `foo`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file +failed to validate exported interface `foo` + +Caused by: + type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file diff --git a/crates/wit-component/tests/components/export-sig-mismatch/world.wit b/crates/wit-component/tests/components/export-sig-mismatch/module.wit similarity index 76% rename from crates/wit-component/tests/components/export-sig-mismatch/world.wit rename to crates/wit-component/tests/components/export-sig-mismatch/module.wit index 3c24047fb4..8e7ef0bbb6 100644 --- a/crates/wit-component/tests/components/export-sig-mismatch/world.wit +++ b/crates/wit-component/tests/components/export-sig-mismatch/module.wit @@ -1,4 +1,4 @@ -world foo { +default world foo { export foo: interface { a: func(x: string) -> string } diff --git a/crates/wit-component/tests/components/exports/component.wat b/crates/wit-component/tests/components/exports/component.wat index 4bcc98a501..7a2283ac25 100644 --- a/crates/wit-component/tests/components/exports/component.wat +++ b/crates/wit-component/tests/components/exports/component.wat @@ -57,45 +57,45 @@ (core instance (;0;) (instantiate 0)) (alias core export 0 "memory" (core memory (;0;))) (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "bar#a" (core func (;1;))) (type (;0;) (flags "a" "b" "c")) (type (;1;) (func (param "x" 0))) + (alias core export 0 "bar#a" (core func (;1;))) (func (;0;) (type 1) (canon lift (core func 1))) (instance (;0;) - (export "a" (func 0)) (export "x" (type 0)) + (export "a" (func 0)) ) (export (;1;) "bar" (instance 0)) - (alias core export 0 "foo#a" (core func (;2;))) (type (;2;) (func)) + (alias core export 0 "foo#a" (core func (;2;))) (func (;1;) (type 2) (canon lift (core func 2))) - (alias core export 0 "foo#b" (core func (;3;))) (type (;3;) (variant (case "a") (case "b" string) (case "c" s64))) (type (;4;) (func (param "x" string) (result 3))) + (alias core export 0 "foo#b" (core func (;3;))) (alias core export 0 "cabi_post_foo#b" (core func (;4;))) (func (;2;) (type 4) (canon lift (core func 3) (memory 0) (realloc 0) string-encoding=utf8 (post-return 4))) - (alias core export 0 "foo#c" (core func (;5;))) (type (;5;) (func (param "x" 3) (result string))) + (alias core export 0 "foo#c" (core func (;5;))) (alias core export 0 "cabi_post_foo#c" (core func (;6;))) (func (;3;) (type 5) (canon lift (core func 5) (memory 0) (realloc 0) string-encoding=utf8 (post-return 6))) (instance (;2;) + (export "x" (type 3)) (export "a" (func 1)) (export "b" (func 2)) (export "c" (func 3)) - (export "x" (type 3)) ) (export (;3;) "foo" (instance 2)) (alias core export 0 "a" (core func (;7;))) (func (;4;) (type 2) (canon lift (core func 7))) - (alias core export 0 "b" (core func (;8;))) + (export (;5;) "a" (func 4)) (type (;6;) (func (param "a" s8) (param "b" s16) (param "c" s32) (param "d" s64) (result string))) + (alias core export 0 "b" (core func (;8;))) (alias core export 0 "cabi_post_b" (core func (;9;))) - (func (;5;) (type 6) (canon lift (core func 8) (memory 0) string-encoding=utf8 (post-return 9))) - (alias core export 0 "c" (core func (;10;))) + (func (;6;) (type 6) (canon lift (core func 8) (memory 0) string-encoding=utf8 (post-return 9))) + (export (;7;) "b" (func 6)) (type (;7;) (tuple s8 s16 s32 s64)) (type (;8;) (func (result 7))) - (func (;6;) (type 8) (canon lift (core func 10) (memory 0))) - (export (;7;) "a" (func 4)) - (export (;8;) "b" (func 5)) - (export (;9;) "c" (func 6)) + (alias core export 0 "c" (core func (;10;))) + (func (;8;) (type 8) (canon lift (core func 10) (memory 0))) + (export (;9;) "c" (func 8)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/exports/component.wit b/crates/wit-component/tests/components/exports/component.wit new file mode 100644 index 0000000000..1e3cf130cb --- /dev/null +++ b/crates/wit-component/tests/components/exports/component.wit @@ -0,0 +1,31 @@ +interface bar { + flags x { + a, + b, + c, + } + + a: func(x: x) +} + +interface foo { + variant x { + a, + b(string), + c(s64), + } + + a: func() + + b: func(x: string) -> x + + c: func(x: x) -> string +} + +default world component { + export bar: self.bar + export foo: self.foo + export a: func() + export b: func(a: s8, b: s16, c: s32, d: s64) -> string + export c: func() -> tuple +} diff --git a/crates/wit-component/tests/components/exports/world.wit b/crates/wit-component/tests/components/exports/module.wit similarity index 63% rename from crates/wit-component/tests/components/exports/world.wit rename to crates/wit-component/tests/components/exports/module.wit index 897dbfda67..478e6876ca 100644 --- a/crates/wit-component/tests/components/exports/world.wit +++ b/crates/wit-component/tests/components/exports/module.wit @@ -1,9 +1,7 @@ -world my-world { - default export interface { - a: func() - b: func(a: s8, b: s16, c: s32, d: s64) -> string - c: func() -> tuple - } +default world my-world { + export a: func() + export b: func(a: s8, b: s16, c: s32, d: s64) -> string + export c: func() -> tuple export bar: interface { flags x { diff --git a/crates/wit-component/tests/components/import-conflict/component.wit b/crates/wit-component/tests/components/import-conflict/component.wit new file mode 100644 index 0000000000..d2726dc7e5 --- /dev/null +++ b/crates/wit-component/tests/components/import-conflict/component.wit @@ -0,0 +1,17 @@ +interface bar { + a: func(x: u64, y: string) +} + +interface baz { + baz: func(x: list) -> list +} + +interface foo { + a: func() +} + +default world component { + import bar: self.bar + import baz: self.baz + import foo: self.foo +} diff --git a/crates/wit-component/tests/components/import-conflict/world.wit b/crates/wit-component/tests/components/import-conflict/module.wit similarity index 59% rename from crates/wit-component/tests/components/import-conflict/world.wit rename to crates/wit-component/tests/components/import-conflict/module.wit index 9b351f1968..4c23d0a296 100644 --- a/crates/wit-component/tests/components/import-conflict/world.wit +++ b/crates/wit-component/tests/components/import-conflict/module.wit @@ -10,8 +10,8 @@ interface baz { baz: func(x: list) -> list } -world my-world { - import bar: bar - import baz: baz - import foo: foo +default world my-world { + import bar: self.bar + import baz: self.baz + import foo: self.foo } diff --git a/crates/wit-component/tests/components/import-empty-interface/component.wit b/crates/wit-component/tests/components/import-empty-interface/component.wit new file mode 100644 index 0000000000..da09ea6eeb --- /dev/null +++ b/crates/wit-component/tests/components/import-empty-interface/component.wit @@ -0,0 +1,2 @@ +default world component { +} diff --git a/crates/wit-component/tests/components/import-empty-interface/world.wit b/crates/wit-component/tests/components/import-empty-interface/module.wit similarity index 59% rename from crates/wit-component/tests/components/import-empty-interface/world.wit rename to crates/wit-component/tests/components/import-empty-interface/module.wit index 0860d64f89..b92bce2a81 100644 --- a/crates/wit-component/tests/components/import-empty-interface/world.wit +++ b/crates/wit-component/tests/components/import-empty-interface/module.wit @@ -1,3 +1,3 @@ -world foo { +default world foo { import foo: interface {} } diff --git a/crates/wit-component/tests/components/import-export/component.wat b/crates/wit-component/tests/components/import-export/component.wat index e5e5c5b8e3..2bc6e56a55 100644 --- a/crates/wit-component/tests/components/import-export/component.wat +++ b/crates/wit-component/tests/components/import-export/component.wat @@ -79,11 +79,11 @@ (with "" (instance 3)) ) ) - (alias core export 2 "bar#a" (core func (;3;))) (type (;1;) (func)) + (alias core export 2 "bar#a" (core func (;3;))) (func (;1;) (type 1) (canon lift (core func 3))) - (alias core export 2 "bar#b" (core func (;4;))) (type (;2;) (func (result string))) + (alias core export 2 "bar#b" (core func (;4;))) (alias core export 2 "cabi_post_bar#b" (core func (;5;))) (func (;2;) (type 2) (canon lift (core func 4) (memory 0) string-encoding=utf8 (post-return 5))) (instance (;1;) @@ -91,9 +91,9 @@ (export "b" (func 2)) ) (export (;2;) "bar" (instance 1)) - (alias core export 2 "a" (core func (;6;))) (type (;3;) (tuple string u32 string)) (type (;4;) (func (param "x" string) (result 3))) + (alias core export 2 "a" (core func (;6;))) (alias core export 2 "cabi_post_a" (core func (;7;))) (func (;3;) (type 4) (canon lift (core func 6) (memory 0) (realloc 1) string-encoding=utf8 (post-return 7))) (export (;4;) "a" (func 3)) diff --git a/crates/wit-component/tests/components/import-export/component.wit b/crates/wit-component/tests/components/import-export/component.wit new file mode 100644 index 0000000000..04294f3da4 --- /dev/null +++ b/crates/wit-component/tests/components/import-export/component.wit @@ -0,0 +1,15 @@ +interface foo { + a: func() -> string +} + +interface bar { + a: func() + + b: func() -> string +} + +default world component { + import foo: self.foo + export bar: self.bar + export a: func(x: string) -> tuple +} diff --git a/crates/wit-component/tests/components/import-export/world.wit b/crates/wit-component/tests/components/import-export/module.wit similarity index 54% rename from crates/wit-component/tests/components/import-export/world.wit rename to crates/wit-component/tests/components/import-export/module.wit index d009ee8e28..3b29a2b865 100644 --- a/crates/wit-component/tests/components/import-export/world.wit +++ b/crates/wit-component/tests/components/import-export/module.wit @@ -1,5 +1,4 @@ - -world my-world { +default world my-world { import foo: interface { a: func() -> string } @@ -9,7 +8,5 @@ world my-world { b: func() -> string } - default export interface { - a: func(x: string) -> tuple - } + export a: func(x: string) -> tuple } diff --git a/crates/wit-component/tests/components/import-sig-mismatch/error.txt b/crates/wit-component/tests/components/import-sig-mismatch/error.txt index b8d68a1bc0..7a6fd7d4e0 100644 --- a/crates/wit-component/tests/components/import-sig-mismatch/error.txt +++ b/crates/wit-component/tests/components/import-sig-mismatch/error.txt @@ -1 +1,4 @@ -type mismatch for function `bar` on imported interface `foo`: expected `[I32, I32] -> []` but found `[] -> []` \ No newline at end of file +failed to validate import interface `foo` + +Caused by: + type mismatch for function `bar`: expected `[I32, I32] -> []` but found `[] -> []` \ No newline at end of file diff --git a/crates/wit-component/tests/components/import-sig-mismatch/world.wit b/crates/wit-component/tests/components/import-sig-mismatch/module.wit similarity index 74% rename from crates/wit-component/tests/components/import-sig-mismatch/world.wit rename to crates/wit-component/tests/components/import-sig-mismatch/module.wit index 3d9643fc72..3e06ced973 100644 --- a/crates/wit-component/tests/components/import-sig-mismatch/world.wit +++ b/crates/wit-component/tests/components/import-sig-mismatch/module.wit @@ -1,4 +1,4 @@ -world foo { +default world foo { import foo: interface { bar: func(s: string) } diff --git a/crates/wit-component/tests/components/imports/component.wat b/crates/wit-component/tests/components/imports/component.wat index f67024ea48..422b2d7cf5 100644 --- a/crates/wit-component/tests/components/imports/component.wat +++ b/crates/wit-component/tests/components/imports/component.wat @@ -5,7 +5,7 @@ (export (;0;) "bar1" (func (type 0))) (type (;1;) (record (field "a" u8))) (export (;2;) "x" (type (eq 1))) - (type (;3;) (func (param "x" 1))) + (type (;3;) (func (param "x" 2))) (export (;1;) "bar2" (func (type 3))) ) ) @@ -19,7 +19,7 @@ (export (;1;) "baz2" (func (type 2))) (type (;3;) s8) (export (;4;) "x" (type (eq 3))) - (type (;5;) (func (param "x" 3))) + (type (;5;) (func (param "x" 4))) (export (;2;) "baz3" (func (type 5))) ) ) diff --git a/crates/wit-component/tests/components/imports/component.wit b/crates/wit-component/tests/components/imports/component.wit new file mode 100644 index 0000000000..cbb6b2de1c --- /dev/null +++ b/crates/wit-component/tests/components/imports/component.wit @@ -0,0 +1,33 @@ +interface bar { + record x { + a: u8, + } + + bar1: func(x: string) + + bar2: func(x: x) +} + +interface baz { + type x = s8 + + baz1: func(x: list) + + baz2: func() + + baz3: func(x: x) +} + +interface foo { + foo1: func() + + foo2: func(x: u8) + + foo3: func(x: float32) +} + +default world component { + import bar: self.bar + import baz: self.baz + import foo: self.foo +} diff --git a/crates/wit-component/tests/components/imports/world.wit b/crates/wit-component/tests/components/imports/module.wit similarity index 95% rename from crates/wit-component/tests/components/imports/world.wit rename to crates/wit-component/tests/components/imports/module.wit index bf20e73eab..c5fad1d34d 100644 --- a/crates/wit-component/tests/components/imports/world.wit +++ b/crates/wit-component/tests/components/imports/module.wit @@ -1,4 +1,4 @@ -world my-world { +default world my-world { import bar: interface { record x { a: u8 diff --git a/crates/wit-component/tests/components/invalid-module-import/module.wit b/crates/wit-component/tests/components/invalid-module-import/module.wit new file mode 100644 index 0000000000..234a91911c --- /dev/null +++ b/crates/wit-component/tests/components/invalid-module-import/module.wit @@ -0,0 +1 @@ +default world foo {} diff --git a/crates/wit-component/tests/components/invalid-module-import/world.wit b/crates/wit-component/tests/components/invalid-module-import/world.wit deleted file mode 100644 index 911d40a020..0000000000 --- a/crates/wit-component/tests/components/invalid-module-import/world.wit +++ /dev/null @@ -1 +0,0 @@ -world foo {} diff --git a/crates/wit-component/tests/components/lift-options/component.wat b/crates/wit-component/tests/components/lift-options/component.wat index cb1ca7f8b5..37404753f8 100644 --- a/crates/wit-component/tests/components/lift-options/component.wat +++ b/crates/wit-component/tests/components/lift-options/component.wat @@ -72,110 +72,113 @@ (memory (;0;) 1) (export "memory" (memory 0)) (export "cabi_realloc" (func 0)) - (export "a" (func 1)) - (export "b" (func 2)) - (export "c" (func 3)) - (export "d" (func 4)) - (export "e" (func 5)) - (export "f" (func 6)) - (export "g" (func 7)) - (export "h" (func 8)) - (export "i" (func 9)) - (export "j" (func 10)) - (export "k" (func 11)) - (export "l" (func 12)) - (export "cabi_post_l" (func 13)) - (export "m" (func 14)) - (export "cabi_post_m" (func 15)) - (export "n" (func 16)) - (export "o" (func 17)) - (export "cabi_post_o" (func 18)) - (export "p" (func 19)) - (export "cabi_post_p" (func 20)) + (export "foo#a" (func 1)) + (export "foo#b" (func 2)) + (export "foo#c" (func 3)) + (export "foo#d" (func 4)) + (export "foo#e" (func 5)) + (export "foo#f" (func 6)) + (export "foo#g" (func 7)) + (export "foo#h" (func 8)) + (export "foo#i" (func 9)) + (export "foo#j" (func 10)) + (export "foo#k" (func 11)) + (export "foo#l" (func 12)) + (export "cabi_post_foo#l" (func 13)) + (export "foo#m" (func 14)) + (export "cabi_post_foo#m" (func 15)) + (export "foo#n" (func 16)) + (export "foo#o" (func 17)) + (export "cabi_post_foo#o" (func 18)) + (export "foo#p" (func 19)) + (export "cabi_post_foo#p" (func 20)) ) (core instance (;0;) (instantiate 0)) (alias core export 0 "memory" (core memory (;0;))) (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "a" (core func (;1;))) (type (;0;) (func)) + (alias core export 0 "foo#a" (core func (;1;))) (func (;0;) (type 0) (canon lift (core func 1))) - (alias core export 0 "b" (core func (;2;))) (type (;1;) (list string)) (type (;2;) (func (param "x" 1))) + (alias core export 0 "foo#b" (core func (;2;))) (func (;1;) (type 2) (canon lift (core func 2) (memory 0) (realloc 0) string-encoding=utf8)) - (alias core export 0 "c" (core func (;3;))) (type (;3;) (record (field "s" string))) (type (;4;) (func (param "x" 3))) + (alias core export 0 "foo#c" (core func (;3;))) (func (;2;) (type 4) (canon lift (core func 3) (memory 0) (realloc 0) string-encoding=utf8)) - (alias core export 0 "d" (core func (;4;))) (type (;5;) (variant (case "s" string))) (type (;6;) (func (param "x" 5))) + (alias core export 0 "foo#d" (core func (;4;))) (func (;3;) (type 6) (canon lift (core func 4) (memory 0) (realloc 0) string-encoding=utf8)) - (alias core export 0 "e" (core func (;5;))) (type (;7;) (record (field "s" u32))) (type (;8;) (func (param "x" 7))) + (alias core export 0 "foo#e" (core func (;5;))) (func (;4;) (type 8) (canon lift (core func 5))) - (alias core export 0 "f" (core func (;6;))) (type (;9;) (variant (case "s" u32))) (type (;10;) (func (param "x" 9))) + (alias core export 0 "foo#f" (core func (;6;))) (func (;5;) (type 10) (canon lift (core func 6))) - (alias core export 0 "g" (core func (;7;))) (type (;11;) (list 3)) (type (;12;) (func (param "x" 11))) + (alias core export 0 "foo#g" (core func (;7;))) (func (;6;) (type 12) (canon lift (core func 7) (memory 0) (realloc 0) string-encoding=utf8)) - (alias core export 0 "h" (core func (;8;))) (type (;13;) (list 5)) (type (;14;) (func (param "x" 13))) + (alias core export 0 "foo#h" (core func (;8;))) (func (;7;) (type 14) (canon lift (core func 8) (memory 0) (realloc 0) string-encoding=utf8)) - (alias core export 0 "i" (core func (;9;))) (type (;15;) (list u32)) (type (;16;) (func (param "x" 15))) + (alias core export 0 "foo#i" (core func (;9;))) (func (;8;) (type 16) (canon lift (core func 9) (memory 0) (realloc 0))) - (alias core export 0 "j" (core func (;10;))) (type (;17;) (func (param "x" u32))) + (alias core export 0 "foo#j" (core func (;10;))) (func (;9;) (type 17) (canon lift (core func 10))) - (alias core export 0 "k" (core func (;11;))) (type (;18;) (tuple u32 u32)) (type (;19;) (func (result 18))) + (alias core export 0 "foo#k" (core func (;11;))) (func (;10;) (type 19) (canon lift (core func 11) (memory 0))) - (alias core export 0 "l" (core func (;12;))) (type (;20;) (func (result string))) - (alias core export 0 "cabi_post_l" (core func (;13;))) + (alias core export 0 "foo#l" (core func (;12;))) + (alias core export 0 "cabi_post_foo#l" (core func (;13;))) (func (;11;) (type 20) (canon lift (core func 12) (memory 0) string-encoding=utf8 (post-return 13))) - (alias core export 0 "m" (core func (;14;))) (type (;21;) (func (result 15))) - (alias core export 0 "cabi_post_m" (core func (;15;))) + (alias core export 0 "foo#m" (core func (;14;))) + (alias core export 0 "cabi_post_foo#m" (core func (;15;))) (func (;12;) (type 21) (canon lift (core func 14) (memory 0) (post-return 15))) - (alias core export 0 "n" (core func (;16;))) (type (;22;) (func (result u32))) + (alias core export 0 "foo#n" (core func (;16;))) (func (;13;) (type 22) (canon lift (core func 16))) - (alias core export 0 "o" (core func (;17;))) (type (;23;) (func (result 5))) - (alias core export 0 "cabi_post_o" (core func (;18;))) + (alias core export 0 "foo#o" (core func (;17;))) + (alias core export 0 "cabi_post_foo#o" (core func (;18;))) (func (;14;) (type 23) (canon lift (core func 17) (memory 0) string-encoding=utf8 (post-return 18))) - (alias core export 0 "p" (core func (;19;))) (type (;24;) (list 9)) (type (;25;) (func (result 24))) - (alias core export 0 "cabi_post_p" (core func (;20;))) + (alias core export 0 "foo#p" (core func (;19;))) + (alias core export 0 "cabi_post_foo#p" (core func (;20;))) (func (;15;) (type 25) (canon lift (core func 19) (memory 0) (post-return 20))) - (export (;16;) "a" (func 0)) - (export (;17;) "b" (func 1)) - (export (;18;) "c" (func 2)) - (export (;19;) "d" (func 3)) - (export (;20;) "e" (func 4)) - (export (;21;) "f" (func 5)) - (export (;22;) "g" (func 6)) - (export (;23;) "h" (func 7)) - (export (;24;) "i" (func 8)) - (export (;25;) "j" (func 9)) - (export (;26;) "k" (func 10)) - (export (;27;) "l" (func 11)) - (export (;28;) "m" (func 12)) - (export (;29;) "n" (func 13)) - (export (;30;) "o" (func 14)) - (export (;31;) "p" (func 15)) - (export (;26;) "r" (type 3)) - (export (;27;) "v" (type 5)) - (export (;28;) "r-no-string" (type 7)) - (export (;29;) "v-no-string" (type 9)) + (instance (;0;) + (export "r" (type 3)) + (export "v" (type 5)) + (export "r-no-string" (type 7)) + (export "v-no-string" (type 9)) + (export "a" (func 0)) + (export "b" (func 1)) + (export "c" (func 2)) + (export "d" (func 3)) + (export "e" (func 4)) + (export "f" (func 5)) + (export "g" (func 6)) + (export "h" (func 7)) + (export "i" (func 8)) + (export "j" (func 9)) + (export "k" (func 10)) + (export "l" (func 11)) + (export "m" (func 12)) + (export "n" (func 13)) + (export "o" (func 14)) + (export "p" (func 15)) + ) + (export (;1;) "foo" (instance 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/lift-options/component.wit b/crates/wit-component/tests/components/lift-options/component.wit new file mode 100644 index 0000000000..ef941fcf95 --- /dev/null +++ b/crates/wit-component/tests/components/lift-options/component.wit @@ -0,0 +1,53 @@ +interface foo { + record r { + s: string, + } + + variant v { + s(string), + } + + record r-no-string { + s: u32, + } + + variant v-no-string { + s(u32), + } + + a: func() + + b: func(x: list) + + c: func(x: r) + + d: func(x: v) + + e: func(x: r-no-string) + + f: func(x: v-no-string) + + g: func(x: list) + + h: func(x: list) + + i: func(x: list) + + j: func(x: u32) + + k: func() -> tuple + + l: func() -> string + + m: func() -> list + + n: func() -> u32 + + o: func() -> v + + p: func() -> list +} + +default world component { + export foo: self.foo +} diff --git a/crates/wit-component/tests/components/lift-options/module.wat b/crates/wit-component/tests/components/lift-options/module.wat index c1191b0bb5..aa6a7acb5b 100644 --- a/crates/wit-component/tests/components/lift-options/module.wat +++ b/crates/wit-component/tests/components/lift-options/module.wat @@ -1,24 +1,24 @@ (module (memory (export "memory") 1) (func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) - (func (export "a") unreachable) - (func (export "b") (param i32 i32) unreachable) - (func (export "c") (param i32 i32) unreachable) - (func (export "d") (param i32 i32 i32) unreachable) - (func (export "e") (param i32) unreachable) - (func (export "f") (param i32 i32) unreachable) - (func (export "g") (param i32 i32) unreachable) - (func (export "h") (param i32 i32) unreachable) - (func (export "i") (param i32 i32) unreachable) - (func (export "j") (param i32) unreachable) - (func (export "k") (result i32) unreachable) - (func (export "l") (result i32) unreachable) - (func (export "cabi_post_l") (param i32) unreachable) - (func (export "m") (result i32) unreachable) - (func (export "cabi_post_m") (param i32) unreachable) - (func (export "n") (result i32) unreachable) - (func (export "o") (result i32) unreachable) - (func (export "cabi_post_o") (param i32) unreachable) - (func (export "p") (result i32) unreachable) - (func (export "cabi_post_p") (param i32) unreachable) + (func (export "foo#a") unreachable) + (func (export "foo#b") (param i32 i32) unreachable) + (func (export "foo#c") (param i32 i32) unreachable) + (func (export "foo#d") (param i32 i32 i32) unreachable) + (func (export "foo#e") (param i32) unreachable) + (func (export "foo#f") (param i32 i32) unreachable) + (func (export "foo#g") (param i32 i32) unreachable) + (func (export "foo#h") (param i32 i32) unreachable) + (func (export "foo#i") (param i32 i32) unreachable) + (func (export "foo#j") (param i32) unreachable) + (func (export "foo#k") (result i32) unreachable) + (func (export "foo#l") (result i32) unreachable) + (func (export "cabi_post_foo#l") (param i32) unreachable) + (func (export "foo#m") (result i32) unreachable) + (func (export "cabi_post_foo#m") (param i32) unreachable) + (func (export "foo#n") (result i32) unreachable) + (func (export "foo#o") (result i32) unreachable) + (func (export "cabi_post_foo#o") (param i32) unreachable) + (func (export "foo#p") (result i32) unreachable) + (func (export "cabi_post_foo#p") (param i32) unreachable) ) diff --git a/crates/wit-component/tests/components/lift-options/world.wit b/crates/wit-component/tests/components/lift-options/module.wit similarity index 90% rename from crates/wit-component/tests/components/lift-options/world.wit rename to crates/wit-component/tests/components/lift-options/module.wit index 28d9a0ccec..b1d4b6db11 100644 --- a/crates/wit-component/tests/components/lift-options/world.wit +++ b/crates/wit-component/tests/components/lift-options/module.wit @@ -33,6 +33,6 @@ interface my-default { p: func() -> list } -world my-world { - default export my-default +default world my-world { + export foo: self.my-default } diff --git a/crates/wit-component/tests/components/lower-options/component.wat b/crates/wit-component/tests/components/lower-options/component.wat index 6fc2d1171a..a629dedf29 100644 --- a/crates/wit-component/tests/components/lower-options/component.wat +++ b/crates/wit-component/tests/components/lower-options/component.wat @@ -8,24 +8,24 @@ (export (;1;) "b" (func (type 2))) (type (;3;) (record (field "s" string))) (export (;4;) "r" (type (eq 3))) - (type (;5;) (func (param "x" 3))) + (type (;5;) (func (param "x" 4))) (export (;2;) "c" (func (type 5))) (type (;6;) (variant (case "s" string))) (export (;7;) "v" (type (eq 6))) - (type (;8;) (func (param "x" 6))) + (type (;8;) (func (param "x" 7))) (export (;3;) "d" (func (type 8))) (type (;9;) (record (field "s" u32))) (export (;10;) "r-no-string" (type (eq 9))) - (type (;11;) (func (param "x" 9))) + (type (;11;) (func (param "x" 10))) (export (;4;) "e" (func (type 11))) (type (;12;) (variant (case "s" u32))) (export (;13;) "v-no-string" (type (eq 12))) - (type (;14;) (func (param "x" 12))) + (type (;14;) (func (param "x" 13))) (export (;5;) "f" (func (type 14))) - (type (;15;) (list 3)) + (type (;15;) (list 4)) (type (;16;) (func (param "x" 15))) (export (;6;) "g" (func (type 16))) - (type (;17;) (list 6)) + (type (;17;) (list 7)) (type (;18;) (func (param "x" 17))) (export (;7;) "h" (func (type 18))) (type (;19;) (list u32)) @@ -42,9 +42,9 @@ (export (;12;) "m" (func (type 25))) (type (;26;) (func (result u32))) (export (;13;) "n" (func (type 26))) - (type (;27;) (func (result 6))) + (type (;27;) (func (result 7))) (export (;14;) "o" (func (type 27))) - (type (;28;) (list 12)) + (type (;28;) (list 13)) (type (;29;) (func (result 28))) (export (;15;) "p" (func (type 29))) ) diff --git a/crates/wit-component/tests/components/lower-options/component.wit b/crates/wit-component/tests/components/lower-options/component.wit new file mode 100644 index 0000000000..b16a03105d --- /dev/null +++ b/crates/wit-component/tests/components/lower-options/component.wit @@ -0,0 +1,53 @@ +interface foo { + record r { + s: string, + } + + variant v { + s(string), + } + + record r-no-string { + s: u32, + } + + variant v-no-string { + s(u32), + } + + a: func() + + b: func(x: list) + + c: func(x: r) + + d: func(x: v) + + e: func(x: r-no-string) + + f: func(x: v-no-string) + + g: func(x: list) + + h: func(x: list) + + i: func(x: list) + + j: func(x: u32) + + k: func() -> tuple + + l: func() -> string + + m: func() -> list + + n: func() -> u32 + + o: func() -> v + + p: func() -> list +} + +default world component { + import foo: self.foo +} diff --git a/crates/wit-component/tests/components/lower-options/world.wit b/crates/wit-component/tests/components/lower-options/module.wit similarity index 91% rename from crates/wit-component/tests/components/lower-options/world.wit rename to crates/wit-component/tests/components/lower-options/module.wit index 6072011f8f..d4dae943f7 100644 --- a/crates/wit-component/tests/components/lower-options/world.wit +++ b/crates/wit-component/tests/components/lower-options/module.wit @@ -33,6 +33,6 @@ interface foo { p: func() -> list } -world my-world { - import foo: foo +default world my-world { + import foo: self.foo } diff --git a/crates/wit-component/tests/components/missing-default-export/module.wit b/crates/wit-component/tests/components/missing-default-export/module.wit new file mode 100644 index 0000000000..4e607cdba0 --- /dev/null +++ b/crates/wit-component/tests/components/missing-default-export/module.wit @@ -0,0 +1,3 @@ +default world my-world { + export a: func() +} diff --git a/crates/wit-component/tests/components/missing-default-export/world.wit b/crates/wit-component/tests/components/missing-default-export/world.wit deleted file mode 100644 index 66ea8bd1c1..0000000000 --- a/crates/wit-component/tests/components/missing-default-export/world.wit +++ /dev/null @@ -1,5 +0,0 @@ -world my-world { - default export interface { - a: func() - } -} diff --git a/crates/wit-component/tests/components/missing-export/error.txt b/crates/wit-component/tests/components/missing-export/error.txt index 821563ee2a..afcfb4e22f 100644 --- a/crates/wit-component/tests/components/missing-export/error.txt +++ b/crates/wit-component/tests/components/missing-export/error.txt @@ -1 +1,4 @@ -module does not export required function `foo#a` \ No newline at end of file +failed to validate exported interface `foo` + +Caused by: + module does not export required function `foo#a` \ No newline at end of file diff --git a/crates/wit-component/tests/components/missing-export/world.wit b/crates/wit-component/tests/components/missing-export/module.wit similarity index 64% rename from crates/wit-component/tests/components/missing-export/world.wit rename to crates/wit-component/tests/components/missing-export/module.wit index 4e07fc1d9c..6b1871aead 100644 --- a/crates/wit-component/tests/components/missing-export/world.wit +++ b/crates/wit-component/tests/components/missing-export/module.wit @@ -1,4 +1,4 @@ -world my-world { +default world my-world { export foo: interface { a: func() } diff --git a/crates/wit-component/tests/components/missing-import-func/error.txt b/crates/wit-component/tests/components/missing-import-func/error.txt index a79587d066..ea9b14277d 100644 --- a/crates/wit-component/tests/components/missing-import-func/error.txt +++ b/crates/wit-component/tests/components/missing-import-func/error.txt @@ -1 +1,4 @@ -import interface `foo` is missing function `bar` that is required by the module \ No newline at end of file +failed to validate import interface `foo` + +Caused by: + import interface `foo` is missing function `bar` that is required by the module \ No newline at end of file diff --git a/crates/wit-component/tests/components/missing-import-func/module.wit b/crates/wit-component/tests/components/missing-import-func/module.wit new file mode 100644 index 0000000000..d0e7916515 --- /dev/null +++ b/crates/wit-component/tests/components/missing-import-func/module.wit @@ -0,0 +1,7 @@ +interface foo { + a: func() +} + +default world my-world { + import foo: self.foo +} diff --git a/crates/wit-component/tests/components/missing-import-func/world.wit b/crates/wit-component/tests/components/missing-import-func/world.wit deleted file mode 100644 index 39a2a8c43f..0000000000 --- a/crates/wit-component/tests/components/missing-import-func/world.wit +++ /dev/null @@ -1,7 +0,0 @@ -interface foo { - a: func() -} - -world my-world { - import foo: foo -} diff --git a/crates/wit-component/tests/components/missing-import/module.wit b/crates/wit-component/tests/components/missing-import/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/missing-import/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/missing-import/world.wit b/crates/wit-component/tests/components/missing-import/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/missing-import/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/no-realloc-required/component.wit b/crates/wit-component/tests/components/no-realloc-required/component.wit new file mode 100644 index 0000000000..eddb9f98d8 --- /dev/null +++ b/crates/wit-component/tests/components/no-realloc-required/component.wit @@ -0,0 +1,7 @@ +interface foo { + log: func(s: string) +} + +default world component { + import foo: self.foo +} diff --git a/crates/wit-component/tests/components/no-realloc-required/world.wit b/crates/wit-component/tests/components/no-realloc-required/module.wit similarity index 74% rename from crates/wit-component/tests/components/no-realloc-required/world.wit rename to crates/wit-component/tests/components/no-realloc-required/module.wit index acf1566794..558f5ac83f 100644 --- a/crates/wit-component/tests/components/no-realloc-required/world.wit +++ b/crates/wit-component/tests/components/no-realloc-required/module.wit @@ -1,4 +1,4 @@ -world foo { +default world foo { import foo: interface { log: func(s: string) } diff --git a/crates/wit-component/tests/components/post-return/component.wat b/crates/wit-component/tests/components/post-return/component.wat index 32e11a4238..d4f2069075 100644 --- a/crates/wit-component/tests/components/post-return/component.wat +++ b/crates/wit-component/tests/components/post-return/component.wat @@ -21,8 +21,8 @@ (core instance (;0;) (instantiate 0)) (alias core export 0 "memory" (core memory (;0;))) (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "a" (core func (;1;))) (type (;0;) (func (result string))) + (alias core export 0 "a" (core func (;1;))) (alias core export 0 "cabi_post_a" (core func (;2;))) (func (;0;) (type 0) (canon lift (core func 1) (memory 0) string-encoding=utf8 (post-return 2))) (export (;1;) "a" (func 0)) diff --git a/crates/wit-component/tests/components/post-return/component.wit b/crates/wit-component/tests/components/post-return/component.wit new file mode 100644 index 0000000000..5fb868d11a --- /dev/null +++ b/crates/wit-component/tests/components/post-return/component.wit @@ -0,0 +1,3 @@ +default world component { + export a: func() -> string +} diff --git a/crates/wit-component/tests/components/post-return/module.wit b/crates/wit-component/tests/components/post-return/module.wit new file mode 100644 index 0000000000..1d99c42228 --- /dev/null +++ b/crates/wit-component/tests/components/post-return/module.wit @@ -0,0 +1,3 @@ +default world foo { + export a: func() -> string +} diff --git a/crates/wit-component/tests/components/post-return/world.wit b/crates/wit-component/tests/components/post-return/world.wit deleted file mode 100644 index 8eb847f411..0000000000 --- a/crates/wit-component/tests/components/post-return/world.wit +++ /dev/null @@ -1,5 +0,0 @@ -world foo { - default export interface { - a: func() -> string - } -} diff --git a/crates/wit-component/tests/components/rename-import-interface/component.wat b/crates/wit-component/tests/components/rename-import-interface/component.wat new file mode 100644 index 0000000000..cc9b4875e9 --- /dev/null +++ b/crates/wit-component/tests/components/rename-import-interface/component.wat @@ -0,0 +1,22 @@ +(component + (type (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "the-func" (func (type 0))) + ) + ) + (import "bar" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func)) + (import "bar" "the-func" (func (;0;) (type 0))) + ) + (alias export 0 "the-func" (func (;0;))) + (core func (;0;) (canon lower (func 0))) + (core instance (;0;) + (export "the-func" (func 0)) + ) + (core instance (;1;) (instantiate 0 + (with "bar" (instance 0)) + ) + ) +) \ No newline at end of file diff --git a/crates/wit-component/tests/components/rename-import-interface/component.wit b/crates/wit-component/tests/components/rename-import-interface/component.wit new file mode 100644 index 0000000000..d2808cbc8e --- /dev/null +++ b/crates/wit-component/tests/components/rename-import-interface/component.wit @@ -0,0 +1,7 @@ +interface bar { + the-func: func() +} + +default world component { + import bar: self.bar +} diff --git a/crates/wit-component/tests/components/rename-import-interface/module.wat b/crates/wit-component/tests/components/rename-import-interface/module.wat new file mode 100644 index 0000000000..e639cf3752 --- /dev/null +++ b/crates/wit-component/tests/components/rename-import-interface/module.wat @@ -0,0 +1,3 @@ +(module + (import "bar" "the-func" (func)) +) diff --git a/crates/wit-component/tests/components/rename-import-interface/module.wit b/crates/wit-component/tests/components/rename-import-interface/module.wit new file mode 100644 index 0000000000..15eee29f5b --- /dev/null +++ b/crates/wit-component/tests/components/rename-import-interface/module.wit @@ -0,0 +1,7 @@ +interface foo { + the-func: func() +} + +default world the-world { + import bar: self.foo +} diff --git a/crates/wit-component/tests/components/simple/component.wat b/crates/wit-component/tests/components/simple/component.wat index 0a313fe437..940bda28a3 100644 --- a/crates/wit-component/tests/components/simple/component.wat +++ b/crates/wit-component/tests/components/simple/component.wat @@ -40,24 +40,18 @@ (core instance (;0;) (instantiate 0)) (alias core export 0 "memory" (core memory (;0;))) (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "a" (core func (;1;))) (type (;0;) (func)) + (alias core export 0 "a" (core func (;1;))) (func (;0;) (type 0) (canon lift (core func 1))) - (alias core export 0 "b" (core func (;2;))) + (export (;1;) "a" (func 0)) (type (;1;) (func (result string))) + (alias core export 0 "b" (core func (;2;))) (alias core export 0 "cabi_post_b" (core func (;3;))) - (func (;1;) (type 1) (canon lift (core func 2) (memory 0) string-encoding=utf8 (post-return 3))) - (alias core export 0 "c" (core func (;4;))) + (func (;2;) (type 1) (canon lift (core func 2) (memory 0) string-encoding=utf8 (post-return 3))) + (export (;3;) "b" (func 2)) (type (;2;) (func (param "x" string) (result string))) + (alias core export 0 "c" (core func (;4;))) (alias core export 0 "cabi_post_c" (core func (;5;))) - (func (;2;) (type 2) (canon lift (core func 4) (memory 0) (realloc 0) string-encoding=utf8 (post-return 5))) - (alias core export 0 "d" (core func (;6;))) - (type (;3;) (list string)) - (type (;4;) (func (param "x" 3))) - (func (;3;) (type 4) (canon lift (core func 6) (memory 0) (realloc 0) string-encoding=utf8)) - (export (;4;) "a" (func 0)) - (export (;5;) "b" (func 1)) - (export (;6;) "c" (func 2)) - (export (;7;) "d" (func 3)) - (export (;5;) "x" (type 3)) + (func (;4;) (type 2) (canon lift (core func 4) (memory 0) (realloc 0) string-encoding=utf8 (post-return 5))) + (export (;5;) "c" (func 4)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/simple/component.wit b/crates/wit-component/tests/components/simple/component.wit new file mode 100644 index 0000000000..be8388ec0c --- /dev/null +++ b/crates/wit-component/tests/components/simple/component.wit @@ -0,0 +1,5 @@ +default world component { + export a: func() + export b: func() -> string + export c: func(x: string) -> string +} diff --git a/crates/wit-component/tests/components/simple/module.wit b/crates/wit-component/tests/components/simple/module.wit new file mode 100644 index 0000000000..af939cdbe4 --- /dev/null +++ b/crates/wit-component/tests/components/simple/module.wit @@ -0,0 +1,5 @@ +default world foo { + export a: func() + export b: func() -> string + export c: func(x: string) -> string +} diff --git a/crates/wit-component/tests/components/simple/world.wit b/crates/wit-component/tests/components/simple/world.wit deleted file mode 100644 index 622050de9b..0000000000 --- a/crates/wit-component/tests/components/simple/world.wit +++ /dev/null @@ -1,10 +0,0 @@ -world foo { - default export interface { - type x = list - - a: func() - b: func() -> string - c: func(x: string) -> string - d: func(x: x) - } -} diff --git a/crates/wit-component/tests/components/unused-import/component.wit b/crates/wit-component/tests/components/unused-import/component.wit new file mode 100644 index 0000000000..6ad8ced63d --- /dev/null +++ b/crates/wit-component/tests/components/unused-import/component.wit @@ -0,0 +1,7 @@ +interface foo { + name: func(x: bool) +} + +default world component { + import foo: self.foo +} diff --git a/crates/wit-component/tests/components/unused-import/module.wit b/crates/wit-component/tests/components/unused-import/module.wit new file mode 100644 index 0000000000..1d978499e3 --- /dev/null +++ b/crates/wit-component/tests/components/unused-import/module.wit @@ -0,0 +1,10 @@ +interface bar {} + +interface foo { + name: func(x: bool) +} + +default world my-world { + import unused: self.bar + import foo: self.foo +} diff --git a/crates/wit-component/tests/components/unused-import/world.wit b/crates/wit-component/tests/components/unused-import/world.wit deleted file mode 100644 index dd26f6213f..0000000000 --- a/crates/wit-component/tests/components/unused-import/world.wit +++ /dev/null @@ -1,10 +0,0 @@ -interface bar {} - -interface foo { - name: func(x: bool) -} - -world my-world { - import unused: bar - import foo: foo -} diff --git a/crates/wit-component/tests/interfaces.rs b/crates/wit-component/tests/interfaces.rs index 50b389c5d1..76dbbbb8a1 100644 --- a/crates/wit-component/tests/interfaces.rs +++ b/crates/wit-component/tests/interfaces.rs @@ -2,113 +2,107 @@ use anyhow::{Context, Result}; use pretty_assertions::assert_eq; use std::fs; use std::path::Path; -use wit_component::{ComponentEncoder, StringEncoding}; -use wit_parser::Document; +use wit_component::DocumentPrinter; +use wit_parser::{Resolve, UnresolvedPackage}; -/// Tests the encoding of the "types only" mode of `wit-component`. +/// Tests the encoding of a WIT package as a WebAssembly binary. /// -/// This test looks in the `interfaces/` directory for test cases in a similar -/// format to those in the `components/` where there's a `world.wit` which is -/// encoded as `types_only.wat` and additionally encoded in normal mode with a -/// dummy module to verify that works as well. +/// This test looks in the `interfaces/` directory for test cases. Each test +/// case is a `*.wit` file or a folder which contains `*.wit` files as part of a +/// multi-file package. Each tests `foo.wit` is accompanied with a `foo.wat` for +/// the WebAssembly text format encoding of the package. Additionally each test +/// has a `foo.print.wit` which is the machine-printed version of the WIT +/// document as decoded from the binary encoded interface. /// /// Run the test with the environment variable `BLESS` set to update -/// the wat baseline file. +/// the baseline files. #[test] fn interface_encoding() -> Result<()> { for entry in fs::read_dir("tests/interfaces")? { let path = entry?.path(); - if !path.is_dir() { - continue; + let name = match path.file_name().and_then(|s| s.to_str()) { + Some(s) => s, + None => continue, + }; + let is_dir = path.is_dir(); + let is_test = is_dir || name.ends_with(".wit"); + if is_test { + run_test(&path, is_dir).context(format!("failed test `{}`", path.display()))?; } - run_test(&path).context(format!("failed test `{}`", path.display()))?; } Ok(()) } -fn run_test(path: &Path) -> Result<()> { - let test_case = path.file_stem().unwrap().to_str().unwrap(); - println!("test {test_case}"); - let wit_path = path.join("world.wit"); - let doc = Document::parse_file(&wit_path)?; - let world = &doc.worlds[doc.default_world()?]; - let world_name = world.name.clone(); - - let assert_output = |wasm: &[u8], wat: &Path| -> Result<()> { - let output = wasmprinter::print_bytes(wasm)?; - - if std::env::var_os("BLESS").is_some() { - fs::write(wat, output)?; - } else { - assert_eq!( - fs::read_to_string(wat)?.replace("\r\n", "\n").trim(), - output.trim(), - "encoding of `{test_case}` did not match the expected wat file `{}`", - wat.display(), - ); - } - - let (decoded_doc, decoded_world) = wit_component::decode_world(&world_name, wasm) - .context(format!("failed to decode bytes for test `{test_case}`"))?; - - if test_case == "empty" { - return Ok(()); - } - - let decoded_world = &decoded_doc.worlds[decoded_world]; - assert_eq!(decoded_world.imports.len(), world.imports.len()); - assert_eq!(decoded_world.exports.len(), world.exports.len()); - assert_eq!(decoded_world.default.is_some(), world.default.is_some()); - - assert_wit(&wit_path, &decoded_doc)?; - Ok(()) +fn run_test(path: &Path, is_dir: bool) -> Result<()> { + println!("running test at {path:?}"); + let mut resolve = Resolve::new(); + let package = if is_dir { + resolve.push_dir(path)?.0 + } else { + resolve.push(UnresolvedPackage::parse_file(path)?, &Default::default())? }; - // Test a types-only component. This ensures that in "types only" mode we - // can recover all the original `*.wit` interfaces from the generated - // artifact. - - println!("testing types only"); - let bytes = ComponentEncoder::default() - .types_only(true) - .validate(true) - .document(doc.clone(), StringEncoding::UTF8)? - .encode() - .with_context(|| { - format!("failed to encode a types-only component for test case `{test_case}`") - })?; - assert_output(&bytes, &path.join("types_only.wat"))?; + let features = wasmparser::WasmFeatures { + component_model: true, + ..Default::default() + }; - // Test a full component with a dummy module as the implementation. This - // tests a different path through `wit-component` to ensure that we can - // recover the original `*.wit` interfaces from the component output. + // First convert the WIT package to a binary WebAssembly output, then + // convert that binary wasm to textual wasm, then assert it matches the + // expectation. + let wasm = wit_component::encode(&resolve, package)?; + let wat = wasmprinter::print_bytes(&wasm)?; + assert_output(&path.with_extension("wat"), &wat)?; + wasmparser::Validator::new_with_features(features) + .validate_all(&wasm) + .context("failed to validate wasm output")?; + + // Next decode a fresh WIT package from the WebAssembly generated. Print + // this package's documents and assert they all match the expectations. + let name = &resolve.packages[package].name; + let decoded = wit_component::decode(name, &wasm)?; + let resolve = decoded.resolve(); + + for (id, pkg) in resolve.packages.iter() { + for (name, doc) in pkg.documents.iter() { + let root = if id == decoded.package() { + path.to_path_buf() + } else { + path.join("deps").join(&pkg.name) + }; + let expected = if is_dir { + root.join(format!("{name}.wit.print")) + } else { + root.with_extension("wit.print") + }; + let output = DocumentPrinter::default().print(&resolve, *doc)?; + assert_output(&expected, &output)?; + } + } - println!("test dummy module"); - let module = wit_component::dummy_module(&doc); - let bytes = ComponentEncoder::default() - .module(&module)? - .validate(true) - .document(doc.clone(), StringEncoding::UTF8)? - .encode() - .with_context(|| format!("failed to encode a component for test case `{test_case}`"))?; - assert_output(&bytes, &path.join("component.wat"))?; + // Finally convert the decoded package to wasm again and make sure it + // matches the prior wasm. + let wasm2 = wit_component::encode(resolve, decoded.package())?; + if wasm != wasm2 { + let wat2 = wasmprinter::print_bytes(&wasm)?; + assert_eq!(wat, wat2, "document did not roundtrip correctly"); + } Ok(()) } -fn assert_wit(wit_path: &Path, doc: &Document) -> Result<()> { - let mut printer = wit_component::DocumentPrinter::default(); - let output = printer.print(doc).context("failed to print interface")?; - +fn assert_output(expected: &Path, actual: &str) -> Result<()> { if std::env::var_os("BLESS").is_some() { - fs::write(&wit_path, output)?; + fs::write(&expected, actual).with_context(|| format!("failed to write {expected:?}"))?; } else { assert_eq!( - fs::read_to_string(&wit_path)?.replace("\r\n", "\n"), - output, - "encoding of wit file `{}` did not match the the decoded interface", - wit_path.display(), + fs::read_to_string(&expected) + .with_context(|| format!("failed to read {expected:?}"))? + .replace("\r\n", "\n"), + actual, + "expectation `{}` did not match actual", + expected.display(), ); } Ok(()) diff --git a/crates/wit-component/tests/interfaces/console.wat b/crates/wit-component/tests/interfaces/console.wat new file mode 100644 index 0000000000..61b1f312d5 --- /dev/null +++ b/crates/wit-component/tests/interfaces/console.wat @@ -0,0 +1,14 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "arg" string))) + (export (;0;) "log" (func (type 0))) + ) + ) + (export (;0;) "console" "pkg:/console/console" (instance (type 0))) + ) + ) + (export (;1;) "console" "pkg:/console" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/console.wit b/crates/wit-component/tests/interfaces/console.wit new file mode 100644 index 0000000000..f558b2b3ca --- /dev/null +++ b/crates/wit-component/tests/interfaces/console.wit @@ -0,0 +1,3 @@ +interface console { + log: func(arg: string) +} diff --git a/crates/wit-component/tests/interfaces/console.wit.print b/crates/wit-component/tests/interfaces/console.wit.print new file mode 100644 index 0000000000..81a8066f62 --- /dev/null +++ b/crates/wit-component/tests/interfaces/console.wit.print @@ -0,0 +1,4 @@ +interface console { + log: func(arg: string) +} + diff --git a/crates/wit-component/tests/interfaces/default/component.wat b/crates/wit-component/tests/interfaces/default/component.wat deleted file mode 100644 index d8c7697049..0000000000 --- a/crates/wit-component/tests/interfaces/default/component.wat +++ /dev/null @@ -1,23 +0,0 @@ -(component - (core module (;0;) - (type (;0;) (func (param i32))) - (type (;1;) (func (param i32 i32 i32 i32) (result i32))) - (func (;0;) (type 0) (param i32) - unreachable - ) - (func (;1;) (type 1) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "foo" (func 0)) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 1)) - ) - (core instance (;0;) (instantiate 0)) - (alias core export 0 "memory" (core memory (;0;))) - (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "foo" (core func (;1;))) - (type (;0;) (func (param "a" u32))) - (func (;0;) (type 0) (canon lift (core func 1))) - (export (;1;) "foo" (func 0)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/default/types_only.wat b/crates/wit-component/tests/interfaces/default/types_only.wat deleted file mode 100644 index d406ff29b4..0000000000 --- a/crates/wit-component/tests/interfaces/default/types_only.wat +++ /dev/null @@ -1,4 +0,0 @@ -(component - (type (;0;) (func (param "a" u32))) - (export (;1;) "foo" (type 0)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/default/world.wit b/crates/wit-component/tests/interfaces/default/world.wit deleted file mode 100644 index aafc89063c..0000000000 --- a/crates/wit-component/tests/interfaces/default/world.wit +++ /dev/null @@ -1,7 +0,0 @@ -interface interface0 { - foo: func(a: u32) -} - -world default-world { - default export interface0 -} diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate.wat b/crates/wit-component/tests/interfaces/diamond-disambiguate.wat new file mode 100644 index 0000000000..01021a108b --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate.wat @@ -0,0 +1,66 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "t1" (type (eq 0))) + ) + ) + (export (;0;) "shared" "pkg:/shared1/shared" (instance (type 0))) + ) + ) + (export (;1;) "shared1" "pkg:/shared1" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "t2" (type (eq 0))) + ) + ) + (export (;0;) "shared" "pkg:/shared2/shared" (instance (type 0))) + ) + ) + (export (;3;) "shared2" "pkg:/shared2" (type 2)) + (type (;4;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "t1" (type (eq 0))) + ) + ) + (import "shared1" "pkg:/shared1/shared" (instance (type 0))) + (alias export 0 "t1" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "t1" (type (eq 0))) + ) + ) + (import "foo" (instance (type 2))) + (type (;3;) + (instance + (type (;0;) u8) + (export (;1;) "t2" (type (eq 0))) + ) + ) + (import "shared" "pkg:/shared2/shared" (instance (type 3))) + (alias export 2 "t2" (type (;4;))) + (type (;5;) + (instance + (alias outer 1 4 (type (;0;))) + (export (;1;) "t2" (type (eq 0))) + ) + ) + (import "bar" (instance (type 5))) + ) + ) + (export (;0;) "w1" "pkg:/join/w1" (component (type 0))) + ) + ) + (export (;5;) "join" "pkg:/join" (type 4)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit b/crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit new file mode 100644 index 0000000000..f8bc80c84c --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit @@ -0,0 +1,10 @@ +world w1 { + import shared1: pkg.shared1 + + import foo: interface { + use pkg.shared1.{t1} + } + import bar: interface { + use pkg.shared2.{t2} + } +} diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit.print b/crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit.print new file mode 100644 index 0000000000..3c17d5670f --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit.print @@ -0,0 +1,10 @@ +world w1 { + import shared1: pkg.shared1.shared + import foo: interface { + use pkg.shared1.shared.{t1} + } + import shared: pkg.shared2.shared + import bar: interface { + use pkg.shared2.shared.{t2} + } +} diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit new file mode 100644 index 0000000000..962bb48cf8 --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit @@ -0,0 +1,3 @@ +default interface shared { + type t1 = u8 +} diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit.print b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit.print new file mode 100644 index 0000000000..5ad822561b --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit.print @@ -0,0 +1,5 @@ +interface shared { + type t1 = u8 + +} + diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit new file mode 100644 index 0000000000..e12498eaeb --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit @@ -0,0 +1,3 @@ +default interface shared { + type t2 = u8 +} diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit.print b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit.print new file mode 100644 index 0000000000..032fea72d2 --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit.print @@ -0,0 +1,5 @@ +interface shared { + type t2 = u8 + +} + diff --git a/crates/wit-component/tests/interfaces/diamond.wat b/crates/wit-component/tests/interfaces/diamond.wat new file mode 100644 index 0000000000..a321b31ce1 --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond.wat @@ -0,0 +1,88 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (enum "a")) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (export (;0;) "shared" "pkg:/diamond/shared" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (enum "a")) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (import "shared" "pkg:/diamond/shared" (instance (type 0))) + (alias export 0 "the-enum" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (import "foo" (instance (type 2))) + (type (;3;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (import "bar" (instance (type 3))) + ) + ) + (export (;0;) "w1" "pkg:/diamond/w1" (component (type 1))) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) (enum "a")) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (import "shared" "pkg:/diamond/shared" (instance (type 0))) + (alias export 0 "the-enum" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (import "foo" (instance (type 2))) + (type (;3;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (export (;0;) "bar" (instance (type 3))) + ) + ) + (export (;1;) "w2" "pkg:/diamond/w2" (component (type 2))) + (type (;3;) + (component + (type (;0;) + (instance + (type (;0;) (enum "a")) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (import "shared" "pkg:/diamond/shared" (instance (type 0))) + (alias export 0 "the-enum" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (export (;0;) "bar" (instance (type 2))) + ) + ) + (export (;2;) "w3" "pkg:/diamond/w3" (component (type 3))) + ) + ) + (export (;1;) "diamond" "pkg:/diamond" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/diamond.wit b/crates/wit-component/tests/interfaces/diamond.wit new file mode 100644 index 0000000000..b5d718b3ae --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond.wit @@ -0,0 +1,29 @@ +interface shared { + enum the-enum { + a + } +} + +world w1 { + import foo: interface { + use self.shared.{the-enum} + } + import bar: interface { + use self.shared.{the-enum} + } +} + +world w2 { + import foo: interface { + use self.shared.{the-enum} + } + export bar: interface { + use self.shared.{the-enum} + } +} + +world w3 { + export bar: interface { + use self.shared.{the-enum} + } +} diff --git a/crates/wit-component/tests/interfaces/diamond.wit.print b/crates/wit-component/tests/interfaces/diamond.wit.print new file mode 100644 index 0000000000..305f426f49 --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond.wit.print @@ -0,0 +1,31 @@ +interface shared { + enum the-enum { + a, + } + +} + +world w1 { + import shared: self.shared + import foo: interface { + use self.shared.{the-enum} + } + import bar: interface { + use self.shared.{the-enum} + } +} +world w2 { + import shared: self.shared + import foo: interface { + use self.shared.{the-enum} + } + export bar: interface { + use self.shared.{the-enum} + } +} +world w3 { + import shared: self.shared + export bar: interface { + use self.shared.{the-enum} + } +} diff --git a/crates/wit-component/tests/interfaces/empty.wat b/crates/wit-component/tests/interfaces/empty.wat new file mode 100644 index 0000000000..fc16b8128c --- /dev/null +++ b/crates/wit-component/tests/interfaces/empty.wat @@ -0,0 +1,28 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance) + ) + (export (;0;) "empty" "pkg:/empty/empty" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance) + ) + (import "empty" "pkg:/empty/empty" (instance (type 0))) + (type (;1;) + (instance) + ) + (export (;0;) "empty" "pkg:/empty/empty" (instance (type 1))) + ) + ) + (export (;0;) "empty-world" "pkg:/empty/empty-world" (component (type 1))) + (type (;2;) + (component) + ) + (export (;1;) "actually-empty-world" "pkg:/empty/actually-empty-world" (component (type 2))) + ) + ) + (export (;1;) "empty" "pkg:/empty" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/empty.wit b/crates/wit-component/tests/interfaces/empty.wit new file mode 100644 index 0000000000..00342e31dc --- /dev/null +++ b/crates/wit-component/tests/interfaces/empty.wit @@ -0,0 +1,8 @@ +interface empty {} + +world empty-world { + import empty: self.empty + export empty: self.empty +} + +world actually-empty-world {} diff --git a/crates/wit-component/tests/interfaces/empty.wit.print b/crates/wit-component/tests/interfaces/empty.wit.print new file mode 100644 index 0000000000..ed1f9ff70f --- /dev/null +++ b/crates/wit-component/tests/interfaces/empty.wit.print @@ -0,0 +1,9 @@ +interface empty { +} + +world empty-world { + import empty: self.empty + export empty: self.empty +} +world actually-empty-world { +} diff --git a/crates/wit-component/tests/interfaces/empty/component.wat b/crates/wit-component/tests/interfaces/empty/component.wat deleted file mode 100644 index cacb66da59..0000000000 --- a/crates/wit-component/tests/interfaces/empty/component.wat +++ /dev/null @@ -1,14 +0,0 @@ -(component - (core module (;0;) - (type (;0;) (func (param i32 i32 i32 i32) (result i32))) - (func (;0;) (type 0) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 0)) - ) - (core instance (;0;) (instantiate 0)) - (alias core export 0 "memory" (core memory (;0;))) - (alias core export 0 "cabi_realloc" (core func (;0;))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/empty/types_only.wat b/crates/wit-component/tests/interfaces/empty/types_only.wat deleted file mode 100644 index 04743950ba..0000000000 --- a/crates/wit-component/tests/interfaces/empty/types_only.wat +++ /dev/null @@ -1 +0,0 @@ -(component) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/empty/world.wit b/crates/wit-component/tests/interfaces/empty/world.wit deleted file mode 100644 index 8e0c7a6e43..0000000000 --- a/crates/wit-component/tests/interfaces/empty/world.wit +++ /dev/null @@ -1,6 +0,0 @@ -interface empty {} - -world empty-world { - import empty: empty - export empty: empty -} diff --git a/crates/wit-component/tests/interfaces/export-other-packages-interface.wat b/crates/wit-component/tests/interfaces/export-other-packages-interface.wat new file mode 100644 index 0000000000..2d8a295812 --- /dev/null +++ b/crates/wit-component/tests/interfaces/export-other-packages-interface.wat @@ -0,0 +1,19 @@ +(component + (type (;0;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "t" (type (eq 0))) + ) + ) + (export (;0;) "foo" "path:/the-dep/the-doc/the-interface" (instance (type 0))) + ) + ) + (export (;0;) "foo" "pkg:/foo/foo" (component (type 0))) + ) + ) + (export (;1;) "foo" "pkg:/foo" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit b/crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit new file mode 100644 index 0000000000..c26eb475e7 --- /dev/null +++ b/crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit @@ -0,0 +1,7 @@ +default interface the-interface { + type t = u8 +} + +interface unused-interface { + type u = u32 +} diff --git a/crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit.print b/crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit.print new file mode 100644 index 0000000000..19e977561b --- /dev/null +++ b/crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit.print @@ -0,0 +1,5 @@ +interface the-interface { + type t = u8 + +} + diff --git a/crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit b/crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit new file mode 100644 index 0000000000..e4ea8aadb2 --- /dev/null +++ b/crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit @@ -0,0 +1,3 @@ +world foo { + export foo: the-dep.the-doc +} diff --git a/crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit.print b/crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit.print new file mode 100644 index 0000000000..46cee8925d --- /dev/null +++ b/crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit.print @@ -0,0 +1,3 @@ +world foo { + export foo: the-dep.the-doc.the-interface +} diff --git a/crates/wit-component/tests/interfaces/exports.wat b/crates/wit-component/tests/interfaces/exports.wat new file mode 100644 index 0000000000..841a18e13d --- /dev/null +++ b/crates/wit-component/tests/interfaces/exports.wat @@ -0,0 +1,30 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (record (field "a" u32))) + (export (;1;) "my-struct" (type (eq 0))) + (type (;2;) (func (param "a" 1) (result string))) + (export (;0;) "my-function" (func (type 2))) + ) + ) + (export (;0;) "foo" "pkg:/exports/foo" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (record (field "a" u32))) + (export (;1;) "my-struct" (type (eq 0))) + (type (;2;) (func (param "a" 1) (result string))) + (export (;0;) "my-function" (func (type 2))) + ) + ) + (export (;0;) "foo" "pkg:/exports/foo" (instance (type 0))) + ) + ) + (export (;0;) "export-foo" "pkg:/exports/export-foo" (component (type 1))) + ) + ) + (export (;1;) "exports" "pkg:/exports" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/exports/world.wit b/crates/wit-component/tests/interfaces/exports.wit similarity index 84% rename from crates/wit-component/tests/interfaces/exports/world.wit rename to crates/wit-component/tests/interfaces/exports.wit index 1ccb2cc808..c29cb7bdba 100644 --- a/crates/wit-component/tests/interfaces/exports/world.wit +++ b/crates/wit-component/tests/interfaces/exports.wit @@ -7,5 +7,5 @@ interface foo { } world export-foo { - export foo: foo + export foo: self.foo } diff --git a/crates/wit-component/tests/interfaces/exports.wit.print b/crates/wit-component/tests/interfaces/exports.wit.print new file mode 100644 index 0000000000..c29cb7bdba --- /dev/null +++ b/crates/wit-component/tests/interfaces/exports.wit.print @@ -0,0 +1,11 @@ +interface foo { + record my-struct { + a: u32, + } + + my-function: func(a: my-struct) -> string +} + +world export-foo { + export foo: self.foo +} diff --git a/crates/wit-component/tests/interfaces/exports/component.wat b/crates/wit-component/tests/interfaces/exports/component.wat deleted file mode 100644 index 235c3cd572..0000000000 --- a/crates/wit-component/tests/interfaces/exports/component.wat +++ /dev/null @@ -1,32 +0,0 @@ -(component - (core module (;0;) - (type (;0;) (func (param i32) (result i32))) - (type (;1;) (func (param i32))) - (type (;2;) (func (param i32 i32 i32 i32) (result i32))) - (func (;0;) (type 0) (param i32) (result i32) - unreachable - ) - (func (;1;) (type 1) (param i32)) - (func (;2;) (type 2) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "foo#my-function" (func 0)) - (export "cabi_post_foo#my-function" (func 1)) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 2)) - ) - (core instance (;0;) (instantiate 0)) - (alias core export 0 "memory" (core memory (;0;))) - (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "foo#my-function" (core func (;1;))) - (type (;0;) (record (field "a" u32))) - (type (;1;) (func (param "a" 0) (result string))) - (alias core export 0 "cabi_post_foo#my-function" (core func (;2;))) - (func (;0;) (type 1) (canon lift (core func 1) (memory 0) string-encoding=utf8 (post-return 2))) - (instance (;0;) - (export "my-function" (func 0)) - (export "my-struct" (type 0)) - ) - (export (;1;) "foo" (instance 0)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/exports/types_only.wat b/crates/wit-component/tests/interfaces/exports/types_only.wat deleted file mode 100644 index d6f3ddcff0..0000000000 --- a/crates/wit-component/tests/interfaces/exports/types_only.wat +++ /dev/null @@ -1,11 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (record (field "a" u32))) - (export (;1;) "my-struct" (type (eq 0))) - (type (;2;) (func (param "a" 0) (result string))) - (export (;0;) "my-function" (func (type 2))) - ) - ) - (export (;1;) "foo" (type 0)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/flags.wat b/crates/wit-component/tests/interfaces/flags.wat new file mode 100644 index 0000000000..c7026c0b4d --- /dev/null +++ b/crates/wit-component/tests/interfaces/flags.wat @@ -0,0 +1,78 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (flags "b0")) + (export (;1;) "flag1" (type (eq 0))) + (type (;2;) (flags "b0" "b1")) + (export (;3;) "flag2" (type (eq 2))) + (type (;4;) (flags "b0" "b1" "b2" "b3")) + (export (;5;) "flag4" (type (eq 4))) + (type (;6;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7")) + (export (;7;) "flag8" (type (eq 6))) + (type (;8;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15")) + (export (;9;) "flag16" (type (eq 8))) + (type (;10;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31")) + (export (;11;) "flag32" (type (eq 10))) + (type (;12;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31" "b32" "b33" "b34" "b35" "b36" "b37" "b38" "b39" "b40" "b41" "b42" "b43" "b44" "b45" "b46" "b47" "b48" "b49" "b50" "b51" "b52" "b53" "b54" "b55" "b56" "b57" "b58" "b59" "b60" "b61" "b62" "b63")) + (export (;13;) "flag64" (type (eq 12))) + (type (;14;) (func (param "x" 1) (result 1))) + (export (;0;) "roundtrip-flag1" (func (type 14))) + (type (;15;) (func (param "x" 3) (result 3))) + (export (;1;) "roundtrip-flag2" (func (type 15))) + (type (;16;) (func (param "x" 5) (result 5))) + (export (;2;) "roundtrip-flag4" (func (type 16))) + (type (;17;) (func (param "x" 7) (result 7))) + (export (;3;) "roundtrip-flag8" (func (type 17))) + (type (;18;) (func (param "x" 9) (result 9))) + (export (;4;) "roundtrip-flag16" (func (type 18))) + (type (;19;) (func (param "x" 11) (result 11))) + (export (;5;) "roundtrip-flag32" (func (type 19))) + (type (;20;) (func (param "x" 13) (result 13))) + (export (;6;) "roundtrip-flag64" (func (type 20))) + ) + ) + (export (;0;) "imports" "pkg:/flags/imports" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (flags "b0")) + (export (;1;) "flag1" (type (eq 0))) + (type (;2;) (flags "b0" "b1")) + (export (;3;) "flag2" (type (eq 2))) + (type (;4;) (flags "b0" "b1" "b2" "b3")) + (export (;5;) "flag4" (type (eq 4))) + (type (;6;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7")) + (export (;7;) "flag8" (type (eq 6))) + (type (;8;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15")) + (export (;9;) "flag16" (type (eq 8))) + (type (;10;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31")) + (export (;11;) "flag32" (type (eq 10))) + (type (;12;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31" "b32" "b33" "b34" "b35" "b36" "b37" "b38" "b39" "b40" "b41" "b42" "b43" "b44" "b45" "b46" "b47" "b48" "b49" "b50" "b51" "b52" "b53" "b54" "b55" "b56" "b57" "b58" "b59" "b60" "b61" "b62" "b63")) + (export (;13;) "flag64" (type (eq 12))) + (type (;14;) (func (param "x" 1) (result 1))) + (export (;0;) "roundtrip-flag1" (func (type 14))) + (type (;15;) (func (param "x" 3) (result 3))) + (export (;1;) "roundtrip-flag2" (func (type 15))) + (type (;16;) (func (param "x" 5) (result 5))) + (export (;2;) "roundtrip-flag4" (func (type 16))) + (type (;17;) (func (param "x" 7) (result 7))) + (export (;3;) "roundtrip-flag8" (func (type 17))) + (type (;18;) (func (param "x" 9) (result 9))) + (export (;4;) "roundtrip-flag16" (func (type 18))) + (type (;19;) (func (param "x" 11) (result 11))) + (export (;5;) "roundtrip-flag32" (func (type 19))) + (type (;20;) (func (param "x" 13) (result 13))) + (export (;6;) "roundtrip-flag64" (func (type 20))) + ) + ) + (import "imports" "pkg:/flags/imports" (instance (type 0))) + ) + ) + (export (;0;) "flags-world" "pkg:/flags/flags-world" (component (type 1))) + ) + ) + (export (;1;) "flags" "pkg:/flags" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/flags/world.wit b/crates/wit-component/tests/interfaces/flags.wit similarity index 98% rename from crates/wit-component/tests/interfaces/flags/world.wit rename to crates/wit-component/tests/interfaces/flags.wit index c1eedf0b35..634968911d 100644 --- a/crates/wit-component/tests/interfaces/flags/world.wit +++ b/crates/wit-component/tests/interfaces/flags.wit @@ -163,5 +163,5 @@ interface imports { } world flags-world { - import imports: imports + import imports: self.imports } diff --git a/crates/wit-component/tests/interfaces/flags.wit.print b/crates/wit-component/tests/interfaces/flags.wit.print new file mode 100644 index 0000000000..634968911d --- /dev/null +++ b/crates/wit-component/tests/interfaces/flags.wit.print @@ -0,0 +1,167 @@ +interface imports { + flags flag1 { + b0, + } + + flags flag2 { + b0, + b1, + } + + flags flag4 { + b0, + b1, + b2, + b3, + } + + flags flag8 { + b0, + b1, + b2, + b3, + b4, + b5, + b6, + b7, + } + + flags flag16 { + b0, + b1, + b2, + b3, + b4, + b5, + b6, + b7, + b8, + b9, + b10, + b11, + b12, + b13, + b14, + b15, + } + + flags flag32 { + b0, + b1, + b2, + b3, + b4, + b5, + b6, + b7, + b8, + b9, + b10, + b11, + b12, + b13, + b14, + b15, + b16, + b17, + b18, + b19, + b20, + b21, + b22, + b23, + b24, + b25, + b26, + b27, + b28, + b29, + b30, + b31, + } + + flags flag64 { + b0, + b1, + b2, + b3, + b4, + b5, + b6, + b7, + b8, + b9, + b10, + b11, + b12, + b13, + b14, + b15, + b16, + b17, + b18, + b19, + b20, + b21, + b22, + b23, + b24, + b25, + b26, + b27, + b28, + b29, + b30, + b31, + b32, + b33, + b34, + b35, + b36, + b37, + b38, + b39, + b40, + b41, + b42, + b43, + b44, + b45, + b46, + b47, + b48, + b49, + b50, + b51, + b52, + b53, + b54, + b55, + b56, + b57, + b58, + b59, + b60, + b61, + b62, + b63, + } + + roundtrip-flag1: func(x: flag1) -> flag1 + + roundtrip-flag2: func(x: flag2) -> flag2 + + roundtrip-flag4: func(x: flag4) -> flag4 + + roundtrip-flag8: func(x: flag8) -> flag8 + + roundtrip-flag16: func(x: flag16) -> flag16 + + roundtrip-flag32: func(x: flag32) -> flag32 + + roundtrip-flag64: func(x: flag64) -> flag64 +} + +world flags-world { + import imports: self.imports +} diff --git a/crates/wit-component/tests/interfaces/flags/component.wat b/crates/wit-component/tests/interfaces/flags/component.wat deleted file mode 100644 index f69be18f33..0000000000 --- a/crates/wit-component/tests/interfaces/flags/component.wat +++ /dev/null @@ -1,112 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (flags "b0")) - (export (;1;) "flag1" (type (eq 0))) - (type (;2;) (func (param "x" 0) (result 0))) - (export (;0;) "roundtrip-flag1" (func (type 2))) - (type (;3;) (flags "b0" "b1")) - (export (;4;) "flag2" (type (eq 3))) - (type (;5;) (func (param "x" 3) (result 3))) - (export (;1;) "roundtrip-flag2" (func (type 5))) - (type (;6;) (flags "b0" "b1" "b2" "b3")) - (export (;7;) "flag4" (type (eq 6))) - (type (;8;) (func (param "x" 6) (result 6))) - (export (;2;) "roundtrip-flag4" (func (type 8))) - (type (;9;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7")) - (export (;10;) "flag8" (type (eq 9))) - (type (;11;) (func (param "x" 9) (result 9))) - (export (;3;) "roundtrip-flag8" (func (type 11))) - (type (;12;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15")) - (export (;13;) "flag16" (type (eq 12))) - (type (;14;) (func (param "x" 12) (result 12))) - (export (;4;) "roundtrip-flag16" (func (type 14))) - (type (;15;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31")) - (export (;16;) "flag32" (type (eq 15))) - (type (;17;) (func (param "x" 15) (result 15))) - (export (;5;) "roundtrip-flag32" (func (type 17))) - (type (;18;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31" "b32" "b33" "b34" "b35" "b36" "b37" "b38" "b39" "b40" "b41" "b42" "b43" "b44" "b45" "b46" "b47" "b48" "b49" "b50" "b51" "b52" "b53" "b54" "b55" "b56" "b57" "b58" "b59" "b60" "b61" "b62" "b63")) - (export (;19;) "flag64" (type (eq 18))) - (type (;20;) (func (param "x" 18) (result 18))) - (export (;6;) "roundtrip-flag64" (func (type 20))) - ) - ) - (import "imports" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32) (result i32))) - (type (;1;) (func (param i32 i32 i32))) - (type (;2;) (func (param i32 i32 i32 i32) (result i32))) - (import "imports" "roundtrip-flag1" (func (;0;) (type 0))) - (import "imports" "roundtrip-flag2" (func (;1;) (type 0))) - (import "imports" "roundtrip-flag4" (func (;2;) (type 0))) - (import "imports" "roundtrip-flag8" (func (;3;) (type 0))) - (import "imports" "roundtrip-flag16" (func (;4;) (type 0))) - (import "imports" "roundtrip-flag32" (func (;5;) (type 0))) - (import "imports" "roundtrip-flag64" (func (;6;) (type 1))) - (func (;7;) (type 2) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 7)) - ) - (core module (;1;) - (type (;0;) (func (param i32 i32 i32))) - (func $indirect-imports-roundtrip-flag64 (;0;) (type 0) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 0 - call_indirect (type 0) - ) - (table (;0;) 1 1 funcref) - (export "0" (func $indirect-imports-roundtrip-flag64)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i32 i32 i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "$imports" (table (;0;) 1 1 funcref)) - (elem (;0;) (i32.const 0) func 0) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias export 0 "roundtrip-flag1" (func (;0;))) - (core func (;1;) (canon lower (func 0))) - (alias export 0 "roundtrip-flag2" (func (;1;))) - (core func (;2;) (canon lower (func 1))) - (alias export 0 "roundtrip-flag4" (func (;2;))) - (core func (;3;) (canon lower (func 2))) - (alias export 0 "roundtrip-flag8" (func (;3;))) - (core func (;4;) (canon lower (func 3))) - (alias export 0 "roundtrip-flag16" (func (;4;))) - (core func (;5;) (canon lower (func 4))) - (alias export 0 "roundtrip-flag32" (func (;5;))) - (core func (;6;) (canon lower (func 5))) - (core instance (;1;) - (export "roundtrip-flag64" (func 0)) - (export "roundtrip-flag1" (func 1)) - (export "roundtrip-flag2" (func 2)) - (export "roundtrip-flag4" (func 3)) - (export "roundtrip-flag8" (func 4)) - (export "roundtrip-flag16" (func 5)) - (export "roundtrip-flag32" (func 6)) - ) - (core instance (;2;) (instantiate 0 - (with "imports" (instance 1)) - ) - ) - (alias core export 2 "memory" (core memory (;0;))) - (alias core export 2 "cabi_realloc" (core func (;7;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 0 "roundtrip-flag64" (func (;6;))) - (core func (;8;) (canon lower (func 6) (memory 0))) - (core instance (;3;) - (export "$imports" (table 0)) - (export "0" (func 8)) - ) - (core instance (;4;) (instantiate 2 - (with "" (instance 3)) - ) - ) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/flags/types_only.wat b/crates/wit-component/tests/interfaces/flags/types_only.wat deleted file mode 100644 index 6669d38e6f..0000000000 --- a/crates/wit-component/tests/interfaces/flags/types_only.wat +++ /dev/null @@ -1,35 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (flags "b0")) - (export (;1;) "flag1" (type (eq 0))) - (type (;2;) (func (param "x" 0) (result 0))) - (export (;0;) "roundtrip-flag1" (func (type 2))) - (type (;3;) (flags "b0" "b1")) - (export (;4;) "flag2" (type (eq 3))) - (type (;5;) (func (param "x" 3) (result 3))) - (export (;1;) "roundtrip-flag2" (func (type 5))) - (type (;6;) (flags "b0" "b1" "b2" "b3")) - (export (;7;) "flag4" (type (eq 6))) - (type (;8;) (func (param "x" 6) (result 6))) - (export (;2;) "roundtrip-flag4" (func (type 8))) - (type (;9;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7")) - (export (;10;) "flag8" (type (eq 9))) - (type (;11;) (func (param "x" 9) (result 9))) - (export (;3;) "roundtrip-flag8" (func (type 11))) - (type (;12;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15")) - (export (;13;) "flag16" (type (eq 12))) - (type (;14;) (func (param "x" 12) (result 12))) - (export (;4;) "roundtrip-flag16" (func (type 14))) - (type (;15;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31")) - (export (;16;) "flag32" (type (eq 15))) - (type (;17;) (func (param "x" 15) (result 15))) - (export (;5;) "roundtrip-flag32" (func (type 17))) - (type (;18;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31" "b32" "b33" "b34" "b35" "b36" "b37" "b38" "b39" "b40" "b41" "b42" "b43" "b44" "b45" "b46" "b47" "b48" "b49" "b50" "b51" "b52" "b53" "b54" "b55" "b56" "b57" "b58" "b59" "b60" "b61" "b62" "b63")) - (export (;19;) "flag64" (type (eq 18))) - (type (;20;) (func (param "x" 18) (result 18))) - (export (;6;) "roundtrip-flag64" (func (type 20))) - ) - ) - (import "imports" (instance (;0;) (type 0))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/floats.wat b/crates/wit-component/tests/interfaces/floats.wat new file mode 100644 index 0000000000..08de2b549d --- /dev/null +++ b/crates/wit-component/tests/interfaces/floats.wat @@ -0,0 +1,38 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "x" float32))) + (export (;0;) "float32-param" (func (type 0))) + (type (;1;) (func (param "x" float64))) + (export (;1;) "float64-param" (func (type 1))) + (type (;2;) (func (result float32))) + (export (;2;) "float32-result" (func (type 2))) + (type (;3;) (func (result float64))) + (export (;3;) "float64-result" (func (type 3))) + ) + ) + (export (;0;) "floats" "pkg:/floats/floats" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "x" float32))) + (export (;0;) "float32-param" (func (type 0))) + (type (;1;) (func (param "x" float64))) + (export (;1;) "float64-param" (func (type 1))) + (type (;2;) (func (result float32))) + (export (;2;) "float32-result" (func (type 2))) + (type (;3;) (func (result float64))) + (export (;3;) "float64-result" (func (type 3))) + ) + ) + (import "floats" "pkg:/floats/floats" (instance (type 0))) + ) + ) + (export (;0;) "floats-world" "pkg:/floats/floats-world" (component (type 1))) + ) + ) + (export (;1;) "floats" "pkg:/floats" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/floats.wit b/crates/wit-component/tests/interfaces/floats.wit new file mode 100644 index 0000000000..8235b096ed --- /dev/null +++ b/crates/wit-component/tests/interfaces/floats.wit @@ -0,0 +1,10 @@ +interface floats { + float32-param: func(x: float32) + float64-param: func(x: float64) + float32-result: func() -> float32 + float64-result: func() -> float64 +} + +world floats-world { + import floats: self.floats +} diff --git a/crates/wit-component/tests/interfaces/floats/world.wit b/crates/wit-component/tests/interfaces/floats.wit.print similarity index 86% rename from crates/wit-component/tests/interfaces/floats/world.wit rename to crates/wit-component/tests/interfaces/floats.wit.print index 8a0ba3e7b4..c3a6f3384a 100644 --- a/crates/wit-component/tests/interfaces/floats/world.wit +++ b/crates/wit-component/tests/interfaces/floats.wit.print @@ -9,5 +9,5 @@ interface floats { } world floats-world { - import floats: floats + import floats: self.floats } diff --git a/crates/wit-component/tests/interfaces/floats/component.wat b/crates/wit-component/tests/interfaces/floats/component.wat deleted file mode 100644 index dbaa2aba96..0000000000 --- a/crates/wit-component/tests/interfaces/floats/component.wat +++ /dev/null @@ -1,52 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (func (param "x" float32))) - (export (;0;) "float32-param" (func (type 0))) - (type (;1;) (func (param "x" float64))) - (export (;1;) "float64-param" (func (type 1))) - (type (;2;) (func (result float32))) - (export (;2;) "float32-result" (func (type 2))) - (type (;3;) (func (result float64))) - (export (;3;) "float64-result" (func (type 3))) - ) - ) - (import "floats" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param f32))) - (type (;1;) (func (param f64))) - (type (;2;) (func (result f32))) - (type (;3;) (func (result f64))) - (type (;4;) (func (param i32 i32 i32 i32) (result i32))) - (import "floats" "float32-param" (func (;0;) (type 0))) - (import "floats" "float64-param" (func (;1;) (type 1))) - (import "floats" "float32-result" (func (;2;) (type 2))) - (import "floats" "float64-result" (func (;3;) (type 3))) - (func (;4;) (type 4) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 4)) - ) - (alias export 0 "float32-param" (func (;0;))) - (core func (;0;) (canon lower (func 0))) - (alias export 0 "float64-param" (func (;1;))) - (core func (;1;) (canon lower (func 1))) - (alias export 0 "float32-result" (func (;2;))) - (core func (;2;) (canon lower (func 2))) - (alias export 0 "float64-result" (func (;3;))) - (core func (;3;) (canon lower (func 3))) - (core instance (;0;) - (export "float32-param" (func 0)) - (export "float64-param" (func 1)) - (export "float32-result" (func 2)) - (export "float64-result" (func 3)) - ) - (core instance (;1;) (instantiate 0 - (with "floats" (instance 0)) - ) - ) - (alias core export 1 "memory" (core memory (;0;))) - (alias core export 1 "cabi_realloc" (core func (;4;))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/floats/types_only.wat b/crates/wit-component/tests/interfaces/floats/types_only.wat deleted file mode 100644 index 7b82c4442c..0000000000 --- a/crates/wit-component/tests/interfaces/floats/types_only.wat +++ /dev/null @@ -1,15 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (func (param "x" float32))) - (export (;0;) "float32-param" (func (type 0))) - (type (;1;) (func (param "x" float64))) - (export (;1;) "float64-param" (func (type 1))) - (type (;2;) (func (result float32))) - (export (;2;) "float32-result" (func (type 2))) - (type (;3;) (func (result float64))) - (export (;3;) "float64-result" (func (type 3))) - ) - ) - (import "floats" (instance (;0;) (type 0))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/import-and-export.wat b/crates/wit-component/tests/interfaces/import-and-export.wat new file mode 100644 index 0000000000..66d779b1a2 --- /dev/null +++ b/crates/wit-component/tests/interfaces/import-and-export.wat @@ -0,0 +1,40 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "foo" (func (type 0))) + ) + ) + (export (;0;) "foo" "pkg:/import-and-export/foo" (instance (type 0))) + (type (;1;) + (instance + (type (;0;) (func)) + (export (;0;) "bar" (func (type 0))) + ) + ) + (export (;1;) "bar" "pkg:/import-and-export/bar" (instance (type 1))) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "foo" (func (type 0))) + ) + ) + (import "foo" "pkg:/import-and-export/foo" (instance (type 0))) + (type (;1;) + (instance + (type (;0;) (func)) + (export (;0;) "bar" (func (type 0))) + ) + ) + (export (;0;) "bar" "pkg:/import-and-export/bar" (instance (type 1))) + ) + ) + (export (;0;) "import-and-export" "pkg:/import-and-export/import-and-export" (component (type 2))) + ) + ) + (export (;1;) "import-and-export" "pkg:/import-and-export" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/import-and-export/world.wit b/crates/wit-component/tests/interfaces/import-and-export.wit similarity index 67% rename from crates/wit-component/tests/interfaces/import-and-export/world.wit rename to crates/wit-component/tests/interfaces/import-and-export.wit index 08761a292b..a6f028317c 100644 --- a/crates/wit-component/tests/interfaces/import-and-export/world.wit +++ b/crates/wit-component/tests/interfaces/import-and-export.wit @@ -7,6 +7,6 @@ interface bar { } world import-and-export { - import foo: foo - export bar: bar + import foo: self.foo + export bar: self.bar } diff --git a/crates/wit-component/tests/interfaces/import-and-export.wit.print b/crates/wit-component/tests/interfaces/import-and-export.wit.print new file mode 100644 index 0000000000..a6f028317c --- /dev/null +++ b/crates/wit-component/tests/interfaces/import-and-export.wit.print @@ -0,0 +1,12 @@ +interface foo { + foo: func() +} + +interface bar { + bar: func() +} + +world import-and-export { + import foo: self.foo + export bar: self.bar +} diff --git a/crates/wit-component/tests/interfaces/import-and-export/component.wat b/crates/wit-component/tests/interfaces/import-and-export/component.wat deleted file mode 100644 index 49ebaa5d52..0000000000 --- a/crates/wit-component/tests/interfaces/import-and-export/component.wat +++ /dev/null @@ -1,42 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (func)) - (export (;0;) "foo" (func (type 0))) - ) - ) - (import "foo" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func)) - (type (;1;) (func (param i32 i32 i32 i32) (result i32))) - (import "foo" "foo" (func (;0;) (type 0))) - (func (;1;) (type 0) - unreachable - ) - (func (;2;) (type 1) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "bar#bar" (func 1)) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 2)) - ) - (alias export 0 "foo" (func (;0;))) - (core func (;0;) (canon lower (func 0))) - (core instance (;0;) - (export "foo" (func 0)) - ) - (core instance (;1;) (instantiate 0 - (with "foo" (instance 0)) - ) - ) - (alias core export 1 "memory" (core memory (;0;))) - (alias core export 1 "cabi_realloc" (core func (;1;))) - (alias core export 1 "bar#bar" (core func (;2;))) - (type (;1;) (func)) - (func (;1;) (type 1) (canon lift (core func 2))) - (instance (;1;) - (export "bar" (func 1)) - ) - (export (;2;) "bar" (instance 1)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/import-and-export/types_only.wat b/crates/wit-component/tests/interfaces/import-and-export/types_only.wat deleted file mode 100644 index c62264a4c2..0000000000 --- a/crates/wit-component/tests/interfaces/import-and-export/types_only.wat +++ /dev/null @@ -1,16 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (func)) - (export (;0;) "foo" (func (type 0))) - ) - ) - (import "foo" (instance (;0;) (type 0))) - (type (;1;) - (instance - (type (;0;) (func)) - (export (;0;) "bar" (func (type 0))) - ) - ) - (export (;2;) "bar" (type 1)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/integers.wat b/crates/wit-component/tests/interfaces/integers.wat new file mode 100644 index 0000000000..63eda7d67a --- /dev/null +++ b/crates/wit-component/tests/interfaces/integers.wat @@ -0,0 +1,100 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "x" u8))) + (export (;0;) "a1" (func (type 0))) + (type (;1;) (func (param "x" s8))) + (export (;1;) "a2" (func (type 1))) + (type (;2;) (func (param "x" u16))) + (export (;2;) "a3" (func (type 2))) + (type (;3;) (func (param "x" s16))) + (export (;3;) "a4" (func (type 3))) + (type (;4;) (func (param "x" u32))) + (export (;4;) "a5" (func (type 4))) + (type (;5;) (func (param "x" s32))) + (export (;5;) "a6" (func (type 5))) + (type (;6;) (func (param "x" u64))) + (export (;6;) "a7" (func (type 6))) + (type (;7;) (func (param "x" s64))) + (export (;7;) "a8" (func (type 7))) + (type (;8;) (func (param "p1" u8) (param "p2" s8) (param "p3" u16) (param "p4" s16) (param "p5" u32) (param "p6" s32) (param "p7" u64) (param "p8" s64))) + (export (;8;) "a9" (func (type 8))) + (type (;9;) (func (result u8))) + (export (;9;) "r1" (func (type 9))) + (type (;10;) (func (result s8))) + (export (;10;) "r2" (func (type 10))) + (type (;11;) (func (result u16))) + (export (;11;) "r3" (func (type 11))) + (type (;12;) (func (result s16))) + (export (;12;) "r4" (func (type 12))) + (type (;13;) (func (result u32))) + (export (;13;) "r5" (func (type 13))) + (type (;14;) (func (result s32))) + (export (;14;) "r6" (func (type 14))) + (type (;15;) (func (result u64))) + (export (;15;) "r7" (func (type 15))) + (type (;16;) (func (result s64))) + (export (;16;) "r8" (func (type 16))) + (type (;17;) (tuple s64 u8)) + (type (;18;) (func (result 17))) + (export (;17;) "pair-ret" (func (type 18))) + (type (;19;) (func (result "a" s64) (result "b" u8))) + (export (;18;) "multi-ret" (func (type 19))) + ) + ) + (export (;0;) "integers" "pkg:/integers/integers" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "x" u8))) + (export (;0;) "a1" (func (type 0))) + (type (;1;) (func (param "x" s8))) + (export (;1;) "a2" (func (type 1))) + (type (;2;) (func (param "x" u16))) + (export (;2;) "a3" (func (type 2))) + (type (;3;) (func (param "x" s16))) + (export (;3;) "a4" (func (type 3))) + (type (;4;) (func (param "x" u32))) + (export (;4;) "a5" (func (type 4))) + (type (;5;) (func (param "x" s32))) + (export (;5;) "a6" (func (type 5))) + (type (;6;) (func (param "x" u64))) + (export (;6;) "a7" (func (type 6))) + (type (;7;) (func (param "x" s64))) + (export (;7;) "a8" (func (type 7))) + (type (;8;) (func (param "p1" u8) (param "p2" s8) (param "p3" u16) (param "p4" s16) (param "p5" u32) (param "p6" s32) (param "p7" u64) (param "p8" s64))) + (export (;8;) "a9" (func (type 8))) + (type (;9;) (func (result u8))) + (export (;9;) "r1" (func (type 9))) + (type (;10;) (func (result s8))) + (export (;10;) "r2" (func (type 10))) + (type (;11;) (func (result u16))) + (export (;11;) "r3" (func (type 11))) + (type (;12;) (func (result s16))) + (export (;12;) "r4" (func (type 12))) + (type (;13;) (func (result u32))) + (export (;13;) "r5" (func (type 13))) + (type (;14;) (func (result s32))) + (export (;14;) "r6" (func (type 14))) + (type (;15;) (func (result u64))) + (export (;15;) "r7" (func (type 15))) + (type (;16;) (func (result s64))) + (export (;16;) "r8" (func (type 16))) + (type (;17;) (tuple s64 u8)) + (type (;18;) (func (result 17))) + (export (;17;) "pair-ret" (func (type 18))) + (type (;19;) (func (result "a" s64) (result "b" u8))) + (export (;18;) "multi-ret" (func (type 19))) + ) + ) + (import "integers" "pkg:/integers/integers" (instance (type 0))) + ) + ) + (export (;0;) "integers-world" "pkg:/integers/integers-world" (component (type 1))) + ) + ) + (export (;1;) "integers" "pkg:/integers" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/integers.wit b/crates/wit-component/tests/interfaces/integers.wit new file mode 100644 index 0000000000..ef67ac4a94 --- /dev/null +++ b/crates/wit-component/tests/interfaces/integers.wit @@ -0,0 +1,25 @@ +interface integers { + a1: func(x: u8) + a2: func(x: s8) + a3: func(x: u16) + a4: func(x: s16) + a5: func(x: u32) + a6: func(x: s32) + a7: func(x: u64) + a8: func(x: s64) + a9: func(p1: u8, p2: s8, p3: u16, p4: s16, p5: u32, p6: s32, p7: u64, p8: s64) + r1: func() -> u8 + r2: func() -> s8 + r3: func() -> u16 + r4: func() -> s16 + r5: func() -> u32 + r6: func() -> s32 + r7: func() -> u64 + r8: func() -> s64 + pair-ret: func() -> tuple + multi-ret: func() -> (a: s64, b: u8) +} + +world integers-world { + import integers: self.integers +} diff --git a/crates/wit-component/tests/interfaces/integers/world.wit b/crates/wit-component/tests/interfaces/integers.wit.print similarity index 94% rename from crates/wit-component/tests/interfaces/integers/world.wit rename to crates/wit-component/tests/interfaces/integers.wit.print index 7de51e03eb..d8d88ea2c6 100644 --- a/crates/wit-component/tests/interfaces/integers/world.wit +++ b/crates/wit-component/tests/interfaces/integers.wit.print @@ -39,5 +39,5 @@ interface integers { } world integers-world { - import integers: integers + import integers: self.integers } diff --git a/crates/wit-component/tests/interfaces/integers/component.wat b/crates/wit-component/tests/interfaces/integers/component.wat deleted file mode 100644 index 6c7261028f..0000000000 --- a/crates/wit-component/tests/interfaces/integers/component.wat +++ /dev/null @@ -1,181 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (func (param "x" u8))) - (export (;0;) "a1" (func (type 0))) - (type (;1;) (func (param "x" s8))) - (export (;1;) "a2" (func (type 1))) - (type (;2;) (func (param "x" u16))) - (export (;2;) "a3" (func (type 2))) - (type (;3;) (func (param "x" s16))) - (export (;3;) "a4" (func (type 3))) - (type (;4;) (func (param "x" u32))) - (export (;4;) "a5" (func (type 4))) - (type (;5;) (func (param "x" s32))) - (export (;5;) "a6" (func (type 5))) - (type (;6;) (func (param "x" u64))) - (export (;6;) "a7" (func (type 6))) - (type (;7;) (func (param "x" s64))) - (export (;7;) "a8" (func (type 7))) - (type (;8;) (func (param "p1" u8) (param "p2" s8) (param "p3" u16) (param "p4" s16) (param "p5" u32) (param "p6" s32) (param "p7" u64) (param "p8" s64))) - (export (;8;) "a9" (func (type 8))) - (type (;9;) (func (result u8))) - (export (;9;) "r1" (func (type 9))) - (type (;10;) (func (result s8))) - (export (;10;) "r2" (func (type 10))) - (type (;11;) (func (result u16))) - (export (;11;) "r3" (func (type 11))) - (type (;12;) (func (result s16))) - (export (;12;) "r4" (func (type 12))) - (type (;13;) (func (result u32))) - (export (;13;) "r5" (func (type 13))) - (type (;14;) (func (result s32))) - (export (;14;) "r6" (func (type 14))) - (type (;15;) (func (result u64))) - (export (;15;) "r7" (func (type 15))) - (type (;16;) (func (result s64))) - (export (;16;) "r8" (func (type 16))) - (type (;17;) (tuple s64 u8)) - (type (;18;) (func (result 17))) - (export (;17;) "pair-ret" (func (type 18))) - (type (;19;) (func (result "a" s64) (result "b" u8))) - (export (;18;) "multi-ret" (func (type 19))) - ) - ) - (import "integers" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32))) - (type (;1;) (func (param i64))) - (type (;2;) (func (param i32 i32 i32 i32 i32 i32 i64 i64))) - (type (;3;) (func (result i32))) - (type (;4;) (func (result i64))) - (type (;5;) (func (param i32 i32 i32 i32) (result i32))) - (import "integers" "a1" (func (;0;) (type 0))) - (import "integers" "a2" (func (;1;) (type 0))) - (import "integers" "a3" (func (;2;) (type 0))) - (import "integers" "a4" (func (;3;) (type 0))) - (import "integers" "a5" (func (;4;) (type 0))) - (import "integers" "a6" (func (;5;) (type 0))) - (import "integers" "a7" (func (;6;) (type 1))) - (import "integers" "a8" (func (;7;) (type 1))) - (import "integers" "a9" (func (;8;) (type 2))) - (import "integers" "r1" (func (;9;) (type 3))) - (import "integers" "r2" (func (;10;) (type 3))) - (import "integers" "r3" (func (;11;) (type 3))) - (import "integers" "r4" (func (;12;) (type 3))) - (import "integers" "r5" (func (;13;) (type 3))) - (import "integers" "r6" (func (;14;) (type 3))) - (import "integers" "r7" (func (;15;) (type 4))) - (import "integers" "r8" (func (;16;) (type 4))) - (import "integers" "pair-ret" (func (;17;) (type 0))) - (import "integers" "multi-ret" (func (;18;) (type 0))) - (func (;19;) (type 5) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 19)) - ) - (core module (;1;) - (type (;0;) (func (param i32))) - (func $indirect-integers-pair-ret (;0;) (type 0) (param i32) - local.get 0 - i32.const 0 - call_indirect (type 0) - ) - (func $indirect-integers-multi-ret (;1;) (type 0) (param i32) - local.get 0 - i32.const 1 - call_indirect (type 0) - ) - (table (;0;) 2 2 funcref) - (export "0" (func $indirect-integers-pair-ret)) - (export "1" (func $indirect-integers-multi-ret)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "1" (func (;1;) (type 0))) - (import "" "$imports" (table (;0;) 2 2 funcref)) - (elem (;0;) (i32.const 0) func 0 1) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) - (alias export 0 "a1" (func (;0;))) - (core func (;2;) (canon lower (func 0))) - (alias export 0 "a2" (func (;1;))) - (core func (;3;) (canon lower (func 1))) - (alias export 0 "a3" (func (;2;))) - (core func (;4;) (canon lower (func 2))) - (alias export 0 "a4" (func (;3;))) - (core func (;5;) (canon lower (func 3))) - (alias export 0 "a5" (func (;4;))) - (core func (;6;) (canon lower (func 4))) - (alias export 0 "a6" (func (;5;))) - (core func (;7;) (canon lower (func 5))) - (alias export 0 "a7" (func (;6;))) - (core func (;8;) (canon lower (func 6))) - (alias export 0 "a8" (func (;7;))) - (core func (;9;) (canon lower (func 7))) - (alias export 0 "a9" (func (;8;))) - (core func (;10;) (canon lower (func 8))) - (alias export 0 "r1" (func (;9;))) - (core func (;11;) (canon lower (func 9))) - (alias export 0 "r2" (func (;10;))) - (core func (;12;) (canon lower (func 10))) - (alias export 0 "r3" (func (;11;))) - (core func (;13;) (canon lower (func 11))) - (alias export 0 "r4" (func (;12;))) - (core func (;14;) (canon lower (func 12))) - (alias export 0 "r5" (func (;13;))) - (core func (;15;) (canon lower (func 13))) - (alias export 0 "r6" (func (;14;))) - (core func (;16;) (canon lower (func 14))) - (alias export 0 "r7" (func (;15;))) - (core func (;17;) (canon lower (func 15))) - (alias export 0 "r8" (func (;16;))) - (core func (;18;) (canon lower (func 16))) - (core instance (;1;) - (export "pair-ret" (func 0)) - (export "multi-ret" (func 1)) - (export "a1" (func 2)) - (export "a2" (func 3)) - (export "a3" (func 4)) - (export "a4" (func 5)) - (export "a5" (func 6)) - (export "a6" (func 7)) - (export "a7" (func 8)) - (export "a8" (func 9)) - (export "a9" (func 10)) - (export "r1" (func 11)) - (export "r2" (func 12)) - (export "r3" (func 13)) - (export "r4" (func 14)) - (export "r5" (func 15)) - (export "r6" (func 16)) - (export "r7" (func 17)) - (export "r8" (func 18)) - ) - (core instance (;2;) (instantiate 0 - (with "integers" (instance 1)) - ) - ) - (alias core export 2 "memory" (core memory (;0;))) - (alias core export 2 "cabi_realloc" (core func (;19;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 0 "pair-ret" (func (;17;))) - (core func (;20;) (canon lower (func 17) (memory 0))) - (alias export 0 "multi-ret" (func (;18;))) - (core func (;21;) (canon lower (func 18) (memory 0))) - (core instance (;3;) - (export "$imports" (table 0)) - (export "0" (func 20)) - (export "1" (func 21)) - ) - (core instance (;4;) (instantiate 2 - (with "" (instance 3)) - ) - ) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/integers/types_only.wat b/crates/wit-component/tests/interfaces/integers/types_only.wat deleted file mode 100644 index 17b8e6ce4d..0000000000 --- a/crates/wit-component/tests/interfaces/integers/types_only.wat +++ /dev/null @@ -1,46 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (func (param "x" u8))) - (export (;0;) "a1" (func (type 0))) - (type (;1;) (func (param "x" s8))) - (export (;1;) "a2" (func (type 1))) - (type (;2;) (func (param "x" u16))) - (export (;2;) "a3" (func (type 2))) - (type (;3;) (func (param "x" s16))) - (export (;3;) "a4" (func (type 3))) - (type (;4;) (func (param "x" u32))) - (export (;4;) "a5" (func (type 4))) - (type (;5;) (func (param "x" s32))) - (export (;5;) "a6" (func (type 5))) - (type (;6;) (func (param "x" u64))) - (export (;6;) "a7" (func (type 6))) - (type (;7;) (func (param "x" s64))) - (export (;7;) "a8" (func (type 7))) - (type (;8;) (func (param "p1" u8) (param "p2" s8) (param "p3" u16) (param "p4" s16) (param "p5" u32) (param "p6" s32) (param "p7" u64) (param "p8" s64))) - (export (;8;) "a9" (func (type 8))) - (type (;9;) (func (result u8))) - (export (;9;) "r1" (func (type 9))) - (type (;10;) (func (result s8))) - (export (;10;) "r2" (func (type 10))) - (type (;11;) (func (result u16))) - (export (;11;) "r3" (func (type 11))) - (type (;12;) (func (result s16))) - (export (;12;) "r4" (func (type 12))) - (type (;13;) (func (result u32))) - (export (;13;) "r5" (func (type 13))) - (type (;14;) (func (result s32))) - (export (;14;) "r6" (func (type 14))) - (type (;15;) (func (result u64))) - (export (;15;) "r7" (func (type 15))) - (type (;16;) (func (result s64))) - (export (;16;) "r8" (func (type 16))) - (type (;17;) (tuple s64 u8)) - (type (;18;) (func (result 17))) - (export (;17;) "pair-ret" (func (type 18))) - (type (;19;) (func (result "a" s64) (result "b" u8))) - (export (;18;) "multi-ret" (func (type 19))) - ) - ) - (import "integers" (instance (;0;) (type 0))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/lists.wat b/crates/wit-component/tests/interfaces/lists.wat new file mode 100644 index 0000000000..8f18a3d755 --- /dev/null +++ b/crates/wit-component/tests/interfaces/lists.wat @@ -0,0 +1,202 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (list u8)) + (type (;1;) (record (field "a1" u32) (field "a2" u64) (field "a3" s32) (field "a4" s64) (field "b" string) (field "c" 0))) + (export (;2;) "other-record" (type (eq 1))) + (type (;3;) (record (field "x" string) (field "y" 2) (field "c1" u32) (field "c2" u64) (field "c3" s32) (field "c4" s64))) + (export (;4;) "some-record" (type (eq 3))) + (type (;5;) (variant (case "a") (case "b" u32) (case "c" string))) + (export (;6;) "other-variant" (type (eq 5))) + (type (;7;) (list 6)) + (type (;8;) (variant (case "a" string) (case "b") (case "c" u32) (case "d" 7))) + (export (;9;) "some-variant" (type (eq 8))) + (type (;10;) (tuple string u8 s8 u16 s16 u32 s32 u64 s64 float32 float64 char)) + (type (;11;) (list 10)) + (export (;12;) "load-store-all-sizes" (type (eq 11))) + (type (;13;) (func (param "x" 0))) + (export (;0;) "list-u8-param" (func (type 13))) + (type (;14;) (list u16)) + (type (;15;) (func (param "x" 14))) + (export (;1;) "list-u16-param" (func (type 15))) + (type (;16;) (list u32)) + (type (;17;) (func (param "x" 16))) + (export (;2;) "list-u32-param" (func (type 17))) + (type (;18;) (list u64)) + (type (;19;) (func (param "x" 18))) + (export (;3;) "list-u64-param" (func (type 19))) + (type (;20;) (list s8)) + (type (;21;) (func (param "x" 20))) + (export (;4;) "list-s8-param" (func (type 21))) + (type (;22;) (list s16)) + (type (;23;) (func (param "x" 22))) + (export (;5;) "list-s16-param" (func (type 23))) + (type (;24;) (list s32)) + (type (;25;) (func (param "x" 24))) + (export (;6;) "list-s32-param" (func (type 25))) + (type (;26;) (list s64)) + (type (;27;) (func (param "x" 26))) + (export (;7;) "list-s64-param" (func (type 27))) + (type (;28;) (list float32)) + (type (;29;) (func (param "x" 28))) + (export (;8;) "list-float32-param" (func (type 29))) + (type (;30;) (list float64)) + (type (;31;) (func (param "x" 30))) + (export (;9;) "list-float64-param" (func (type 31))) + (type (;32;) (func (result 0))) + (export (;10;) "list-u8-ret" (func (type 32))) + (type (;33;) (func (result 14))) + (export (;11;) "list-u16-ret" (func (type 33))) + (type (;34;) (func (result 16))) + (export (;12;) "list-u32-ret" (func (type 34))) + (type (;35;) (func (result 18))) + (export (;13;) "list-u64-ret" (func (type 35))) + (type (;36;) (func (result 20))) + (export (;14;) "list-s8-ret" (func (type 36))) + (type (;37;) (func (result 22))) + (export (;15;) "list-s16-ret" (func (type 37))) + (type (;38;) (func (result 24))) + (export (;16;) "list-s32-ret" (func (type 38))) + (type (;39;) (func (result 26))) + (export (;17;) "list-s64-ret" (func (type 39))) + (type (;40;) (func (result 28))) + (export (;18;) "list-float32-ret" (func (type 40))) + (type (;41;) (func (result 30))) + (export (;19;) "list-float64-ret" (func (type 41))) + (type (;42;) (tuple u8 s8)) + (type (;43;) (list 42)) + (type (;44;) (tuple s64 u32)) + (type (;45;) (list 44)) + (type (;46;) (func (param "x" 43) (result 45))) + (export (;20;) "tuple-list" (func (type 46))) + (type (;47;) (list string)) + (type (;48;) (func (param "a" 47))) + (export (;21;) "string-list-arg" (func (type 48))) + (type (;49;) (func (result 47))) + (export (;22;) "string-list-ret" (func (type 49))) + (type (;50;) (tuple u8 string)) + (type (;51;) (list 50)) + (type (;52;) (tuple string u8)) + (type (;53;) (list 52)) + (type (;54;) (func (param "x" 51) (result 53))) + (export (;23;) "tuple-string-list" (func (type 54))) + (type (;55;) (func (param "x" 47) (result 47))) + (export (;24;) "string-list" (func (type 55))) + (type (;56;) (list 4)) + (type (;57;) (list 2)) + (type (;58;) (func (param "x" 56) (result 57))) + (export (;25;) "record-list" (func (type 58))) + (type (;59;) (list 9)) + (type (;60;) (func (param "x" 59) (result 7))) + (export (;26;) "variant-list" (func (type 60))) + (type (;61;) (func (param "a" 12) (result 12))) + (export (;27;) "load-store-everything" (func (type 61))) + ) + ) + (export (;0;) "lists" "pkg:/lists/lists" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (list u8)) + (type (;1;) (record (field "a1" u32) (field "a2" u64) (field "a3" s32) (field "a4" s64) (field "b" string) (field "c" 0))) + (export (;2;) "other-record" (type (eq 1))) + (type (;3;) (record (field "x" string) (field "y" 2) (field "c1" u32) (field "c2" u64) (field "c3" s32) (field "c4" s64))) + (export (;4;) "some-record" (type (eq 3))) + (type (;5;) (variant (case "a") (case "b" u32) (case "c" string))) + (export (;6;) "other-variant" (type (eq 5))) + (type (;7;) (list 6)) + (type (;8;) (variant (case "a" string) (case "b") (case "c" u32) (case "d" 7))) + (export (;9;) "some-variant" (type (eq 8))) + (type (;10;) (tuple string u8 s8 u16 s16 u32 s32 u64 s64 float32 float64 char)) + (type (;11;) (list 10)) + (export (;12;) "load-store-all-sizes" (type (eq 11))) + (type (;13;) (func (param "x" 0))) + (export (;0;) "list-u8-param" (func (type 13))) + (type (;14;) (list u16)) + (type (;15;) (func (param "x" 14))) + (export (;1;) "list-u16-param" (func (type 15))) + (type (;16;) (list u32)) + (type (;17;) (func (param "x" 16))) + (export (;2;) "list-u32-param" (func (type 17))) + (type (;18;) (list u64)) + (type (;19;) (func (param "x" 18))) + (export (;3;) "list-u64-param" (func (type 19))) + (type (;20;) (list s8)) + (type (;21;) (func (param "x" 20))) + (export (;4;) "list-s8-param" (func (type 21))) + (type (;22;) (list s16)) + (type (;23;) (func (param "x" 22))) + (export (;5;) "list-s16-param" (func (type 23))) + (type (;24;) (list s32)) + (type (;25;) (func (param "x" 24))) + (export (;6;) "list-s32-param" (func (type 25))) + (type (;26;) (list s64)) + (type (;27;) (func (param "x" 26))) + (export (;7;) "list-s64-param" (func (type 27))) + (type (;28;) (list float32)) + (type (;29;) (func (param "x" 28))) + (export (;8;) "list-float32-param" (func (type 29))) + (type (;30;) (list float64)) + (type (;31;) (func (param "x" 30))) + (export (;9;) "list-float64-param" (func (type 31))) + (type (;32;) (func (result 0))) + (export (;10;) "list-u8-ret" (func (type 32))) + (type (;33;) (func (result 14))) + (export (;11;) "list-u16-ret" (func (type 33))) + (type (;34;) (func (result 16))) + (export (;12;) "list-u32-ret" (func (type 34))) + (type (;35;) (func (result 18))) + (export (;13;) "list-u64-ret" (func (type 35))) + (type (;36;) (func (result 20))) + (export (;14;) "list-s8-ret" (func (type 36))) + (type (;37;) (func (result 22))) + (export (;15;) "list-s16-ret" (func (type 37))) + (type (;38;) (func (result 24))) + (export (;16;) "list-s32-ret" (func (type 38))) + (type (;39;) (func (result 26))) + (export (;17;) "list-s64-ret" (func (type 39))) + (type (;40;) (func (result 28))) + (export (;18;) "list-float32-ret" (func (type 40))) + (type (;41;) (func (result 30))) + (export (;19;) "list-float64-ret" (func (type 41))) + (type (;42;) (tuple u8 s8)) + (type (;43;) (list 42)) + (type (;44;) (tuple s64 u32)) + (type (;45;) (list 44)) + (type (;46;) (func (param "x" 43) (result 45))) + (export (;20;) "tuple-list" (func (type 46))) + (type (;47;) (list string)) + (type (;48;) (func (param "a" 47))) + (export (;21;) "string-list-arg" (func (type 48))) + (type (;49;) (func (result 47))) + (export (;22;) "string-list-ret" (func (type 49))) + (type (;50;) (tuple u8 string)) + (type (;51;) (list 50)) + (type (;52;) (tuple string u8)) + (type (;53;) (list 52)) + (type (;54;) (func (param "x" 51) (result 53))) + (export (;23;) "tuple-string-list" (func (type 54))) + (type (;55;) (func (param "x" 47) (result 47))) + (export (;24;) "string-list" (func (type 55))) + (type (;56;) (list 4)) + (type (;57;) (list 2)) + (type (;58;) (func (param "x" 56) (result 57))) + (export (;25;) "record-list" (func (type 58))) + (type (;59;) (list 9)) + (type (;60;) (func (param "x" 59) (result 7))) + (export (;26;) "variant-list" (func (type 60))) + (type (;61;) (func (param "a" 12) (result 12))) + (export (;27;) "load-store-everything" (func (type 61))) + ) + ) + (import "lists" "pkg:/lists/lists" (instance (type 0))) + ) + ) + (export (;0;) "lists-world" "pkg:/lists/lists-world" (component (type 1))) + ) + ) + (export (;1;) "lists" "pkg:/lists" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/lists/world.wit b/crates/wit-component/tests/interfaces/lists.wit similarity index 98% rename from crates/wit-component/tests/interfaces/lists/world.wit rename to crates/wit-component/tests/interfaces/lists.wit index 749e1079ad..38a84ecab8 100644 --- a/crates/wit-component/tests/interfaces/lists/world.wit +++ b/crates/wit-component/tests/interfaces/lists.wit @@ -90,5 +90,5 @@ interface lists { } world lists-world { - import lists: lists + import lists: self.lists } diff --git a/crates/wit-component/tests/interfaces/lists.wit.print b/crates/wit-component/tests/interfaces/lists.wit.print new file mode 100644 index 0000000000..38a84ecab8 --- /dev/null +++ b/crates/wit-component/tests/interfaces/lists.wit.print @@ -0,0 +1,94 @@ +interface lists { + record other-record { + a1: u32, + a2: u64, + a3: s32, + a4: s64, + b: string, + c: list, + } + + record some-record { + x: string, + y: other-record, + c1: u32, + c2: u64, + c3: s32, + c4: s64, + } + + variant other-variant { + a, + b(u32), + c(string), + } + + variant some-variant { + a(string), + b, + c(u32), + d(list), + } + + type load-store-all-sizes = list> + + list-u8-param: func(x: list) + + list-u16-param: func(x: list) + + list-u32-param: func(x: list) + + list-u64-param: func(x: list) + + list-s8-param: func(x: list) + + list-s16-param: func(x: list) + + list-s32-param: func(x: list) + + list-s64-param: func(x: list) + + list-float32-param: func(x: list) + + list-float64-param: func(x: list) + + list-u8-ret: func() -> list + + list-u16-ret: func() -> list + + list-u32-ret: func() -> list + + list-u64-ret: func() -> list + + list-s8-ret: func() -> list + + list-s16-ret: func() -> list + + list-s32-ret: func() -> list + + list-s64-ret: func() -> list + + list-float32-ret: func() -> list + + list-float64-ret: func() -> list + + tuple-list: func(x: list>) -> list> + + string-list-arg: func(a: list) + + string-list-ret: func() -> list + + tuple-string-list: func(x: list>) -> list> + + string-list: func(x: list) -> list + + record-list: func(x: list) -> list + + variant-list: func(x: list) -> list + + load-store-everything: func(a: load-store-all-sizes) -> load-store-all-sizes +} + +world lists-world { + import lists: self.lists +} diff --git a/crates/wit-component/tests/interfaces/lists/component.wat b/crates/wit-component/tests/interfaces/lists/component.wat deleted file mode 100644 index be22b6e12a..0000000000 --- a/crates/wit-component/tests/interfaces/lists/component.wat +++ /dev/null @@ -1,527 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (list u8)) - (type (;1;) (func (param "x" 0))) - (export (;0;) "list-u8-param" (func (type 1))) - (type (;2;) (list u16)) - (type (;3;) (func (param "x" 2))) - (export (;1;) "list-u16-param" (func (type 3))) - (type (;4;) (list u32)) - (type (;5;) (func (param "x" 4))) - (export (;2;) "list-u32-param" (func (type 5))) - (type (;6;) (list u64)) - (type (;7;) (func (param "x" 6))) - (export (;3;) "list-u64-param" (func (type 7))) - (type (;8;) (list s8)) - (type (;9;) (func (param "x" 8))) - (export (;4;) "list-s8-param" (func (type 9))) - (type (;10;) (list s16)) - (type (;11;) (func (param "x" 10))) - (export (;5;) "list-s16-param" (func (type 11))) - (type (;12;) (list s32)) - (type (;13;) (func (param "x" 12))) - (export (;6;) "list-s32-param" (func (type 13))) - (type (;14;) (list s64)) - (type (;15;) (func (param "x" 14))) - (export (;7;) "list-s64-param" (func (type 15))) - (type (;16;) (list float32)) - (type (;17;) (func (param "x" 16))) - (export (;8;) "list-float32-param" (func (type 17))) - (type (;18;) (list float64)) - (type (;19;) (func (param "x" 18))) - (export (;9;) "list-float64-param" (func (type 19))) - (type (;20;) (func (result 0))) - (export (;10;) "list-u8-ret" (func (type 20))) - (type (;21;) (func (result 2))) - (export (;11;) "list-u16-ret" (func (type 21))) - (type (;22;) (func (result 4))) - (export (;12;) "list-u32-ret" (func (type 22))) - (type (;23;) (func (result 6))) - (export (;13;) "list-u64-ret" (func (type 23))) - (type (;24;) (func (result 8))) - (export (;14;) "list-s8-ret" (func (type 24))) - (type (;25;) (func (result 10))) - (export (;15;) "list-s16-ret" (func (type 25))) - (type (;26;) (func (result 12))) - (export (;16;) "list-s32-ret" (func (type 26))) - (type (;27;) (func (result 14))) - (export (;17;) "list-s64-ret" (func (type 27))) - (type (;28;) (func (result 16))) - (export (;18;) "list-float32-ret" (func (type 28))) - (type (;29;) (func (result 18))) - (export (;19;) "list-float64-ret" (func (type 29))) - (type (;30;) (tuple u8 s8)) - (type (;31;) (list 30)) - (type (;32;) (tuple s64 u32)) - (type (;33;) (list 32)) - (type (;34;) (func (param "x" 31) (result 33))) - (export (;20;) "tuple-list" (func (type 34))) - (type (;35;) (list string)) - (type (;36;) (func (param "a" 35))) - (export (;21;) "string-list-arg" (func (type 36))) - (type (;37;) (func (result 35))) - (export (;22;) "string-list-ret" (func (type 37))) - (type (;38;) (tuple u8 string)) - (type (;39;) (list 38)) - (type (;40;) (tuple string u8)) - (type (;41;) (list 40)) - (type (;42;) (func (param "x" 39) (result 41))) - (export (;23;) "tuple-string-list" (func (type 42))) - (type (;43;) (func (param "x" 35) (result 35))) - (export (;24;) "string-list" (func (type 43))) - (type (;44;) (record (field "a1" u32) (field "a2" u64) (field "a3" s32) (field "a4" s64) (field "b" string) (field "c" 0))) - (export (;45;) "other-record" (type (eq 44))) - (type (;46;) (record (field "x" string) (field "y" 44) (field "c1" u32) (field "c2" u64) (field "c3" s32) (field "c4" s64))) - (export (;47;) "some-record" (type (eq 46))) - (type (;48;) (list 46)) - (type (;49;) (list 44)) - (type (;50;) (func (param "x" 48) (result 49))) - (export (;25;) "record-list" (func (type 50))) - (type (;51;) (variant (case "a") (case "b" u32) (case "c" string))) - (export (;52;) "other-variant" (type (eq 51))) - (type (;53;) (list 51)) - (type (;54;) (variant (case "a" string) (case "b") (case "c" u32) (case "d" 53))) - (export (;55;) "some-variant" (type (eq 54))) - (type (;56;) (list 54)) - (type (;57;) (func (param "x" 56) (result 53))) - (export (;26;) "variant-list" (func (type 57))) - (type (;58;) (tuple string u8 s8 u16 s16 u32 s32 u64 s64 float32 float64 char)) - (type (;59;) (list 58)) - (export (;60;) "load-store-all-sizes" (type (eq 59))) - (type (;61;) (func (param "a" 59) (result 59))) - (export (;27;) "load-store-everything" (func (type 61))) - ) - ) - (import "lists" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32))) - (type (;2;) (func (param i32 i32 i32))) - (type (;3;) (func (param i32 i32 i32 i32) (result i32))) - (import "lists" "list-u8-param" (func (;0;) (type 0))) - (import "lists" "list-u16-param" (func (;1;) (type 0))) - (import "lists" "list-u32-param" (func (;2;) (type 0))) - (import "lists" "list-u64-param" (func (;3;) (type 0))) - (import "lists" "list-s8-param" (func (;4;) (type 0))) - (import "lists" "list-s16-param" (func (;5;) (type 0))) - (import "lists" "list-s32-param" (func (;6;) (type 0))) - (import "lists" "list-s64-param" (func (;7;) (type 0))) - (import "lists" "list-float32-param" (func (;8;) (type 0))) - (import "lists" "list-float64-param" (func (;9;) (type 0))) - (import "lists" "list-u8-ret" (func (;10;) (type 1))) - (import "lists" "list-u16-ret" (func (;11;) (type 1))) - (import "lists" "list-u32-ret" (func (;12;) (type 1))) - (import "lists" "list-u64-ret" (func (;13;) (type 1))) - (import "lists" "list-s8-ret" (func (;14;) (type 1))) - (import "lists" "list-s16-ret" (func (;15;) (type 1))) - (import "lists" "list-s32-ret" (func (;16;) (type 1))) - (import "lists" "list-s64-ret" (func (;17;) (type 1))) - (import "lists" "list-float32-ret" (func (;18;) (type 1))) - (import "lists" "list-float64-ret" (func (;19;) (type 1))) - (import "lists" "tuple-list" (func (;20;) (type 2))) - (import "lists" "string-list-arg" (func (;21;) (type 0))) - (import "lists" "string-list-ret" (func (;22;) (type 1))) - (import "lists" "tuple-string-list" (func (;23;) (type 2))) - (import "lists" "string-list" (func (;24;) (type 2))) - (import "lists" "record-list" (func (;25;) (type 2))) - (import "lists" "variant-list" (func (;26;) (type 2))) - (import "lists" "load-store-everything" (func (;27;) (type 2))) - (func (;28;) (type 3) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 28)) - ) - (core module (;1;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32))) - (type (;2;) (func (param i32 i32 i32))) - (func $indirect-lists-list-u8-param (;0;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 0 - call_indirect (type 0) - ) - (func $indirect-lists-list-u16-param (;1;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 1 - call_indirect (type 0) - ) - (func $indirect-lists-list-u32-param (;2;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 2 - call_indirect (type 0) - ) - (func $indirect-lists-list-u64-param (;3;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 3 - call_indirect (type 0) - ) - (func $indirect-lists-list-s8-param (;4;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 4 - call_indirect (type 0) - ) - (func $indirect-lists-list-s16-param (;5;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 5 - call_indirect (type 0) - ) - (func $indirect-lists-list-s32-param (;6;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 6 - call_indirect (type 0) - ) - (func $indirect-lists-list-s64-param (;7;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 7 - call_indirect (type 0) - ) - (func $indirect-lists-list-float32-param (;8;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 8 - call_indirect (type 0) - ) - (func $indirect-lists-list-float64-param (;9;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 9 - call_indirect (type 0) - ) - (func $indirect-lists-list-u8-ret (;10;) (type 1) (param i32) - local.get 0 - i32.const 10 - call_indirect (type 1) - ) - (func $indirect-lists-list-u16-ret (;11;) (type 1) (param i32) - local.get 0 - i32.const 11 - call_indirect (type 1) - ) - (func $indirect-lists-list-u32-ret (;12;) (type 1) (param i32) - local.get 0 - i32.const 12 - call_indirect (type 1) - ) - (func $indirect-lists-list-u64-ret (;13;) (type 1) (param i32) - local.get 0 - i32.const 13 - call_indirect (type 1) - ) - (func $indirect-lists-list-s8-ret (;14;) (type 1) (param i32) - local.get 0 - i32.const 14 - call_indirect (type 1) - ) - (func $indirect-lists-list-s16-ret (;15;) (type 1) (param i32) - local.get 0 - i32.const 15 - call_indirect (type 1) - ) - (func $indirect-lists-list-s32-ret (;16;) (type 1) (param i32) - local.get 0 - i32.const 16 - call_indirect (type 1) - ) - (func $indirect-lists-list-s64-ret (;17;) (type 1) (param i32) - local.get 0 - i32.const 17 - call_indirect (type 1) - ) - (func $indirect-lists-list-float32-ret (;18;) (type 1) (param i32) - local.get 0 - i32.const 18 - call_indirect (type 1) - ) - (func $indirect-lists-list-float64-ret (;19;) (type 1) (param i32) - local.get 0 - i32.const 19 - call_indirect (type 1) - ) - (func $indirect-lists-tuple-list (;20;) (type 2) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 20 - call_indirect (type 2) - ) - (func $indirect-lists-string-list-arg (;21;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 21 - call_indirect (type 0) - ) - (func $indirect-lists-string-list-ret (;22;) (type 1) (param i32) - local.get 0 - i32.const 22 - call_indirect (type 1) - ) - (func $indirect-lists-tuple-string-list (;23;) (type 2) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 23 - call_indirect (type 2) - ) - (func $indirect-lists-string-list (;24;) (type 2) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 24 - call_indirect (type 2) - ) - (func $indirect-lists-record-list (;25;) (type 2) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 25 - call_indirect (type 2) - ) - (func $indirect-lists-variant-list (;26;) (type 2) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 26 - call_indirect (type 2) - ) - (func $indirect-lists-load-store-everything (;27;) (type 2) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 27 - call_indirect (type 2) - ) - (table (;0;) 28 28 funcref) - (export "0" (func $indirect-lists-list-u8-param)) - (export "1" (func $indirect-lists-list-u16-param)) - (export "2" (func $indirect-lists-list-u32-param)) - (export "3" (func $indirect-lists-list-u64-param)) - (export "4" (func $indirect-lists-list-s8-param)) - (export "5" (func $indirect-lists-list-s16-param)) - (export "6" (func $indirect-lists-list-s32-param)) - (export "7" (func $indirect-lists-list-s64-param)) - (export "8" (func $indirect-lists-list-float32-param)) - (export "9" (func $indirect-lists-list-float64-param)) - (export "10" (func $indirect-lists-list-u8-ret)) - (export "11" (func $indirect-lists-list-u16-ret)) - (export "12" (func $indirect-lists-list-u32-ret)) - (export "13" (func $indirect-lists-list-u64-ret)) - (export "14" (func $indirect-lists-list-s8-ret)) - (export "15" (func $indirect-lists-list-s16-ret)) - (export "16" (func $indirect-lists-list-s32-ret)) - (export "17" (func $indirect-lists-list-s64-ret)) - (export "18" (func $indirect-lists-list-float32-ret)) - (export "19" (func $indirect-lists-list-float64-ret)) - (export "20" (func $indirect-lists-tuple-list)) - (export "21" (func $indirect-lists-string-list-arg)) - (export "22" (func $indirect-lists-string-list-ret)) - (export "23" (func $indirect-lists-tuple-string-list)) - (export "24" (func $indirect-lists-string-list)) - (export "25" (func $indirect-lists-record-list)) - (export "26" (func $indirect-lists-variant-list)) - (export "27" (func $indirect-lists-load-store-everything)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32))) - (type (;2;) (func (param i32 i32 i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "1" (func (;1;) (type 0))) - (import "" "2" (func (;2;) (type 0))) - (import "" "3" (func (;3;) (type 0))) - (import "" "4" (func (;4;) (type 0))) - (import "" "5" (func (;5;) (type 0))) - (import "" "6" (func (;6;) (type 0))) - (import "" "7" (func (;7;) (type 0))) - (import "" "8" (func (;8;) (type 0))) - (import "" "9" (func (;9;) (type 0))) - (import "" "10" (func (;10;) (type 1))) - (import "" "11" (func (;11;) (type 1))) - (import "" "12" (func (;12;) (type 1))) - (import "" "13" (func (;13;) (type 1))) - (import "" "14" (func (;14;) (type 1))) - (import "" "15" (func (;15;) (type 1))) - (import "" "16" (func (;16;) (type 1))) - (import "" "17" (func (;17;) (type 1))) - (import "" "18" (func (;18;) (type 1))) - (import "" "19" (func (;19;) (type 1))) - (import "" "20" (func (;20;) (type 2))) - (import "" "21" (func (;21;) (type 0))) - (import "" "22" (func (;22;) (type 1))) - (import "" "23" (func (;23;) (type 2))) - (import "" "24" (func (;24;) (type 2))) - (import "" "25" (func (;25;) (type 2))) - (import "" "26" (func (;26;) (type 2))) - (import "" "27" (func (;27;) (type 2))) - (import "" "$imports" (table (;0;) 28 28 funcref)) - (elem (;0;) (i32.const 0) func 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) - (alias core export 0 "2" (core func (;2;))) - (alias core export 0 "3" (core func (;3;))) - (alias core export 0 "4" (core func (;4;))) - (alias core export 0 "5" (core func (;5;))) - (alias core export 0 "6" (core func (;6;))) - (alias core export 0 "7" (core func (;7;))) - (alias core export 0 "8" (core func (;8;))) - (alias core export 0 "9" (core func (;9;))) - (alias core export 0 "10" (core func (;10;))) - (alias core export 0 "11" (core func (;11;))) - (alias core export 0 "12" (core func (;12;))) - (alias core export 0 "13" (core func (;13;))) - (alias core export 0 "14" (core func (;14;))) - (alias core export 0 "15" (core func (;15;))) - (alias core export 0 "16" (core func (;16;))) - (alias core export 0 "17" (core func (;17;))) - (alias core export 0 "18" (core func (;18;))) - (alias core export 0 "19" (core func (;19;))) - (alias core export 0 "20" (core func (;20;))) - (alias core export 0 "21" (core func (;21;))) - (alias core export 0 "22" (core func (;22;))) - (alias core export 0 "23" (core func (;23;))) - (alias core export 0 "24" (core func (;24;))) - (alias core export 0 "25" (core func (;25;))) - (alias core export 0 "26" (core func (;26;))) - (alias core export 0 "27" (core func (;27;))) - (core instance (;1;) - (export "list-u8-param" (func 0)) - (export "list-u16-param" (func 1)) - (export "list-u32-param" (func 2)) - (export "list-u64-param" (func 3)) - (export "list-s8-param" (func 4)) - (export "list-s16-param" (func 5)) - (export "list-s32-param" (func 6)) - (export "list-s64-param" (func 7)) - (export "list-float32-param" (func 8)) - (export "list-float64-param" (func 9)) - (export "list-u8-ret" (func 10)) - (export "list-u16-ret" (func 11)) - (export "list-u32-ret" (func 12)) - (export "list-u64-ret" (func 13)) - (export "list-s8-ret" (func 14)) - (export "list-s16-ret" (func 15)) - (export "list-s32-ret" (func 16)) - (export "list-s64-ret" (func 17)) - (export "list-float32-ret" (func 18)) - (export "list-float64-ret" (func 19)) - (export "tuple-list" (func 20)) - (export "string-list-arg" (func 21)) - (export "string-list-ret" (func 22)) - (export "tuple-string-list" (func 23)) - (export "string-list" (func 24)) - (export "record-list" (func 25)) - (export "variant-list" (func 26)) - (export "load-store-everything" (func 27)) - ) - (core instance (;2;) (instantiate 0 - (with "lists" (instance 1)) - ) - ) - (alias core export 2 "memory" (core memory (;0;))) - (alias core export 2 "cabi_realloc" (core func (;28;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 0 "list-u8-param" (func (;0;))) - (core func (;29;) (canon lower (func 0) (memory 0))) - (alias export 0 "list-u16-param" (func (;1;))) - (core func (;30;) (canon lower (func 1) (memory 0))) - (alias export 0 "list-u32-param" (func (;2;))) - (core func (;31;) (canon lower (func 2) (memory 0))) - (alias export 0 "list-u64-param" (func (;3;))) - (core func (;32;) (canon lower (func 3) (memory 0))) - (alias export 0 "list-s8-param" (func (;4;))) - (core func (;33;) (canon lower (func 4) (memory 0))) - (alias export 0 "list-s16-param" (func (;5;))) - (core func (;34;) (canon lower (func 5) (memory 0))) - (alias export 0 "list-s32-param" (func (;6;))) - (core func (;35;) (canon lower (func 6) (memory 0))) - (alias export 0 "list-s64-param" (func (;7;))) - (core func (;36;) (canon lower (func 7) (memory 0))) - (alias export 0 "list-float32-param" (func (;8;))) - (core func (;37;) (canon lower (func 8) (memory 0))) - (alias export 0 "list-float64-param" (func (;9;))) - (core func (;38;) (canon lower (func 9) (memory 0))) - (alias export 0 "list-u8-ret" (func (;10;))) - (core func (;39;) (canon lower (func 10) (memory 0) (realloc 28))) - (alias export 0 "list-u16-ret" (func (;11;))) - (core func (;40;) (canon lower (func 11) (memory 0) (realloc 28))) - (alias export 0 "list-u32-ret" (func (;12;))) - (core func (;41;) (canon lower (func 12) (memory 0) (realloc 28))) - (alias export 0 "list-u64-ret" (func (;13;))) - (core func (;42;) (canon lower (func 13) (memory 0) (realloc 28))) - (alias export 0 "list-s8-ret" (func (;14;))) - (core func (;43;) (canon lower (func 14) (memory 0) (realloc 28))) - (alias export 0 "list-s16-ret" (func (;15;))) - (core func (;44;) (canon lower (func 15) (memory 0) (realloc 28))) - (alias export 0 "list-s32-ret" (func (;16;))) - (core func (;45;) (canon lower (func 16) (memory 0) (realloc 28))) - (alias export 0 "list-s64-ret" (func (;17;))) - (core func (;46;) (canon lower (func 17) (memory 0) (realloc 28))) - (alias export 0 "list-float32-ret" (func (;18;))) - (core func (;47;) (canon lower (func 18) (memory 0) (realloc 28))) - (alias export 0 "list-float64-ret" (func (;19;))) - (core func (;48;) (canon lower (func 19) (memory 0) (realloc 28))) - (alias export 0 "tuple-list" (func (;20;))) - (core func (;49;) (canon lower (func 20) (memory 0) (realloc 28))) - (alias export 0 "string-list-arg" (func (;21;))) - (core func (;50;) (canon lower (func 21) (memory 0) string-encoding=utf8)) - (alias export 0 "string-list-ret" (func (;22;))) - (core func (;51;) (canon lower (func 22) (memory 0) (realloc 28) string-encoding=utf8)) - (alias export 0 "tuple-string-list" (func (;23;))) - (core func (;52;) (canon lower (func 23) (memory 0) (realloc 28) string-encoding=utf8)) - (alias export 0 "string-list" (func (;24;))) - (core func (;53;) (canon lower (func 24) (memory 0) (realloc 28) string-encoding=utf8)) - (alias export 0 "record-list" (func (;25;))) - (core func (;54;) (canon lower (func 25) (memory 0) (realloc 28) string-encoding=utf8)) - (alias export 0 "variant-list" (func (;26;))) - (core func (;55;) (canon lower (func 26) (memory 0) (realloc 28) string-encoding=utf8)) - (alias export 0 "load-store-everything" (func (;27;))) - (core func (;56;) (canon lower (func 27) (memory 0) (realloc 28) string-encoding=utf8)) - (core instance (;3;) - (export "$imports" (table 0)) - (export "0" (func 29)) - (export "1" (func 30)) - (export "2" (func 31)) - (export "3" (func 32)) - (export "4" (func 33)) - (export "5" (func 34)) - (export "6" (func 35)) - (export "7" (func 36)) - (export "8" (func 37)) - (export "9" (func 38)) - (export "10" (func 39)) - (export "11" (func 40)) - (export "12" (func 41)) - (export "13" (func 42)) - (export "14" (func 43)) - (export "15" (func 44)) - (export "16" (func 45)) - (export "17" (func 46)) - (export "18" (func 47)) - (export "19" (func 48)) - (export "20" (func 49)) - (export "21" (func 50)) - (export "22" (func 51)) - (export "23" (func 52)) - (export "24" (func 53)) - (export "25" (func 54)) - (export "26" (func 55)) - (export "27" (func 56)) - ) - (core instance (;4;) (instantiate 2 - (with "" (instance 3)) - ) - ) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/lists/types_only.wat b/crates/wit-component/tests/interfaces/lists/types_only.wat deleted file mode 100644 index 8595288322..0000000000 --- a/crates/wit-component/tests/interfaces/lists/types_only.wat +++ /dev/null @@ -1,97 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (list u8)) - (type (;1;) (func (param "x" 0))) - (export (;0;) "list-u8-param" (func (type 1))) - (type (;2;) (list u16)) - (type (;3;) (func (param "x" 2))) - (export (;1;) "list-u16-param" (func (type 3))) - (type (;4;) (list u32)) - (type (;5;) (func (param "x" 4))) - (export (;2;) "list-u32-param" (func (type 5))) - (type (;6;) (list u64)) - (type (;7;) (func (param "x" 6))) - (export (;3;) "list-u64-param" (func (type 7))) - (type (;8;) (list s8)) - (type (;9;) (func (param "x" 8))) - (export (;4;) "list-s8-param" (func (type 9))) - (type (;10;) (list s16)) - (type (;11;) (func (param "x" 10))) - (export (;5;) "list-s16-param" (func (type 11))) - (type (;12;) (list s32)) - (type (;13;) (func (param "x" 12))) - (export (;6;) "list-s32-param" (func (type 13))) - (type (;14;) (list s64)) - (type (;15;) (func (param "x" 14))) - (export (;7;) "list-s64-param" (func (type 15))) - (type (;16;) (list float32)) - (type (;17;) (func (param "x" 16))) - (export (;8;) "list-float32-param" (func (type 17))) - (type (;18;) (list float64)) - (type (;19;) (func (param "x" 18))) - (export (;9;) "list-float64-param" (func (type 19))) - (type (;20;) (func (result 0))) - (export (;10;) "list-u8-ret" (func (type 20))) - (type (;21;) (func (result 2))) - (export (;11;) "list-u16-ret" (func (type 21))) - (type (;22;) (func (result 4))) - (export (;12;) "list-u32-ret" (func (type 22))) - (type (;23;) (func (result 6))) - (export (;13;) "list-u64-ret" (func (type 23))) - (type (;24;) (func (result 8))) - (export (;14;) "list-s8-ret" (func (type 24))) - (type (;25;) (func (result 10))) - (export (;15;) "list-s16-ret" (func (type 25))) - (type (;26;) (func (result 12))) - (export (;16;) "list-s32-ret" (func (type 26))) - (type (;27;) (func (result 14))) - (export (;17;) "list-s64-ret" (func (type 27))) - (type (;28;) (func (result 16))) - (export (;18;) "list-float32-ret" (func (type 28))) - (type (;29;) (func (result 18))) - (export (;19;) "list-float64-ret" (func (type 29))) - (type (;30;) (tuple u8 s8)) - (type (;31;) (list 30)) - (type (;32;) (tuple s64 u32)) - (type (;33;) (list 32)) - (type (;34;) (func (param "x" 31) (result 33))) - (export (;20;) "tuple-list" (func (type 34))) - (type (;35;) (list string)) - (type (;36;) (func (param "a" 35))) - (export (;21;) "string-list-arg" (func (type 36))) - (type (;37;) (func (result 35))) - (export (;22;) "string-list-ret" (func (type 37))) - (type (;38;) (tuple u8 string)) - (type (;39;) (list 38)) - (type (;40;) (tuple string u8)) - (type (;41;) (list 40)) - (type (;42;) (func (param "x" 39) (result 41))) - (export (;23;) "tuple-string-list" (func (type 42))) - (type (;43;) (func (param "x" 35) (result 35))) - (export (;24;) "string-list" (func (type 43))) - (type (;44;) (record (field "a1" u32) (field "a2" u64) (field "a3" s32) (field "a4" s64) (field "b" string) (field "c" 0))) - (export (;45;) "other-record" (type (eq 44))) - (type (;46;) (record (field "x" string) (field "y" 44) (field "c1" u32) (field "c2" u64) (field "c3" s32) (field "c4" s64))) - (export (;47;) "some-record" (type (eq 46))) - (type (;48;) (list 46)) - (type (;49;) (list 44)) - (type (;50;) (func (param "x" 48) (result 49))) - (export (;25;) "record-list" (func (type 50))) - (type (;51;) (variant (case "a") (case "b" u32) (case "c" string))) - (export (;52;) "other-variant" (type (eq 51))) - (type (;53;) (list 51)) - (type (;54;) (variant (case "a" string) (case "b") (case "c" u32) (case "d" 53))) - (export (;55;) "some-variant" (type (eq 54))) - (type (;56;) (list 54)) - (type (;57;) (func (param "x" 56) (result 53))) - (export (;26;) "variant-list" (func (type 57))) - (type (;58;) (tuple string u8 s8 u16 s16 u32 s32 u64 s64 float32 float64 char)) - (type (;59;) (list 58)) - (export (;60;) "load-store-all-sizes" (type (eq 59))) - (type (;61;) (func (param "a" 59) (result 59))) - (export (;27;) "load-store-everything" (func (type 61))) - ) - ) - (import "lists" (instance (;0;) (type 0))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/multi-doc.wat b/crates/wit-component/tests/interfaces/multi-doc.wat new file mode 100644 index 0000000000..1280107929 --- /dev/null +++ b/crates/wit-component/tests/interfaces/multi-doc.wat @@ -0,0 +1,58 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "the-type" (type (eq 0))) + ) + ) + (export (;0;) "b" "pkg:/b/b" (instance (type 0))) + (alias export 0 "the-type" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-type" (type (eq 0))) + ) + ) + (export (;1;) "a" "pkg:/b/a" (instance (type 2))) + ) + ) + (export (;1;) "b" "pkg:/b" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "the-type" (type (eq 0))) + ) + ) + (import "b" "pkg:/b/b" (instance (type 0))) + (alias export 0 "the-type" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-type" (type (eq 0))) + ) + ) + (import "a" "pkg:/b/a" (instance (type 2))) + (alias export 1 "the-type" (type (;3;))) + (type (;4;) + (instance + (alias outer 1 3 (type (;0;))) + (export (;1;) "the-type" (type (eq 0))) + ) + ) + (export (;0;) "b" "pkg:/a/b" (instance (type 4))) + (alias export 2 "the-type" (type (;5;))) + (type (;6;) + (instance + (alias outer 1 5 (type (;0;))) + (export (;1;) "the-type" (type (eq 0))) + ) + ) + (export (;1;) "a" "pkg:/a/a" (instance (type 6))) + ) + ) + (export (;3;) "a" "pkg:/a" (type 2)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/multi-doc/a.wit b/crates/wit-component/tests/interfaces/multi-doc/a.wit new file mode 100644 index 0000000000..71fb0ce245 --- /dev/null +++ b/crates/wit-component/tests/interfaces/multi-doc/a.wit @@ -0,0 +1,7 @@ +default interface a { + use self.b.{the-type} +} + +interface b { + use pkg.b.{the-type} +} diff --git a/crates/wit-component/tests/interfaces/multi-doc/a.wit.print b/crates/wit-component/tests/interfaces/multi-doc/a.wit.print new file mode 100644 index 0000000000..a6c65878ba --- /dev/null +++ b/crates/wit-component/tests/interfaces/multi-doc/a.wit.print @@ -0,0 +1,8 @@ +interface b { + use pkg.b.b.{the-type} +} + +interface a { + use pkg.b.b.{the-type} +} + diff --git a/crates/wit-component/tests/interfaces/multi-doc/b.wit b/crates/wit-component/tests/interfaces/multi-doc/b.wit new file mode 100644 index 0000000000..da21c2e976 --- /dev/null +++ b/crates/wit-component/tests/interfaces/multi-doc/b.wit @@ -0,0 +1,7 @@ +default interface a { + use self.b.{the-type} +} + +interface b { + record the-type {} +} diff --git a/crates/wit-component/tests/interfaces/multi-doc/b.wit.print b/crates/wit-component/tests/interfaces/multi-doc/b.wit.print new file mode 100644 index 0000000000..4fa21babb6 --- /dev/null +++ b/crates/wit-component/tests/interfaces/multi-doc/b.wit.print @@ -0,0 +1,10 @@ +interface b { + record the-type { + } + +} + +interface a { + use self.b.{the-type} +} + diff --git a/crates/wit-component/tests/interfaces/preserve-foreign-reexport.wat b/crates/wit-component/tests/interfaces/preserve-foreign-reexport.wat new file mode 100644 index 0000000000..30f021ad37 --- /dev/null +++ b/crates/wit-component/tests/interfaces/preserve-foreign-reexport.wat @@ -0,0 +1,20 @@ +(component + (type (;0;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "foo" (type (eq 0))) + (export (;2;) "bar" (type (eq 1))) + ) + ) + (export (;0;) "foo" "path:/my-dep/my-doc/my-interface" (instance (type 0))) + ) + ) + (export (;0;) "foo" "pkg:/foo/foo" (component (type 0))) + ) + ) + (export (;1;) "foo" "pkg:/foo" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit new file mode 100644 index 0000000000..2eeed9cd16 --- /dev/null +++ b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit @@ -0,0 +1,4 @@ +default interface my-interface { + record foo {} + type bar = foo +} diff --git a/crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit.print b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit.print new file mode 100644 index 0000000000..0c86fc6cc5 --- /dev/null +++ b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit.print @@ -0,0 +1,8 @@ +interface my-interface { + record foo { + } + + type bar = foo + +} + diff --git a/crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit new file mode 100644 index 0000000000..ec472fe210 --- /dev/null +++ b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit @@ -0,0 +1,3 @@ +world foo { + export foo: my-dep.my-doc +} diff --git a/crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit.print b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit.print new file mode 100644 index 0000000000..f9406dd11a --- /dev/null +++ b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit.print @@ -0,0 +1,3 @@ +world foo { + export foo: my-dep.my-doc.my-interface +} diff --git a/crates/wit-component/tests/interfaces/print-keyword.wat b/crates/wit-component/tests/interfaces/print-keyword.wat new file mode 100644 index 0000000000..8f5268005b --- /dev/null +++ b/crates/wit-component/tests/interfaces/print-keyword.wat @@ -0,0 +1,17 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u32) + (export (;1;) "type" (type (eq 0))) + (export (;2;) "world" (type (eq 1))) + (type (;3;) (record (field "variant" 2))) + (export (;4;) "record" (type (eq 3))) + ) + ) + (export (;0;) "interface" "pkg:/print-keyword/interface" (instance (type 0))) + ) + ) + (export (;1;) "print-keyword" "pkg:/print-keyword" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/print-keyword.wit b/crates/wit-component/tests/interfaces/print-keyword.wit new file mode 100644 index 0000000000..9d938a98e8 --- /dev/null +++ b/crates/wit-component/tests/interfaces/print-keyword.wit @@ -0,0 +1,8 @@ +interface %interface { + type %type = u32 + type %world = %type + + record %record { + %variant: %world + } +} diff --git a/crates/wit-component/tests/interfaces/print-keyword.wit.print b/crates/wit-component/tests/interfaces/print-keyword.wit.print new file mode 100644 index 0000000000..7d930bdcde --- /dev/null +++ b/crates/wit-component/tests/interfaces/print-keyword.wit.print @@ -0,0 +1,11 @@ +interface %interface { + type %type = u32 + + type %world = %type + + record %record { + %variant: %world, + } + +} + diff --git a/crates/wit-component/tests/interfaces/records.wat b/crates/wit-component/tests/interfaces/records.wat new file mode 100644 index 0000000000..2f98a0bdf9 --- /dev/null +++ b/crates/wit-component/tests/interfaces/records.wat @@ -0,0 +1,132 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "empty" (type (eq 0))) + (type (;2;) (record (field "a" u32) (field "b" u32))) + (export (;3;) "scalars" (type (eq 2))) + (type (;4;) (flags "a" "b" "c" "d" "e" "f" "g" "h" "i")) + (export (;5;) "really-flags" (type (eq 4))) + (type (;6;) (record (field "a" 3) (field "b" u32) (field "c" 1) (field "d" string) (field "e" 5))) + (export (;7;) "aggregates" (type (eq 6))) + (type (;8;) s32) + (export (;9;) "int-typedef" (type (eq 8))) + (type (;10;) (tuple 9)) + (export (;11;) "tuple-typedef2" (type (eq 10))) + (type (;12;) (tuple char u32)) + (type (;13;) (func (param "x" 12))) + (export (;0;) "tuple-arg" (func (type 13))) + (type (;14;) (func (result 12))) + (export (;1;) "tuple-result" (func (type 14))) + (type (;15;) (func (param "x" 1))) + (export (;2;) "empty-arg" (func (type 15))) + (type (;16;) (func (result 1))) + (export (;3;) "empty-result" (func (type 16))) + (type (;17;) (func (param "x" 3))) + (export (;4;) "scalar-arg" (func (type 17))) + (type (;18;) (func (result 3))) + (export (;5;) "scalar-result" (func (type 18))) + (type (;19;) (func (param "x" 5))) + (export (;6;) "flags-arg" (func (type 19))) + (type (;20;) (func (result 5))) + (export (;7;) "flags-result" (func (type 20))) + (type (;21;) (func (param "x" 7))) + (export (;8;) "aggregate-arg" (func (type 21))) + (type (;22;) (func (result 7))) + (export (;9;) "aggregate-result" (func (type 22))) + (type (;23;) (func (param "e" 11) (result s32))) + (export (;10;) "typedef-inout" (func (type 23))) + ) + ) + (export (;0;) "records" "pkg:/records/records" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "empty" (type (eq 0))) + (type (;2;) (record (field "a" u32) (field "b" u32))) + (export (;3;) "scalars" (type (eq 2))) + (type (;4;) (flags "a" "b" "c" "d" "e" "f" "g" "h" "i")) + (export (;5;) "really-flags" (type (eq 4))) + (type (;6;) (record (field "a" 3) (field "b" u32) (field "c" 1) (field "d" string) (field "e" 5))) + (export (;7;) "aggregates" (type (eq 6))) + (type (;8;) s32) + (export (;9;) "int-typedef" (type (eq 8))) + (type (;10;) (tuple 9)) + (export (;11;) "tuple-typedef2" (type (eq 10))) + (type (;12;) (tuple char u32)) + (type (;13;) (func (param "x" 12))) + (export (;0;) "tuple-arg" (func (type 13))) + (type (;14;) (func (result 12))) + (export (;1;) "tuple-result" (func (type 14))) + (type (;15;) (func (param "x" 1))) + (export (;2;) "empty-arg" (func (type 15))) + (type (;16;) (func (result 1))) + (export (;3;) "empty-result" (func (type 16))) + (type (;17;) (func (param "x" 3))) + (export (;4;) "scalar-arg" (func (type 17))) + (type (;18;) (func (result 3))) + (export (;5;) "scalar-result" (func (type 18))) + (type (;19;) (func (param "x" 5))) + (export (;6;) "flags-arg" (func (type 19))) + (type (;20;) (func (result 5))) + (export (;7;) "flags-result" (func (type 20))) + (type (;21;) (func (param "x" 7))) + (export (;8;) "aggregate-arg" (func (type 21))) + (type (;22;) (func (result 7))) + (export (;9;) "aggregate-result" (func (type 22))) + (type (;23;) (func (param "e" 11) (result s32))) + (export (;10;) "typedef-inout" (func (type 23))) + ) + ) + (import "records" "pkg:/records/records" (instance (type 0))) + (alias export 0 "empty" (type (;1;))) + (alias export 0 "scalars" (type (;2;))) + (alias export 0 "really-flags" (type (;3;))) + (alias export 0 "aggregates" (type (;4;))) + (alias export 0 "int-typedef" (type (;5;))) + (alias export 0 "tuple-typedef2" (type (;6;))) + (type (;7;) + (instance + (alias outer 1 1 (type (;0;))) + (alias outer 1 2 (type (;1;))) + (alias outer 1 3 (type (;2;))) + (alias outer 1 4 (type (;3;))) + (alias outer 1 5 (type (;4;))) + (alias outer 1 6 (type (;5;))) + (type (;6;) (tuple char u32)) + (type (;7;) (func (param "x" 6))) + (export (;0;) "tuple-arg" (func (type 7))) + (type (;8;) (func (result 6))) + (export (;1;) "tuple-result" (func (type 8))) + (type (;9;) (func (param "x" 0))) + (export (;2;) "empty-arg" (func (type 9))) + (type (;10;) (func (result 0))) + (export (;3;) "empty-result" (func (type 10))) + (type (;11;) (func (param "x" 1))) + (export (;4;) "scalar-arg" (func (type 11))) + (type (;12;) (func (result 1))) + (export (;5;) "scalar-result" (func (type 12))) + (type (;13;) (func (param "x" 2))) + (export (;6;) "flags-arg" (func (type 13))) + (type (;14;) (func (result 2))) + (export (;7;) "flags-result" (func (type 14))) + (type (;15;) (func (param "x" 3))) + (export (;8;) "aggregate-arg" (func (type 15))) + (type (;16;) (func (result 3))) + (export (;9;) "aggregate-result" (func (type 16))) + (type (;17;) (func (param "e" 5) (result s32))) + (export (;10;) "typedef-inout" (func (type 17))) + ) + ) + (export (;0;) "records" "pkg:/records/records" (instance (type 7))) + ) + ) + (export (;0;) "records-world" "pkg:/records/records-world" (component (type 1))) + ) + ) + (export (;1;) "records" "pkg:/records" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/records.wit b/crates/wit-component/tests/interfaces/records.wit new file mode 100644 index 0000000000..79fbf3b7ff --- /dev/null +++ b/crates/wit-component/tests/interfaces/records.wit @@ -0,0 +1,60 @@ +interface records { + record empty { + } + + record scalars { + a: u32, + b: u32, + } + + flags really-flags { + a, + b, + c, + d, + e, + f, + g, + h, + i, + } + + record aggregates { + a: scalars, + b: u32, + c: empty, + d: string, + e: really-flags, + } + + type int-typedef = s32 + + type tuple-typedef2 = tuple + + tuple-arg: func(x: tuple) + + tuple-result: func() -> tuple + + empty-arg: func(x: empty) + + empty-result: func() -> empty + + scalar-arg: func(x: scalars) + + scalar-result: func() -> scalars + + flags-arg: func(x: really-flags) + + flags-result: func() -> really-flags + + aggregate-arg: func(x: aggregates) + + aggregate-result: func() -> aggregates + + typedef-inout: func(e: tuple-typedef2) -> s32 +} + +world records-world { + import records: self.records + export records: self.records +} diff --git a/crates/wit-component/tests/interfaces/records.wit.print b/crates/wit-component/tests/interfaces/records.wit.print new file mode 100644 index 0000000000..79fbf3b7ff --- /dev/null +++ b/crates/wit-component/tests/interfaces/records.wit.print @@ -0,0 +1,60 @@ +interface records { + record empty { + } + + record scalars { + a: u32, + b: u32, + } + + flags really-flags { + a, + b, + c, + d, + e, + f, + g, + h, + i, + } + + record aggregates { + a: scalars, + b: u32, + c: empty, + d: string, + e: really-flags, + } + + type int-typedef = s32 + + type tuple-typedef2 = tuple + + tuple-arg: func(x: tuple) + + tuple-result: func() -> tuple + + empty-arg: func(x: empty) + + empty-result: func() -> empty + + scalar-arg: func(x: scalars) + + scalar-result: func() -> scalars + + flags-arg: func(x: really-flags) + + flags-result: func() -> really-flags + + aggregate-arg: func(x: aggregates) + + aggregate-result: func() -> aggregates + + typedef-inout: func(e: tuple-typedef2) -> s32 +} + +world records-world { + import records: self.records + export records: self.records +} diff --git a/crates/wit-component/tests/interfaces/records/component.wat b/crates/wit-component/tests/interfaces/records/component.wat deleted file mode 100644 index 12c610f09e..0000000000 --- a/crates/wit-component/tests/interfaces/records/component.wat +++ /dev/null @@ -1,290 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (tuple char u32)) - (type (;1;) (func (param "x" 0))) - (export (;0;) "tuple-arg" (func (type 1))) - (type (;2;) (func (result 0))) - (export (;1;) "tuple-result" (func (type 2))) - (type (;3;) (record)) - (export (;4;) "empty" (type (eq 3))) - (type (;5;) (func (param "x" 3))) - (export (;2;) "empty-arg" (func (type 5))) - (type (;6;) (func (result 3))) - (export (;3;) "empty-result" (func (type 6))) - (type (;7;) (record (field "a" u32) (field "b" u32))) - (export (;8;) "scalars" (type (eq 7))) - (type (;9;) (func (param "x" 7))) - (export (;4;) "scalar-arg" (func (type 9))) - (type (;10;) (func (result 7))) - (export (;5;) "scalar-result" (func (type 10))) - (type (;11;) (flags "a" "b" "c" "d" "e" "f" "g" "h" "i")) - (export (;12;) "really-flags" (type (eq 11))) - (type (;13;) (func (param "x" 11))) - (export (;6;) "flags-arg" (func (type 13))) - (type (;14;) (func (result 11))) - (export (;7;) "flags-result" (func (type 14))) - (type (;15;) (record (field "a" 7) (field "b" u32) (field "c" 3) (field "d" string) (field "e" 11))) - (export (;16;) "aggregates" (type (eq 15))) - (type (;17;) (func (param "x" 15))) - (export (;8;) "aggregate-arg" (func (type 17))) - (type (;18;) (func (result 15))) - (export (;9;) "aggregate-result" (func (type 18))) - (type (;19;) s32) - (export (;20;) "int-typedef" (type (eq 19))) - (type (;21;) (tuple 19)) - (export (;22;) "tuple-typedef2" (type (eq 21))) - (type (;23;) (func (param "e" 21) (result s32))) - (export (;10;) "typedef-inout" (func (type 23))) - ) - ) - (import "records" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32))) - (type (;2;) (func)) - (type (;3;) (func (result i32))) - (type (;4;) (func (param i32 i32 i32 i32 i32 i32))) - (type (;5;) (func (param i32) (result i32))) - (type (;6;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32))) - (type (;7;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32))) - (type (;8;) (func (param i32 i32 i32 i32) (result i32))) - (import "records" "tuple-arg" (func (;0;) (type 0))) - (import "records" "tuple-result" (func (;1;) (type 1))) - (import "records" "empty-arg" (func (;2;) (type 2))) - (import "records" "empty-result" (func (;3;) (type 2))) - (import "records" "scalar-arg" (func (;4;) (type 0))) - (import "records" "scalar-result" (func (;5;) (type 1))) - (import "records" "flags-arg" (func (;6;) (type 1))) - (import "records" "flags-result" (func (;7;) (type 3))) - (import "records" "aggregate-arg" (func (;8;) (type 4))) - (import "records" "aggregate-result" (func (;9;) (type 1))) - (import "records" "typedef-inout" (func (;10;) (type 5))) - (func (;11;) (type 0) (param i32 i32) - unreachable - ) - (func (;12;) (type 3) (result i32) - unreachable - ) - (func (;13;) (type 2) - unreachable - ) - (func (;14;) (type 2) - unreachable - ) - (func (;15;) (type 0) (param i32 i32) - unreachable - ) - (func (;16;) (type 3) (result i32) - unreachable - ) - (func (;17;) (type 6) (param i32 i32 i32 i32 i32 i32 i32 i32 i32) - unreachable - ) - (func (;18;) (type 3) (result i32) - unreachable - ) - (func (;19;) (type 7) (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) - unreachable - ) - (func (;20;) (type 3) (result i32) - unreachable - ) - (func (;21;) (type 1) (param i32)) - (func (;22;) (type 5) (param i32) (result i32) - unreachable - ) - (func (;23;) (type 3) (result i32) - unreachable - ) - (func (;24;) (type 8) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "records#tuple-arg" (func 11)) - (export "records#tuple-result" (func 12)) - (export "records#empty-arg" (func 13)) - (export "records#empty-result" (func 14)) - (export "records#scalar-arg" (func 15)) - (export "records#scalar-result" (func 16)) - (export "records#flags-arg" (func 17)) - (export "records#flags-result" (func 18)) - (export "records#aggregate-arg" (func 19)) - (export "records#aggregate-result" (func 20)) - (export "cabi_post_records#aggregate-result" (func 21)) - (export "records#typedef-inout" (func 22)) - (export "records#tuple-tupledef-user" (func 23)) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 24)) - ) - (core module (;1;) - (type (;0;) (func (param i32))) - (type (;1;) (func (param i32 i32 i32 i32 i32 i32))) - (func $indirect-records-tuple-result (;0;) (type 0) (param i32) - local.get 0 - i32.const 0 - call_indirect (type 0) - ) - (func $indirect-records-scalar-result (;1;) (type 0) (param i32) - local.get 0 - i32.const 1 - call_indirect (type 0) - ) - (func $indirect-records-aggregate-arg (;2;) (type 1) (param i32 i32 i32 i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - local.get 4 - local.get 5 - i32.const 2 - call_indirect (type 1) - ) - (func $indirect-records-aggregate-result (;3;) (type 0) (param i32) - local.get 0 - i32.const 3 - call_indirect (type 0) - ) - (table (;0;) 4 4 funcref) - (export "0" (func $indirect-records-tuple-result)) - (export "1" (func $indirect-records-scalar-result)) - (export "2" (func $indirect-records-aggregate-arg)) - (export "3" (func $indirect-records-aggregate-result)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i32))) - (type (;1;) (func (param i32 i32 i32 i32 i32 i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "1" (func (;1;) (type 0))) - (import "" "2" (func (;2;) (type 1))) - (import "" "3" (func (;3;) (type 0))) - (import "" "$imports" (table (;0;) 4 4 funcref)) - (elem (;0;) (i32.const 0) func 0 1 2 3) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) - (alias core export 0 "2" (core func (;2;))) - (alias core export 0 "3" (core func (;3;))) - (alias export 0 "tuple-arg" (func (;0;))) - (core func (;4;) (canon lower (func 0))) - (alias export 0 "empty-arg" (func (;1;))) - (core func (;5;) (canon lower (func 1))) - (alias export 0 "empty-result" (func (;2;))) - (core func (;6;) (canon lower (func 2))) - (alias export 0 "scalar-arg" (func (;3;))) - (core func (;7;) (canon lower (func 3))) - (alias export 0 "flags-arg" (func (;4;))) - (core func (;8;) (canon lower (func 4))) - (alias export 0 "flags-result" (func (;5;))) - (core func (;9;) (canon lower (func 5))) - (alias export 0 "typedef-inout" (func (;6;))) - (core func (;10;) (canon lower (func 6))) - (core instance (;1;) - (export "tuple-result" (func 0)) - (export "scalar-result" (func 1)) - (export "aggregate-arg" (func 2)) - (export "aggregate-result" (func 3)) - (export "tuple-arg" (func 4)) - (export "empty-arg" (func 5)) - (export "empty-result" (func 6)) - (export "scalar-arg" (func 7)) - (export "flags-arg" (func 8)) - (export "flags-result" (func 9)) - (export "typedef-inout" (func 10)) - ) - (core instance (;2;) (instantiate 0 - (with "records" (instance 1)) - ) - ) - (alias core export 2 "memory" (core memory (;0;))) - (alias core export 2 "cabi_realloc" (core func (;11;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 0 "tuple-result" (func (;7;))) - (core func (;12;) (canon lower (func 7) (memory 0))) - (alias export 0 "scalar-result" (func (;8;))) - (core func (;13;) (canon lower (func 8) (memory 0))) - (alias export 0 "aggregate-arg" (func (;9;))) - (core func (;14;) (canon lower (func 9) (memory 0) string-encoding=utf8)) - (alias export 0 "aggregate-result" (func (;10;))) - (core func (;15;) (canon lower (func 10) (memory 0) (realloc 11) string-encoding=utf8)) - (core instance (;3;) - (export "$imports" (table 0)) - (export "0" (func 12)) - (export "1" (func 13)) - (export "2" (func 14)) - (export "3" (func 15)) - ) - (core instance (;4;) (instantiate 2 - (with "" (instance 3)) - ) - ) - (alias core export 2 "records#tuple-arg" (core func (;16;))) - (type (;1;) (tuple char u32)) - (type (;2;) (func (param "x" 1))) - (func (;11;) (type 2) (canon lift (core func 16))) - (alias core export 2 "records#tuple-result" (core func (;17;))) - (type (;3;) (func (result 1))) - (func (;12;) (type 3) (canon lift (core func 17) (memory 0))) - (alias core export 2 "records#empty-arg" (core func (;18;))) - (type (;4;) (record)) - (type (;5;) (func (param "x" 4))) - (func (;13;) (type 5) (canon lift (core func 18))) - (alias core export 2 "records#empty-result" (core func (;19;))) - (type (;6;) (func (result 4))) - (func (;14;) (type 6) (canon lift (core func 19))) - (alias core export 2 "records#scalar-arg" (core func (;20;))) - (type (;7;) (record (field "a" u32) (field "b" u32))) - (type (;8;) (func (param "x" 7))) - (func (;15;) (type 8) (canon lift (core func 20))) - (alias core export 2 "records#scalar-result" (core func (;21;))) - (type (;9;) (func (result 7))) - (func (;16;) (type 9) (canon lift (core func 21) (memory 0))) - (alias core export 2 "records#flags-arg" (core func (;22;))) - (type (;10;) (record (field "a" bool) (field "b" bool) (field "c" bool) (field "d" bool) (field "e" bool) (field "f" bool) (field "g" bool) (field "h" bool) (field "i" bool))) - (type (;11;) (func (param "x" 10))) - (func (;17;) (type 11) (canon lift (core func 22))) - (alias core export 2 "records#flags-result" (core func (;23;))) - (type (;12;) (func (result 10))) - (func (;18;) (type 12) (canon lift (core func 23) (memory 0))) - (alias core export 2 "records#aggregate-arg" (core func (;24;))) - (type (;13;) (record (field "a" 7) (field "b" u32) (field "c" 4) (field "d" string) (field "e" 10))) - (type (;14;) (func (param "x" 13))) - (func (;19;) (type 14) (canon lift (core func 24) (memory 0) (realloc 11) string-encoding=utf8)) - (alias core export 2 "records#aggregate-result" (core func (;25;))) - (type (;15;) (func (result 13))) - (alias core export 2 "cabi_post_records#aggregate-result" (core func (;26;))) - (func (;20;) (type 15) (canon lift (core func 25) (memory 0) string-encoding=utf8 (post-return 26))) - (alias core export 2 "records#typedef-inout" (core func (;27;))) - (type (;16;) s32) - (type (;17;) (tuple 16)) - (type (;18;) (func (param "e" 17) (result s32))) - (func (;21;) (type 18) (canon lift (core func 27))) - (alias core export 2 "records#tuple-tupledef-user" (core func (;28;))) - (type (;19;) (tuple s32)) - (type (;20;) (func (result 19))) - (func (;22;) (type 20) (canon lift (core func 28))) - (instance (;1;) - (export "tuple-arg" (func 11)) - (export "tuple-result" (func 12)) - (export "empty-arg" (func 13)) - (export "empty-result" (func 14)) - (export "scalar-arg" (func 15)) - (export "scalar-result" (func 16)) - (export "flags-arg" (func 17)) - (export "flags-result" (func 18)) - (export "aggregate-arg" (func 19)) - (export "aggregate-result" (func 20)) - (export "typedef-inout" (func 21)) - (export "tuple-tupledef-user" (func 22)) - (export "empty" (type 4)) - (export "scalars" (type 7)) - (export "really-flags" (type 10)) - (export "aggregates" (type 13)) - (export "int-typedef" (type 16)) - (export "tuple-typedef2" (type 17)) - (export "tuple-typedef" (type 19)) - ) - (export (;2;) "records" (instance 1)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/records/types_only.wat b/crates/wit-component/tests/interfaces/records/types_only.wat deleted file mode 100644 index 017ad2221d..0000000000 --- a/crates/wit-component/tests/interfaces/records/types_only.wat +++ /dev/null @@ -1,86 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (tuple char u32)) - (type (;1;) (func (param "x" 0))) - (export (;0;) "tuple-arg" (func (type 1))) - (type (;2;) (func (result 0))) - (export (;1;) "tuple-result" (func (type 2))) - (type (;3;) (record)) - (export (;4;) "empty" (type (eq 3))) - (type (;5;) (func (param "x" 3))) - (export (;2;) "empty-arg" (func (type 5))) - (type (;6;) (func (result 3))) - (export (;3;) "empty-result" (func (type 6))) - (type (;7;) (record (field "a" u32) (field "b" u32))) - (export (;8;) "scalars" (type (eq 7))) - (type (;9;) (func (param "x" 7))) - (export (;4;) "scalar-arg" (func (type 9))) - (type (;10;) (func (result 7))) - (export (;5;) "scalar-result" (func (type 10))) - (type (;11;) (flags "a" "b" "c" "d" "e" "f" "g" "h" "i")) - (export (;12;) "really-flags" (type (eq 11))) - (type (;13;) (func (param "x" 11))) - (export (;6;) "flags-arg" (func (type 13))) - (type (;14;) (func (result 11))) - (export (;7;) "flags-result" (func (type 14))) - (type (;15;) (record (field "a" 7) (field "b" u32) (field "c" 3) (field "d" string) (field "e" 11))) - (export (;16;) "aggregates" (type (eq 15))) - (type (;17;) (func (param "x" 15))) - (export (;8;) "aggregate-arg" (func (type 17))) - (type (;18;) (func (result 15))) - (export (;9;) "aggregate-result" (func (type 18))) - (type (;19;) s32) - (export (;20;) "int-typedef" (type (eq 19))) - (type (;21;) (tuple 19)) - (export (;22;) "tuple-typedef2" (type (eq 21))) - (type (;23;) (func (param "e" 21) (result s32))) - (export (;10;) "typedef-inout" (func (type 23))) - ) - ) - (import "records" (instance (;0;) (type 0))) - (type (;1;) - (instance - (type (;0;) (tuple char u32)) - (type (;1;) (func (param "x" 0))) - (export (;0;) "tuple-arg" (func (type 1))) - (type (;2;) (func (result 0))) - (export (;1;) "tuple-result" (func (type 2))) - (type (;3;) (record)) - (export (;4;) "empty" (type (eq 3))) - (type (;5;) (func (param "x" 3))) - (export (;2;) "empty-arg" (func (type 5))) - (type (;6;) (func (result 3))) - (export (;3;) "empty-result" (func (type 6))) - (type (;7;) (record (field "a" u32) (field "b" u32))) - (export (;8;) "scalars" (type (eq 7))) - (type (;9;) (func (param "x" 7))) - (export (;4;) "scalar-arg" (func (type 9))) - (type (;10;) (func (result 7))) - (export (;5;) "scalar-result" (func (type 10))) - (type (;11;) (record (field "a" bool) (field "b" bool) (field "c" bool) (field "d" bool) (field "e" bool) (field "f" bool) (field "g" bool) (field "h" bool) (field "i" bool))) - (export (;12;) "really-flags" (type (eq 11))) - (type (;13;) (func (param "x" 11))) - (export (;6;) "flags-arg" (func (type 13))) - (type (;14;) (func (result 11))) - (export (;7;) "flags-result" (func (type 14))) - (type (;15;) (record (field "a" 7) (field "b" u32) (field "c" 3) (field "d" string) (field "e" 11))) - (export (;16;) "aggregates" (type (eq 15))) - (type (;17;) (func (param "x" 15))) - (export (;8;) "aggregate-arg" (func (type 17))) - (type (;18;) (func (result 15))) - (export (;9;) "aggregate-result" (func (type 18))) - (type (;19;) s32) - (export (;20;) "int-typedef" (type (eq 19))) - (type (;21;) (tuple 19)) - (export (;22;) "tuple-typedef2" (type (eq 21))) - (type (;23;) (func (param "e" 21) (result s32))) - (export (;10;) "typedef-inout" (func (type 23))) - (type (;24;) (tuple s32)) - (export (;25;) "tuple-typedef" (type (eq 24))) - (type (;26;) (func (result 24))) - (export (;11;) "tuple-tupledef-user" (func (type 26))) - ) - ) - (export (;2;) "records" (type 1)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/records/world.wit b/crates/wit-component/tests/interfaces/records/world.wit deleted file mode 100644 index 34a42ac276..0000000000 --- a/crates/wit-component/tests/interfaces/records/world.wit +++ /dev/null @@ -1,120 +0,0 @@ -interface records { - record empty { - } - - record scalars { - a: u32, - b: u32, - } - - flags really-flags { - a, - b, - c, - d, - e, - f, - g, - h, - i, - } - - record aggregates { - a: scalars, - b: u32, - c: empty, - d: string, - e: really-flags, - } - - type int-typedef = s32 - - type tuple-typedef2 = tuple - - tuple-arg: func(x: tuple) - - tuple-result: func() -> tuple - - empty-arg: func(x: empty) - - empty-result: func() -> empty - - scalar-arg: func(x: scalars) - - scalar-result: func() -> scalars - - flags-arg: func(x: really-flags) - - flags-result: func() -> really-flags - - aggregate-arg: func(x: aggregates) - - aggregate-result: func() -> aggregates - - typedef-inout: func(e: tuple-typedef2) -> s32 -} - -interface records0 { - record empty { - } - - record scalars { - a: u32, - b: u32, - } - - record really-flags { - a: bool, - b: bool, - c: bool, - d: bool, - e: bool, - f: bool, - g: bool, - h: bool, - i: bool, - } - - record aggregates { - a: scalars, - b: u32, - c: empty, - d: string, - e: really-flags, - } - - type int-typedef = s32 - - type tuple-typedef2 = tuple - - type tuple-typedef = tuple - - tuple-arg: func(x: tuple) - - tuple-result: func() -> tuple - - empty-arg: func(x: empty) - - empty-result: func() -> empty - - scalar-arg: func(x: scalars) - - scalar-result: func() -> scalars - - flags-arg: func(x: really-flags) - - flags-result: func() -> really-flags - - aggregate-arg: func(x: aggregates) - - aggregate-result: func() -> aggregates - - typedef-inout: func(e: tuple-typedef2) -> s32 - - tuple-tupledef-user: func() -> tuple-typedef -} - -world records-world { - import records: records - export records: records0 -} diff --git a/crates/wit-component/tests/interfaces/reference-out-of-order.wat b/crates/wit-component/tests/interfaces/reference-out-of-order.wat new file mode 100644 index 0000000000..bab0d67963 --- /dev/null +++ b/crates/wit-component/tests/interfaces/reference-out-of-order.wat @@ -0,0 +1,54 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (record (field "s" string))) + (export (;1;) "r" (type (eq 0))) + (type (;2;) (variant (case "s" string))) + (export (;3;) "v" (type (eq 2))) + (type (;4;) (record (field "s" u32))) + (export (;5;) "r-no-string" (type (eq 4))) + (type (;6;) (variant (case "s" u32))) + (export (;7;) "v-no-string" (type (eq 6))) + (type (;8;) (func (param "x" 1))) + (export (;0;) "a" (func (type 8))) + (type (;9;) (func (param "x" 3))) + (export (;1;) "b" (func (type 9))) + (type (;10;) (func (param "x" 5))) + (export (;2;) "c" (func (type 10))) + (type (;11;) (func (param "x" 7))) + (export (;3;) "d" (func (type 11))) + ) + ) + (export (;0;) "foo" "pkg:/reference-out-of-order/foo" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (record (field "s" string))) + (export (;1;) "r" (type (eq 0))) + (type (;2;) (variant (case "s" string))) + (export (;3;) "v" (type (eq 2))) + (type (;4;) (record (field "s" u32))) + (export (;5;) "r-no-string" (type (eq 4))) + (type (;6;) (variant (case "s" u32))) + (export (;7;) "v-no-string" (type (eq 6))) + (type (;8;) (func (param "x" 1))) + (export (;0;) "a" (func (type 8))) + (type (;9;) (func (param "x" 3))) + (export (;1;) "b" (func (type 9))) + (type (;10;) (func (param "x" 5))) + (export (;2;) "c" (func (type 10))) + (type (;11;) (func (param "x" 7))) + (export (;3;) "d" (func (type 11))) + ) + ) + (import "foo" "pkg:/reference-out-of-order/foo" (instance (type 0))) + ) + ) + (export (;0;) "foo-world" "pkg:/reference-out-of-order/foo-world" (component (type 1))) + ) + ) + (export (;1;) "reference-out-of-order" "pkg:/reference-out-of-order" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/reference-out-of-order.wit b/crates/wit-component/tests/interfaces/reference-out-of-order.wit new file mode 100644 index 0000000000..82a7aaf9c4 --- /dev/null +++ b/crates/wit-component/tests/interfaces/reference-out-of-order.wit @@ -0,0 +1,26 @@ +interface foo { + a: func(x: r) + b: func(x: v) + c: func(x: r-no-string) + d: func(x: v-no-string) + + record r { + s: string, + } + + variant v { + s(string), + } + + record r-no-string { + s: u32, + } + + variant v-no-string { + s(u32), + } +} + +world foo-world { + import foo: self.foo +} diff --git a/crates/wit-component/tests/interfaces/reference-out-of-order/world.wit b/crates/wit-component/tests/interfaces/reference-out-of-order.wit.print similarity index 92% rename from crates/wit-component/tests/interfaces/reference-out-of-order/world.wit rename to crates/wit-component/tests/interfaces/reference-out-of-order.wit.print index 09a0e13aa5..a7979f3827 100644 --- a/crates/wit-component/tests/interfaces/reference-out-of-order/world.wit +++ b/crates/wit-component/tests/interfaces/reference-out-of-order.wit.print @@ -25,5 +25,5 @@ interface foo { } world foo-world { - import foo: foo + import foo: self.foo } diff --git a/crates/wit-component/tests/interfaces/reference-out-of-order/component.wat b/crates/wit-component/tests/interfaces/reference-out-of-order/component.wat deleted file mode 100644 index 3a4a5ddfd0..0000000000 --- a/crates/wit-component/tests/interfaces/reference-out-of-order/component.wat +++ /dev/null @@ -1,101 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (record (field "s" string))) - (export (;1;) "r" (type (eq 0))) - (type (;2;) (func (param "x" 0))) - (export (;0;) "a" (func (type 2))) - (type (;3;) (variant (case "s" string))) - (export (;4;) "v" (type (eq 3))) - (type (;5;) (func (param "x" 3))) - (export (;1;) "b" (func (type 5))) - (type (;6;) (record (field "s" u32))) - (export (;7;) "r-no-string" (type (eq 6))) - (type (;8;) (func (param "x" 6))) - (export (;2;) "c" (func (type 8))) - (type (;9;) (variant (case "s" u32))) - (export (;10;) "v-no-string" (type (eq 9))) - (type (;11;) (func (param "x" 9))) - (export (;3;) "d" (func (type 11))) - ) - ) - (import "foo" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32 i32 i32))) - (type (;2;) (func (param i32))) - (type (;3;) (func (param i32 i32 i32 i32) (result i32))) - (import "foo" "a" (func (;0;) (type 0))) - (import "foo" "b" (func (;1;) (type 1))) - (import "foo" "c" (func (;2;) (type 2))) - (import "foo" "d" (func (;3;) (type 0))) - (func (;4;) (type 3) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 4)) - ) - (core module (;1;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32 i32 i32))) - (func $indirect-foo-a (;0;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 0 - call_indirect (type 0) - ) - (func $indirect-foo-b (;1;) (type 1) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 1 - call_indirect (type 1) - ) - (table (;0;) 2 2 funcref) - (export "0" (func $indirect-foo-a)) - (export "1" (func $indirect-foo-b)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32 i32 i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "1" (func (;1;) (type 1))) - (import "" "$imports" (table (;0;) 2 2 funcref)) - (elem (;0;) (i32.const 0) func 0 1) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) - (alias export 0 "c" (func (;0;))) - (core func (;2;) (canon lower (func 0))) - (alias export 0 "d" (func (;1;))) - (core func (;3;) (canon lower (func 1))) - (core instance (;1;) - (export "a" (func 0)) - (export "b" (func 1)) - (export "c" (func 2)) - (export "d" (func 3)) - ) - (core instance (;2;) (instantiate 0 - (with "foo" (instance 1)) - ) - ) - (alias core export 2 "memory" (core memory (;0;))) - (alias core export 2 "cabi_realloc" (core func (;4;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 0 "a" (func (;2;))) - (core func (;5;) (canon lower (func 2) (memory 0) string-encoding=utf8)) - (alias export 0 "b" (func (;3;))) - (core func (;6;) (canon lower (func 3) (memory 0) string-encoding=utf8)) - (core instance (;3;) - (export "$imports" (table 0)) - (export "0" (func 5)) - (export "1" (func 6)) - ) - (core instance (;4;) (instantiate 2 - (with "" (instance 3)) - ) - ) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/reference-out-of-order/types_only.wat b/crates/wit-component/tests/interfaces/reference-out-of-order/types_only.wat deleted file mode 100644 index 5795b97d1c..0000000000 --- a/crates/wit-component/tests/interfaces/reference-out-of-order/types_only.wat +++ /dev/null @@ -1,23 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (record (field "s" string))) - (export (;1;) "r" (type (eq 0))) - (type (;2;) (func (param "x" 0))) - (export (;0;) "a" (func (type 2))) - (type (;3;) (variant (case "s" string))) - (export (;4;) "v" (type (eq 3))) - (type (;5;) (func (param "x" 3))) - (export (;1;) "b" (func (type 5))) - (type (;6;) (record (field "s" u32))) - (export (;7;) "r-no-string" (type (eq 6))) - (type (;8;) (func (param "x" 6))) - (export (;2;) "c" (func (type 8))) - (type (;9;) (variant (case "s" u32))) - (export (;10;) "v-no-string" (type (eq 9))) - (type (;11;) (func (param "x" 9))) - (export (;3;) "d" (func (type 11))) - ) - ) - (import "foo" (instance (;0;) (type 0))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-deps.wat b/crates/wit-component/tests/interfaces/simple-deps.wat new file mode 100644 index 0000000000..7d25c30d1d --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-deps.wat @@ -0,0 +1,22 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "some-type" (type (eq 0))) + ) + ) + (import "types" "path:/some-dep/types/types" (instance (type 0))) + (alias export 0 "some-type" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "some-type" (type (eq 0))) + ) + ) + (export (;0;) "foo" "pkg:/foo/foo" (instance (type 2))) + ) + ) + (export (;1;) "foo" "pkg:/foo" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit b/crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit new file mode 100644 index 0000000000..1fd776144d --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit @@ -0,0 +1,3 @@ +default interface types { + type some-type = u8 +} diff --git a/crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit.print b/crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit.print new file mode 100644 index 0000000000..0c1b0124fc --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit.print @@ -0,0 +1,5 @@ +interface types { + type some-type = u8 + +} + diff --git a/crates/wit-component/tests/interfaces/simple-deps/foo.wit b/crates/wit-component/tests/interfaces/simple-deps/foo.wit new file mode 100644 index 0000000000..0b034021cb --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-deps/foo.wit @@ -0,0 +1,3 @@ +interface foo { + use some-dep.types.{some-type} +} diff --git a/crates/wit-component/tests/interfaces/simple-deps/foo.wit.print b/crates/wit-component/tests/interfaces/simple-deps/foo.wit.print new file mode 100644 index 0000000000..eaf5632f3c --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-deps/foo.wit.print @@ -0,0 +1,4 @@ +interface foo { + use some-dep.types.types.{some-type} +} + diff --git a/crates/wit-component/tests/interfaces/simple-multi.wat b/crates/wit-component/tests/interfaces/simple-multi.wat new file mode 100644 index 0000000000..4ea016cd98 --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-multi.wat @@ -0,0 +1,20 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance) + ) + (export (;0;) "bar" "pkg:/bar/bar" (instance (type 0))) + ) + ) + (export (;1;) "bar" "pkg:/bar" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance) + ) + (export (;0;) "foo" "pkg:/foo/foo" (instance (type 0))) + ) + ) + (export (;3;) "foo" "pkg:/foo" (type 2)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-multi/bar.wit b/crates/wit-component/tests/interfaces/simple-multi/bar.wit new file mode 100644 index 0000000000..4e0eb61bca --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-multi/bar.wit @@ -0,0 +1 @@ +interface bar {} diff --git a/crates/wit-component/tests/interfaces/simple-multi/bar.wit.print b/crates/wit-component/tests/interfaces/simple-multi/bar.wit.print new file mode 100644 index 0000000000..1f15fd4e7b --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-multi/bar.wit.print @@ -0,0 +1,3 @@ +interface bar { +} + diff --git a/crates/wit-component/tests/interfaces/simple-multi/foo.wit b/crates/wit-component/tests/interfaces/simple-multi/foo.wit new file mode 100644 index 0000000000..27947d8a47 --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-multi/foo.wit @@ -0,0 +1 @@ +interface foo {} diff --git a/crates/wit-component/tests/interfaces/simple-multi/foo.wit.print b/crates/wit-component/tests/interfaces/simple-multi/foo.wit.print new file mode 100644 index 0000000000..80bb52742a --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-multi/foo.wit.print @@ -0,0 +1,3 @@ +interface foo { +} + diff --git a/crates/wit-component/tests/interfaces/simple-use.wat b/crates/wit-component/tests/interfaces/simple-use.wat new file mode 100644 index 0000000000..54c217edeb --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-use.wat @@ -0,0 +1,24 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (enum "info" "debug")) + (export (;1;) "level" (type (eq 0))) + ) + ) + (export (;0;) "types" "pkg:/simple-use/types" (instance (type 0))) + (alias export 0 "level" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "level" (type (eq 0))) + (type (;2;) (func (param "level" 1) (param "msg" string))) + (export (;0;) "log" (func (type 2))) + ) + ) + (export (;1;) "console" "pkg:/simple-use/console" (instance (type 2))) + ) + ) + (export (;1;) "simple-use" "pkg:/simple-use" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-use.wit b/crates/wit-component/tests/interfaces/simple-use.wit new file mode 100644 index 0000000000..c09566dbe0 --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-use.wit @@ -0,0 +1,11 @@ +interface types { + enum level { + info, + debug, + } +} + +interface console { + use self.types.{level} + log: func(level: level, msg: string) +} diff --git a/crates/wit-component/tests/interfaces/simple-use.wit.print b/crates/wit-component/tests/interfaces/simple-use.wit.print new file mode 100644 index 0000000000..a71079d3d4 --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-use.wit.print @@ -0,0 +1,13 @@ +interface types { + enum level { + info, + debug, + } + +} + +interface console { + use self.types.{level} + log: func(level: level, msg: string) +} + diff --git a/crates/wit-component/tests/interfaces/simple-world.wat b/crates/wit-component/tests/interfaces/simple-world.wat new file mode 100644 index 0000000000..3c408281a7 --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-world.wat @@ -0,0 +1,26 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "arg" string))) + (export (;0;) "log" (func (type 0))) + ) + ) + (export (;0;) "console" "pkg:/simple-world/console" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "arg" string))) + (export (;0;) "log" (func (type 0))) + ) + ) + (import "console" "pkg:/simple-world/console" (instance (type 0))) + ) + ) + (export (;0;) "the-world" "pkg:/simple-world/the-world" (component (type 1))) + ) + ) + (export (;1;) "simple-world" "pkg:/simple-world" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-world.wit b/crates/wit-component/tests/interfaces/simple-world.wit new file mode 100644 index 0000000000..c5d1ef2bb7 --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-world.wit @@ -0,0 +1,7 @@ +world the-world { + import console: self.console +} + +interface console { + log: func(arg: string) +} diff --git a/crates/wit-component/tests/interfaces/simple-world.wit.print b/crates/wit-component/tests/interfaces/simple-world.wit.print new file mode 100644 index 0000000000..e059d63baf --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-world.wit.print @@ -0,0 +1,7 @@ +interface console { + log: func(arg: string) +} + +world the-world { + import console: self.console +} diff --git a/crates/wit-component/tests/interfaces/single-named-result.wat b/crates/wit-component/tests/interfaces/single-named-result.wat new file mode 100644 index 0000000000..6c8616bc14 --- /dev/null +++ b/crates/wit-component/tests/interfaces/single-named-result.wat @@ -0,0 +1,14 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (func (result "a" u32))) + (export (;0;) "a" (func (type 0))) + ) + ) + (export (;0;) "foo" "pkg:/single-named-result/foo" (instance (type 0))) + ) + ) + (export (;1;) "single-named-result" "pkg:/single-named-result" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/single-named-result.wit b/crates/wit-component/tests/interfaces/single-named-result.wit new file mode 100644 index 0000000000..13afc00d1b --- /dev/null +++ b/crates/wit-component/tests/interfaces/single-named-result.wit @@ -0,0 +1,3 @@ +interface foo { + a: func() -> (a: u32) +} diff --git a/crates/wit-component/tests/interfaces/single-named-result.wit.print b/crates/wit-component/tests/interfaces/single-named-result.wit.print new file mode 100644 index 0000000000..cd6cb560dc --- /dev/null +++ b/crates/wit-component/tests/interfaces/single-named-result.wit.print @@ -0,0 +1,4 @@ +interface foo { + a: func() -> (a: u32) +} + diff --git a/crates/wit-component/tests/interfaces/type-alias.wat b/crates/wit-component/tests/interfaces/type-alias.wat new file mode 100644 index 0000000000..d6ad881a26 --- /dev/null +++ b/crates/wit-component/tests/interfaces/type-alias.wat @@ -0,0 +1,43 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "a" (type (eq 0))) + (export (;2;) "b" (type (eq 1))) + (type (;3;) (func (param "a" 1) (result 2))) + (export (;0;) "f" (func (type 3))) + ) + ) + (export (;0;) "foo" "pkg:/type-alias/foo" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "a" (type (eq 0))) + (export (;2;) "b" (type (eq 1))) + (type (;3;) (func (param "a" 1) (result 2))) + (export (;0;) "f" (func (type 3))) + ) + ) + (import "foo" "pkg:/type-alias/foo" (instance (type 0))) + (alias export 0 "a" (type (;1;))) + (alias export 0 "b" (type (;2;))) + (type (;3;) + (instance + (alias outer 1 1 (type (;0;))) + (alias outer 1 2 (type (;1;))) + (type (;2;) (func (param "a" 0) (result 1))) + (export (;0;) "f" (func (type 2))) + ) + ) + (export (;0;) "foo" "pkg:/type-alias/foo" (instance (type 3))) + ) + ) + (export (;0;) "my-world" "pkg:/type-alias/my-world" (component (type 1))) + ) + ) + (export (;1;) "type-alias" "pkg:/type-alias" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/type-alias.wit b/crates/wit-component/tests/interfaces/type-alias.wit new file mode 100644 index 0000000000..f8dc235017 --- /dev/null +++ b/crates/wit-component/tests/interfaces/type-alias.wit @@ -0,0 +1,11 @@ +interface foo { + type a = u8 + type b = a + + f: func(a: a) -> b +} + +world my-world { + import foo: self.foo + export foo: self.foo +} diff --git a/crates/wit-component/tests/interfaces/type-alias.wit.print b/crates/wit-component/tests/interfaces/type-alias.wit.print new file mode 100644 index 0000000000..64049b2474 --- /dev/null +++ b/crates/wit-component/tests/interfaces/type-alias.wit.print @@ -0,0 +1,12 @@ +interface foo { + type a = u8 + + type b = a + + f: func(a: a) -> b +} + +world my-world { + import foo: self.foo + export foo: self.foo +} diff --git a/crates/wit-component/tests/interfaces/type-alias/component.wat b/crates/wit-component/tests/interfaces/type-alias/component.wat deleted file mode 100644 index 1a93d48b88..0000000000 --- a/crates/wit-component/tests/interfaces/type-alias/component.wat +++ /dev/null @@ -1,50 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) u8) - (export (;1;) "a" (type (eq 0))) - (alias outer 0 0 (type (;2;))) - (export (;3;) "b" (type (eq 2))) - (type (;4;) (func (param "a" 0) (result 2))) - (export (;0;) "f" (func (type 4))) - ) - ) - (import "foo" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32) (result i32))) - (type (;1;) (func (param i32 i32 i32 i32) (result i32))) - (import "foo" "f" (func (;0;) (type 0))) - (func (;1;) (type 0) (param i32) (result i32) - unreachable - ) - (func (;2;) (type 1) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "foo#f" (func 1)) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 2)) - ) - (alias export 0 "f" (func (;0;))) - (core func (;0;) (canon lower (func 0))) - (core instance (;0;) - (export "f" (func 0)) - ) - (core instance (;1;) (instantiate 0 - (with "foo" (instance 0)) - ) - ) - (alias core export 1 "memory" (core memory (;0;))) - (alias core export 1 "cabi_realloc" (core func (;1;))) - (alias core export 1 "foo#f" (core func (;2;))) - (type (;1;) u8) - (alias outer 0 1 (type (;2;))) - (type (;3;) (func (param "a" 1) (result 2))) - (func (;1;) (type 3) (canon lift (core func 2))) - (instance (;1;) - (export "f" (func 1)) - (export "a" (type 1)) - (export "b" (type 2)) - ) - (export (;2;) "foo" (instance 1)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/type-alias/types_only.wat b/crates/wit-component/tests/interfaces/type-alias/types_only.wat deleted file mode 100644 index c7eb03fdba..0000000000 --- a/crates/wit-component/tests/interfaces/type-alias/types_only.wat +++ /dev/null @@ -1,24 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) u8) - (export (;1;) "a" (type (eq 0))) - (alias outer 0 0 (type (;2;))) - (export (;3;) "b" (type (eq 2))) - (type (;4;) (func (param "a" 0) (result 2))) - (export (;0;) "f" (func (type 4))) - ) - ) - (import "foo" (instance (;0;) (type 0))) - (type (;1;) - (instance - (type (;0;) u8) - (export (;1;) "a" (type (eq 0))) - (alias outer 0 0 (type (;2;))) - (export (;3;) "b" (type (eq 2))) - (type (;4;) (func (param "a" 0) (result 2))) - (export (;0;) "f" (func (type 4))) - ) - ) - (export (;2;) "foo" (type 1)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/type-alias/world.wit b/crates/wit-component/tests/interfaces/type-alias/world.wit deleted file mode 100644 index 0a727a2b35..0000000000 --- a/crates/wit-component/tests/interfaces/type-alias/world.wit +++ /dev/null @@ -1,20 +0,0 @@ -interface foo { - type a = u8 - - type b = a - - f: func(a: a) -> b -} - -interface foo0 { - type a = u8 - - type b = a - - f: func(a: a) -> b -} - -world my-world { - import foo: foo - export foo: foo0 -} diff --git a/crates/wit-component/tests/interfaces/type-alias2.wat b/crates/wit-component/tests/interfaces/type-alias2.wat new file mode 100644 index 0000000000..acccc8e42c --- /dev/null +++ b/crates/wit-component/tests/interfaces/type-alias2.wat @@ -0,0 +1,32 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (flags "b")) + (export (;1;) "a" (type (eq 0))) + (export (;2;) "b" (type (eq 1))) + (type (;3;) (func (result 2))) + (export (;0;) "f" (func (type 3))) + ) + ) + (export (;0;) "foo" "pkg:/type-alias2/foo" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (flags "b")) + (export (;1;) "a" (type (eq 0))) + (export (;2;) "b" (type (eq 1))) + (type (;3;) (func (result 2))) + (export (;0;) "f" (func (type 3))) + ) + ) + (export (;0;) "foo" "pkg:/type-alias2/foo" (instance (type 0))) + ) + ) + (export (;0;) "my-world" "pkg:/type-alias2/my-world" (component (type 1))) + ) + ) + (export (;1;) "type-alias2" "pkg:/type-alias2" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/type-alias2/world.wit b/crates/wit-component/tests/interfaces/type-alias2.wit similarity index 80% rename from crates/wit-component/tests/interfaces/type-alias2/world.wit rename to crates/wit-component/tests/interfaces/type-alias2.wit index 1f18d6b5f6..0af05e5896 100644 --- a/crates/wit-component/tests/interfaces/type-alias2/world.wit +++ b/crates/wit-component/tests/interfaces/type-alias2.wit @@ -9,5 +9,5 @@ interface foo { } world my-world { - export foo: foo + export foo: self.foo } diff --git a/crates/wit-component/tests/interfaces/type-alias2.wit.print b/crates/wit-component/tests/interfaces/type-alias2.wit.print new file mode 100644 index 0000000000..0af05e5896 --- /dev/null +++ b/crates/wit-component/tests/interfaces/type-alias2.wit.print @@ -0,0 +1,13 @@ +interface foo { + flags a { + b, + } + + type b = a + + f: func() -> b +} + +world my-world { + export foo: self.foo +} diff --git a/crates/wit-component/tests/interfaces/type-alias2/component.wat b/crates/wit-component/tests/interfaces/type-alias2/component.wat deleted file mode 100644 index 35b47d6f4e..0000000000 --- a/crates/wit-component/tests/interfaces/type-alias2/component.wat +++ /dev/null @@ -1,30 +0,0 @@ -(component - (core module (;0;) - (type (;0;) (func (result i32))) - (type (;1;) (func (param i32 i32 i32 i32) (result i32))) - (func (;0;) (type 0) (result i32) - unreachable - ) - (func (;1;) (type 1) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "foo#f" (func 0)) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 1)) - ) - (core instance (;0;) (instantiate 0)) - (alias core export 0 "memory" (core memory (;0;))) - (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "foo#f" (core func (;1;))) - (type (;0;) (flags "b")) - (alias outer 0 0 (type (;1;))) - (type (;2;) (func (result 1))) - (func (;0;) (type 2) (canon lift (core func 1))) - (instance (;0;) - (export "f" (func 0)) - (export "a" (type 0)) - (export "b" (type 1)) - ) - (export (;1;) "foo" (instance 0)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/type-alias2/types_only.wat b/crates/wit-component/tests/interfaces/type-alias2/types_only.wat deleted file mode 100644 index 6962ffedcc..0000000000 --- a/crates/wit-component/tests/interfaces/type-alias2/types_only.wat +++ /dev/null @@ -1,13 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (flags "b")) - (export (;1;) "a" (type (eq 0))) - (alias outer 0 0 (type (;2;))) - (export (;3;) "b" (type (eq 2))) - (type (;4;) (func (result 2))) - (export (;0;) "f" (func (type 4))) - ) - ) - (export (;1;) "foo" (type 0)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/use-chain.wat b/crates/wit-component/tests/interfaces/use-chain.wat new file mode 100644 index 0000000000..4a923636f4 --- /dev/null +++ b/crates/wit-component/tests/interfaces/use-chain.wat @@ -0,0 +1,16 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u32) + (export (;1;) "a" (type (eq 0))) + (export (;2;) "b" (type (eq 1))) + (export (;3;) "c" (type (eq 2))) + ) + ) + (export (;0;) "foo" "pkg:/use-chain/foo" (instance (type 0))) + ) + ) + (export (;1;) "use-chain" "pkg:/use-chain" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/use-chain.wit b/crates/wit-component/tests/interfaces/use-chain.wit new file mode 100644 index 0000000000..d4c1ca53af --- /dev/null +++ b/crates/wit-component/tests/interfaces/use-chain.wit @@ -0,0 +1,5 @@ +interface foo { + type a = u32 + type b = a + type c = b +} diff --git a/crates/wit-component/tests/interfaces/use-chain.wit.print b/crates/wit-component/tests/interfaces/use-chain.wit.print new file mode 100644 index 0000000000..64b0f45c0b --- /dev/null +++ b/crates/wit-component/tests/interfaces/use-chain.wit.print @@ -0,0 +1,9 @@ +interface foo { + type a = u32 + + type b = a + + type c = b + +} + diff --git a/crates/wit-component/tests/interfaces/variants.wat b/crates/wit-component/tests/interfaces/variants.wat new file mode 100644 index 0000000000..bba5ea7757 --- /dev/null +++ b/crates/wit-component/tests/interfaces/variants.wat @@ -0,0 +1,202 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (enum "a")) + (export (;1;) "e1" (type (eq 0))) + (type (;2;) (union u32 float32)) + (export (;3;) "u1" (type (eq 2))) + (type (;4;) (record)) + (export (;5;) "empty" (type (eq 4))) + (type (;6;) (variant (case "a") (case "b" 3) (case "c" 1) (case "d" string) (case "e" 5) (case "f") (case "g" u32))) + (export (;7;) "v1" (type (eq 6))) + (type (;8;) (variant (case "a" s32) (case "b" float32))) + (export (;9;) "casts1" (type (eq 8))) + (type (;10;) (variant (case "a" float64) (case "b" float32))) + (export (;11;) "casts2" (type (eq 10))) + (type (;12;) (variant (case "a" float64) (case "b" u64))) + (export (;13;) "casts3" (type (eq 12))) + (type (;14;) (variant (case "a" u32) (case "b" s64))) + (export (;15;) "casts4" (type (eq 14))) + (type (;16;) (variant (case "a" float32) (case "b" s64))) + (export (;17;) "casts5" (type (eq 16))) + (type (;18;) (tuple float32 u32)) + (type (;19;) (tuple u32 u32)) + (type (;20;) (variant (case "a" 18) (case "b" 19))) + (export (;21;) "casts6" (type (eq 20))) + (type (;22;) (enum "bad1" "bad2")) + (export (;23;) "my-errno" (type (eq 22))) + (type (;24;) (func (param "x" 1))) + (export (;0;) "e1-arg" (func (type 24))) + (type (;25;) (func (result 1))) + (export (;1;) "e1-result" (func (type 25))) + (type (;26;) (func (param "x" 3))) + (export (;2;) "u1-arg" (func (type 26))) + (type (;27;) (func (result 3))) + (export (;3;) "u1-result" (func (type 27))) + (type (;28;) (func (param "x" 7))) + (export (;4;) "v1-arg" (func (type 28))) + (type (;29;) (func (result 7))) + (export (;5;) "v1-result" (func (type 29))) + (type (;30;) (func (param "x" bool))) + (export (;6;) "bool-arg" (func (type 30))) + (type (;31;) (func (result bool))) + (export (;7;) "bool-result" (func (type 31))) + (type (;32;) (option bool)) + (type (;33;) (tuple)) + (type (;34;) (option 33)) + (type (;35;) (option u32)) + (type (;36;) (option 1)) + (type (;37;) (option float32)) + (type (;38;) (option 3)) + (type (;39;) (option 32)) + (type (;40;) (func (param "a" 32) (param "b" 34) (param "c" 35) (param "d" 36) (param "e" 37) (param "f" 38) (param "g" 39))) + (export (;8;) "option-arg" (func (type 40))) + (type (;41;) (tuple 32 34 35 36 37 38 39)) + (type (;42;) (func (result 41))) + (export (;9;) "option-result" (func (type 42))) + (type (;43;) (tuple 9 11 13 15 17 21)) + (type (;44;) (func (param "a" 9) (param "b" 11) (param "c" 13) (param "d" 15) (param "e" 17) (param "f" 21) (result 43))) + (export (;10;) "casts" (func (type 44))) + (type (;45;) (result)) + (type (;46;) (result (error 1))) + (type (;47;) (result 1)) + (type (;48;) (result 33 (error 33))) + (type (;49;) (result u32 (error 7))) + (type (;50;) (list u8)) + (type (;51;) (result string (error 50))) + (type (;52;) (func (param "a" 45) (param "b" 46) (param "c" 47) (param "d" 48) (param "e" 49) (param "f" 51))) + (export (;11;) "expected-arg" (func (type 52))) + (type (;53;) (tuple 45 46 47 48 49 51)) + (type (;54;) (func (result 53))) + (export (;12;) "expected-result" (func (type 54))) + (type (;55;) (result s32 (error 23))) + (type (;56;) (func (result 55))) + (export (;13;) "return-expected-sugar" (func (type 56))) + (type (;57;) (result (error 23))) + (type (;58;) (func (result 57))) + (export (;14;) "return-expected-sugar2" (func (type 58))) + (type (;59;) (result 23 (error 23))) + (type (;60;) (func (result 59))) + (export (;15;) "return-expected-sugar3" (func (type 60))) + (type (;61;) (tuple s32 u32)) + (type (;62;) (result 61 (error 23))) + (type (;63;) (func (result 62))) + (export (;16;) "return-expected-sugar4" (func (type 63))) + (type (;64;) (option s32)) + (type (;65;) (func (result 64))) + (export (;17;) "return-option-sugar" (func (type 65))) + (type (;66;) (option 23)) + (type (;67;) (func (result 66))) + (export (;18;) "return-option-sugar2" (func (type 67))) + (type (;68;) (result u32 (error s32))) + (type (;69;) (func (result 68))) + (export (;19;) "expected-simple" (func (type 69))) + ) + ) + (export (;0;) "variants" "pkg:/variants/variants" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (enum "a")) + (export (;1;) "e1" (type (eq 0))) + (type (;2;) (union u32 float32)) + (export (;3;) "u1" (type (eq 2))) + (type (;4;) (record)) + (export (;5;) "empty" (type (eq 4))) + (type (;6;) (variant (case "a") (case "b" 3) (case "c" 1) (case "d" string) (case "e" 5) (case "f") (case "g" u32))) + (export (;7;) "v1" (type (eq 6))) + (type (;8;) (variant (case "a" s32) (case "b" float32))) + (export (;9;) "casts1" (type (eq 8))) + (type (;10;) (variant (case "a" float64) (case "b" float32))) + (export (;11;) "casts2" (type (eq 10))) + (type (;12;) (variant (case "a" float64) (case "b" u64))) + (export (;13;) "casts3" (type (eq 12))) + (type (;14;) (variant (case "a" u32) (case "b" s64))) + (export (;15;) "casts4" (type (eq 14))) + (type (;16;) (variant (case "a" float32) (case "b" s64))) + (export (;17;) "casts5" (type (eq 16))) + (type (;18;) (tuple float32 u32)) + (type (;19;) (tuple u32 u32)) + (type (;20;) (variant (case "a" 18) (case "b" 19))) + (export (;21;) "casts6" (type (eq 20))) + (type (;22;) (enum "bad1" "bad2")) + (export (;23;) "my-errno" (type (eq 22))) + (type (;24;) (func (param "x" 1))) + (export (;0;) "e1-arg" (func (type 24))) + (type (;25;) (func (result 1))) + (export (;1;) "e1-result" (func (type 25))) + (type (;26;) (func (param "x" 3))) + (export (;2;) "u1-arg" (func (type 26))) + (type (;27;) (func (result 3))) + (export (;3;) "u1-result" (func (type 27))) + (type (;28;) (func (param "x" 7))) + (export (;4;) "v1-arg" (func (type 28))) + (type (;29;) (func (result 7))) + (export (;5;) "v1-result" (func (type 29))) + (type (;30;) (func (param "x" bool))) + (export (;6;) "bool-arg" (func (type 30))) + (type (;31;) (func (result bool))) + (export (;7;) "bool-result" (func (type 31))) + (type (;32;) (option bool)) + (type (;33;) (tuple)) + (type (;34;) (option 33)) + (type (;35;) (option u32)) + (type (;36;) (option 1)) + (type (;37;) (option float32)) + (type (;38;) (option 3)) + (type (;39;) (option 32)) + (type (;40;) (func (param "a" 32) (param "b" 34) (param "c" 35) (param "d" 36) (param "e" 37) (param "f" 38) (param "g" 39))) + (export (;8;) "option-arg" (func (type 40))) + (type (;41;) (tuple 32 34 35 36 37 38 39)) + (type (;42;) (func (result 41))) + (export (;9;) "option-result" (func (type 42))) + (type (;43;) (tuple 9 11 13 15 17 21)) + (type (;44;) (func (param "a" 9) (param "b" 11) (param "c" 13) (param "d" 15) (param "e" 17) (param "f" 21) (result 43))) + (export (;10;) "casts" (func (type 44))) + (type (;45;) (result)) + (type (;46;) (result (error 1))) + (type (;47;) (result 1)) + (type (;48;) (result 33 (error 33))) + (type (;49;) (result u32 (error 7))) + (type (;50;) (list u8)) + (type (;51;) (result string (error 50))) + (type (;52;) (func (param "a" 45) (param "b" 46) (param "c" 47) (param "d" 48) (param "e" 49) (param "f" 51))) + (export (;11;) "expected-arg" (func (type 52))) + (type (;53;) (tuple 45 46 47 48 49 51)) + (type (;54;) (func (result 53))) + (export (;12;) "expected-result" (func (type 54))) + (type (;55;) (result s32 (error 23))) + (type (;56;) (func (result 55))) + (export (;13;) "return-expected-sugar" (func (type 56))) + (type (;57;) (result (error 23))) + (type (;58;) (func (result 57))) + (export (;14;) "return-expected-sugar2" (func (type 58))) + (type (;59;) (result 23 (error 23))) + (type (;60;) (func (result 59))) + (export (;15;) "return-expected-sugar3" (func (type 60))) + (type (;61;) (tuple s32 u32)) + (type (;62;) (result 61 (error 23))) + (type (;63;) (func (result 62))) + (export (;16;) "return-expected-sugar4" (func (type 63))) + (type (;64;) (option s32)) + (type (;65;) (func (result 64))) + (export (;17;) "return-option-sugar" (func (type 65))) + (type (;66;) (option 23)) + (type (;67;) (func (result 66))) + (export (;18;) "return-option-sugar2" (func (type 67))) + (type (;68;) (result u32 (error s32))) + (type (;69;) (func (result 68))) + (export (;19;) "expected-simple" (func (type 69))) + ) + ) + (import "variants" "pkg:/variants/variants" (instance (type 0))) + ) + ) + (export (;0;) "variants-world" "pkg:/variants/variants-world" (component (type 1))) + ) + ) + (export (;1;) "variants" "pkg:/variants" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/variants.wit b/crates/wit-component/tests/interfaces/variants.wit new file mode 100644 index 0000000000..bb642626f6 --- /dev/null +++ b/crates/wit-component/tests/interfaces/variants.wit @@ -0,0 +1,83 @@ +interface variants { + enum e1 { + a, + } + + union u1 { + u32, + float32, + } + + record empty { + } + + variant v1 { + a, + b(u1), + c(e1), + d(string), + e(empty), + f, + g(u32), + } + + variant casts1 { + a(s32), + b(float32), + } + + variant casts2 { + a(float64), + b(float32), + } + + variant casts3 { + a(float64), + b(u64), + } + + variant casts4 { + a(u32), + b(s64), + } + + variant casts5 { + a(float32), + b(s64), + } + + variant casts6 { + a(tuple), + b(tuple), + } + + enum my-errno { + bad1, + bad2, + } + + e1-arg: func(x: e1) + e1-result: func() -> e1 + u1-arg: func(x: u1) + u1-result: func() -> u1 + v1-arg: func(x: v1) + v1-result: func() -> v1 + bool-arg: func(x: bool) + bool-result: func() -> bool + option-arg: func(a: option, b: option>, c: option, d: option, e: option, f: option, g: option>) + option-result: func() -> tuple, option>, option, option, option, option, option>> + casts: func(a: casts1, b: casts2, c: casts3, d: casts4, e: casts5, f: casts6) -> tuple + expected-arg: func(a: result, b: result<_, e1>, c: result, d: result, tuple<>>, e: result, f: result>) + expected-result: func() -> tuple, result, result, tuple<>>, result, result>> + return-expected-sugar: func() -> result + return-expected-sugar2: func() -> result<_, my-errno> + return-expected-sugar3: func() -> result + return-expected-sugar4: func() -> result, my-errno> + return-option-sugar: func() -> option + return-option-sugar2: func() -> option + expected-simple: func() -> result +} + +world variants-world { + import variants: self.variants +} diff --git a/crates/wit-component/tests/interfaces/variants/world.wit b/crates/wit-component/tests/interfaces/variants.wit.print similarity index 98% rename from crates/wit-component/tests/interfaces/variants/world.wit rename to crates/wit-component/tests/interfaces/variants.wit.print index f32fa7f260..d0cc686215 100644 --- a/crates/wit-component/tests/interfaces/variants/world.wit +++ b/crates/wit-component/tests/interfaces/variants.wit.print @@ -98,5 +98,5 @@ interface variants { } world variants-world { - import variants: variants + import variants: self.variants } diff --git a/crates/wit-component/tests/interfaces/variants/component.wat b/crates/wit-component/tests/interfaces/variants/component.wat deleted file mode 100644 index 90530ef7c1..0000000000 --- a/crates/wit-component/tests/interfaces/variants/component.wat +++ /dev/null @@ -1,379 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (enum "a")) - (export (;1;) "e1" (type (eq 0))) - (type (;2;) (func (param "x" 0))) - (export (;0;) "e1-arg" (func (type 2))) - (type (;3;) (func (result 0))) - (export (;1;) "e1-result" (func (type 3))) - (type (;4;) (union u32 float32)) - (export (;5;) "u1" (type (eq 4))) - (type (;6;) (func (param "x" 4))) - (export (;2;) "u1-arg" (func (type 6))) - (type (;7;) (func (result 4))) - (export (;3;) "u1-result" (func (type 7))) - (type (;8;) (record)) - (export (;9;) "empty" (type (eq 8))) - (type (;10;) (variant (case "a") (case "b" 4) (case "c" 0) (case "d" string) (case "e" 8) (case "f") (case "g" u32))) - (export (;11;) "v1" (type (eq 10))) - (type (;12;) (func (param "x" 10))) - (export (;4;) "v1-arg" (func (type 12))) - (type (;13;) (func (result 10))) - (export (;5;) "v1-result" (func (type 13))) - (type (;14;) (func (param "x" bool))) - (export (;6;) "bool-arg" (func (type 14))) - (type (;15;) (func (result bool))) - (export (;7;) "bool-result" (func (type 15))) - (type (;16;) (option bool)) - (type (;17;) (tuple)) - (type (;18;) (option 17)) - (type (;19;) (option u32)) - (type (;20;) (option 0)) - (type (;21;) (option float32)) - (type (;22;) (option 4)) - (type (;23;) (option 16)) - (type (;24;) (func (param "a" 16) (param "b" 18) (param "c" 19) (param "d" 20) (param "e" 21) (param "f" 22) (param "g" 23))) - (export (;8;) "option-arg" (func (type 24))) - (type (;25;) (tuple 16 18 19 20 21 22 23)) - (type (;26;) (func (result 25))) - (export (;9;) "option-result" (func (type 26))) - (type (;27;) (variant (case "a" s32) (case "b" float32))) - (export (;28;) "casts1" (type (eq 27))) - (type (;29;) (variant (case "a" float64) (case "b" float32))) - (export (;30;) "casts2" (type (eq 29))) - (type (;31;) (variant (case "a" float64) (case "b" u64))) - (export (;32;) "casts3" (type (eq 31))) - (type (;33;) (variant (case "a" u32) (case "b" s64))) - (export (;34;) "casts4" (type (eq 33))) - (type (;35;) (variant (case "a" float32) (case "b" s64))) - (export (;36;) "casts5" (type (eq 35))) - (type (;37;) (tuple float32 u32)) - (type (;38;) (tuple u32 u32)) - (type (;39;) (variant (case "a" 37) (case "b" 38))) - (export (;40;) "casts6" (type (eq 39))) - (type (;41;) (tuple 27 29 31 33 35 39)) - (type (;42;) (func (param "a" 27) (param "b" 29) (param "c" 31) (param "d" 33) (param "e" 35) (param "f" 39) (result 41))) - (export (;10;) "casts" (func (type 42))) - (type (;43;) (result)) - (type (;44;) (result (error 0))) - (type (;45;) (result 0)) - (type (;46;) (result 17 (error 17))) - (type (;47;) (result u32 (error 10))) - (type (;48;) (list u8)) - (type (;49;) (result string (error 48))) - (type (;50;) (func (param "a" 43) (param "b" 44) (param "c" 45) (param "d" 46) (param "e" 47) (param "f" 49))) - (export (;11;) "expected-arg" (func (type 50))) - (type (;51;) (tuple 43 44 45 46 47 49)) - (type (;52;) (func (result 51))) - (export (;12;) "expected-result" (func (type 52))) - (type (;53;) (enum "bad1" "bad2")) - (export (;54;) "my-errno" (type (eq 53))) - (type (;55;) (result s32 (error 53))) - (type (;56;) (func (result 55))) - (export (;13;) "return-expected-sugar" (func (type 56))) - (type (;57;) (result (error 53))) - (type (;58;) (func (result 57))) - (export (;14;) "return-expected-sugar2" (func (type 58))) - (type (;59;) (result 53 (error 53))) - (type (;60;) (func (result 59))) - (export (;15;) "return-expected-sugar3" (func (type 60))) - (type (;61;) (tuple s32 u32)) - (type (;62;) (result 61 (error 53))) - (type (;63;) (func (result 62))) - (export (;16;) "return-expected-sugar4" (func (type 63))) - (type (;64;) (option s32)) - (type (;65;) (func (result 64))) - (export (;17;) "return-option-sugar" (func (type 65))) - (type (;66;) (option 53)) - (type (;67;) (func (result 66))) - (export (;18;) "return-option-sugar2" (func (type 67))) - (type (;68;) (result u32 (error s32))) - (type (;69;) (func (result 68))) - (export (;19;) "expected-simple" (func (type 69))) - ) - ) - (import "variants" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32))) - (type (;1;) (func (result i32))) - (type (;2;) (func (param i32 i32))) - (type (;3;) (func (param i32 i32 i32))) - (type (;4;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 f32 i32 i32 i32 i32 i32 i32))) - (type (;5;) (func (param i32 i32 i32 i64 i32 i64 i32 i64 i32 i64 i32 i32 i32 i32))) - (type (;6;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32))) - (type (;7;) (func (param i32 i32 i32 i32) (result i32))) - (import "variants" "e1-arg" (func (;0;) (type 0))) - (import "variants" "e1-result" (func (;1;) (type 1))) - (import "variants" "u1-arg" (func (;2;) (type 2))) - (import "variants" "u1-result" (func (;3;) (type 0))) - (import "variants" "v1-arg" (func (;4;) (type 3))) - (import "variants" "v1-result" (func (;5;) (type 0))) - (import "variants" "bool-arg" (func (;6;) (type 0))) - (import "variants" "bool-result" (func (;7;) (type 1))) - (import "variants" "option-arg" (func (;8;) (type 4))) - (import "variants" "option-result" (func (;9;) (type 0))) - (import "variants" "casts" (func (;10;) (type 5))) - (import "variants" "expected-arg" (func (;11;) (type 6))) - (import "variants" "expected-result" (func (;12;) (type 0))) - (import "variants" "return-expected-sugar" (func (;13;) (type 0))) - (import "variants" "return-expected-sugar2" (func (;14;) (type 0))) - (import "variants" "return-expected-sugar3" (func (;15;) (type 0))) - (import "variants" "return-expected-sugar4" (func (;16;) (type 0))) - (import "variants" "return-option-sugar" (func (;17;) (type 0))) - (import "variants" "return-option-sugar2" (func (;18;) (type 0))) - (import "variants" "expected-simple" (func (;19;) (type 0))) - (func (;20;) (type 7) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 20)) - ) - (core module (;1;) - (type (;0;) (func (param i32))) - (type (;1;) (func (param i32 i32 i32))) - (type (;2;) (func (param i32 i32 i32 i64 i32 i64 i32 i64 i32 i64 i32 i32 i32 i32))) - (type (;3;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32))) - (func $indirect-variants-u1-result (;0;) (type 0) (param i32) - local.get 0 - i32.const 0 - call_indirect (type 0) - ) - (func $indirect-variants-v1-arg (;1;) (type 1) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 1 - call_indirect (type 1) - ) - (func $indirect-variants-v1-result (;2;) (type 0) (param i32) - local.get 0 - i32.const 2 - call_indirect (type 0) - ) - (func $indirect-variants-option-result (;3;) (type 0) (param i32) - local.get 0 - i32.const 3 - call_indirect (type 0) - ) - (func $indirect-variants-casts (;4;) (type 2) (param i32 i32 i32 i64 i32 i64 i32 i64 i32 i64 i32 i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - local.get 4 - local.get 5 - local.get 6 - local.get 7 - local.get 8 - local.get 9 - local.get 10 - local.get 11 - local.get 12 - local.get 13 - i32.const 4 - call_indirect (type 2) - ) - (func $indirect-variants-expected-arg (;5;) (type 3) (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - local.get 4 - local.get 5 - local.get 6 - local.get 7 - local.get 8 - local.get 9 - local.get 10 - local.get 11 - local.get 12 - i32.const 5 - call_indirect (type 3) - ) - (func $indirect-variants-expected-result (;6;) (type 0) (param i32) - local.get 0 - i32.const 6 - call_indirect (type 0) - ) - (func $indirect-variants-return-expected-sugar (;7;) (type 0) (param i32) - local.get 0 - i32.const 7 - call_indirect (type 0) - ) - (func $indirect-variants-return-expected-sugar2 (;8;) (type 0) (param i32) - local.get 0 - i32.const 8 - call_indirect (type 0) - ) - (func $indirect-variants-return-expected-sugar3 (;9;) (type 0) (param i32) - local.get 0 - i32.const 9 - call_indirect (type 0) - ) - (func $indirect-variants-return-expected-sugar4 (;10;) (type 0) (param i32) - local.get 0 - i32.const 10 - call_indirect (type 0) - ) - (func $indirect-variants-return-option-sugar (;11;) (type 0) (param i32) - local.get 0 - i32.const 11 - call_indirect (type 0) - ) - (func $indirect-variants-return-option-sugar2 (;12;) (type 0) (param i32) - local.get 0 - i32.const 12 - call_indirect (type 0) - ) - (func $indirect-variants-expected-simple (;13;) (type 0) (param i32) - local.get 0 - i32.const 13 - call_indirect (type 0) - ) - (table (;0;) 14 14 funcref) - (export "0" (func $indirect-variants-u1-result)) - (export "1" (func $indirect-variants-v1-arg)) - (export "2" (func $indirect-variants-v1-result)) - (export "3" (func $indirect-variants-option-result)) - (export "4" (func $indirect-variants-casts)) - (export "5" (func $indirect-variants-expected-arg)) - (export "6" (func $indirect-variants-expected-result)) - (export "7" (func $indirect-variants-return-expected-sugar)) - (export "8" (func $indirect-variants-return-expected-sugar2)) - (export "9" (func $indirect-variants-return-expected-sugar3)) - (export "10" (func $indirect-variants-return-expected-sugar4)) - (export "11" (func $indirect-variants-return-option-sugar)) - (export "12" (func $indirect-variants-return-option-sugar2)) - (export "13" (func $indirect-variants-expected-simple)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i32))) - (type (;1;) (func (param i32 i32 i32))) - (type (;2;) (func (param i32 i32 i32 i64 i32 i64 i32 i64 i32 i64 i32 i32 i32 i32))) - (type (;3;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "1" (func (;1;) (type 1))) - (import "" "2" (func (;2;) (type 0))) - (import "" "3" (func (;3;) (type 0))) - (import "" "4" (func (;4;) (type 2))) - (import "" "5" (func (;5;) (type 3))) - (import "" "6" (func (;6;) (type 0))) - (import "" "7" (func (;7;) (type 0))) - (import "" "8" (func (;8;) (type 0))) - (import "" "9" (func (;9;) (type 0))) - (import "" "10" (func (;10;) (type 0))) - (import "" "11" (func (;11;) (type 0))) - (import "" "12" (func (;12;) (type 0))) - (import "" "13" (func (;13;) (type 0))) - (import "" "$imports" (table (;0;) 14 14 funcref)) - (elem (;0;) (i32.const 0) func 0 1 2 3 4 5 6 7 8 9 10 11 12 13) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) - (alias core export 0 "2" (core func (;2;))) - (alias core export 0 "3" (core func (;3;))) - (alias core export 0 "4" (core func (;4;))) - (alias core export 0 "5" (core func (;5;))) - (alias core export 0 "6" (core func (;6;))) - (alias core export 0 "7" (core func (;7;))) - (alias core export 0 "8" (core func (;8;))) - (alias core export 0 "9" (core func (;9;))) - (alias core export 0 "10" (core func (;10;))) - (alias core export 0 "11" (core func (;11;))) - (alias core export 0 "12" (core func (;12;))) - (alias core export 0 "13" (core func (;13;))) - (alias export 0 "e1-arg" (func (;0;))) - (core func (;14;) (canon lower (func 0))) - (alias export 0 "e1-result" (func (;1;))) - (core func (;15;) (canon lower (func 1))) - (alias export 0 "u1-arg" (func (;2;))) - (core func (;16;) (canon lower (func 2))) - (alias export 0 "bool-arg" (func (;3;))) - (core func (;17;) (canon lower (func 3))) - (alias export 0 "bool-result" (func (;4;))) - (core func (;18;) (canon lower (func 4))) - (alias export 0 "option-arg" (func (;5;))) - (core func (;19;) (canon lower (func 5))) - (core instance (;1;) - (export "u1-result" (func 0)) - (export "v1-arg" (func 1)) - (export "v1-result" (func 2)) - (export "option-result" (func 3)) - (export "casts" (func 4)) - (export "expected-arg" (func 5)) - (export "expected-result" (func 6)) - (export "return-expected-sugar" (func 7)) - (export "return-expected-sugar2" (func 8)) - (export "return-expected-sugar3" (func 9)) - (export "return-expected-sugar4" (func 10)) - (export "return-option-sugar" (func 11)) - (export "return-option-sugar2" (func 12)) - (export "expected-simple" (func 13)) - (export "e1-arg" (func 14)) - (export "e1-result" (func 15)) - (export "u1-arg" (func 16)) - (export "bool-arg" (func 17)) - (export "bool-result" (func 18)) - (export "option-arg" (func 19)) - ) - (core instance (;2;) (instantiate 0 - (with "variants" (instance 1)) - ) - ) - (alias core export 2 "memory" (core memory (;0;))) - (alias core export 2 "cabi_realloc" (core func (;20;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 0 "u1-result" (func (;6;))) - (core func (;21;) (canon lower (func 6) (memory 0))) - (alias export 0 "v1-arg" (func (;7;))) - (core func (;22;) (canon lower (func 7) (memory 0) string-encoding=utf8)) - (alias export 0 "v1-result" (func (;8;))) - (core func (;23;) (canon lower (func 8) (memory 0) (realloc 20) string-encoding=utf8)) - (alias export 0 "option-result" (func (;9;))) - (core func (;24;) (canon lower (func 9) (memory 0))) - (alias export 0 "casts" (func (;10;))) - (core func (;25;) (canon lower (func 10) (memory 0))) - (alias export 0 "expected-arg" (func (;11;))) - (core func (;26;) (canon lower (func 11) (memory 0) string-encoding=utf8)) - (alias export 0 "expected-result" (func (;12;))) - (core func (;27;) (canon lower (func 12) (memory 0) (realloc 20) string-encoding=utf8)) - (alias export 0 "return-expected-sugar" (func (;13;))) - (core func (;28;) (canon lower (func 13) (memory 0))) - (alias export 0 "return-expected-sugar2" (func (;14;))) - (core func (;29;) (canon lower (func 14) (memory 0))) - (alias export 0 "return-expected-sugar3" (func (;15;))) - (core func (;30;) (canon lower (func 15) (memory 0))) - (alias export 0 "return-expected-sugar4" (func (;16;))) - (core func (;31;) (canon lower (func 16) (memory 0))) - (alias export 0 "return-option-sugar" (func (;17;))) - (core func (;32;) (canon lower (func 17) (memory 0))) - (alias export 0 "return-option-sugar2" (func (;18;))) - (core func (;33;) (canon lower (func 18) (memory 0))) - (alias export 0 "expected-simple" (func (;19;))) - (core func (;34;) (canon lower (func 19) (memory 0))) - (core instance (;3;) - (export "$imports" (table 0)) - (export "0" (func 21)) - (export "1" (func 22)) - (export "2" (func 23)) - (export "3" (func 24)) - (export "4" (func 25)) - (export "5" (func 26)) - (export "6" (func 27)) - (export "7" (func 28)) - (export "8" (func 29)) - (export "9" (func 30)) - (export "10" (func 31)) - (export "11" (func 32)) - (export "12" (func 33)) - (export "13" (func 34)) - ) - (core instance (;4;) (instantiate 2 - (with "" (instance 3)) - ) - ) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/variants/types_only.wat b/crates/wit-component/tests/interfaces/variants/types_only.wat deleted file mode 100644 index 22bcc35c1a..0000000000 --- a/crates/wit-component/tests/interfaces/variants/types_only.wat +++ /dev/null @@ -1,97 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (enum "a")) - (export (;1;) "e1" (type (eq 0))) - (type (;2;) (func (param "x" 0))) - (export (;0;) "e1-arg" (func (type 2))) - (type (;3;) (func (result 0))) - (export (;1;) "e1-result" (func (type 3))) - (type (;4;) (union u32 float32)) - (export (;5;) "u1" (type (eq 4))) - (type (;6;) (func (param "x" 4))) - (export (;2;) "u1-arg" (func (type 6))) - (type (;7;) (func (result 4))) - (export (;3;) "u1-result" (func (type 7))) - (type (;8;) (record)) - (export (;9;) "empty" (type (eq 8))) - (type (;10;) (variant (case "a") (case "b" 4) (case "c" 0) (case "d" string) (case "e" 8) (case "f") (case "g" u32))) - (export (;11;) "v1" (type (eq 10))) - (type (;12;) (func (param "x" 10))) - (export (;4;) "v1-arg" (func (type 12))) - (type (;13;) (func (result 10))) - (export (;5;) "v1-result" (func (type 13))) - (type (;14;) (func (param "x" bool))) - (export (;6;) "bool-arg" (func (type 14))) - (type (;15;) (func (result bool))) - (export (;7;) "bool-result" (func (type 15))) - (type (;16;) (option bool)) - (type (;17;) (tuple)) - (type (;18;) (option 17)) - (type (;19;) (option u32)) - (type (;20;) (option 0)) - (type (;21;) (option float32)) - (type (;22;) (option 4)) - (type (;23;) (option 16)) - (type (;24;) (func (param "a" 16) (param "b" 18) (param "c" 19) (param "d" 20) (param "e" 21) (param "f" 22) (param "g" 23))) - (export (;8;) "option-arg" (func (type 24))) - (type (;25;) (tuple 16 18 19 20 21 22 23)) - (type (;26;) (func (result 25))) - (export (;9;) "option-result" (func (type 26))) - (type (;27;) (variant (case "a" s32) (case "b" float32))) - (export (;28;) "casts1" (type (eq 27))) - (type (;29;) (variant (case "a" float64) (case "b" float32))) - (export (;30;) "casts2" (type (eq 29))) - (type (;31;) (variant (case "a" float64) (case "b" u64))) - (export (;32;) "casts3" (type (eq 31))) - (type (;33;) (variant (case "a" u32) (case "b" s64))) - (export (;34;) "casts4" (type (eq 33))) - (type (;35;) (variant (case "a" float32) (case "b" s64))) - (export (;36;) "casts5" (type (eq 35))) - (type (;37;) (tuple float32 u32)) - (type (;38;) (tuple u32 u32)) - (type (;39;) (variant (case "a" 37) (case "b" 38))) - (export (;40;) "casts6" (type (eq 39))) - (type (;41;) (tuple 27 29 31 33 35 39)) - (type (;42;) (func (param "a" 27) (param "b" 29) (param "c" 31) (param "d" 33) (param "e" 35) (param "f" 39) (result 41))) - (export (;10;) "casts" (func (type 42))) - (type (;43;) (result)) - (type (;44;) (result (error 0))) - (type (;45;) (result 0)) - (type (;46;) (result 17 (error 17))) - (type (;47;) (result u32 (error 10))) - (type (;48;) (list u8)) - (type (;49;) (result string (error 48))) - (type (;50;) (func (param "a" 43) (param "b" 44) (param "c" 45) (param "d" 46) (param "e" 47) (param "f" 49))) - (export (;11;) "expected-arg" (func (type 50))) - (type (;51;) (tuple 43 44 45 46 47 49)) - (type (;52;) (func (result 51))) - (export (;12;) "expected-result" (func (type 52))) - (type (;53;) (enum "bad1" "bad2")) - (export (;54;) "my-errno" (type (eq 53))) - (type (;55;) (result s32 (error 53))) - (type (;56;) (func (result 55))) - (export (;13;) "return-expected-sugar" (func (type 56))) - (type (;57;) (result (error 53))) - (type (;58;) (func (result 57))) - (export (;14;) "return-expected-sugar2" (func (type 58))) - (type (;59;) (result 53 (error 53))) - (type (;60;) (func (result 59))) - (export (;15;) "return-expected-sugar3" (func (type 60))) - (type (;61;) (tuple s32 u32)) - (type (;62;) (result 61 (error 53))) - (type (;63;) (func (result 62))) - (export (;16;) "return-expected-sugar4" (func (type 63))) - (type (;64;) (option s32)) - (type (;65;) (func (result 64))) - (export (;17;) "return-option-sugar" (func (type 65))) - (type (;66;) (option 53)) - (type (;67;) (func (result 66))) - (export (;18;) "return-option-sugar2" (func (type 67))) - (type (;68;) (result u32 (error s32))) - (type (;69;) (func (result 68))) - (export (;19;) "expected-simple" (func (type 69))) - ) - ) - (import "variants" (instance (;0;) (type 0))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/wasi-http.wat b/crates/wit-component/tests/interfaces/wasi-http.wat new file mode 100644 index 0000000000..3229974168 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http.wat @@ -0,0 +1,95 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "request" (type (eq 0))) + (type (;2;) (record)) + (export (;3;) "response" (type (eq 2))) + ) + ) + (export (;0;) "types" "pkg:/types/types" (instance (type 0))) + ) + ) + (export (;1;) "types" "pkg:/types" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "response" (type (eq 0))) + (type (;2;) (record)) + (export (;3;) "request" (type (eq 2))) + ) + ) + (import "types" "pkg:/types/types" (instance (type 0))) + (alias export 0 "request" (type (;1;))) + (alias export 0 "response" (type (;2;))) + (type (;3;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "request" (type (eq 0))) + (alias outer 1 2 (type (;2;))) + (export (;3;) "response" (type (eq 2))) + (type (;4;) (func (param "request" 1) (result 3))) + (export (;0;) "handle" (func (type 4))) + ) + ) + (export (;0;) "handler" "pkg:/handler/handler" (instance (type 3))) + ) + ) + (export (;3;) "handler" "pkg:/handler" (type 2)) + (type (;4;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (enum "info" "debug")) + (export (;1;) "level" (type (eq 0))) + (type (;2;) (func (param "level" 1) (param "msg" string))) + (export (;0;) "log" (func (type 2))) + ) + ) + (import "console" "path:/wasi-logging/backend/backend" (instance (type 0))) + (type (;1;) + (instance + (type (;0;) (record)) + (export (;1;) "request" (type (eq 0))) + (type (;2;) (record)) + (export (;3;) "response" (type (eq 2))) + ) + ) + (import "types" "pkg:/types/types" (instance (type 1))) + (alias export 1 "request" (type (;2;))) + (alias export 1 "response" (type (;3;))) + (type (;4;) + (instance + (alias outer 1 2 (type (;0;))) + (export (;1;) "request" (type (eq 0))) + (alias outer 1 3 (type (;2;))) + (export (;3;) "response" (type (eq 2))) + (type (;4;) (func (param "request" 1) (result 3))) + (export (;0;) "handle" (func (type 4))) + ) + ) + (import "origin" "pkg:/handler/handler" (instance (type 4))) + (alias export 2 "request" (type (;5;))) + (alias export 2 "response" (type (;6;))) + (type (;7;) + (instance + (alias outer 1 5 (type (;0;))) + (alias outer 1 6 (type (;1;))) + (type (;2;) (func (param "request" 0) (result 1))) + (export (;0;) "handle" (func (type 2))) + ) + ) + (export (;0;) "handler" "pkg:/handler/handler" (instance (type 7))) + ) + ) + (export (;0;) "proxy" "pkg:/proxy/proxy" (component (type 0))) + ) + ) + (export (;5;) "proxy" "pkg:/proxy" (type 4)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit b/crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit new file mode 100644 index 0000000000..e3293d5115 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit @@ -0,0 +1,8 @@ +default interface backend { + enum level { + info, + debug, + } + + log: func(level: level, msg: string) +} diff --git a/crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit.print b/crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit.print new file mode 100644 index 0000000000..9833d6f7f5 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit.print @@ -0,0 +1,9 @@ +interface backend { + enum level { + info, + debug, + } + + log: func(level: level, msg: string) +} + diff --git a/crates/wit-component/tests/interfaces/wasi-http/handler.wit b/crates/wit-component/tests/interfaces/wasi-http/handler.wit new file mode 100644 index 0000000000..873938d7f4 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/handler.wit @@ -0,0 +1,4 @@ +default interface handler { + use pkg.types.{request, response} + handle: func(request: request) -> response +} diff --git a/crates/wit-component/tests/interfaces/wasi-http/handler.wit.print b/crates/wit-component/tests/interfaces/wasi-http/handler.wit.print new file mode 100644 index 0000000000..70836e2955 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/handler.wit.print @@ -0,0 +1,5 @@ +interface handler { + use pkg.types.types.{request, response} + handle: func(request: request) -> response +} + diff --git a/crates/wit-component/tests/interfaces/wasi-http/proxy.wit b/crates/wit-component/tests/interfaces/wasi-http/proxy.wit new file mode 100644 index 0000000000..2651e5fec4 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/proxy.wit @@ -0,0 +1,5 @@ +default world proxy { + import console: wasi-logging.backend + import origin: pkg.handler + export handler: pkg.handler +} diff --git a/crates/wit-component/tests/interfaces/wasi-http/proxy.wit.print b/crates/wit-component/tests/interfaces/wasi-http/proxy.wit.print new file mode 100644 index 0000000000..9d2d081ca0 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/proxy.wit.print @@ -0,0 +1,6 @@ +world proxy { + import console: wasi-logging.backend.backend + import types: pkg.types.types + import origin: pkg.handler.handler + export handler: pkg.handler.handler +} diff --git a/crates/wit-component/tests/interfaces/wasi-http/types.wit b/crates/wit-component/tests/interfaces/wasi-http/types.wit new file mode 100644 index 0000000000..9f55e779c0 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/types.wit @@ -0,0 +1,4 @@ +default interface types { + record request { } + record response { } +} diff --git a/crates/wit-component/tests/interfaces/wasi-http/types.wit.print b/crates/wit-component/tests/interfaces/wasi-http/types.wit.print new file mode 100644 index 0000000000..2419c2f452 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/types.wit.print @@ -0,0 +1,9 @@ +interface types { + record request { + } + + record response { + } + +} + diff --git a/crates/wit-component/tests/interfaces/world-inline-interface.wat b/crates/wit-component/tests/interfaces/world-inline-interface.wat new file mode 100644 index 0000000000..782ecd441a --- /dev/null +++ b/crates/wit-component/tests/interfaces/world-inline-interface.wat @@ -0,0 +1,20 @@ +(component + (type (;0;) + (component + (type (;0;) + (component + (type (;0;) + (instance) + ) + (import "foo" (instance (type 0))) + (type (;1;) + (instance) + ) + (export (;0;) "bar" (instance (type 1))) + ) + ) + (export (;0;) "has-inline" "pkg:/world-inline-interface/has-inline" (component (type 0))) + ) + ) + (export (;1;) "world-inline-interface" "pkg:/world-inline-interface" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/world-inline-interface.wit b/crates/wit-component/tests/interfaces/world-inline-interface.wit new file mode 100644 index 0000000000..9940392eca --- /dev/null +++ b/crates/wit-component/tests/interfaces/world-inline-interface.wit @@ -0,0 +1,4 @@ +world has-inline { + import foo: interface {} + export bar: interface {} +} diff --git a/crates/wit-component/tests/interfaces/world-inline-interface.wit.print b/crates/wit-component/tests/interfaces/world-inline-interface.wit.print new file mode 100644 index 0000000000..33ec54b02d --- /dev/null +++ b/crates/wit-component/tests/interfaces/world-inline-interface.wit.print @@ -0,0 +1,6 @@ +world has-inline { + import foo: interface { + } + export bar: interface { + } +} diff --git a/crates/wit-component/tests/interfaces/world-top-level.wat b/crates/wit-component/tests/interfaces/world-top-level.wat new file mode 100644 index 0000000000..88cc8e3cfb --- /dev/null +++ b/crates/wit-component/tests/interfaces/world-top-level.wat @@ -0,0 +1,42 @@ +(component + (type (;0;) + (component + (type (;0;) + (component + (type (;0;) + (instance) + ) + (import "some-interface" (instance (type 0))) + (type (;1;) (func)) + (import "foo" (func (type 1))) + (type (;2;) (func (param "arg" u32))) + (import "bar" (func (type 2))) + (type (;3;) + (instance) + ) + (export (;0;) "another-interface" (instance (type 3))) + (type (;4;) (func)) + (export (;0;) "foo" (func (type 4))) + (type (;5;) (func (result u32))) + (export (;1;) "bar" (func (type 5))) + ) + ) + (export (;0;) "foo" "pkg:/world-top-level/foo" (component (type 0))) + (type (;1;) + (component + (type (;0;) (func)) + (import "foo" (func (type 0))) + ) + ) + (export (;1;) "just-import" "pkg:/world-top-level/just-import" (component (type 1))) + (type (;2;) + (component + (type (;0;) (func)) + (export (;0;) "foo" (func (type 0))) + ) + ) + (export (;2;) "just-export" "pkg:/world-top-level/just-export" (component (type 2))) + ) + ) + (export (;1;) "world-top-level" "pkg:/world-top-level" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/world-top-level.wit b/crates/wit-component/tests/interfaces/world-top-level.wit new file mode 100644 index 0000000000..6333efb7c8 --- /dev/null +++ b/crates/wit-component/tests/interfaces/world-top-level.wit @@ -0,0 +1,18 @@ +world foo { + import foo: func() + export foo: func() + + import bar: func(arg: u32) + export bar: func() -> u32 + + import some-interface: interface {} + export another-interface: interface {} +} + +world just-import { + import foo: func() +} + +world just-export { + export foo: func() +} diff --git a/crates/wit-component/tests/interfaces/world-top-level.wit.print b/crates/wit-component/tests/interfaces/world-top-level.wit.print new file mode 100644 index 0000000000..c9871e04fb --- /dev/null +++ b/crates/wit-component/tests/interfaces/world-top-level.wit.print @@ -0,0 +1,16 @@ +world foo { + import some-interface: interface { + } + import foo: func() + import bar: func(arg: u32) + export another-interface: interface { + } + export foo: func() + export bar: func() -> u32 +} +world just-import { + import foo: func() +} +world just-export { + export foo: func() +} diff --git a/crates/wit-parser/Cargo.toml b/crates/wit-parser/Cargo.toml index b4f71b0046..6bde1ff2fc 100644 --- a/crates/wit-parser/Cargo.toml +++ b/crates/wit-parser/Cargo.toml @@ -18,11 +18,13 @@ anyhow = { workspace = true } indexmap = { workspace = true } pulldown-cmark = { version = "0.8", default-features = false } unicode-xid = "0.2.2" +log = { workspace = true } +url = { workspace = true } [dev-dependencies] rayon = "1" -serde_json = "1" -serde = { version = "1", features = ['derive'] } +env_logger = { workspace = true } +pretty_assertions = "1.3.0" [[test]] name = "all" diff --git a/crates/wit-component/fuzz/.gitignore b/crates/wit-parser/fuzz/.gitignore similarity index 100% rename from crates/wit-component/fuzz/.gitignore rename to crates/wit-parser/fuzz/.gitignore diff --git a/crates/wit-component/fuzz/Cargo.toml b/crates/wit-parser/fuzz/Cargo.toml similarity index 74% rename from crates/wit-component/fuzz/Cargo.toml rename to crates/wit-parser/fuzz/Cargo.toml index 59be356b2a..6be0c5262c 100644 --- a/crates/wit-component/fuzz/Cargo.toml +++ b/crates/wit-parser/fuzz/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "wit-component-fuzz" +name = "wit-parser-fuzz" version = "0.0.1" publish = false edition.workspace = true @@ -13,11 +13,10 @@ env_logger = { workspace = true } libfuzzer-sys = { workspace = true } log = { workspace = true } wasmprinter = { workspace = true } -wit-component = { workspace = true } wit-parser = { workspace = true } [[bin]] -name = "roundtrip-wit" -path = "fuzz_targets/roundtrip-wit.rs" +name = "parse" +path = "fuzz_targets/parse.rs" test = false doc = false diff --git a/crates/wit-parser/fuzz/fuzz_targets/parse.rs b/crates/wit-parser/fuzz/fuzz_targets/parse.rs new file mode 100644 index 0000000000..4bc47fb839 --- /dev/null +++ b/crates/wit-parser/fuzz/fuzz_targets/parse.rs @@ -0,0 +1,14 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + drop(env_logger::try_init()); + + let data = match std::str::from_utf8(data) { + Ok(s) => s, + Err(_) => return, + }; + + drop(wit_parser::UnresolvedPackage::parse("foo".as_ref(), &data)); +}); diff --git a/crates/wit-parser/src/abi.rs b/crates/wit-parser/src/abi.rs index c6c8bbb14f..6caf5f497f 100644 --- a/crates/wit-parser/src/abi.rs +++ b/crates/wit-parser/src/abi.rs @@ -1,6 +1,6 @@ use crate::sizealign::align_to; use crate::{ - Document, Enum, Flags, FlagsRepr, Function, Int, Record, Result_, Results, Tuple, Type, + Enum, Flags, FlagsRepr, Function, Int, Record, Resolve, Result_, Results, Tuple, Type, TypeDefKind, TypeId, Union, Variant, }; @@ -651,7 +651,7 @@ pub trait Bindgen { /// push the appropriate number of results or binding generation will panic. fn emit( &mut self, - doc: &Document, + resolve: &Resolve, inst: &Instruction<'_>, operands: &mut Vec, results: &mut Vec, @@ -694,10 +694,10 @@ pub trait Bindgen { /// Returns whether or not the specified element type is represented in a /// "canonical" form for lists. This dictates whether the `ListCanonLower` /// and `ListCanonLift` instructions are used or not. - fn is_list_canonical(&self, doc: &Document, element: &Type) -> bool; + fn is_list_canonical(&self, resolve: &Resolve, element: &Type) -> bool; } -impl Document { +impl Resolve { /// Get the WebAssembly type signature for this interface function /// /// The first entry returned is the list of parameters and the second entry @@ -824,6 +824,8 @@ impl Document { TypeDefKind::Stream(_) => { result.push(WasmType::I32); } + + TypeDefKind::Unknown => unreachable!(), }, } } @@ -912,6 +914,7 @@ impl Document { .any(|t| self.needs_post_return(t)), TypeDefKind::Flags(_) | TypeDefKind::Enum(_) => false, TypeDefKind::Future(_) | TypeDefKind::Stream(_) => unimplemented!(), + TypeDefKind::Unknown => unreachable!(), }, Type::Bool @@ -950,7 +953,7 @@ struct Generator<'a, B: Bindgen> { variant: AbiVariant, lift_lower: LiftLower, bindgen: &'a mut B, - doc: &'a Document, + resolve: &'a Resolve, operands: Vec, results: Vec, stack: Vec, @@ -959,13 +962,13 @@ struct Generator<'a, B: Bindgen> { impl<'a, B: Bindgen> Generator<'a, B> { fn new( - doc: &'a Document, + resolve: &'a Resolve, variant: AbiVariant, lift_lower: LiftLower, bindgen: &'a mut B, ) -> Generator<'a, B> { Generator { - doc, + resolve, variant, lift_lower, bindgen, @@ -977,7 +980,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { } fn call(&mut self, func: &Function) { - let sig = self.doc.wasm_signature(self.variant, func); + let sig = self.resolve.wasm_signature(self.variant, func); match self.lift_lower { LiftLower::LowerArgsLiftResults => { @@ -1082,7 +1085,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { let mut temp = Vec::new(); for (_, ty) in func.params.iter() { temp.truncate(0); - self.doc.push_wasm(self.variant, ty, &mut temp); + self.resolve.push_wasm(self.variant, ty, &mut temp); for _ in 0..temp.len() { self.emit(&Instruction::GetArg { nth: offset }); offset += 1; @@ -1176,7 +1179,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { } fn post_return(&mut self, func: &Function) { - let sig = self.doc.wasm_signature(self.variant, func); + let sig = self.resolve.wasm_signature(self.variant, func); // Currently post-return is only used for lists and lists are always // returned indirectly through memory due to their flat representation @@ -1218,7 +1221,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.results.reserve(inst.results_len()); self.bindgen - .emit(self.doc, inst, &mut self.operands, &mut self.results); + .emit(self.resolve, inst, &mut self.operands, &mut self.results); assert_eq!( self.results.len(), @@ -1266,11 +1269,11 @@ impl<'a, B: Bindgen> Generator<'a, B> { let realloc = self.list_realloc(); self.emit(&StringLower { realloc }); } - Type::Id(id) => match &self.doc.types[id].kind { + Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.lower(t), TypeDefKind::List(element) => { let realloc = self.list_realloc(); - if self.bindgen.is_list_canonical(self.doc, element) { + if self.bindgen.is_list_canonical(self.resolve, element) { self.emit(&ListCanonLower { element, realloc }); } else { self.push_block(); @@ -1286,7 +1289,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&RecordLower { record, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); let values = self .stack @@ -1313,7 +1316,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&FlagsLower { flags, ty: id, - name: self.doc.types[id].name.as_ref().unwrap(), + name: self.resolve.types[id].name.as_ref().unwrap(), }); } @@ -1324,14 +1327,14 @@ impl<'a, B: Bindgen> Generator<'a, B> { variant: v, ty: id, results: &results, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Enum(enum_) => { self.emit(&EnumLower { enum_, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Option(t) => { @@ -1357,11 +1360,12 @@ impl<'a, B: Bindgen> Generator<'a, B> { union, ty: id, results: &results, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Future(_) => todo!("lower future"), TypeDefKind::Stream(_) => todo!("lower stream"), + TypeDefKind::Unknown => unreachable!(), }, } } @@ -1375,7 +1379,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { let mut results = Vec::new(); let mut temp = Vec::new(); let mut casts = Vec::new(); - self.doc.push_wasm(self.variant, ty, &mut results); + self.resolve.push_wasm(self.variant, ty, &mut results); for (i, ty) in cases.into_iter().enumerate() { self.push_block(); self.emit(&VariantPayloadName); @@ -1392,7 +1396,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // pushed, and record how many. If we pushed too few // then we'll need to push some zeros after this. temp.truncate(0); - self.doc.push_wasm(self.variant, ty, &mut temp); + self.resolve.push_wasm(self.variant, ty, &mut temp); pushed += temp.len(); // For all the types pushed we may need to insert some @@ -1450,10 +1454,12 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Float32 => self.emit(&Float32FromF32), Type::Float64 => self.emit(&Float64FromF64), Type::String => self.emit(&StringLift), - Type::Id(id) => match &self.doc.types[id].kind { + Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.lift(t), TypeDefKind::List(element) => { - if self.is_char(element) || self.bindgen.is_list_canonical(self.doc, element) { + if self.is_char(element) + || self.bindgen.is_list_canonical(self.resolve, element) + { self.emit(&ListCanonLift { element, ty: id }); } else { self.push_block(); @@ -1466,33 +1472,33 @@ impl<'a, B: Bindgen> Generator<'a, B> { } TypeDefKind::Record(record) => { let mut temp = Vec::new(); - self.doc.push_wasm(self.variant, ty, &mut temp); + self.resolve.push_wasm(self.variant, ty, &mut temp); let mut args = self .stack .drain(self.stack.len() - temp.len()..) .collect::>(); for field in record.fields.iter() { temp.truncate(0); - self.doc.push_wasm(self.variant, &field.ty, &mut temp); + self.resolve.push_wasm(self.variant, &field.ty, &mut temp); self.stack.extend(args.drain(..temp.len())); self.lift(&field.ty); } self.emit(&RecordLift { record, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Tuple(tuple) => { let mut temp = Vec::new(); - self.doc.push_wasm(self.variant, ty, &mut temp); + self.resolve.push_wasm(self.variant, ty, &mut temp); let mut args = self .stack .drain(self.stack.len() - temp.len()..) .collect::>(); for ty in tuple.types.iter() { temp.truncate(0); - self.doc.push_wasm(self.variant, ty, &mut temp); + self.resolve.push_wasm(self.variant, ty, &mut temp); self.stack.extend(args.drain(..temp.len())); self.lift(ty); } @@ -1502,7 +1508,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&FlagsLift { flags, ty: id, - name: self.doc.types[id].name.as_ref().unwrap(), + name: self.resolve.types[id].name.as_ref().unwrap(), }); } @@ -1511,7 +1517,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&VariantLift { variant: v, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } @@ -1519,7 +1525,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&EnumLift { enum_, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } @@ -1538,12 +1544,13 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&UnionLift { union, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Future(_) => todo!("lift future"), TypeDefKind::Stream(_) => todo!("lift stream"), + TypeDefKind::Unknown => unreachable!(), }, } } @@ -1556,7 +1563,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { let mut params = Vec::new(); let mut temp = Vec::new(); let mut casts = Vec::new(); - self.doc.push_wasm(self.variant, ty, &mut params); + self.resolve.push_wasm(self.variant, ty, &mut params); let block_inputs = self .stack .drain(self.stack.len() + 1 - params.len()..) @@ -1567,7 +1574,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // Push only the values we need for this variant onto // the stack. temp.truncate(0); - self.doc.push_wasm(self.variant, ty, &mut temp); + self.resolve.push_wasm(self.variant, ty, &mut temp); self.stack .extend(block_inputs[..temp.len()].iter().cloned()); @@ -1606,7 +1613,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Float64 => self.lower_and_emit(ty, addr, &F64Store { offset }), Type::String => self.write_list_to_memory(ty, addr, offset), - Type::Id(id) => match &self.doc.types[id].kind { + Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.write_to_memory(t, addr, offset), TypeDefKind::List(_) => self.write_list_to_memory(ty, addr, offset), @@ -1616,7 +1623,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&RecordLower { record, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); self.write_fields_to_memory(record.fields.iter().map(|f| &f.ty), addr, offset); } @@ -1662,7 +1669,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { variant: v, ty: id, results: &[], - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } @@ -1706,12 +1713,13 @@ impl<'a, B: Bindgen> Generator<'a, B> { union, ty: id, results: &[], - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Future(_) => todo!("write future to memory"), TypeDefKind::Stream(_) => todo!("write stream to memory"), + TypeDefKind::Unknown => unreachable!(), }, } } @@ -1803,7 +1811,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Float64 => self.emit_and_lift(ty, addr, &F64Load { offset }), Type::String => self.read_list_from_memory(ty, addr, offset), - Type::Id(id) => match &self.doc.types[id].kind { + Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.read_from_memory(t, addr, offset), TypeDefKind::List(_) => self.read_list_from_memory(ty, addr, offset), @@ -1816,7 +1824,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&RecordLift { record, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Tuple(tuple) => { @@ -1860,7 +1868,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&VariantLift { variant, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } @@ -1895,12 +1903,13 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&UnionLift { union, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Future(_) => todo!("read future from memory"), TypeDefKind::Stream(_) => todo!("read stream from memory"), + TypeDefKind::Unknown => unreachable!(), }, } } @@ -1977,7 +1986,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { fn is_char(&self, ty: &Type) -> bool { match ty { Type::Char => true, - Type::Id(id) => match &self.doc.types[*id].kind { + Type::Id(id) => match &self.resolve.types[*id].kind { TypeDefKind::Type(t) => self.is_char(t), _ => false, }, @@ -1990,7 +1999,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // No need to execute any instructions if this type itself doesn't // require any form of post-return. - if !self.doc.needs_post_return(ty) { + if !self.resolve.needs_post_return(ty) { return; } @@ -2016,7 +2025,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { | Type::Float32 | Type::Float64 => {} - Type::Id(id) => match &self.doc.types[id].kind { + Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.deallocate(t, addr, offset), TypeDefKind::List(element) => { @@ -2084,6 +2093,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { TypeDefKind::Future(_) => todo!("read future from memory"), TypeDefKind::Stream(_) => todo!("read stream from memory"), + TypeDefKind::Unknown => unreachable!(), }, } } diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index b08efa04be..9c282d49c9 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -1,13 +1,16 @@ -use anyhow::Result; +use crate::{Error, UnresolvedPackage}; +use anyhow::{bail, Context, Result}; use lex::{Span, Token, Tokenizer}; use std::borrow::Cow; use std::convert::TryFrom; use std::fmt; +use std::path::{Path, PathBuf}; pub mod lex; pub use resolve::Resolver; mod resolve; +pub mod toposort; pub use lex::validate_id; @@ -25,18 +28,59 @@ impl<'a> Ast<'a> { Ok(Self { items }) } - pub fn worlds(&self) -> impl Iterator> { - self.items.iter().filter_map(|item| match item { - AstItem::World(item) => Some(item), - AstItem::Interface(_) => None, - }) - } + fn for_each_path<'b>( + &'b self, + mut f: impl FnMut(Option<&'b Id<'a>>, &'b UsePath<'a>, Option<&[UseName<'a>]>) -> Result<()>, + ) -> Result<()> { + for item in self.items.iter() { + match item { + AstItem::World(world) => { + // Visit imports here first before exports to help preserve + // round-tripping of documents because printing a world puts + // imports first but textually they can be listed with + // exports first. + let mut imports = Vec::new(); + let mut exports = Vec::new(); + for item in world.items.iter() { + match item { + // WorldItem::Use(u) => f(None, &u.from, Some(&u.names))?, + WorldItem::Import(Import { kind, .. }) => imports.push(kind), + WorldItem::Export(Export { kind, .. }) => exports.push(kind), + } + } + + let mut visit_kind = |kind: &'b ExternKind<'a>| match kind { + ExternKind::Interface(_, items) => { + for item in items { + match item { + InterfaceItem::Use(u) => f(None, &u.from, Some(&u.names))?, + _ => {} + } + } + Ok(()) + } + ExternKind::Path(path) => f(None, path, None), + ExternKind::Func(_) => Ok(()), + }; - pub fn interfaces(&self) -> impl Iterator> { - self.items.iter().filter_map(|item| match item { - AstItem::Interface(item) => Some(item), - AstItem::World(_) => None, - }) + for kind in imports { + visit_kind(kind)?; + } + for kind in exports { + visit_kind(kind)?; + } + } + AstItem::Interface(i) => { + for item in i.items.iter() { + match item { + InterfaceItem::Use(u) => f(Some(&i.name), &u.from, Some(&u.names))?, + _ => {} + } + } + } + } + } + Ok(()) } } @@ -47,7 +91,12 @@ pub enum AstItem<'a> { impl<'a> AstItem<'a> { fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { - match tokens.clone().next()? { + let mut clone = tokens.clone(); + let token = match clone.next()? { + Some((_span, Token::Default)) => clone.next()?, + other => other, + }; + match token { Some((_span, Token::Interface)) => Interface::parse(tokens, docs).map(Self::Interface), Some((_span, Token::World)) => World::parse(tokens, docs).map(Self::World), other => Err(err_expected(tokens, "`default`, `world` or `interface`", other).into()), @@ -59,14 +108,21 @@ pub struct World<'a> { docs: Docs<'a>, name: Id<'a>, items: Vec>, + default: bool, } impl<'a> World<'a> { fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + let default = tokens.eat(Token::Default)?; tokens.expect(Token::World)?; let name = parse_id(tokens)?; let items = Self::parse_items(tokens)?; - Ok(World { docs, name, items }) + Ok(World { + docs, + name, + items, + default, + }) } fn parse_items(tokens: &mut Tokenizer<'a>) -> Result>> { @@ -85,7 +141,7 @@ impl<'a> World<'a> { pub enum WorldItem<'a> { Import(Import<'a>), Export(Export<'a>), - ExportDefault(ExternKind<'a>), + // Use(Use<'a>), } impl<'a> WorldItem<'a> { @@ -93,11 +149,8 @@ impl<'a> WorldItem<'a> { match tokens.clone().next()? { Some((_span, Token::Import)) => Import::parse(tokens).map(WorldItem::Import), Some((_span, Token::Export)) => Export::parse(tokens).map(WorldItem::Export), - Some((_span, Token::Default)) => { - tokens.expect(Token::Default)?; - tokens.expect(Token::Export)?; - ExternKind::parse(tokens).map(WorldItem::ExportDefault) - } + // TODO: should parse this when it's implemented + // Some((_span, Token::Use)) => Use::parse(tokens).map(WorldItem::Use), other => Err(err_expected(tokens, "`import` or `export`", other).into()), } } @@ -135,44 +188,46 @@ impl<'a> Export<'a> { pub enum ExternKind<'a> { Interface(Span, Vec>), - Id(Id<'a>), + Path(UsePath<'a>), + Func(Func<'a>), } impl<'a> ExternKind<'a> { fn parse(tokens: &mut Tokenizer<'a>) -> Result> { match tokens.clone().next()? { - Some((_span, Token::Id | Token::StrLit | Token::ExplicitId)) => { - parse_id(tokens).map(ExternKind::Id) + Some((_span, Token::Id | Token::ExplicitId | Token::Pkg | Token::Self_)) => { + UsePath::parse(tokens).map(ExternKind::Path) } Some((_span, Token::Interface)) => { let span = tokens.expect(Token::Interface)?; let items = Interface::parse_items(tokens)?; Ok(ExternKind::Interface(span, items)) } + Some((_span, Token::Func)) => Ok(ExternKind::Func(Func::parse(tokens)?)), other => Err(err_expected(tokens, "path, value, or interface", other).into()), } } - - fn span(&self) -> Span { - match self { - ExternKind::Interface(span, _) => *span, - ExternKind::Id(id) => id.span, - } - } } pub struct Interface<'a> { docs: Docs<'a>, name: Id<'a>, items: Vec>, + default: bool, } impl<'a> Interface<'a> { fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + let default = tokens.eat(Token::Default)?; tokens.expect(Token::Interface)?; let name = parse_id(tokens)?; let items = Self::parse_items(tokens)?; - Ok(Interface { docs, name, items }) + Ok(Interface { + docs, + name, + items, + default, + }) } pub(super) fn parse_items(tokens: &mut Tokenizer<'a>) -> Result>> { @@ -192,24 +247,106 @@ impl<'a> Interface<'a> { pub enum InterfaceItem<'a> { TypeDef(TypeDef<'a>), Value(Value<'a>), + Use(Use<'a>), } -pub struct Id<'a> { - pub name: Cow<'a, str>, - pub span: Span, +pub struct Use<'a> { + pub from: UsePath<'a>, + pub names: Vec>, } -impl<'a> From<&'a str> for Id<'a> { - fn from(s: &'a str) -> Id<'a> { - Id { - name: s.into(), - span: Span { start: 0, end: 0 }, +pub enum UsePath<'a> { + Self_(Id<'a>), + Package { + doc: Id<'a>, + iface: Option>, + }, + Dependency { + dep: Id<'a>, + doc: Id<'a>, + iface: Option>, + }, +} + +pub struct UseName<'a> { + pub name: Id<'a>, + pub as_: Option>, +} + +impl<'a> Use<'a> { + fn parse(tokens: &mut Tokenizer<'a>) -> Result { + tokens.expect(Token::Use)?; + let from = UsePath::parse(tokens)?; + tokens.expect(Token::Period)?; + tokens.expect(Token::LeftBrace)?; + + let mut names = Vec::new(); + while !tokens.eat(Token::RightBrace)? { + let mut name = UseName { + name: parse_id(tokens)?, + as_: None, + }; + if tokens.eat(Token::As)? { + name.as_ = Some(parse_id(tokens)?); + } + names.push(name); + if !tokens.eat(Token::Comma)? { + tokens.expect(Token::RightBrace)?; + break; + } } + Ok(Use { from, names }) } } -impl<'a> From for Id<'a> { - fn from(s: String) -> Id<'a> { +impl<'a> UsePath<'a> { + fn parse(tokens: &mut Tokenizer<'a>) -> Result { + match tokens.clone().next()? { + Some((_span, Token::Self_)) => { + tokens.expect(Token::Self_)?; + tokens.expect(Token::Period)?; + let name = parse_id(tokens)?; + Ok(UsePath::Self_(name)) + } + Some((_span, Token::Pkg)) => { + tokens.expect(Token::Pkg)?; + tokens.expect(Token::Period)?; + let doc = parse_id(tokens)?; + let mut clone = tokens.clone(); + let iface = if clone.eat(Token::Period)? && !clone.eat(Token::LeftBrace)? { + tokens.expect(Token::Period)?; + Some(parse_id(tokens)?) + } else { + None + }; + Ok(UsePath::Package { doc, iface }) + } + Some((_span, Token::Id | Token::ExplicitId)) => { + let dep = parse_id(tokens)?; + tokens.expect(Token::Period)?; + let doc = parse_id(tokens)?; + let mut clone = tokens.clone(); + let iface = if clone.eat(Token::Period)? && !clone.eat(Token::LeftBrace)? { + tokens.expect(Token::Period)?; + Some(parse_id(tokens)?) + } else { + None + }; + Ok(UsePath::Dependency { dep, doc, iface }) + } + other => return Err(err_expected(tokens, "`self`, `pkg`, or identifier", other).into()), + } + } +} + +#[derive(Debug, Clone)] +pub struct Id<'a> { + pub name: &'a str, + pub span: Span, +} + +impl<'a> From<&'a str> for Id<'a> { + fn from(s: &'a str) -> Id<'a> { Id { name: s.into(), span: Span { start: 0, end: 0 }, @@ -333,7 +470,7 @@ enum ValueKind<'a> { Func(Func<'a>), } -struct Func<'a> { +pub struct Func<'a> { params: ParamList<'a>, results: ResultList<'a>, } @@ -352,6 +489,7 @@ impl<'a> Func<'a> { }) } + tokens.expect(Token::Func)?; let params = parse_params(tokens, true)?; let results = if tokens.eat(Token::RArrow)? { // If we eat a '(', parse the remainder of the named @@ -372,7 +510,6 @@ impl<'a> Func<'a> { impl<'a> ValueKind<'a> { fn parse(tokens: &mut Tokenizer<'a>) -> Result> { - tokens.eat(Token::Func)?; Func::parse(tokens).map(ValueKind::Func) } } @@ -399,6 +536,7 @@ impl<'a> InterfaceItem<'a> { Some((_span, Token::Id)) | Some((_span, Token::ExplicitId)) => { Value::parse(tokens, docs).map(InterfaceItem::Value) } + Some((_span, Token::Use)) => Use::parse(tokens).map(InterfaceItem::Use), other => Err(err_expected(tokens, "`type` or `func`", other).into()), } } @@ -522,16 +660,12 @@ impl<'a> Value<'a> { fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result> { match tokens.next()? { - Some((span, Token::StrLit)) => Ok(Id { - name: tokens.parse_str(span)?.into(), - span, - }), Some((span, Token::Id)) => Ok(Id { - name: tokens.parse_id(span)?.into(), + name: tokens.parse_id(span)?, span, }), Some((span, Token::ExplicitId)) => Ok(Id { - name: tokens.parse_explicit_id(span)?.into(), + name: tokens.parse_explicit_id(span)?, span, }), other => Err(err_expected(tokens, "an identifier or string", other).into()), @@ -729,79 +863,218 @@ fn err_expected( } } -#[derive(Debug)] -struct Error { - span: Span, - msg: String, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.msg.fmt(f) - } -} - -impl std::error::Error for Error {} - -pub fn rewrite_error(err: &mut anyhow::Error, file: &str, contents: &str) { - let parse = match err.downcast_mut::() { - Some(err) => err, - None => return lex::rewrite_error(err, file, contents), - }; - let msg = highlight_err( - parse.span.start as usize, - Some(parse.span.end as usize), - file, - contents, - &parse.msg, - ); - *err = anyhow::anyhow!("{}", msg); -} - -fn highlight_err( - start: usize, - end: Option, - file: &str, - input: &str, - err: impl fmt::Display, -) -> String { - let (line, col) = linecol_in(start, input); - let snippet = input.lines().nth(line).unwrap_or(""); - let mut msg = format!( - "\ +/// A listing of source files which are used to get parsed into an +/// [`UnresolvedPackage`]. +#[derive(Clone, Default)] +pub struct SourceMap { + sources: Vec, + offset: u32, +} + +#[derive(Clone)] +struct Source { + offset: u32, + path: PathBuf, + name: String, + contents: String, +} + +impl SourceMap { + /// Creates a new empty source map. + pub fn new() -> SourceMap { + SourceMap::default() + } + + /// Reads the file `path` on the filesystem and appends its contents to this + /// [`SourceMap`]. + /// + /// This method pushes a new document into the source map. The name of the + /// document is derived from the filename of the `path` provided. + pub fn push_file(&mut self, path: &Path) -> Result<()> { + let contents = std::fs::read_to_string(path) + .with_context(|| format!("failed to read file {path:?}"))?; + let filename = match path.file_name().and_then(|s| s.to_str()) { + Some(stem) => stem, + None => bail!("no filename for {path:?}"), + }; + let name = match filename.find('.') { + Some(i) => &filename[..i], + None => filename, + }; + self.push(path, name, contents); + Ok(()) + } + + /// Appends the given contents with the given path into this source map. + /// + /// Each path added to a [`SourceMap`] will become a document in the final + /// package. The `path` provided is not read from the filesystem and is + /// instead only used during error messages. The `name` provided is the name + /// of the document within the WIT package and must be a valid WIT + /// identifier. + pub fn push(&mut self, path: &Path, name: &str, contents: impl Into) { + let mut contents = contents.into(); + if path.extension().and_then(|s| s.to_str()) == Some("md") { + log::debug!("automatically unwrapping markdown container"); + contents = unwrap_md(&contents); + } + let new_offset = self.offset + u32::try_from(contents.len()).unwrap(); + self.sources.push(Source { + offset: self.offset, + path: path.to_path_buf(), + contents, + name: name.to_string(), + }); + self.offset = new_offset; + + fn unwrap_md(contents: &str) -> String { + use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag}; + + let mut wit = String::new(); + let mut last_pos = 0; + let mut in_wit_code_block = false; + Parser::new_ext(contents, Options::empty()) + .into_offset_iter() + .for_each(|(event, range)| match (event, range) { + ( + Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed( + "wit", + )))), + _, + ) => { + in_wit_code_block = true; + } + (Event::Text(text), range) if in_wit_code_block => { + // Ensure that offsets are correct by inserting newlines to + // cover the Markdown content outside of wit code blocks. + for _ in contents[last_pos..range.start].lines() { + wit.push('\n'); + } + wit.push_str(&text); + last_pos = range.end; + } + ( + Event::End(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed("wit")))), + _, + ) => { + in_wit_code_block = false; + } + _ => {} + }); + wit + } + } + + /// Parses the files added to this source map into an [`UnresolvedPackage`]. + /// + /// All files previously added are considered documents of the package to be + /// returned. + pub fn parse(self, name: &str, url: Option<&str>) -> Result { + let mut doc = self.rewrite_error(|| { + let mut resolver = Resolver::default(); + let mut srcs = self.sources.iter().collect::>(); + srcs.sort_by_key(|src| &src.name); + for src in srcs { + let mut tokens = Tokenizer::new(&src.contents, src.offset) + .with_context(|| format!("failed to tokenize path: {}", src.path.display()))?; + let ast = Ast::parse(&mut tokens)?; + resolver.push(&src.name, ast).with_context(|| { + format!("failed to start resolving path: {}", src.path.display()) + })?; + } + resolver.resolve(name, url) + })?; + doc.source_map = self; + Ok(doc) + } + + pub(crate) fn rewrite_error(&self, f: F) -> Result + where + F: FnOnce() -> Result, + { + let err = match f() { + Ok(t) => return Ok(t), + Err(e) => e, + }; + if let Some(parse) = err.downcast_ref::() { + let msg = self.highlight_err(parse.span.start, Some(parse.span.end), parse); + bail!("{msg}") + } + + if let Some(lex) = err.downcast_ref::() { + let pos = match lex { + lex::Error::Unexpected(at, _) + | lex::Error::UnterminatedComment(at) + | lex::Error::Wanted { at, .. } + | lex::Error::InvalidCharInId(at, _) + | lex::Error::IdPartEmpty(at) + | lex::Error::InvalidEscape(at, _) => *at, + }; + let msg = self.highlight_err(pos, None, lex); + bail!("{msg}") + } + + if let Some(sort) = err.downcast_ref::() { + let span = match sort { + toposort::Error::NonexistentDep { span, .. } + | toposort::Error::Cycle { span, .. } => *span, + }; + let msg = self.highlight_err(span.start, Some(span.end), sort); + bail!("{msg}") + } + + Err(err) + } + + fn highlight_err(&self, start: u32, end: Option, err: impl fmt::Display) -> String { + let i = match self.sources.binary_search_by_key(&start, |src| src.offset) { + Ok(i) => i, + Err(i) => i - 1, + }; + let src = &self.sources[i]; + let start = usize::try_from(start - src.offset).unwrap(); + let end = end.map(|end| usize::try_from(end - src.offset).unwrap()); + let (line, col) = linecol_in(start, &src.contents); + let snippet = src.contents.lines().nth(line).unwrap_or(""); + let mut msg = format!( + "\ {err} --> {file}:{line}:{col} | {line:4} | {snippet} | {marker:>0$}", - col + 1, - file = file, - line = line + 1, - col = col + 1, - err = err, - snippet = snippet, - marker = "^", - ); - if let Some(end) = end { - if let Some(s) = input.get(start..end) { - for _ in s.chars().skip(1) { - msg.push('-'); + col + 1, + file = src.path.display(), + line = line + 1, + col = col + 1, + marker = "^", + ); + if let Some(end) = end { + if let Some(s) = src.contents.get(start..end) { + for _ in s.chars().skip(1) { + msg.push('-'); + } } } - } - return msg; - - fn linecol_in(pos: usize, text: &str) -> (usize, usize) { - let mut cur = 0; - // Use split_terminator instead of lines so that if there is a `\r`, - // it is included in the offset calculation. The `+1` values below - // account for the `\n`. - for (i, line) in text.split_terminator('\n').enumerate() { - if cur + line.len() + 1 > pos { - return (i, pos - cur); + return msg; + + fn linecol_in(pos: usize, text: &str) -> (usize, usize) { + let mut cur = 0; + // Use split_terminator instead of lines so that if there is a `\r`, + // it is included in the offset calculation. The `+1` values below + // account for the `\n`. + for (i, line) in text.split_terminator('\n').enumerate() { + if cur + line.len() + 1 > pos { + return (i, pos - cur); + } + cur += line.len() + 1; } - cur += line.len() + 1; + (text.lines().count(), 0) } - (text.lines().count(), 0) + } + + /// Returns an iterator over all filenames added to this source map. + pub fn source_files(&self) -> impl Iterator { + self.sources.iter().map(|src| src.path.as_path()) } } diff --git a/crates/wit-parser/src/ast/lex.rs b/crates/wit-parser/src/ast/lex.rs index cee13ac267..5023a854a9 100644 --- a/crates/wit-parser/src/ast/lex.rs +++ b/crates/wit-parser/src/ast/lex.rs @@ -10,6 +10,7 @@ use self::Token::*; #[derive(Clone)] pub struct Tokenizer<'a> { input: &'a str, + span_offset: u32, chars: CrlfFold<'a>, } @@ -35,6 +36,7 @@ pub enum Token { Equals, Comma, Colon, + Period, Semicolon, LeftParen, RightParen, @@ -82,38 +84,35 @@ pub enum Token { Export, World, Default, + Pkg, + Self_, Id, ExplicitId, - StrLit, } #[derive(Eq, PartialEq, Debug)] #[allow(dead_code)] pub enum Error { - InvalidCharInString(usize, char), - InvalidCharInId(usize, char), - IdPartEmpty(usize), - InvalidEscape(usize, char), - // InvalidHexEscape(usize, char), - // InvalidEscapeValue(usize, u32), - Unexpected(usize, char), - UnterminatedComment(usize), - UnterminatedString(usize), - NewlineInString(usize), + InvalidCharInId(u32, char), + IdPartEmpty(u32), + InvalidEscape(u32, char), + Unexpected(u32, char), + UnterminatedComment(u32), Wanted { - at: usize, + at: u32, expected: &'static str, found: &'static str, }, } impl<'a> Tokenizer<'a> { - pub fn new(input: &'a str) -> Result> { + pub fn new(input: &'a str, span_offset: u32) -> Result> { detect_invalid_input(input)?; let mut t = Tokenizer { input, + span_offset, chars: CrlfFold { chars: input.char_indices(), }, @@ -128,33 +127,22 @@ impl<'a> Tokenizer<'a> { } pub fn get_span(&self, span: Span) -> &'a str { - &self.input[span.start as usize..span.end as usize] + let start = usize::try_from(span.start - self.span_offset).unwrap(); + let end = usize::try_from(span.end - self.span_offset).unwrap(); + &self.input[start..end] } - pub fn parse_id(&self, span: Span) -> Result { - let ret = self.get_span(span).to_owned(); - validate_id(span.start as usize, &ret)?; + pub fn parse_id(&self, span: Span) -> Result<&'a str> { + let ret = self.get_span(span); + validate_id(span.start, &ret)?; Ok(ret) } - // This is going to be used shortly for a new feature. - #[allow(dead_code)] - pub fn parse_str(&self, span: Span) -> Result { - let mut ret = String::new(); - let s = self.get_span(span); - let mut l = Tokenizer::new(s)?; - assert!(matches!(l.chars.next(), Some((_, '"')))); - while let Some(c) = l.eat_str_char(0).unwrap() { - ret.push(c); - } - Ok(ret) - } - - pub fn parse_explicit_id(&self, span: Span) -> Result { + pub fn parse_explicit_id(&self, span: Span) -> Result<&'a str> { let token = self.get_span(span); let id_part = token.strip_prefix('%').unwrap(); - validate_id(span.start as usize, id_part)?; - Ok(id_part.to_owned()) + validate_id(span.start, id_part)?; + Ok(id_part) } pub fn next(&mut self) -> Result, Error> { @@ -167,10 +155,11 @@ impl<'a> Tokenizer<'a> { } pub fn next_raw(&mut self) -> Result, Error> { - let (start, ch) = match self.chars.next() { + let (str_start, ch) = match self.chars.next() { Some(pair) => pair, None => return Ok(None), }; + let start = self.span_offset + u32::try_from(str_start).unwrap(); let token = match ch { '\n' | '\t' | ' ' => { // Eat all contiguous whitespace tokens @@ -208,6 +197,7 @@ impl<'a> Tokenizer<'a> { '=' => Equals, ',' => Comma, ':' => Colon, + '.' => Period, ';' => Semicolon, '(' => LeftParen, ')' => RightParen, @@ -223,10 +213,6 @@ impl<'a> Tokenizer<'a> { return Err(Error::Unexpected(start, '-')); } } - '"' => { - while let Some(_ch) = self.eat_str_char(start)? {} - StrLit - } '%' => { let mut iter = self.chars.clone(); if let Some((_, ch)) = iter.next() { @@ -251,8 +237,9 @@ impl<'a> Tokenizer<'a> { } self.chars = iter.clone(); } - let end = start + ch.len_utf8() + (remaining - self.chars.chars.as_str().len()); - match &self.input[start..end] { + let str_end = + str_start + ch.len_utf8() + (remaining - self.chars.chars.as_str().len()); + match &self.input[str_start..str_end] { "use" => Use, "type" => Type, "func" => Func, @@ -290,6 +277,8 @@ impl<'a> Tokenizer<'a> { "import" => Import, "export" => Export, "default" => Default, + "pkg" => Pkg, + "self" => Self_, _ => Id, } } @@ -300,8 +289,7 @@ impl<'a> Tokenizer<'a> { None => self.input.len(), }; - let start = u32::try_from(start).unwrap(); - let end = u32::try_from(end).unwrap(); + let end = self.span_offset + u32::try_from(end).unwrap(); Ok(Some((Span { start, end }, token))) } @@ -324,36 +312,14 @@ impl<'a> Tokenizer<'a> { Ok(span) } else { Err(Error::Wanted { - at: usize::try_from(span.start).unwrap(), - expected: expected.describe(), - found: found.describe(), - }) - } - } - None => Err(Error::Wanted { - at: self.input.len(), - expected: expected.describe(), - found: "eof", - }), - } - } - - #[allow(dead_code)] // TODO - pub fn expect_raw(&mut self, expected: Token) -> Result { - match self.next_raw()? { - Some((span, found)) => { - if expected == found { - Ok(span) - } else { - Err(Error::Wanted { - at: usize::try_from(span.start).unwrap(), + at: span.start, expected: expected.describe(), found: found.describe(), }) } } None => Err(Error::Wanted { - at: self.input.len(), + at: self.span_offset + u32::try_from(self.input.len()).unwrap(), expected: expected.describe(), found: "eof", }), @@ -370,32 +336,6 @@ impl<'a> Tokenizer<'a> { _ => false, } } - - fn eat_str_char(&mut self, start: usize) -> Result, Error> { - let ch = match self.chars.next() { - Some((_, '"')) => return Ok(None), - Some((_, '\\')) => match self.chars.next() { - Some((_, '"')) => '"', - Some((_, '\'')) => '\'', - Some((_, 't')) => '\t', - Some((_, 'n')) => '\n', - Some((_, 'r')) => '\r', - Some((_, '\\')) => '\\', - Some((i, c)) => return Err(Error::InvalidEscape(i, c)), - None => return Err(Error::UnterminatedString(start)), - }, - Some((_, ch)) - if ch == '\u{09}' - || (('\u{20}'..='\u{10ffff}').contains(&ch) && ch != '\u{7f}') => - { - ch - } - Some((i, '\n')) => return Err(Error::NewlineInString(i)), - Some((i, ch)) => return Err(Error::InvalidCharInString(i, ch)), - None => return Err(Error::UnterminatedString(start)), - }; - Ok(Some(ch)) - } } impl<'a> Iterator for CrlfFold<'a> { @@ -478,7 +418,7 @@ fn is_keylike_continue(ch: char) -> bool { UnicodeXID::is_xid_continue(ch) || ch == '-' } -pub fn validate_id(start: usize, id: &str) -> Result<(), Error> { +pub fn validate_id(start: u32, id: &str) -> Result<(), Error> { // IDs must have at least one part. if id.is_empty() { return Err(Error::IdPartEmpty(start)); @@ -525,6 +465,7 @@ impl Token { Equals => "'='", Comma => "','", Colon => "':'", + Period => "'.'", Semicolon => "';'", LeftParen => "'('", RightParen => "')'", @@ -561,7 +502,6 @@ impl Token { Underscore => "keyword `_`", Id => "an identifier", ExplicitId => "an '%' identifier", - StrLit => "a string", RArrow => "`->`", Star => "`*`", As => "keyword `as`", @@ -574,6 +514,8 @@ impl Token { Export => "keyword `export`", World => "keyword `world`", Default => "keyword `default`", + Self_ => "keyword `self`", + Pkg => "keyword `pkg`", } } } @@ -588,9 +530,6 @@ impl fmt::Display for Error { Error::Wanted { expected, found, .. } => write!(f, "expected {}, found {}", expected, found), - Error::UnterminatedString(_) => write!(f, "unterminated string literal"), - Error::NewlineInString(_) => write!(f, "newline in string literal"), - Error::InvalidCharInString(_, ch) => write!(f, "invalid character in string {:?}", ch), Error::InvalidCharInId(_, ch) => write!(f, "invalid character in identifier {:?}", ch), Error::IdPartEmpty(_) => write!(f, "identifiers must have characters between '-'s"), Error::InvalidEscape(_, ch) => write!(f, "invalid escape in string {:?}", ch), @@ -598,26 +537,6 @@ impl fmt::Display for Error { } } -pub fn rewrite_error(err: &mut anyhow::Error, file: &str, contents: &str) { - let lex = match err.downcast_mut::() { - Some(err) => err, - None => return, - }; - let pos = match lex { - Error::Unexpected(at, _) - | Error::UnterminatedComment(at) - | Error::Wanted { at, .. } - | Error::UnterminatedString(at) - | Error::NewlineInString(at) - | Error::InvalidCharInString(at, _) - | Error::InvalidCharInId(at, _) - | Error::IdPartEmpty(at) - | Error::InvalidEscape(at, _) => *at, - }; - let msg = super::highlight_err(pos, None, file, contents, lex); - *err = anyhow::anyhow!("{}", msg); -} - #[test] fn test_validate_id() { validate_id(0, "apple").unwrap(); @@ -679,7 +598,7 @@ fn test_validate_id() { #[test] fn test_tokenizer() { fn collect(s: &str) -> Result> { - let mut t = Tokenizer::new(s)?; + let mut t = Tokenizer::new(s, 0)?; let mut tokens = Vec::new(); while let Some(token) = t.next()? { tokens.push(token.1); @@ -730,10 +649,6 @@ fn test_tokenizer() { ] ); - assert_eq!(collect("\"a\"").unwrap(), vec![Token::StrLit]); - assert_eq!(collect("\"a-a\"").unwrap(), vec![Token::StrLit]); - assert_eq!(collect("\"bool\"").unwrap(), vec![Token::StrLit]); - assert!(collect("\u{149}").is_err(), "strongly discouraged"); assert!(collect("\u{673}").is_err(), "strongly discouraged"); assert!(collect("\u{17a3}").is_err(), "strongly discouraged"); diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 0b9a943963..6bb5c3a696 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1,18 +1,41 @@ -use super::{Error, Func, InterfaceItem, ParamList, ResultList, Span, Value, ValueKind, WorldItem}; +use super::{Error, ParamList, ResultList, ValueKind}; +use crate::ast::toposort::toposort; use crate::*; -use anyhow::Result; +use anyhow::{anyhow, bail, Result}; use indexmap::IndexMap; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::mem; #[derive(Default)] -pub struct Resolver { - type_lookup: IndexMap, +pub struct Resolver<'a> { + asts: IndexMap<&'a str, ast::Ast<'a>>, types: Arena, anon_types: HashMap, - functions: Vec, interfaces: Arena, + documents: Arena, worlds: Arena, + document_lookup: IndexMap<&'a str, DocumentId>, + document_interfaces: Vec>, + interface_types: Vec>, + foreign_deps: IndexMap<&'a str, IndexMap<&'a str, DocumentId>>, + + unknown_type_spans: Vec, + world_spans: Vec<(Vec, Vec)>, + document_spans: Vec, + interface_spans: Vec, + foreign_dep_spans: Vec, +} + +#[derive(Copy, Clone)] +enum DocumentItem { + Interface(InterfaceId), + World(WorldId), +} + +#[derive(Copy, Clone)] +enum InterfaceItem { + Type(TypeId), + Func, } #[derive(PartialEq, Eq, Hash)] @@ -30,205 +53,645 @@ enum Key { Stream(Option, Option), } -impl Resolver { - pub(crate) fn resolve(&mut self, ast: &ast::Ast<'_>) -> Result { - let mut interface_map = IndexMap::new(); +impl<'a> Resolver<'a> { + pub(crate) fn push(&mut self, name: &'a str, ast: ast::Ast<'a>) -> Result<()> { + // Note that this specifically uses `map_err` instead of `with_context` + // since the error returned from `validate_id` is an `Error` which has a + // filename and a line number, but there's no filename or line number + // associated with this error so we just want the string message. + crate::validate_id(name) + .map_err(|e| anyhow!("name of document isn't a valid WIT identifier `{name}`: {e}"))?; + + let prev = self.asts.insert(name, ast); + if prev.is_some() { + bail!("document `{name}` is defined more than once"); + } + Ok(()) + } + + pub(crate) fn resolve(&mut self, name: &str, url: Option<&str>) -> Result { + self.populate_foreign_deps(); + + // Determine the dependencies between documents in the current package + // we're parsing to perform a topological sort and how to visit the + // documents in order. + let mut doc_deps = IndexMap::new(); + for (name, ast) in self.asts.iter() { + let mut deps = Vec::new(); + ast.for_each_path(|_, path, _names| { + let doc = match path { + ast::UsePath::Package { doc, iface: _ } => doc, + _ => return Ok(()), + }; + // If this document imports from itself using `pkg` syntax + // that's ok and skip this dependency to prevent it from + // otherwise being flagged as cyclic. + if doc.name == *name { + return Ok(()); + } + deps.push(doc.clone()); + Ok(()) + }) + .unwrap(); + + let prev = doc_deps.insert(*name, deps); + assert!(prev.is_none()); + } - for interface in ast.interfaces() { - let name = &interface.name.name; - let instance = self.resolve_interface(name, &interface.items, &interface.docs)?; + let order = toposort("document", &doc_deps)?; + log::debug!("toposort for documents is {order:?}"); + let mut asts = mem::take(&mut self.asts); + for name in order { + let ast = asts.remove(&name).unwrap(); + self.resolve_document(name, &ast)?; + } - if interface_map.insert(name.to_string(), instance).is_some() { + Ok(UnresolvedPackage { + name: name.to_string(), + url: url.map(|s| s.to_string()), + worlds: mem::take(&mut self.worlds), + types: mem::take(&mut self.types), + interfaces: mem::take(&mut self.interfaces), + documents: mem::take(&mut self.documents), + foreign_deps: self + .foreign_deps + .iter() + .map(|(name, deps)| { + ( + name.to_string(), + deps.iter() + .map(|(name, id)| (name.to_string(), *id)) + .collect(), + ) + }) + .collect(), + unknown_type_spans: mem::take(&mut self.unknown_type_spans), + world_spans: mem::take(&mut self.world_spans), + document_spans: mem::take(&mut self.document_spans), + interface_spans: mem::take(&mut self.interface_spans), + foreign_dep_spans: mem::take(&mut self.foreign_dep_spans), + source_map: SourceMap::default(), + }) + } + + /// Populate "unknown" for all types referenced from foreign packages as + /// well as inserting interfaces for referenced interfaces. + /// + /// This will populate the initial set of documents/interfaces/types with + /// everything referenced from foreign packages, or those through + /// `UsePath::Dependency`. The items created here are extracted later via + /// `resolve_path`. + fn populate_foreign_deps(&mut self) { + for (_, ast) in self.asts.iter() { + ast.for_each_path(|_, path, names| { + let (dep, doc, iface) = match path { + ast::UsePath::Dependency { dep, doc, iface } => (dep, doc, iface), + _ => return Ok(()), + }; + + let deps = self.foreign_deps.entry(dep.name).or_insert_with(|| { + self.foreign_dep_spans.push(dep.span); + IndexMap::new() + }); + let doc_span = doc.span; + let doc = *deps.entry(doc.name).or_insert_with(|| { + log::trace!("creating a document for foreign dep: {}", dep.name); + self.document_interfaces.push(IndexMap::new()); + self.document_spans.push(doc_span); + self.documents.alloc(Document { + name: doc.name.to_string(), + default_interface: None, + default_world: None, + interfaces: IndexMap::new(), + worlds: IndexMap::new(), + package: None, + }) + }); + + let iface = match iface { + Some(iface) => { + let item = self.document_interfaces[doc.index()] + .entry(iface.name) + .or_insert_with(|| { + self.interface_types.push(IndexMap::new()); + self.interface_spans.push(iface.span); + let id = self.interfaces.alloc(Interface { + name: Some(iface.name.to_string()), + types: IndexMap::new(), + docs: Docs::default(), + document: doc, + functions: IndexMap::new(), + }); + DocumentItem::Interface(id) + }); + match item { + DocumentItem::Interface(id) => *id, + _ => unreachable!(), + } + } + None => *self.documents[doc] + .default_interface + .get_or_insert_with(|| { + self.interface_types.push(IndexMap::new()); + self.interface_spans.push(doc_span); + self.interfaces.alloc(Interface { + name: None, + types: IndexMap::new(), + docs: Docs::default(), + document: doc, + functions: IndexMap::new(), + }) + }), + }; + + let names = match names { + Some(names) => names, + None => return Ok(()), + }; + let lookup = &mut self.interface_types[iface.index()]; + for name in names { + // If this name has already been defined then use that prior + // definition, otherwise create a new type with an unknown + // representation and insert it into the various maps. + if lookup.contains_key(name.name.name) { + continue; + } + let id = self.types.alloc(TypeDef { + docs: Docs::default(), + kind: TypeDefKind::Unknown, + name: Some(name.name.name.to_string()), + owner: TypeOwner::Interface(iface), + }); + self.unknown_type_spans.push(name.name.span); + lookup.insert(name.name.name, InterfaceItem::Type(id)); + self.interfaces[iface] + .types + .insert(name.name.name.to_string(), id); + } + + Ok(()) + }) + .unwrap(); + } + } + + fn resolve_document(&mut self, name: &'a str, ast: &ast::Ast<'a>) -> Result { + // Verify all top-level names in this document are unique, and save off + // the name of the default interface for drawing topological + // dependencies in a moment. + let mut names = HashMap::new(); + let mut default_interface_name = None; + let mut deps = IndexMap::new(); + for item in ast.items.iter() { + let name = match item { + ast::AstItem::Interface(i) => { + if i.default && default_interface_name.is_none() { + default_interface_name = Some(i.name.name); + } + &i.name + } + ast::AstItem::World(w) => &w.name, + }; + deps.insert(name.name, Vec::new()); + if names.insert(name.name, item).is_some() { return Err(Error { - span: interface.name.span, - msg: format!("interface `{name}` is defined more than once"), + span: name.span, + msg: format!("name `{}` previously defined in document", name.name), } .into()); } } - for w in ast.worlds() { - let mut world = World { - name: w.name.name.to_string(), - docs: self.docs(&w.docs), - imports: Default::default(), - exports: Default::default(), - default: None, + // Walk all `UsePath` entries in this AST and record dependencies + // between interfaces. These are dependencies specified via `use + // self...` or via `use pkg.this-doc...`. + ast.for_each_path(|iface, path, _names| { + // If this import isn't contained within an interface then it's in a + // world and it doesn't need to participate in our topo-sort. + let iface = match iface { + Some(name) => name, + None => return Ok(()), }; - - for item in w.items.iter() { - match item { - WorldItem::Import(import) => { - let ast::Import { name, kind } = import; - self.insert_extern( - name, - kind, - "import", - &mut world.imports, - &interface_map, - )?; - } - WorldItem::Export(export) => { - let ast::Export { name, kind } = export; - self.insert_extern( - name, - kind, - "export", - &mut world.exports, - &interface_map, - )?; + let deps = &mut deps[iface.name]; + match path { + // Self-deps are what we're mostly interested in here. + ast::UsePath::Self_(name) => deps.push(name.clone()), + + // Self-deps via the package happen when the `doc` name listed + // is the same as the name of the document being defined. + ast::UsePath::Package { doc, iface } => { + if doc.name != name { + return Ok(()); } - WorldItem::ExportDefault(iface) => { - if world.default.is_some() { - return Err(Error { - span: iface.span(), - msg: "more than one default interface was defined".to_string(), + let name = match iface { + Some(name) => name.clone(), + None => { + let name = default_interface_name.ok_or_else(|| Error { + span: doc.span, + msg: format!("no `default` interface in document to use from"), + })?; + ast::Id { + span: doc.span, + name, } - .into()); } + }; + deps.push(name); + } - let iface = self.resolve_extern(iface, &interface_map)?; - world.default = Some(iface); + // Dependencies on other packages don't participate in this + // topological ordering. + ast::UsePath::Dependency { .. } => {} + } + Ok(()) + })?; + let order = toposort("interface", &deps)?; + log::debug!("toposort for interfaces in `{name}` is {order:?}"); + + // Allocate a document ID and then start processing all of the + // interfaces as defined by our `order` to insert new interfaces into + // this. + let document_id = self.documents.alloc(Document { + name: name.to_string(), + default_interface: None, + default_world: None, + interfaces: IndexMap::new(), + worlds: IndexMap::new(), + package: None, + }); + self.document_interfaces.push(IndexMap::new()); + self.document_lookup.insert(name, document_id); + + let mut worlds = Vec::new(); + for name in order { + let interface = match names.remove(&name).unwrap() { + ast::AstItem::Interface(i) => i, + ast::AstItem::World(world) => { + worlds.push((name, world)); + continue; + } + }; + let id = self.resolve_interface( + document_id, + Some(interface.name.name), + &interface.items, + &interface.docs, + )?; + if interface.default { + let prev = &mut self.documents[document_id].default_interface; + if prev.is_some() { + return Err(Error { + span: interface.name.span, + msg: format!("cannot specify more than one `default` interface"), } + .into()); } + *prev = Some(id); } + let prev = self.documents[document_id] + .interfaces + .insert(name.to_string(), id); + assert!(prev.is_none()); + } - self.worlds.alloc(world); + // After all interfaces are defined then process all worlds as all items + // they import from should now be available. + for (name, world) in worlds { + let id = self.resolve_world(document_id, world)?; + if world.default { + let prev = &mut self.documents[document_id].default_world; + if prev.is_some() { + return Err(Error { + span: world.name.span, + msg: format!("cannot specify more than one `default` world"), + } + .into()); + } + *prev = Some(id); + } + let prev = self.documents[document_id] + .worlds + .insert(name.to_string(), id); + assert!(prev.is_none()); } - Ok(Document { - worlds: mem::take(&mut self.worlds), - interfaces: mem::take(&mut self.interfaces), - types: mem::take(&mut self.types), - }) + Ok(document_id) } - fn insert_extern( - &mut self, - id: &ast::Id<'_>, - kind: &ast::ExternKind<'_>, - direction: &str, - resolved: &mut IndexMap, - lookup: &IndexMap, - ) -> Result<()> { - let interface = self.resolve_extern(kind, lookup)?; - if resolved.insert(id.name.to_string(), interface).is_some() { - return Err(Error { - span: id.span, - msg: format!("duplicate {direction} {}", id.name), - } - .into()); + fn resolve_world(&mut self, document: DocumentId, world: &ast::World<'a>) -> Result { + let docs = self.docs(&world.docs); + let world_id = self.worlds.alloc(World { + docs, + document, + name: world.name.name.to_string(), + exports: IndexMap::new(), + imports: IndexMap::new(), + }); + self.document_interfaces[document.index()] + .insert(world.name.name, DocumentItem::World(world_id)); + + let mut imported_interfaces = HashMap::new(); + let mut exported_interfaces = HashMap::new(); + let mut import_spans = Vec::new(); + let mut export_spans = Vec::new(); + for item in world.items.iter() { + match item { + // ast::WorldItem::Use(_) => todo!(), + ast::WorldItem::Import(import) => { + let item = self.resolve_world_item(import.name.name, document, &import.kind)?; + if let WorldItem::Interface(id) = item { + if imported_interfaces.insert(id, import.name.name).is_some() { + return Err(Error { + span: import.name.span, + msg: format!( + "interface `{name}` imported more than once", + name = import.name.name + ), + } + .into()); + } + } + let imports = &mut self.worlds[world_id].imports; + let prev = imports.insert(import.name.name.to_string(), item); + if prev.is_some() { + return Err(Error { + span: import.name.span, + msg: format!( + "name `{name}` imported more than once", + name = import.name.name + ), + } + .into()); + } + import_spans.push(import.name.span); + } + ast::WorldItem::Export(export) => { + let item = self.resolve_world_item(export.name.name, document, &export.kind)?; + if let WorldItem::Interface(id) = item { + if exported_interfaces.insert(id, export.name.name).is_some() { + return Err(Error { + span: export.name.span, + msg: format!( + "interface `{name}` cannot be exported more than once", + name = export.name.name + ), + } + .into()); + } + } + let exports = &mut self.worlds[world_id].exports; + let prev = exports.insert(export.name.name.to_string(), item); + if prev.is_some() { + return Err(Error { + span: export.name.span, + msg: format!( + "name `{name}` cannot be exported more than once", + name = export.name.name + ), + } + .into()); + } + export_spans.push(export.name.span); + } + } } - Ok(()) + self.world_spans.push((import_spans, export_spans)); + + Ok(world_id) } - fn resolve_extern( + fn resolve_world_item( &mut self, - kind: &ast::ExternKind<'_>, - lookup: &IndexMap, - ) -> Result { + name: &str, + document: DocumentId, + kind: &ast::ExternKind<'a>, + ) -> Result { match kind { ast::ExternKind::Interface(_span, items) => { - self.resolve_interface("", items, &Default::default()) + let id = self.resolve_interface(document, None, items, &Default::default())?; + Ok(WorldItem::Interface(id)) + } + ast::ExternKind::Path(path) => { + let id = self.resolve_path(path)?; + Ok(WorldItem::Interface(id)) + } + ast::ExternKind::Func(func) => { + // Push a dummy type lookup which will get used during type + // resolution which is empty. + self.interface_types.push(IndexMap::default()); + let func = self.resolve_function(Docs::default(), name, func)?; + self.interface_types.pop(); + Ok(WorldItem::Function(func)) } - ast::ExternKind::Id(id) => lookup.get(&*id.name).cloned().ok_or_else(|| { - Error { - span: id.span, - msg: format!("{} not defined", id.name), - } - .into() - }), } } - pub(crate) fn resolve_interface( + fn resolve_interface( &mut self, - name: &str, - fields: &[InterfaceItem<'_>], - docs: &super::Docs<'_>, + document: DocumentId, + name: Option<&'a str>, + fields: &[ast::InterfaceItem<'a>], + docs: &super::Docs<'a>, ) -> Result { - // ... then register our own names - self.register_names(fields)?; + let docs = self.docs(docs); + let interface_id = self.interfaces.alloc(Interface { + docs, + document, + name: name.map(|s| s.to_string()), + functions: IndexMap::new(), + types: IndexMap::new(), + }); + assert_eq!(interface_id.index(), self.interface_types.len()); + self.interface_types.push(IndexMap::new()); + if let Some(name) = name { + self.document_interfaces[document.index()] + .insert(name, DocumentItem::Interface(interface_id)); + } - // With all names registered we can now fully expand and translate all - // types. + // First, populate our namespace with `use` statements for field in fields { - let t = match field { - InterfaceItem::TypeDef(t) => t, - _ => continue, - }; - let id = self.type_lookup[&*t.name.name]; - let kind = self.resolve_type_def(&t.ty)?; - self.types.get_mut(id).unwrap().kind = kind; + match field { + ast::InterfaceItem::Use(u) => { + let use_from = self.resolve_path(&u.from)?; + for name in u.names.iter() { + let lookup = &self.interface_types[use_from.index()]; + let id = match lookup.get(name.name.name) { + Some(InterfaceItem::Type(id)) => *id, + Some(InterfaceItem::Func) => { + bail!(Error { + span: name.name.span, + msg: format!("cannot import function `{}`", name.name.name), + }) + } + None => bail!(Error { + span: name.name.span, + msg: format!("name `{}` is not defined", name.name.name), + }), + }; + let name = name.as_.as_ref().unwrap_or(&name.name); + let id = self.types.alloc(TypeDef { + docs: Docs::default(), + kind: TypeDefKind::Type(Type::Id(id)), + name: Some(name.name.to_string()), + owner: TypeOwner::Interface(interface_id), + }); + self.define_interface_name(name, InterfaceItem::Type(id))?; + self.interfaces[interface_id] + .types + .insert(name.name.to_string(), id); + } + } + ast::InterfaceItem::TypeDef(_) | ast::InterfaceItem::Value(_) => {} + } } - // And finally we can resolve all type references in functions/globals - // and additionally validate that types thesmelves are not recursive - let mut valid_types = HashSet::new(); - let mut visiting = HashSet::new(); + // Next determine dependencies between types, perform a topological + // sort, and then define all types. This will define types in a + // topological fashion, forbid cycles, and weed out references to + // undefined types all in one go. + let mut type_deps = IndexMap::new(); + let mut type_defs = IndexMap::new(); for field in fields { match field { - InterfaceItem::Value(v) => self.resolve_value(v)?, - InterfaceItem::TypeDef(t) => { - self.validate_type_not_recursive( - t.name.span, - self.type_lookup[&*t.name.name], - &mut visiting, - &mut valid_types, - )?; + ast::InterfaceItem::TypeDef(t) => { + let prev = type_defs.insert(t.name.name, t); + if prev.is_some() { + return Err(Error { + span: t.name.span, + msg: format!("name `{}` is defined more than once", t.name.name), + } + .into()); + } + let mut deps = Vec::new(); + collect_deps(&t.ty, &mut deps); + type_deps.insert(t.name.name, deps); } + ast::InterfaceItem::Use(_) | ast::InterfaceItem::Value(_) => {} } } + let order = toposort("type", &type_deps)?; + for ty in order { + let def = type_defs.remove(&ty).unwrap(); + let docs = self.docs(&def.docs); + let kind = self.resolve_type_def(&def.ty)?; + let id = self.types.alloc(TypeDef { + docs, + kind, + name: Some(def.name.name.to_string()), + owner: TypeOwner::Interface(interface_id), + }); + self.define_interface_name(&def.name, InterfaceItem::Type(id))?; + self.interfaces[interface_id] + .types + .insert(def.name.name.to_string(), id); + } + + // Finally process all function definitions now that all types are + // defined. + for field in fields { + match field { + ast::InterfaceItem::Value(value) => { + let docs = self.docs(&value.docs); + match &value.kind { + ValueKind::Func(func) => { + self.define_interface_name(&value.name, InterfaceItem::Func)?; + let func = self.resolve_function(docs, value.name.name, func)?; + let prev = self.interfaces[interface_id] + .functions + .insert(value.name.name.to_string(), func); + assert!(prev.is_none()); + } + } + } + ast::InterfaceItem::Use(_) | ast::InterfaceItem::TypeDef(_) => {} + } + } + + Ok(interface_id) + } - let iface = Interface { + fn resolve_function(&mut self, docs: Docs, name: &str, func: &ast::Func) -> Result { + let params = self.resolve_params(&func.params)?; + let results = self.resolve_results(&func.results)?; + Ok(Function { + docs, name: name.to_string(), - url: None, - docs: self.docs(docs), - types: mem::take(&mut self.type_lookup) - .into_iter() - .map(|(_, ty)| ty) - .collect(), - functions: mem::take(&mut self.functions), - }; - Ok(self.interfaces.alloc(iface)) + kind: FunctionKind::Freestanding, + params, + results, + }) } - fn register_names(&mut self, fields: &[InterfaceItem<'_>]) -> Result<()> { - let mut values = HashSet::new(); - for field in fields { - match field { - InterfaceItem::TypeDef(t) => { - let docs = self.docs(&t.docs); - let id = self.types.alloc(TypeDef { - docs, - // a dummy kind is used for now which will get filled in - // later with the actual desired contents. - kind: TypeDefKind::List(Type::U8), - name: Some(t.name.name.to_string()), - interface: Some(self.interfaces.next_id()), - }); - self.define_type(&t.name.name, t.name.span, id)?; + fn resolve_path(&self, path: &ast::UsePath<'a>) -> Result { + match path { + ast::UsePath::Self_(iface) => { + match self.document_interfaces.last().unwrap().get(iface.name) { + Some(DocumentItem::Interface(id)) => Ok(*id), + Some(DocumentItem::World(_)) => bail!(Error { + span: iface.span, + msg: format!( + "name `{}` is defined as a world, not an interface", + iface.name + ), + }), + None => bail!(Error { + span: iface.span, + msg: format!("interface does not exist"), + }), } - InterfaceItem::Value(f) => { - if !values.insert(&f.name.name) { - return Err(Error { - span: f.name.span, - msg: format!("`{}` is defined more than once", f.name.name), + } + ast::UsePath::Package { doc, iface } => { + let doc_id = self.document_lookup[doc.name]; + match iface { + // If `iface` was provided then it must have been previously + // processed + Some(id) => { + let lookup = &self.document_interfaces[doc_id.index()]; + match lookup.get(id.name) { + Some(DocumentItem::Interface(id)) => Ok(*id), + Some(DocumentItem::World(_)) => Err(Error { + span: id.span, + msg: format!("cannot import from world `{}`", id.name), + } + .into()), + None => bail!(Error { + span: id.span, + msg: format!("interface does not exist"), + }), } - .into()); } + None => self.documents[doc_id].default_interface.ok_or_else(|| { + Error { + span: doc.span, + msg: format!("document does not specify a default interface"), + } + .into() + }), + } + } + ast::UsePath::Dependency { dep, doc, iface } => { + let doc = self.foreign_deps[dep.name][doc.name]; + match iface { + Some(name) => match self.document_interfaces[doc.index()][name.name] { + DocumentItem::Interface(id) => Ok(id), + DocumentItem::World(_) => unreachable!(), + }, + None => Ok(self.documents[doc].default_interface.unwrap()), } } } - - Ok(()) } - fn define_type(&mut self, name: &str, span: Span, id: TypeId) -> Result<()> { - if self.type_lookup.insert(name.to_string(), id).is_some() { + fn define_interface_name(&mut self, name: &ast::Id<'a>, item: InterfaceItem) -> Result<()> { + let prev = self + .interface_types + .last_mut() + .unwrap() + .insert(name.name, item); + if prev.is_some() { Err(Error { - span, - msg: format!("type `{}` is defined more than once", name), + span: name.span, + msg: format!("name `{}` is defined more than once", name.name), } .into()) } else { @@ -236,39 +699,41 @@ impl Resolver { } } - fn resolve_type_def(&mut self, ty: &super::Type<'_>) -> Result { + fn resolve_type_def(&mut self, ty: &ast::Type<'_>) -> Result { Ok(match ty { - super::Type::Bool => TypeDefKind::Type(Type::Bool), - super::Type::U8 => TypeDefKind::Type(Type::U8), - super::Type::U16 => TypeDefKind::Type(Type::U16), - super::Type::U32 => TypeDefKind::Type(Type::U32), - super::Type::U64 => TypeDefKind::Type(Type::U64), - super::Type::S8 => TypeDefKind::Type(Type::S8), - super::Type::S16 => TypeDefKind::Type(Type::S16), - super::Type::S32 => TypeDefKind::Type(Type::S32), - super::Type::S64 => TypeDefKind::Type(Type::S64), - super::Type::Float32 => TypeDefKind::Type(Type::Float32), - super::Type::Float64 => TypeDefKind::Type(Type::Float64), - super::Type::Char => TypeDefKind::Type(Type::Char), - super::Type::String => TypeDefKind::Type(Type::String), - super::Type::Name(name) => { - let id = match self.type_lookup.get(&*name.name) { - Some(id) => *id, - None => { - return Err(Error { - span: name.span, - msg: format!("no type named `{}`", name.name), - } - .into()) - } + ast::Type::Bool => TypeDefKind::Type(Type::Bool), + ast::Type::U8 => TypeDefKind::Type(Type::U8), + ast::Type::U16 => TypeDefKind::Type(Type::U16), + ast::Type::U32 => TypeDefKind::Type(Type::U32), + ast::Type::U64 => TypeDefKind::Type(Type::U64), + ast::Type::S8 => TypeDefKind::Type(Type::S8), + ast::Type::S16 => TypeDefKind::Type(Type::S16), + ast::Type::S32 => TypeDefKind::Type(Type::S32), + ast::Type::S64 => TypeDefKind::Type(Type::S64), + ast::Type::Float32 => TypeDefKind::Type(Type::Float32), + ast::Type::Float64 => TypeDefKind::Type(Type::Float64), + ast::Type::Char => TypeDefKind::Type(Type::Char), + ast::Type::String => TypeDefKind::Type(Type::String), + ast::Type::Name(name) => { + let interface_types = self.interface_types.last().unwrap(); + let id = match interface_types.get(name.name) { + Some(InterfaceItem::Type(id)) => *id, + Some(InterfaceItem::Func) => bail!(Error { + span: name.span, + msg: format!("cannot use function `{name}` as a type", name = name.name), + }), + None => bail!(Error { + span: name.span, + msg: format!("name `{name}` is not defined", name = name.name), + }), }; TypeDefKind::Type(Type::Id(id)) } - super::Type::List(list) => { + ast::Type::List(list) => { let ty = self.resolve_type(list)?; TypeDefKind::List(ty) } - super::Type::Record(record) => { + ast::Type::Record(record) => { let fields = record .fields .iter() @@ -282,7 +747,7 @@ impl Resolver { .collect::>>()?; TypeDefKind::Record(Record { fields }) } - super::Type::Flags(flags) => { + ast::Type::Flags(flags) => { let flags = flags .flags .iter() @@ -293,14 +758,14 @@ impl Resolver { .collect::>(); TypeDefKind::Flags(Flags { flags }) } - super::Type::Tuple(types) => { + ast::Type::Tuple(types) => { let types = types .iter() .map(|ty| self.resolve_type(ty)) .collect::>>()?; TypeDefKind::Tuple(Tuple { types }) } - super::Type::Variant(variant) => { + ast::Type::Variant(variant) => { if variant.cases.is_empty() { return Err(Error { span: variant.span, @@ -321,7 +786,7 @@ impl Resolver { .collect::>>()?; TypeDefKind::Variant(Variant { cases }) } - super::Type::Enum(e) => { + ast::Type::Enum(e) => { if e.cases.is_empty() { return Err(Error { span: e.span, @@ -341,12 +806,12 @@ impl Resolver { .collect::>>()?; TypeDefKind::Enum(Enum { cases }) } - super::Type::Option(ty) => TypeDefKind::Option(self.resolve_type(ty)?), - super::Type::Result(r) => TypeDefKind::Result(Result_ { + ast::Type::Option(ty) => TypeDefKind::Option(self.resolve_type(ty)?), + ast::Type::Result(r) => TypeDefKind::Result(Result_ { ok: self.resolve_optional_type(r.ok.as_deref())?, err: self.resolve_optional_type(r.err.as_deref())?, }), - super::Type::Union(e) => { + ast::Type::Union(e) => { if e.cases.is_empty() { return Err(Error { span: e.span, @@ -366,10 +831,8 @@ impl Resolver { .collect::>>()?; TypeDefKind::Union(Union { cases }) } - super::Type::Future(t) => { - TypeDefKind::Future(self.resolve_optional_type(t.as_deref())?) - } - super::Type::Stream(s) => TypeDefKind::Stream(Stream { + ast::Type::Future(t) => TypeDefKind::Future(self.resolve_optional_type(t.as_deref())?), + ast::Type::Stream(s) => TypeDefKind::Stream(Stream { element: self.resolve_optional_type(s.element.as_deref())?, end: self.resolve_optional_type(s.end.as_deref())?, }), @@ -382,7 +845,7 @@ impl Resolver { kind, name: None, docs: Docs::default(), - interface: None, + owner: TypeOwner::None, })) } @@ -421,6 +884,7 @@ impl Resolver { TypeDefKind::Union(u) => Key::Union(u.cases.iter().map(|c| c.ty).collect()), TypeDefKind::Future(ty) => Key::Future(*ty), TypeDefKind::Stream(s) => Key::Stream(s.element, s.end), + TypeDefKind::Unknown => unreachable!(), }; let types = &mut self.types; let id = self @@ -455,24 +919,6 @@ impl Resolver { Docs { contents: docs } } - fn resolve_value(&mut self, value: &Value<'_>) -> Result<()> { - let docs = self.docs(&value.docs); - match &value.kind { - ValueKind::Func(Func { params, results }) => { - let params = self.resolve_params(params)?; - let results = self.resolve_results(results)?; - self.functions.push(Function { - docs, - name: value.name.name.to_string(), - kind: FunctionKind::Freestanding, - params, - results, - }); - } - } - Ok(()) - } - fn resolve_params(&mut self, params: &ParamList<'_>) -> Result { params .iter() @@ -486,93 +932,70 @@ impl Resolver { ResultList::Anon(ty) => Ok(Results::Anon(self.resolve_type(ty)?)), } } +} - fn validate_type_not_recursive( - &self, - span: Span, - ty: TypeId, - visiting: &mut HashSet, - valid: &mut HashSet, - ) -> Result<()> { - if valid.contains(&ty) { - return Ok(()); - } - if !visiting.insert(ty) { - return Err(Error { - span, - msg: "type can recursively refer to itself".to_string(), +fn collect_deps<'a>(ty: &ast::Type<'a>, deps: &mut Vec>) { + match ty { + ast::Type::Bool + | ast::Type::U8 + | ast::Type::U16 + | ast::Type::U32 + | ast::Type::U64 + | ast::Type::S8 + | ast::Type::S16 + | ast::Type::S32 + | ast::Type::S64 + | ast::Type::Float32 + | ast::Type::Float64 + | ast::Type::Char + | ast::Type::String + | ast::Type::Flags(_) + | ast::Type::Enum(_) => {} + ast::Type::Name(name) => deps.push(name.clone()), + ast::Type::List(list) => collect_deps(list, deps), + ast::Type::Record(record) => { + for field in record.fields.iter() { + collect_deps(&field.ty, deps); } - .into()); } - - match &self.types[ty].kind { - TypeDefKind::List(Type::Id(id)) | TypeDefKind::Type(Type::Id(id)) => { - self.validate_type_not_recursive(span, *id, visiting, valid)? - } - TypeDefKind::Variant(v) => { - for case in v.cases.iter() { - if let Some(Type::Id(id)) = case.ty { - self.validate_type_not_recursive(span, id, visiting, valid)?; - } - } + ast::Type::Tuple(types) => { + for ty in types { + collect_deps(ty, deps); } - TypeDefKind::Record(r) => { - for case in r.fields.iter() { - if let Type::Id(id) = case.ty { - self.validate_type_not_recursive(span, id, visiting, valid)?; - } + } + ast::Type::Variant(variant) => { + for case in variant.cases.iter() { + if let Some(ty) = &case.ty { + collect_deps(ty, deps); } } - TypeDefKind::Tuple(t) => { - for ty in t.types.iter() { - if let Type::Id(id) = *ty { - self.validate_type_not_recursive(span, id, visiting, valid)?; - } - } + } + ast::Type::Option(ty) => collect_deps(ty, deps), + ast::Type::Result(r) => { + if let Some(ty) = &r.ok { + collect_deps(ty, deps); } - - TypeDefKind::Option(t) => { - if let Type::Id(id) = *t { - self.validate_type_not_recursive(span, id, visiting, valid)? - } + if let Some(ty) = &r.err { + collect_deps(ty, deps); } - TypeDefKind::Result(r) => { - if let Some(Type::Id(id)) = r.ok { - self.validate_type_not_recursive(span, id, visiting, valid)? - } - if let Some(Type::Id(id)) = r.err { - self.validate_type_not_recursive(span, id, visiting, valid)? - } + } + ast::Type::Union(e) => { + for case in e.cases.iter() { + collect_deps(&case.ty, deps); } - TypeDefKind::Future(t) => { - if let Some(Type::Id(id)) = *t { - self.validate_type_not_recursive(span, id, visiting, valid)? - } + } + ast::Type::Future(t) => { + if let Some(t) = t { + collect_deps(t, deps) } - TypeDefKind::Stream(s) => { - if let Some(Type::Id(id)) = s.element { - self.validate_type_not_recursive(span, id, visiting, valid)? - } - if let Some(Type::Id(id)) = s.end { - self.validate_type_not_recursive(span, id, visiting, valid)? - } + } + ast::Type::Stream(s) => { + if let Some(t) = &s.element { + collect_deps(t, deps); } - TypeDefKind::Union(u) => { - for c in u.cases.iter() { - if let Type::Id(id) = c.ty { - self.validate_type_not_recursive(span, id, visiting, valid)? - } - } + if let Some(t) = &s.end { + collect_deps(t, deps); } - - TypeDefKind::Flags(_) - | TypeDefKind::List(_) - | TypeDefKind::Type(_) - | TypeDefKind::Enum(_) => {} } - - valid.insert(ty); - visiting.remove(&ty); - Ok(()) } } diff --git a/crates/wit-parser/src/ast/toposort.rs b/crates/wit-parser/src/ast/toposort.rs new file mode 100644 index 0000000000..c009d24f14 --- /dev/null +++ b/crates/wit-parser/src/ast/toposort.rs @@ -0,0 +1,149 @@ +use crate::ast::{Id, Span}; +use anyhow::Result; +use indexmap::{IndexMap, IndexSet}; +use std::collections::HashSet; +use std::fmt; + +pub fn toposort<'a>( + kind: &str, + deps: &IndexMap<&'a str, Vec>>, +) -> Result, Error> { + // First make sure that all dependencies actually point to other valid items + // that are known. + for (_, names) in deps { + for name in names { + deps.get(name.name).ok_or_else(|| Error::NonexistentDep { + span: name.span, + name: name.name.to_string(), + kind: kind.to_string(), + })?; + } + } + + // Then recursively visit all dependencies building up the topological order + // as we go and guarding against cycles with a separate visitation set. + let mut order = IndexSet::new(); + let mut visiting = HashSet::new(); + for dep in deps.keys() { + visit(dep, deps, &mut order, &mut visiting, kind)?; + } + Ok(order.into_iter().collect()) +} + +fn visit<'a>( + dep: &'a str, + deps: &IndexMap<&'a str, Vec>>, + order: &mut IndexSet<&'a str>, + visiting: &mut HashSet<&'a str>, + kind: &str, +) -> Result<(), Error> { + if order.contains(dep) { + return Ok(()); + } + + for dep in deps[dep].iter() { + if !visiting.insert(dep.name) { + return Err(Error::Cycle { + span: dep.span, + name: dep.name.to_string(), + kind: kind.to_string(), + }); + } + visit(dep.name, deps, order, visiting, kind)?; + assert!(visiting.remove(&dep.name)); + } + + assert!(order.insert(dep)); + Ok(()) +} + +#[derive(Debug)] +pub enum Error { + NonexistentDep { + span: Span, + name: String, + kind: String, + }, + Cycle { + span: Span, + name: String, + kind: String, + }, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::NonexistentDep { kind, name, .. } => { + write!(f, "{kind} `{name}` does not exist") + } + Error::Cycle { kind, name, .. } => { + write!(f, "{kind} `{name}` depends on itself") + } + } + } +} + +impl std::error::Error for Error {} + +#[cfg(test)] +mod tests { + use super::*; + + fn id(name: &str) -> Id<'_> { + Id { + name, + span: Span { start: 0, end: 0 }, + } + } + + #[test] + fn smoke() { + let empty: Vec<&str> = Vec::new(); + assert_eq!(toposort("", &IndexMap::new()).unwrap(), empty); + + let mut nonexistent = IndexMap::new(); + nonexistent.insert("a", vec![id("b")]); + assert!(matches!( + toposort("", &nonexistent), + Err(Error::NonexistentDep { .. }) + )); + + let mut one = IndexMap::new(); + one.insert("a", vec![]); + assert_eq!(toposort("", &one).unwrap(), ["a"]); + + let mut two = IndexMap::new(); + two.insert("a", vec![]); + two.insert("b", vec![id("a")]); + assert_eq!(toposort("", &two).unwrap(), ["a", "b"]); + + let mut two = IndexMap::new(); + two.insert("a", vec![id("b")]); + two.insert("b", vec![]); + assert_eq!(toposort("", &two).unwrap(), ["b", "a"]); + } + + #[test] + fn cycles() { + let mut cycle = IndexMap::new(); + cycle.insert("a", vec![id("a")]); + let mut set = IndexSet::new(); + set.insert("a"); + assert!(matches!(toposort("", &cycle), Err(Error::Cycle { .. }))); + + let mut cycle = IndexMap::new(); + cycle.insert("a", vec![id("b")]); + cycle.insert("b", vec![id("c")]); + cycle.insert("c", vec![id("a")]); + assert!(matches!(toposort("", &cycle), Err(Error::Cycle { .. }))); + } + + #[test] + fn depend_twice() { + let mut two = IndexMap::new(); + two.insert("b", vec![id("a"), id("a")]); + two.insert("a", vec![]); + assert_eq!(toposort("", &two).unwrap(), ["a", "b"]); + } +} diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 3be1769e33..5e246256a2 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -1,18 +1,20 @@ -use anyhow::{bail, Context, Result}; -use ast::lex::Tokenizer; +use anyhow::{anyhow, Context, Result}; use id_arena::{Arena, Id}; use indexmap::IndexMap; -use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag}; use std::borrow::Cow; -use std::collections::HashSet; +use std::fmt; use std::path::Path; pub mod abi; mod ast; +use ast::lex::Span; +pub use ast::SourceMap; mod sizealign; pub use sizealign::*; -mod merge; -pub use merge::*; +mod resolve; +pub use resolve::{Package, PackageId, Remap, Resolve}; +mod live; +pub use live::LiveTypes; /// Checks if the given string is a legal identifier in wit. pub fn validate_id(s: &str) -> Result<()> { @@ -20,260 +22,268 @@ pub fn validate_id(s: &str) -> Result<()> { Ok(()) } -fn unwrap_md(contents: &str) -> String { - let mut wit = String::new(); - let mut last_pos = 0; - let mut in_wit_code_block = false; - Parser::new_ext(contents, Options::empty()) - .into_offset_iter() - .for_each(|(event, range)| match (event, range) { - (Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed("wit")))), _) => { - in_wit_code_block = true; - } - (Event::Text(text), range) if in_wit_code_block => { - // Ensure that offsets are correct by inserting newlines to - // cover the Markdown content outside of wit code blocks. - for _ in contents[last_pos..range.start].lines() { - wit.push('\n'); - } - wit.push_str(&text); - last_pos = range.end; - } - (Event::End(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed("wit")))), _) => { - in_wit_code_block = false; - } - _ => {} - }); - wit -} - -/// Represents the result of parsing a wit document. -#[derive(Default, Clone)] -pub struct Document { - /// The worlds contained in the document. - pub worlds: Arena, - /// The top-level interfaces contained in the document. - pub interfaces: Arena, - /// All types in all interfaces - pub types: Arena, -} - pub type WorldId = Id; pub type InterfaceId = Id; pub type TypeId = Id; +pub type DocumentId = Id; + +/// Representation of a parsed WIT package which has not resolved external +/// dependencies yet. +/// +/// This representation has performed internal resolution of the WIT package +/// itself, ensuring that all references internally are valid and the WIT was +/// syntactically valid and such. +/// +/// The fields of this structure represent a flat list of arrays unioned from +/// all documents within the WIT package. This means, for example, that all +/// types from all documents are located in `self.types`. The fields of each +/// item can help splitting back out into packages/interfaces/etc as necessary. +/// +/// Note that an `UnresolvedPackage` cannot be queried in general about +/// information such as size or alignment as that would require resolution of +/// foreign dependencies. Translations such as to-binary additionally are not +/// supported on an `UnresolvedPackage` due to the lack of knowledge about the +/// foreign types. This is intended to be an intermediate state which can be +/// inspected by embedders, if necessary, before quickly transforming to a +/// [`Resolve`] to fully work with a WIT package. +/// +/// After an [`UnresolvedPackage`] is parsed it can be fully resolved with +/// [`Resolve::push`]. During this operation a dependency map is specified which +/// will connect the `foreign_deps` field of this structure to packages +/// previously inserted within the [`Resolve`]. Embedders are responsible for +/// performing this resolution themselves. +#[derive(Clone)] +pub struct UnresolvedPackage { + /// Local name for this package. + pub name: String, -impl Document { - /// Parses the given string as a wit document. + /// Optionally-specified URL for this package. /// - /// The `path` argument is used for error reporting. - pub fn parse(path: &Path, contents: &str) -> Result { - Self::_parse(path, contents) - } + /// Must be specified for non-local dependencies. Note that this is never + /// automatically set from [`UnresolvedPackage::parse`] methods, and it must + /// be manually configured in the return value. + pub url: Option, - /// Parses the given string as a wit document. + /// All worlds from all documents within this package. /// - /// The `path` argument is used for error reporting. - pub fn parse_file(path: &Path) -> Result { - let contents = std::fs::read_to_string(path) - .with_context(|| format!("failed to read file {path:?}"))?; - Self::_parse(path, &contents) - } + /// Each world lists the document that it is from. + pub worlds: Arena, - // This will eventually grow a closure which is "go read some other file" - // for `use` statements - fn _parse(path: &Path, contents: &str) -> Result { - // If we have a ".md" file, it's a wit file wrapped in a markdown file; - // parse the markdown to extract the `wit` code blocks. - let contents: Cow<'_, str> = if path.extension().and_then(|s| s.to_str()) == Some("md") { - unwrap_md(contents).into() - } else { - contents.into() - }; + /// All interfaces from all documents within this package. + /// + /// Each interface lists the document that it is from. Interfaces are listed + /// in topological order as well so iteration through this arena will only + /// reference prior elements already visited when working with recursive + /// references. + pub interfaces: Arena, - Self::rewrite_error(path, &contents, || { - let mut lexer = Tokenizer::new(&contents)?; - let ast = ast::Ast::parse(&mut lexer)?; - ast::Resolver::default().resolve(&ast) - }) - } + /// All types from all documents within this package. + /// + /// Each type lists the interface or world that defined it, or nothing if + /// it's an anonymous type. Types are listed in this arena in topological + /// order to ensure that iteration through this arena will only reference + /// other types transitively that are already iterated over. + pub types: Arena, - /// Converts the document into a single world definition. + /// All documents found within this package. /// - /// Returns an error if there were no worlds defined in the document or - /// if there were multiple worlds defined. - pub fn default_world(&self) -> Result { - match self.worlds.len() { - 0 => bail!("no worlds were defined in the document"), - 1 => Ok(self.worlds.iter().next().unwrap().0), - _ => bail!("more than one world was defined in the document",), - } - } + /// Documents are sorted topologically in this arena with respect to imports + /// between them. + pub documents: Arena, - /// Converts the document into a single interface definition. + /// All foreign dependencies that this package depends on. /// - /// Returns an error if there were no worlds defined in the document or - /// if there were multiple worlds defined. - pub fn default_interface(&self) -> Result { - if self.worlds.len() > 0 { - bail!("a world may not be defined in an interface definition"); - } + /// These foreign dependencies must be resolved to convert this unresolved + /// package into a `Resolve`. The map here is keyed by the name of the + /// foreign package that this depends on, and the sub-map is keyed by a + /// document name followed by the identifier within `self.documents`. The + /// fields of `self.documents` describes the required types, interfaces, + /// etc, that are required from each foreign package. + pub foreign_deps: IndexMap>, + + unknown_type_spans: Vec, + world_spans: Vec<(Vec, Vec)>, + document_spans: Vec, + interface_spans: Vec, + foreign_dep_spans: Vec, + source_map: SourceMap, +} - match self.interfaces.len() { - 0 => bail!("no interfaces were defined in the document"), - 1 => Ok(self.interfaces.iter().next().unwrap().0), - _ => bail!("more than one interface was defined in the document"), - } +#[derive(Debug)] +struct Error { + span: Span, + msg: String, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.msg.fmt(f) } +} - fn rewrite_error(path: &Path, contents: &str, f: F) -> Result - where - F: FnOnce() -> Result, - { - match f() { - Ok(t) => Ok(t), - Err(mut e) => { - let file = path.display().to_string(); - ast::rewrite_error(&mut e, &file, contents); - Err(e) - } - } +impl std::error::Error for Error {} + +impl UnresolvedPackage { + /// Parses the given string as a wit document. + /// + /// The `path` argument is used for error reporting. The `contents` provided + /// will not be able to use `pkg` use paths to other documents. + pub fn parse(path: &Path, contents: &str) -> Result { + let mut map = SourceMap::default(); + let name = path + .file_name() + .and_then(|s| s.to_str()) + .ok_or_else(|| anyhow!("path doesn't end in a valid package name {path:?}"))?; + let name = match name.find('.') { + Some(i) => &name[..i], + None => name, + }; + map.push(path, name, contents); + map.parse(name, None) } - pub fn topological_types(&self) -> Vec { - let mut ret = Vec::new(); - let mut visited = HashSet::new(); - for (id, _) in self.types.iter() { - self.topo_visit(id, &mut ret, &mut visited); + /// Parse a WIT package at the provided path. + /// + /// The path provided is inferred whether it's a file or a directory. A file + /// is parsed with [`UnresolvedPackage::parse_file`] and a directory is + /// parsed with [`UnresolvedPackage::parse_dir`]. + pub fn parse_path(path: &Path) -> Result { + if path.is_dir() { + UnresolvedPackage::parse_dir(path) + } else { + UnresolvedPackage::parse_file(path) } - ret } - fn topo_visit(&self, id: TypeId, list: &mut Vec, visited: &mut HashSet) { - if !visited.insert(id) { - return; - } - match &self.types[id].kind { - TypeDefKind::Flags(_) | TypeDefKind::Enum(_) => {} - TypeDefKind::Type(t) | TypeDefKind::List(t) => self.topo_visit_ty(t, list, visited), - TypeDefKind::Record(r) => { - for f in r.fields.iter() { - self.topo_visit_ty(&f.ty, list, visited); - } - } - TypeDefKind::Tuple(t) => { - for t in t.types.iter() { - self.topo_visit_ty(t, list, visited); - } - } - TypeDefKind::Variant(v) => { - for v in v.cases.iter() { - if let Some(ty) = v.ty { - self.topo_visit_ty(&ty, list, visited); - } - } - } - TypeDefKind::Option(ty) => self.topo_visit_ty(ty, list, visited), - TypeDefKind::Result(r) => { - if let Some(ok) = r.ok { - self.topo_visit_ty(&ok, list, visited); - } - if let Some(err) = r.err { - self.topo_visit_ty(&err, list, visited); - } - } - TypeDefKind::Union(u) => { - for t in u.cases.iter() { - self.topo_visit_ty(&t.ty, list, visited); - } + /// Parses a WIT package from the file provided. + /// + /// The WIT package returned will be a single-document package and will not + /// be able to use `pkg` paths to other documents. + pub fn parse_file(path: &Path) -> Result { + let contents = std::fs::read_to_string(path) + .with_context(|| format!("failed to read file {path:?}"))?; + Self::parse(path, &contents) + } + + /// Parses a WIT package from the directory provided. + /// + /// All files with the extension `*.wit` or `*.wit.md` will be loaded from + /// `path` into the returned package. + pub fn parse_dir(path: &Path) -> Result { + let mut map = SourceMap::default(); + let name = path + .file_name() + .and_then(|s| s.to_str()) + .ok_or_else(|| anyhow!("path doesn't end in a valid package name {path:?}"))?; + let cx = || format!("failed to read directory {path:?}"); + for entry in path.read_dir().with_context(&cx)? { + let entry = entry.with_context(&cx)?; + let path = entry.path(); + let ty = entry.file_type().with_context(&cx)?; + if ty.is_dir() { + continue; } - TypeDefKind::Future(ty) => { - if let Some(ty) = ty { - self.topo_visit_ty(ty, list, visited); + if ty.is_symlink() { + if path.is_dir() { + continue; } } - TypeDefKind::Stream(s) => { - if let Some(element) = s.element { - self.topo_visit_ty(&element, list, visited); - } - if let Some(end) = s.end { - self.topo_visit_ty(&end, list, visited); - } + let filename = match path.file_name().and_then(|s| s.to_str()) { + Some(name) => name, + None => continue, + }; + if !filename.ends_with(".wit") && !filename.ends_with(".wit.md") { + continue; } + map.push_file(&path)?; } - list.push(id); + map.parse(name, None) } - fn topo_visit_ty(&self, ty: &Type, list: &mut Vec, visited: &mut HashSet) { - if let Type::Id(id) = ty { - self.topo_visit(*id, list, visited); - } + /// Returns an iterator over the list of source files that were read when + /// parsing this package. + pub fn source_files(&self) -> impl Iterator { + self.source_map.source_files() } +} - pub fn all_bits_valid(&self, ty: &Type) -> bool { - match ty { - Type::U8 - | Type::S8 - | Type::U16 - | Type::S16 - | Type::U32 - | Type::S32 - | Type::U64 - | Type::S64 - | Type::Float32 - | Type::Float64 => true, - - Type::Bool | Type::Char | Type::String => false, - - Type::Id(id) => match &self.types[*id].kind { - TypeDefKind::List(_) - | TypeDefKind::Variant(_) - | TypeDefKind::Enum(_) - | TypeDefKind::Option(_) - | TypeDefKind::Result(_) - | TypeDefKind::Future(_) - | TypeDefKind::Stream(_) - | TypeDefKind::Union(_) => false, - TypeDefKind::Type(t) => self.all_bits_valid(t), - TypeDefKind::Record(r) => r.fields.iter().all(|f| self.all_bits_valid(&f.ty)), - TypeDefKind::Tuple(t) => t.types.iter().all(|t| self.all_bits_valid(t)), - - // FIXME: this could perhaps be `true` for multiples-of-32 but - // seems better to probably leave this as unconditionally - // `false` for now, may want to reconsider later? - TypeDefKind::Flags(_) => false, - }, - } - } +/// Represents the result of parsing a wit document. +#[derive(Debug, Clone)] +pub struct Document { + pub name: String, + + /// The top-level interfaces contained in the document. + /// + /// The interfaces here are listed in topological order of the + /// dependencies between them. + pub interfaces: IndexMap, + + /// The worlds contained in the document. + pub worlds: IndexMap, + + /// The default interface of this document, if any. + /// + /// This interface will also be listed in `self.interfaces` + pub default_interface: Option, + + /// The default world of this document, if any. + /// + /// This will also be listed in `self.worlds`. + pub default_world: Option, + + /// The package that this document belongs to. + pub package: Option, } -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone)] pub struct World { + /// The WIT identifier name of this world. pub name: String, + + /// Documentation associated with this world declaration. pub docs: Docs, - pub default: Option, - pub imports: IndexMap, - pub exports: IndexMap, -} - -impl World { - /// Returns an iterator which visits all the exported interfaces, both named - /// and default. The second entry in each pair the export name of the - /// interface, or `None` if it's the default export interface. - pub fn exports(&self) -> impl Iterator)> + '_ { - self.exports - .iter() - .map(|(name, i)| (*i, Some(name.as_str()))) - .chain(self.default.iter().map(|i| (*i, None))) - } + + /// All imported items into this interface, both worlds and functions. + pub imports: IndexMap, + + /// All exported items from this interface, both worlds and functions. + pub exports: IndexMap, + + /// The document that owns this world. + pub document: DocumentId, +} + +#[derive(Debug, Clone)] +pub enum WorldItem { + /// An interface is being imported or exported from a world, indicating that + /// it's a namespace of functions. + Interface(InterfaceId), + + /// A function is being directly imported or exported from this world. + Function(Function), } -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone)] pub struct Interface { - pub name: String, - pub url: Option, + /// Optionally listed name of this interface. + /// + /// This is `None` for inline interfaces in worlds. + pub name: Option, + + /// Documentation associated with this interface. pub docs: Docs, - pub types: Vec, - pub functions: Vec, + + /// Exported types from this interface. + /// + /// Export names are listed within the types themselves. Note that the + /// export name here matches the name listed in the `TypeDef`. + pub types: IndexMap, + + /// Exported functions from this interface. + pub functions: IndexMap, + + /// The document that this interface belongs to. + pub document: DocumentId, } #[derive(Debug, Clone, PartialEq)] @@ -281,7 +291,7 @@ pub struct TypeDef { pub docs: Docs, pub kind: TypeDefKind, pub name: Option, - pub interface: Option, + pub owner: TypeOwner, } #[derive(Debug, Clone, PartialEq)] @@ -298,6 +308,24 @@ pub enum TypeDefKind { Future(Option), Stream(Stream), Type(Type), + + /// This represents a type of unknown structure imported from a foreign + /// interface. + /// + /// This variant is only used during the creation of `UnresolvedPackage` but + /// by the time a `Resolve` is created then this will not exist. + Unknown, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum TypeOwner { + /// This type was defined within a `world` block. + World(WorldId), + /// This type was defined within an `interface` block. + Interface(InterfaceId), + /// This type wasn't inherently defined anywhere, such as a `list`, which + /// doesn't need an owner. + None, } #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] @@ -512,12 +540,12 @@ impl Results { } } - pub fn throws<'a>(&self, doc: &'a Document) -> Option<(Option<&'a Type>, Option<&'a Type>)> { + pub fn throws<'a>(&self, resolve: &'a Resolve) -> Option<(Option<&'a Type>, Option<&'a Type>)> { if self.len() != 1 { return None; } match self.iter_types().next().unwrap() { - Type::Id(id) => match &doc.types[*id].kind { + Type::Id(id) => match &resolve.types[*id].kind { TypeDefKind::Result(r) => Some((r.ok.as_ref(), r.err.as_ref())), _ => None, }, diff --git a/crates/wit-parser/src/live.rs b/crates/wit-parser/src/live.rs new file mode 100644 index 0000000000..e5300dbda1 --- /dev/null +++ b/crates/wit-parser/src/live.rs @@ -0,0 +1,119 @@ +use crate::{ + DocumentId, Function, InterfaceId, Resolve, Type, TypeDefKind, TypeId, WorldId, WorldItem, +}; +use indexmap::IndexSet; + +#[derive(Default)] +pub struct LiveTypes { + set: IndexSet, +} + +impl LiveTypes { + pub fn iter(&self) -> impl Iterator + '_ { + // Note the reverse iteration order to ensure that everything is visited + // in a topological order. + self.set.iter().rev().copied() + } + + pub fn add_document(&mut self, resolve: &Resolve, doc: DocumentId) { + let doc = &resolve.documents[doc]; + for (_, id) in doc.interfaces.iter() { + self.add_interface(resolve, *id); + } + for (_, id) in doc.worlds.iter() { + self.add_world(resolve, *id); + } + } + + pub fn add_interface(&mut self, resolve: &Resolve, iface: InterfaceId) { + let iface = &resolve.interfaces[iface]; + for (_, id) in iface.types.iter() { + self.add_type_id(resolve, *id); + } + for (_, func) in iface.functions.iter() { + self.add_func(resolve, func); + } + } + + pub fn add_world(&mut self, resolve: &Resolve, world: WorldId) { + let world = &resolve.worlds[world]; + for (_, item) in world.imports.iter().chain(world.exports.iter()) { + self.add_world_item(resolve, item); + } + } + + pub fn add_world_item(&mut self, resolve: &Resolve, item: &WorldItem) { + match item { + WorldItem::Interface(id) => self.add_interface(resolve, *id), + WorldItem::Function(f) => self.add_func(resolve, f), + } + } + + pub fn add_func(&mut self, resolve: &Resolve, func: &Function) { + for (_, ty) in func.params.iter() { + self.add_type(resolve, ty); + } + for ty in func.results.iter_types() { + self.add_type(resolve, ty); + } + } + + pub fn add_type_id(&mut self, resolve: &Resolve, ty: TypeId) { + if !self.set.insert(ty) { + return; + } + match &resolve.types[ty].kind { + TypeDefKind::Type(t) + | TypeDefKind::List(t) + | TypeDefKind::Option(t) + | TypeDefKind::Future(Some(t)) => self.add_type(resolve, t), + TypeDefKind::Record(r) => { + for field in r.fields.iter() { + self.add_type(resolve, &field.ty); + } + } + TypeDefKind::Tuple(r) => { + for ty in r.types.iter() { + self.add_type(resolve, ty); + } + } + TypeDefKind::Variant(v) => { + for case in v.cases.iter() { + if let Some(ty) = &case.ty { + self.add_type(resolve, ty); + } + } + } + TypeDefKind::Union(u) => { + for case in u.cases.iter() { + self.add_type(resolve, &case.ty); + } + } + TypeDefKind::Result(r) => { + if let Some(ty) = &r.ok { + self.add_type(resolve, ty); + } + if let Some(ty) = &r.err { + self.add_type(resolve, ty); + } + } + TypeDefKind::Stream(s) => { + if let Some(ty) = &s.element { + self.add_type(resolve, ty); + } + if let Some(ty) = &s.end { + self.add_type(resolve, ty); + } + } + TypeDefKind::Flags(_) | TypeDefKind::Enum(_) | TypeDefKind::Future(None) => {} + TypeDefKind::Unknown => unreachable!(), + } + } + + pub fn add_type(&mut self, resolve: &Resolve, ty: &Type) { + match ty { + Type::Id(id) => self.add_type_id(resolve, *id), + _ => {} + } + } +} diff --git a/crates/wit-parser/src/merge.rs b/crates/wit-parser/src/merge.rs deleted file mode 100644 index 01183664ab..0000000000 --- a/crates/wit-parser/src/merge.rs +++ /dev/null @@ -1,214 +0,0 @@ -use super::*; - -impl Document { - /// Merges a different wit document into this one. - /// - /// This method is an infallible operation that will append all of the - /// contents of `other` into `self`. All types, interfaces, and worlds are - /// moved over en-masse. The return value is a structure which can be used to - /// remap old within `Document` to new ids. - // - // Note that this should eventually not error on duplicate interfaces but - // rather attempt to perform a deep merge on duplicate interfaces. - pub fn merge(&mut self, other: Document) -> Merge { - let mut merge = Merge::default(); - merge.merge(self, other); - merge - } - - /// Merges the world `from` into the world `into`. - /// - /// This will attempt to merge one world into another, unioning all of its - /// imports and exports together. This is an operation performed by - /// `wit-component`, for example where two different worlds from two - /// different libraries were linked into the same core wasm file and are - /// producing a singular world that will be the final component's - /// interface. - /// - /// This operation can fail if the imports/exports overlap. - // - // TODO: overlap shouldn't be a hard error here, there should be some form - // of comparing names/urls/deep merging or such to get this working. - pub fn merge_worlds(&mut self, from: WorldId, into: WorldId) -> Result<()> { - let mut new_imports = Vec::new(); - let mut new_exports = Vec::new(); - - let from_world = &self.worlds[from]; - let into_world = &self.worlds[into]; - for (name, import) in from_world.imports.iter() { - match into_world.imports.get(name) { - Some(_) => bail!("duplicate import found for interface `{name}`"), - None => new_imports.push((name.clone(), *import)), - } - } - for (name, export) in from_world.exports.iter() { - match into_world.exports.get(name) { - Some(_) => bail!("duplicate export found for interface `{name}`"), - None => new_exports.push((name.clone(), *export)), - } - } - let from_world_default = from_world.default; - - let into = &mut self.worlds[into]; - for (name, import) in new_imports { - let prev = into.imports.insert(name, import); - assert!(prev.is_none()); - } - for (name, import) in new_exports { - let prev = into.exports.insert(name, import); - assert!(prev.is_none()); - } - if let Some(i) = from_world_default { - if into.default.is_some() { - bail!("duplicate default export found") - } - into.default = Some(i); - } - - Ok(()) - } -} - -#[derive(Default)] -#[allow(missing_docs)] -pub struct Merge { - pub type_map: Vec>, - pub iface_map: Vec, - pub world_map: Vec, -} - -impl Merge { - fn merge(&mut self, into: &mut Document, other: Document) { - let mut topo_map = vec![0; other.types.len()]; - for (i, id) in other.topological_types().into_iter().enumerate() { - topo_map[id.index()] = i; - } - let Document { - worlds, - interfaces, - types, - } = other; - let mut types = types.into_iter().collect::>(); - types.sort_by_key(|(id, _)| topo_map[id.index()]); - self.type_map.extend((0..types.len()).map(|_| None)); - for (id, mut ty) in types { - self.update_typedef(&mut ty); - let new_id = into.types.alloc(ty); - self.type_map[id.index()] = Some(new_id); - } - - for (id, mut iface) in interfaces { - self.update_interface(&mut iface); - let new_id = into.interfaces.alloc(iface); - assert_eq!(self.iface_map.len(), id.index()); - self.iface_map.push(new_id); - } - - for (id, mut world) in worlds { - self.update_world(&mut world); - let new_id = into.worlds.alloc(world); - assert_eq!(self.world_map.len(), id.index()); - self.world_map.push(new_id); - } - - // Update the `interface` field of `TypeDef` now that all interfaces - // have been processed. - for ty in self.type_map.iter() { - let ty = ty.unwrap(); - if let Some(id) = &mut into.types[ty].interface { - *id = self.iface_map[id.index()]; - } - } - } - - fn update_typedef(&self, typedef: &mut TypeDef) { - self.update_typedef_kind(&mut typedef.kind); - // Note that the interface isn't updated here, it's updated as a - // final pass. - } - - fn update_typedef_kind(&self, typedef: &mut TypeDefKind) { - use TypeDefKind::*; - match typedef { - Record(r) => { - for field in r.fields.iter_mut() { - self.update_ty(&mut field.ty); - } - } - Tuple(t) => { - for ty in t.types.iter_mut() { - self.update_ty(ty); - } - } - Variant(v) => { - for case in v.cases.iter_mut() { - if let Some(t) = &mut case.ty { - self.update_ty(t); - } - } - } - Option(t) => self.update_ty(t), - Result(r) => { - if let Some(ty) = &mut r.ok { - self.update_ty(ty); - } - if let Some(ty) = &mut r.err { - self.update_ty(ty); - } - } - Union(u) => { - for case in u.cases.iter_mut() { - self.update_ty(&mut case.ty); - } - } - List(t) => self.update_ty(t), - Future(Some(t)) => self.update_ty(t), - Stream(t) => { - if let Some(ty) = &mut t.element { - self.update_ty(ty); - } - if let Some(ty) = &mut t.end { - self.update_ty(ty); - } - } - Type(t) => self.update_ty(t), - - // nothing to do for these as they're just names or empty - Flags(_) | Enum(_) | Future(None) => {} - } - } - - fn update_ty(&self, ty: &mut Type) { - if let Type::Id(id) = ty { - *id = self.type_map[id.index()].expect("should visit in topo-order"); - } - } - - fn update_interface(&self, iface: &mut Interface) { - for ty in iface.types.iter_mut() { - *ty = self.type_map[ty.index()].expect("types should all be visited"); - } - for func in iface.functions.iter_mut() { - for (_, ty) in func.params.iter_mut() { - self.update_ty(ty); - } - match &mut func.results { - Results::Named(named) => { - for (_, ty) in named.iter_mut() { - self.update_ty(ty); - } - } - Results::Anon(ty) => self.update_ty(ty), - } - } - } - - fn update_world(&self, world: &mut World) { - if let Some(i) = &mut world.default { - *i = self.iface_map[i.index()]; - } - for (_, i) in world.imports.iter_mut().chain(&mut world.exports) { - *i = self.iface_map[i.index()]; - } - } -} diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs new file mode 100644 index 0000000000..41d72f88a7 --- /dev/null +++ b/crates/wit-parser/src/resolve.rs @@ -0,0 +1,917 @@ +use crate::ast::lex::Span; +use crate::{ + Document, DocumentId, Error, Function, Interface, InterfaceId, Results, Type, TypeDef, + TypeDefKind, TypeId, TypeOwner, UnresolvedPackage, World, WorldId, WorldItem, +}; +use anyhow::{bail, Context, Result}; +use id_arena::{Arena, Id}; +use indexmap::{IndexMap, IndexSet}; +use std::collections::{HashMap, HashSet}; +use std::fmt::Write; +use std::mem; +use std::path::{Path, PathBuf}; +use url::Url; + +/// Representation of a fully resolved set of WIT packages. +/// +/// This structure contains a graph of WIT packages and all of their contents +/// merged together into the contained arenas. All items are sorted +/// topologically and everything here is fully resolved, so with a `Resolve` no +/// name lookups are necessary and instead everything is index-based. +/// +/// Working with a WIT package requires inserting it into a `Resolve` to ensure +/// that all of its dependencies are satisfied. This will give the full picture +/// of that package's types and such. +/// +/// Each item in a `Resolve` has a parent link to trace it back to the original +/// package as necessary. +#[derive(Default, Clone)] +pub struct Resolve { + pub worlds: Arena, + pub interfaces: Arena, + pub types: Arena, + pub documents: Arena, + pub packages: Arena, +} + +#[derive(Clone)] +pub struct Package { + /// Locally-known name of this package. + pub name: String, + + /// Optionally-specified URL of this package, must be specified for remote + /// dependencies. + pub url: Option, + + /// Documents contained within this package, organized by name. + pub documents: IndexMap, +} + +pub type PackageId = Id; + +impl Resolve { + /// Creates a new [`Resolve`] with no packages/items inside of it. + pub fn new() -> Resolve { + Resolve::default() + } + + /// Parses the filesystem directory at `path` as a WIT package and returns + /// the fully resolved [`PackageId`] as a result. + /// + /// This method is intended for testing and convenience for now and isn't + /// the only way to push packages into this [`Resolve`]. This will + /// interpret `path` as a directory where all `*.wit` files in that + /// directory are members of the package. + /// + /// Dependencies referenced by the WIT package at `path` will be loaded from + /// a `deps/$name` directory under `path` where `$name` is the name of the + /// dependency loaded. If `deps/$name` does not exist then an error will be + /// returned indicating that the dependency is not defined. All dependencies + /// are listed in a flat namespace under `$path/deps` so they can refer to + /// each other. + /// + /// This function returns the [`PackageId`] of the root parsed package at + /// `path`, along with a list of all paths that were consumed during parsing + /// for the root package and all dependency packages. + pub fn push_dir(&mut self, path: &Path) -> Result<(PackageId, Vec)> { + // Maintain a `to_parse` stack of packages that have yet to be parsed + // along with an `enqueued` set of all the prior parsed packages and + // packages enqueued to be parsed. These are then used to fill the + // `packages` map with parsed, but unresolved, packages. The `pkg_deps` + // map then tracks dependencies between packages. + let mut to_parse = Vec::new(); + let mut enqueued = HashSet::new(); + let mut packages = IndexMap::new(); + let mut pkg_deps = IndexMap::new(); + to_parse.push((path.to_path_buf(), None)); + enqueued.insert(path.to_path_buf()); + while let Some((pkg_root, url)) = to_parse.pop() { + let mut pkg = UnresolvedPackage::parse_dir(&pkg_root) + .with_context(|| format!("failed to parse package: {}", path.display()))?; + pkg.url = url; + + let mut deps = Vec::new(); + pkg.source_map.rewrite_error(|| { + for (i, (dep, _)) in pkg.foreign_deps.iter().enumerate() { + let path = path.join("deps").join(dep); + let span = pkg.foreign_dep_spans[i]; + // If this is the first request to parse `path` then push it + // onto our stack, otherwise it's already there so skip it. + if enqueued.insert(path.clone()) { + if !path.is_dir() { + bail!(Error { + span, + msg: format!( + "dependency on `{dep}` doesn't exist at: {}", + path.display() + ), + }) + } + let url = Some(format!("path:/{dep}")); + to_parse.push((path.clone(), url)); + } + deps.push((path, span)); + } + Ok(()) + })?; + + let prev = packages.insert(pkg_root.clone(), pkg); + assert!(prev.is_none()); + pkg_deps.insert(pkg_root, deps); + } + + // Perform a simple topological sort which will bail out on cycles + // and otherwise determine the order that packages must be added to + // this `Resolve`. + let mut order = IndexSet::new(); + let mut visiting = HashSet::new(); + for (dep, _) in pkg_deps.iter() { + visit(dep, &pkg_deps, &packages, &mut order, &mut visiting)?; + } + + // Using the topological ordering insert each package incrementally. + // Additionally note that the last item visited here is the root + // package, which is the one returned here. + let mut package_ids = IndexMap::new(); + let mut last = None; + let mut files = Vec::new(); + for path in order { + let pkg = packages.remove(path).unwrap(); + let mut deps = HashMap::new(); + for ((dep, _), (path, _span)) in pkg.foreign_deps.iter().zip(&pkg_deps[path]) { + deps.insert(dep.clone(), package_ids[&**path]); + } + files.extend(pkg.source_files().map(|p| p.to_path_buf())); + let pkgid = self.push(pkg, &deps)?; + package_ids.insert(path, pkgid); + last = Some(pkgid); + } + + return Ok((last.unwrap(), files)); + + fn visit<'a>( + path: &'a Path, + deps: &'a IndexMap>, + pkgs: &IndexMap, + order: &mut IndexSet<&'a Path>, + visiting: &mut HashSet<&'a Path>, + ) -> Result<()> { + if order.contains(path) { + return Ok(()); + } + pkgs[path].source_map.rewrite_error(|| { + for (dep, span) in deps[path].iter() { + if !visiting.insert(dep) { + bail!(Error { + span: *span, + msg: format!("package depends on itself"), + }); + } + visit(dep, deps, pkgs, order, visiting)?; + assert!(visiting.remove(&**dep)); + } + assert!(order.insert(path)); + Ok(()) + }) + } + } + + /// Appends a new [`UnresolvedPackage`] to this [`Resolve`], creating a + /// fully resolved package with no dangling references. + /// + /// The `deps` argument indicates that the named dependencies in + /// `unresolved` to packages are resolved by the mapping specified. + /// + /// Any dependency resolution error or otherwise world-elaboration error + /// will be returned here. If successful a package identifier is returned. + pub fn push( + &mut self, + mut unresolved: UnresolvedPackage, + deps: &HashMap, + ) -> Result { + let source_map = mem::take(&mut unresolved.source_map); + source_map.rewrite_error(|| Remap::default().append(self, unresolved, deps)) + } + + pub fn all_bits_valid(&self, ty: &Type) -> bool { + match ty { + Type::U8 + | Type::S8 + | Type::U16 + | Type::S16 + | Type::U32 + | Type::S32 + | Type::U64 + | Type::S64 + | Type::Float32 + | Type::Float64 => true, + + Type::Bool | Type::Char | Type::String => false, + + Type::Id(id) => match &self.types[*id].kind { + TypeDefKind::List(_) + | TypeDefKind::Variant(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Option(_) + | TypeDefKind::Result(_) + | TypeDefKind::Future(_) + | TypeDefKind::Stream(_) + | TypeDefKind::Union(_) => false, + TypeDefKind::Type(t) => self.all_bits_valid(t), + TypeDefKind::Record(r) => r.fields.iter().all(|f| self.all_bits_valid(&f.ty)), + TypeDefKind::Tuple(t) => t.types.iter().all(|t| self.all_bits_valid(t)), + + // FIXME: this could perhaps be `true` for multiples-of-32 but + // seems better to probably leave this as unconditionally + // `false` for now, may want to reconsider later? + TypeDefKind::Flags(_) => false, + + TypeDefKind::Unknown => unreachable!(), + }, + } + } + + /// Merges all the contents of a different `Resolve` into this one. The + /// `Remap` structure returned provides a mapping from all old indices to + /// new indices + pub fn merge(&mut self, resolve: Resolve) -> Remap { + let mut remap = Remap::default(); + let Resolve { + types, + worlds, + interfaces, + documents, + packages, + } = resolve; + + for (id, mut ty) in types { + remap.update_typedef(&mut ty); + let new_id = self.types.alloc(ty); + assert_eq!(remap.types.len(), id.index()); + remap.types.push(new_id); + } + + for (id, mut iface) in interfaces { + remap.update_interface(&mut iface); + let new_id = self.interfaces.alloc(iface); + assert_eq!(remap.interfaces.len(), id.index()); + remap.interfaces.push(new_id); + } + + for (id, mut world) in worlds { + for (_, item) in world.imports.iter_mut().chain(&mut world.exports) { + match item { + WorldItem::Function(f) => remap.update_function(f), + WorldItem::Interface(i) => *i = remap.interfaces[i.index()], + } + } + let new_id = self.worlds.alloc(world); + assert_eq!(remap.worlds.len(), id.index()); + remap.worlds.push(new_id); + } + + for (id, mut doc) in documents { + remap.update_document(&mut doc); + let new_id = self.documents.alloc(doc); + assert_eq!(remap.documents.len(), id.index()); + remap.documents.push(new_id); + } + + for (id, mut pkg) in packages { + for (_, doc) in pkg.documents.iter_mut() { + *doc = remap.documents[doc.index()]; + } + let new_id = self.packages.alloc(pkg); + assert_eq!(remap.packages.len(), id.index()); + remap.packages.push(new_id); + } + + // Fixup all "parent" links now + for id in remap.documents.iter().copied() { + if let Some(pkg) = &mut self.documents[id].package { + *pkg = remap.packages[pkg.index()]; + } + } + for id in remap.worlds.iter().copied() { + let doc = &mut self.worlds[id].document; + *doc = remap.documents[doc.index()]; + } + for id in remap.interfaces.iter().copied() { + let doc = &mut self.interfaces[id].document; + *doc = remap.documents[doc.index()]; + } + for id in remap.types.iter().copied() { + match &mut self.types[id].owner { + TypeOwner::Interface(id) => *id = remap.interfaces[id.index()], + TypeOwner::World(id) => *id = remap.worlds[id.index()], + TypeOwner::None => {} + } + } + + remap + } + + /// Merges the world `from` into the world `into`. + /// + /// This will attempt to merge one world into another, unioning all of its + /// imports and exports together. This is an operation performed by + /// `wit-component`, for example where two different worlds from two + /// different libraries were linked into the same core wasm file and are + /// producing a singular world that will be the final component's + /// interface. + /// + /// This operation can fail if the imports/exports overlap. + // + // TODO: overlap shouldn't be a hard error here, there should be some form + // of comparing names/urls/deep merging or such to get this working. + pub fn merge_worlds(&mut self, from: WorldId, into: WorldId) -> Result<()> { + let mut new_imports = Vec::new(); + let mut new_exports = Vec::new(); + + let from_world = &self.worlds[from]; + let into_world = &self.worlds[into]; + for (name, import) in from_world.imports.iter() { + match into_world.imports.get(name) { + Some(_) => bail!("duplicate import found for interface `{name}`"), + None => new_imports.push((name.clone(), import.clone())), + } + } + for (name, export) in from_world.exports.iter() { + match into_world.exports.get(name) { + Some(_) => bail!("duplicate export found for interface `{name}`"), + None => new_exports.push((name.clone(), export.clone())), + } + } + + let into = &mut self.worlds[into]; + for (name, import) in new_imports { + let prev = into.imports.insert(name, import); + assert!(prev.is_none()); + } + for (name, import) in new_exports { + let prev = into.exports.insert(name, import); + assert!(prev.is_none()); + } + + Ok(()) + } + + /// Returns the URL of the specified `interface`, if available. + /// + /// This currently creates a URL based on the URL of the package that + /// `interface` resides in. If the package owner of `interface` does not + /// specify a URL then `None` will be returned. + /// + /// If the `interface` specified does not have a name then `None` will be + /// returned as well. + pub fn url_of(&self, interface: InterfaceId) -> Option { + let interface = &self.interfaces[interface]; + let doc = &self.documents[interface.document]; + let package = &self.packages[doc.package.unwrap()]; + let mut base = Url::parse(package.url.as_ref()?).unwrap(); + base.path_segments_mut() + .unwrap() + .push(&doc.name) + .push(interface.name.as_ref()?); + Some(base.to_string()) + } +} + +/// Structure returned by [`Resolve::merge`] which contains mappings from +/// old-ids to new-ids after the merge. +#[derive(Default)] +pub struct Remap { + pub types: Vec, + pub interfaces: Vec, + pub worlds: Vec, + pub documents: Vec, + pub packages: Vec, +} + +impl Remap { + fn append( + &mut self, + resolve: &mut Resolve, + unresolved: UnresolvedPackage, + deps: &HashMap, + ) -> Result { + self.process_foreign_deps(resolve, &unresolved, deps)?; + + let foreign_types = self.types.len(); + let foreign_interfaces = self.interfaces.len(); + let foreign_documents = self.documents.len(); + let foreign_worlds = self.worlds.len(); + + // Copy over all types first, updating any intra-type references. Note + // that types are sorted topologically which means this iteration + // order should be sufficient. Also note though that the interface + // owner of a type isn't updated here due to interfaces not being known + // yet. + for (id, mut ty) in unresolved.types.into_iter().skip(foreign_types) { + self.update_typedef(&mut ty); + let new_id = resolve.types.alloc(ty); + assert_eq!(self.types.len(), id.index()); + self.types.push(new_id); + } + + // Next transfer all interfaces into `Resolve`, updating type ids + // referenced along the way. + for (id, mut iface) in unresolved.interfaces.into_iter().skip(foreign_interfaces) { + self.update_interface(&mut iface); + let new_id = resolve.interfaces.alloc(iface); + assert_eq!(self.interfaces.len(), id.index()); + self.interfaces.push(new_id); + } + + // Now that interfaces are identified go back through the types and + // update their interface owners. + for id in self.types.iter().skip(foreign_types) { + match &mut resolve.types[*id].owner { + TypeOwner::Interface(id) => *id = self.interfaces[id.index()], + TypeOwner::World(_) => unimplemented!(), + TypeOwner::None => {} + } + } + + // Perform a weighty step of full resolution of worlds. This will fully + // expand imports/exports for a world and create the topological + // ordering necessary for this. + // + // This is done after types/interfaces are fully settled so the + // transitive relation between interfaces, through types, is understood + // here. + assert_eq!(unresolved.worlds.len(), unresolved.world_spans.len()); + for ((id, mut world), (import_spans, export_spans)) in unresolved + .worlds + .into_iter() + .skip(foreign_worlds) + .zip(unresolved.world_spans) + { + self.update_world(&mut world, resolve, &import_spans, &export_spans)?; + let new_id = resolve.worlds.alloc(world); + assert_eq!(self.worlds.len(), id.index()); + self.worlds.push(new_id); + } + + // And the final major step is transferring documents to `Resolve` + // which is just updating a few identifiers here and there. + for (id, mut doc) in unresolved.documents.into_iter().skip(foreign_documents) { + self.update_document(&mut doc); + let new_id = resolve.documents.alloc(doc); + assert_eq!(self.documents.len(), id.index()); + self.documents.push(new_id); + } + + // Fixup "parent" ids now that everything has been identifier + for id in self.interfaces.iter().skip(foreign_interfaces) { + let doc = &mut resolve.interfaces[*id].document; + *doc = self.documents[doc.index()]; + } + for id in self.worlds.iter().skip(foreign_worlds) { + let doc = &mut resolve.worlds[*id].document; + *doc = self.documents[doc.index()]; + } + let mut documents = IndexMap::new(); + for id in self.documents.iter().skip(foreign_documents) { + let prev = documents.insert(resolve.documents[*id].name.clone(), *id); + assert!(prev.is_none()); + } + let pkgid = resolve.packages.alloc(Package { + name: unresolved.name, + url: unresolved.url, + documents, + }); + for (_, id) in resolve.packages[pkgid].documents.iter() { + resolve.documents[*id].package = Some(pkgid); + } + Ok(pkgid) + } + + fn process_foreign_deps( + &mut self, + resolve: &mut Resolve, + unresolved: &UnresolvedPackage, + deps: &HashMap, + ) -> Result<()> { + // First, connect all references to foreign documents to actual + // documents within `resolve`, building up the initial entries of + // the `self.documents` mapping. + let mut document_to_package = HashMap::new(); + for (i, (pkg, docs)) in unresolved.foreign_deps.iter().enumerate() { + for (doc, unresolved_doc_id) in docs { + let prev = document_to_package.insert( + *unresolved_doc_id, + (pkg, doc, unresolved.foreign_dep_spans[i]), + ); + assert!(prev.is_none()); + } + } + for (unresolved_doc_id, _doc) in unresolved.documents.iter() { + let (pkg, doc, span) = match document_to_package.get(&unresolved_doc_id) { + Some(items) => *items, + None => break, + }; + let pkgid = *deps.get(pkg).ok_or_else(|| Error { + span, + msg: format!("no package dependency specified for `{pkg}`"), + })?; + let package = &resolve.packages[pkgid]; + + let docid = *package.documents.get(doc).ok_or_else(|| Error { + span: unresolved.document_spans[unresolved_doc_id.index()], + msg: format!("package `{pkg}` does not define document `{doc}`"), + })?; + + assert_eq!(self.documents.len(), unresolved_doc_id.index()); + self.documents.push(docid); + } + for (id, _) in unresolved.documents.iter().skip(self.documents.len()) { + assert!( + document_to_package.get(&id).is_none(), + "found foreign document after local documents" + ); + } + + // Next, for all documents that are referenced in this `Resolve` + // determine the mapping of all interfaces that they refer to. + for (unresolved_iface_id, unresolved_iface) in unresolved.interfaces.iter() { + let doc_id = match self.documents.get(unresolved_iface.document.index()) { + Some(i) => *i, + // All foreign interfaces are defined first, so the first one + // which is defined in a non-foreign document means that all + // further interfaces will be non-foreign as well. + None => break, + }; + + // Functions can't be imported so this should be empty. + assert!(unresolved_iface.functions.is_empty()); + + let document = &resolve.documents[doc_id]; + let span = unresolved.interface_spans[unresolved_iface_id.index()]; + let iface_id = match &unresolved_iface.name { + Some(name) => *document.interfaces.get(name).ok_or_else(|| Error { + span, + msg: format!("interface not defined in document"), + })?, + None => document.default_interface.ok_or_else(|| Error { + span, + msg: format!("default interface not specified in document"), + })?, + }; + assert_eq!(self.interfaces.len(), unresolved_iface_id.index()); + self.interfaces.push(iface_id); + } + + for (_, iface) in unresolved.interfaces.iter().skip(self.interfaces.len()) { + if self.documents.get(iface.document.index()).is_some() { + panic!("found foreign interface after local interfaces"); + } + } + + // And finally iterate over all foreign-defined types and determine + // what they map to. + for (unresolved_type_id, unresolved_ty) in unresolved.types.iter() { + // All "Unknown" types should appear first so once we're no longer + // in unknown territory it's package-defined types so break out of + // this loop. + match unresolved_ty.kind { + TypeDefKind::Unknown => {} + _ => break, + } + let unresolved_iface_id = match unresolved_ty.owner { + TypeOwner::Interface(id) => id, + _ => unreachable!(), + }; + let iface_id = self.interfaces[unresolved_iface_id.index()]; + let name = unresolved_ty.name.as_ref().unwrap(); + let span = unresolved.unknown_type_spans[unresolved_type_id.index()]; + let type_id = *resolve.interfaces[iface_id] + .types + .get(name) + .ok_or_else(|| Error { + span, + msg: format!("type not defined in interface"), + })?; + assert_eq!(self.types.len(), unresolved_type_id.index()); + self.types.push(type_id); + } + + for (_, ty) in unresolved.types.iter().skip(self.types.len()) { + if let TypeDefKind::Unknown = ty.kind { + panic!("unknown type after defined type"); + } + } + + Ok(()) + } + + fn update_typedef(&self, ty: &mut TypeDef) { + // NB: note that `ty.owner` is not updated here since interfaces + // haven't been mapped yet and that's done in a separate step. + use crate::TypeDefKind::*; + match &mut ty.kind { + Record(r) => { + for field in r.fields.iter_mut() { + self.update_ty(&mut field.ty); + } + } + Tuple(t) => { + for ty in t.types.iter_mut() { + self.update_ty(ty); + } + } + Variant(v) => { + for case in v.cases.iter_mut() { + if let Some(t) = &mut case.ty { + self.update_ty(t); + } + } + } + Option(t) => self.update_ty(t), + Result(r) => { + if let Some(ty) = &mut r.ok { + self.update_ty(ty); + } + if let Some(ty) = &mut r.err { + self.update_ty(ty); + } + } + Union(u) => { + for case in u.cases.iter_mut() { + self.update_ty(&mut case.ty); + } + } + List(t) => self.update_ty(t), + Future(Some(t)) => self.update_ty(t), + Stream(t) => { + if let Some(ty) = &mut t.element { + self.update_ty(ty); + } + if let Some(ty) = &mut t.end { + self.update_ty(ty); + } + } + Type(t) => self.update_ty(t), + + // nothing to do for these as they're just names or empty + Flags(_) | Enum(_) | Future(None) => {} + + Unknown => unreachable!(), + } + } + + fn update_ty(&self, ty: &mut Type) { + if let Type::Id(id) = ty { + *id = self.types[id.index()]; + } + } + + fn update_interface(&self, iface: &mut Interface) { + // NB: note that `iface.doc` is not updated here since interfaces + // haven't been mapped yet and that's done in a separate step. + for (_name, ty) in iface.types.iter_mut() { + *ty = self.types[ty.index()]; + } + for (_, func) in iface.functions.iter_mut() { + self.update_function(func); + } + } + + fn update_function(&self, func: &mut Function) { + for (_, ty) in func.params.iter_mut() { + self.update_ty(ty); + } + match &mut func.results { + Results::Named(named) => { + for (_, ty) in named.iter_mut() { + self.update_ty(ty); + } + } + Results::Anon(ty) => self.update_ty(ty), + } + } + + fn update_world( + &self, + world: &mut World, + resolve: &Resolve, + import_spans: &[Span], + export_spans: &[Span], + ) -> Result<()> { + // NB: this function is more more complicated than the prior versions + // of merging an item because this is the location that elaboration of + // imports/exports of a world are fully resolved. With full transitive + // knowledge of all interfaces a worlds imports, for example, are + // expanded fully to ensure that all transitive items are necessarily + // imported. + assert_eq!(world.imports.len(), import_spans.len()); + assert_eq!(world.exports.len(), export_spans.len()); + + // First up, process all the `imports` of the world. Note that this + // starts by gutting the list of imports stored in `world` to get + // rebuilt iteratively below. + // + // Here each import of an interface is recorded and then additionally + // explicitly named imports of interfaces are recorded as well for + // determining names later on. + let mut explicit_import_names = HashMap::new(); + let mut explicit_export_names = HashMap::new(); + let mut imports = Vec::new(); + let mut exports = Vec::new(); + let mut import_funcs = Vec::new(); + let mut export_funcs = Vec::new(); + for ((name, item), span) in mem::take(&mut world.imports).into_iter().zip(import_spans) { + match item { + WorldItem::Interface(id) => { + let id = self.interfaces[id.index()]; + imports.push((id, *span)); + let prev = explicit_import_names.insert(id, name); + assert!(prev.is_none()); + } + WorldItem::Function(mut f) => { + self.update_function(&mut f); + import_funcs.push((name, f)); + } + } + } + for ((name, item), span) in mem::take(&mut world.exports).into_iter().zip(export_spans) { + match item { + WorldItem::Interface(id) => { + let id = self.interfaces[id.index()]; + exports.push((id, *span)); + let prev = explicit_export_names.insert(id, name); + assert!(prev.is_none()); + } + WorldItem::Function(mut f) => { + self.update_function(&mut f); + export_funcs.push((name, f)); + } + } + } + + // Next all imports and their transitive imports are processed. This + // is done through a `stack` of `Action` items which is processed in + // LIFO order, meaning that an action of processing the dependencies + // is pushed after processing the node itself. The dependency processing + // will push more items onto the stack as necessary. + let mut elaborate = WorldElaborator { + resolve, + world, + imports_processed: Default::default(), + exports_processed: Default::default(), + resolving_stack: Default::default(), + explicit_import_names: &explicit_import_names, + explicit_export_names: &explicit_export_names, + }; + for (id, span) in imports { + elaborate.import(id, span)?; + } + for (id, span) in exports { + elaborate.export(id, span)?; + } + + for (name, func) in import_funcs { + let prev = world + .imports + .insert(name.clone(), WorldItem::Function(func)); + if prev.is_some() { + bail!("import of function `{name}` shadows previously imported interface"); + } + } + for (name, func) in export_funcs { + let prev = world + .exports + .insert(name.clone(), WorldItem::Function(func)); + if prev.is_some() { + bail!("export of function `{name}` shadows previously exported interface"); + } + } + + log::trace!("imports = {:?}", world.imports); + log::trace!("exports = {:?}", world.exports); + + Ok(()) + } + + fn update_document(&self, doc: &mut Document) { + for (_name, iface) in doc.interfaces.iter_mut() { + *iface = self.interfaces[iface.index()]; + } + for (_name, world) in doc.worlds.iter_mut() { + *world = self.worlds[world.index()]; + } + if let Some(default) = &mut doc.default_interface { + *default = self.interfaces[default.index()]; + } + if let Some(default) = &mut doc.default_world { + *default = self.worlds[default.index()]; + } + } +} + +struct WorldElaborator<'a, 'b> { + resolve: &'a Resolve, + world: &'b mut World, + explicit_import_names: &'a HashMap, + explicit_export_names: &'a HashMap, + + /// Set of imports which are either imported into the world already or in + /// the `stack` to get processed, used to ensure the same dependency isn't + /// pushed multiple times into the stack. + imports_processed: HashSet, + exports_processed: HashSet, + + /// Dependency chain of why we're importing the top of `stack`, used to + /// print an error message. + resolving_stack: Vec<(InterfaceId, bool)>, +} + +impl<'a> WorldElaborator<'a, '_> { + fn import(&mut self, id: InterfaceId, span: Span) -> Result<()> { + self.recurse(id, span, true) + } + + fn export(&mut self, id: InterfaceId, span: Span) -> Result<()> { + self.recurse(id, span, false) + } + + fn recurse(&mut self, id: InterfaceId, span: Span, import: bool) -> Result<()> { + let processed = if import { + &mut self.imports_processed + } else { + &mut self.exports_processed + }; + if !processed.insert(id) { + return Ok(()); + } + + self.resolving_stack.push((id, import)); + for (_, ty) in self.resolve.interfaces[id].types.iter() { + let ty = match self.resolve.types[*ty].kind { + TypeDefKind::Type(Type::Id(id)) => id, + _ => continue, + }; + let dep = match self.resolve.types[ty].owner { + TypeOwner::None => continue, + TypeOwner::Interface(other) => other, + TypeOwner::World(_) => unreachable!(), + }; + let import = import || !self.explicit_export_names.contains_key(&dep); + + self.recurse(dep, span, import)?; + } + assert_eq!(self.resolving_stack.pop(), Some((id, import))); + + let name = self.name_of(id, import); + let set = if import { + &mut self.world.imports + } else { + &mut self.world.exports + }; + let prev = set.insert(name.clone(), WorldItem::Interface(id)); + if prev.is_none() { + return Ok(()); + } + + let desc = |import: bool| { + if import { + "import" + } else { + "export" + } + }; + + let mut msg = format!("{} of `{}`", desc(import), self.name_of(id, import)); + if self.resolving_stack.is_empty() { + msg.push_str(" "); + } else { + msg.push_str("\n"); + } + for (i, import) in self.resolving_stack.iter().rev() { + writeln!( + msg, + " .. which is depended on by {} `{}`", + desc(*import), + self.name_of(*i, *import) + ) + .unwrap(); + } + writeln!( + msg, + "conflicts with a previously imported interface \ + using the name `{name}`", + ) + .unwrap(); + bail!(Error { span, msg }) + } + + fn name_of(&self, id: InterfaceId, import: bool) -> &'a String { + let set = if import { + &self.explicit_import_names + } else { + &self.explicit_export_names + }; + set.get(&id) + .unwrap_or_else(|| self.resolve.interfaces[id].name.as_ref().unwrap()) + } +} diff --git a/crates/wit-parser/src/sizealign.rs b/crates/wit-parser/src/sizealign.rs index a06600ea7d..c1aac71de5 100644 --- a/crates/wit-parser/src/sizealign.rs +++ b/crates/wit-parser/src/sizealign.rs @@ -1,4 +1,4 @@ -use crate::{Document, FlagsRepr, Int, Type, TypeDef, TypeDefKind}; +use crate::{FlagsRepr, Int, Resolve, Type, TypeDef, TypeDefKind}; #[derive(Default)] pub struct SizeAlign { @@ -6,11 +6,11 @@ pub struct SizeAlign { } impl SizeAlign { - pub fn fill(&mut self, doc: &Document) { - self.map = vec![(0, 0); doc.types.len()]; - for ty in doc.topological_types() { - let pair = self.calculate(&doc.types[ty]); - self.map[ty.index()] = pair; + pub fn fill(&mut self, resolve: &Resolve) { + self.map = Vec::new(); + for (_, ty) in resolve.types.iter() { + let pair = self.calculate(ty); + self.map.push(pair); } } @@ -34,6 +34,7 @@ impl SizeAlign { TypeDefKind::Future(_) => (4, 4), // A stream is represented as an index. TypeDefKind::Stream(_) => (4, 4), + TypeDefKind::Unknown => unreachable!(), } } diff --git a/crates/wit-parser/tests/all.rs b/crates/wit-parser/tests/all.rs index bc31faeb65..8e41fc5b91 100644 --- a/crates/wit-parser/tests/all.rs +++ b/crates/wit-parser/tests/all.rs @@ -8,8 +8,8 @@ //! cargo test --test all foo.wit use anyhow::{bail, Context, Result}; +use pretty_assertions::StrComparison; use rayon::prelude::*; -use serde::Serialize; use std::env; use std::ffi::OsStr; use std::fs; @@ -20,6 +20,7 @@ use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; use wit_parser::*; fn main() { + env_logger::init(); let tests = find_tests(); let filter = std::env::args().nth(1); @@ -33,8 +34,7 @@ fn main() { } } } - let contents = fs::read(test).unwrap(); - Some((test, contents)) + Some(test) }) .collect::>(); @@ -43,9 +43,9 @@ fn main() { let ntests = AtomicUsize::new(0); let errors = tests .par_iter() - .filter_map(|(test, contents)| { + .filter_map(|test| { Runner { ntests: &ntests } - .run(test, contents) + .run(test) .context(format!("test {:?} failed", test)) .err() }) @@ -70,23 +70,28 @@ fn main() { fn find_tests() -> Vec { let mut tests = Vec::new(); find_tests("tests/ui".as_ref(), &mut tests); + find_tests("tests/ui/parse-fail".as_ref(), &mut tests); tests.sort(); return tests; fn find_tests(path: &Path, tests: &mut Vec) { for f in path.read_dir().unwrap() { let f = f.unwrap(); + let path = f.path(); + if path.file_name().unwrap().to_str().unwrap() == "parse-fail" { + continue; + } if f.file_type().unwrap().is_dir() { - find_tests(&f.path(), tests); + tests.push(path); continue; } - match f.path().extension().and_then(|s| s.to_str()) { + match path.extension().and_then(|s| s.to_str()) { Some("md") => {} Some("wit") => {} _ => continue, } - tests.push(f.path()); + tests.push(path); } } } @@ -96,12 +101,15 @@ struct Runner<'a> { } impl Runner<'_> { - fn run(&mut self, test: &Path, contents: &[u8]) -> Result<()> { - let contents = str::from_utf8(contents)?; - - let result = Document::parse(test, contents); + fn run(&mut self, test: &Path) -> Result<()> { + let mut resolve = Resolve::new(); + let result = if test.is_dir() { + resolve.push_dir(test).map(|(id, _)| id) + } else { + UnresolvedPackage::parse_file(test).and_then(|p| resolve.push(p, &Default::default())) + }; - let result = if contents.contains("// parse-fail") { + let result = if test.iter().any(|s| s == "parse-fail") { match result { Ok(_) => bail!("expected test to not parse but it did"), Err(mut e) => { @@ -111,13 +119,12 @@ impl Runner<'_> { "some generic platform-agnostic error message", ); } - normalize(test, &format!("{:?}", e)) + normalize(&format!("{:?}", e)) } } } else { - let document = result?; - test_document(&document); - to_json(&document) + result?; + return Ok(()); }; // "foo.wit" => "foo.wit.result" @@ -139,25 +146,19 @@ impl Runner<'_> { "failed to read test expectation file {:?}\nthis can be fixed with BLESS=1", result_file ))?; - let expected = normalize(test, &expected); + let expected = normalize(&expected); if expected != result { bail!( - "failed test: expected `{:?}` but found `{:?}`", - expected, - result + "failed test: result is not as expected:{}", + StrComparison::new(&expected, &result), ); } } self.bump_ntests(); return Ok(()); - fn normalize(test: &Path, s: &str) -> String { - s.replace( - &test.display().to_string(), - &test.display().to_string().replace('\\', "/"), - ) - .replace("\\parse-fail\\", "/parse-fail/") - .replace("\r\n", "\n") + fn normalize(s: &str) -> String { + s.replace('\\', "/").replace("\r\n", "\n") } } @@ -165,235 +166,3 @@ impl Runner<'_> { self.ntests.fetch_add(1, SeqCst); } } - -fn to_json(doc: &Document) -> String { - #[derive(Serialize)] - struct Document { - #[serde(skip_serializing_if = "Vec::is_empty")] - worlds: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] - interfaces: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] - types: Vec, - } - - #[derive(Serialize)] - struct World { - name: String, - #[serde(skip_serializing_if = "Option::is_none")] - default: Option, - #[serde(skip_serializing_if = "Vec::is_empty")] - imports: Vec<(String, String)>, - #[serde(skip_serializing_if = "Vec::is_empty")] - exports: Vec<(String, String)>, - } - - #[derive(Serialize)] - struct Interface { - name: String, - #[serde(skip_serializing_if = "Vec::is_empty")] - types: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] - functions: Vec, - } - - #[derive(Serialize)] - struct Resource { - name: String, - #[serde(skip_serializing_if = "Option::is_none")] - supertype: Option, - #[serde(skip_serializing_if = "Option::is_none")] - foreign_module: Option, - } - - #[derive(Serialize)] - struct TypeDef { - idx: usize, - #[serde(skip_serializing_if = "Option::is_none")] - name: Option, - #[serde(flatten)] - ty: Type, - #[serde(skip_serializing_if = "Option::is_none")] - foreign_module: Option, - } - - #[derive(Serialize)] - #[serde(rename_all = "kebab-case")] - enum Type { - Primitive(String), - Record { - fields: Vec<(String, String)>, - }, - Flags { - flags: Vec, - }, - Enum { - cases: Vec, - }, - Variant { - cases: Vec<(String, Option)>, - }, - Tuple { - types: Vec, - }, - Option(String), - Result { - ok: Option, - err: Option, - }, - Future(Option), - Stream { - element: Option, - end: Option, - }, - List(String), - Union { - cases: Vec, - }, - } - - #[derive(Serialize)] - struct Function { - name: String, - params: Vec, - results: Vec, - } - - #[derive(Serialize)] - struct Global { - name: String, - ty: String, - } - - let document = Document { - worlds: doc - .worlds - .iter() - .map(|(_, world)| translate_world(world)) - .collect(), - interfaces: doc - .interfaces - .iter() - .map(|(_, interface)| translate_interface(interface)) - .collect(), - types: doc - .types - .iter() - .map(|(_, ty)| translate_typedef(ty)) - .collect(), - }; - - return serde_json::to_string_pretty(&document).unwrap(); - - fn translate_world(w: &wit_parser::World) -> World { - World { - name: w.name.clone(), - default: w.default.map(|i| format!("i{}", i.index())), - imports: w - .imports - .iter() - .map(|(name, iface)| (name.clone(), format!("i{}", iface.index()))) - .collect(), - exports: w - .exports - .iter() - .map(|(name, iface)| (name.clone(), format!("i{}", iface.index()))) - .collect(), - } - } - - fn translate_interface(i: &wit_parser::Interface) -> Interface { - let types = i - .types - .iter() - .map(|i| format!("type-{}", i.index())) - .collect::>(); - let functions = i - .functions - .iter() - .map(|f| Function { - name: f.name.clone(), - params: f.params.iter().map(|(_, ty)| translate_type(ty)).collect(), - results: f.results.iter_types().map(translate_type).collect(), - }) - .collect::>(); - - Interface { - name: i.name.clone(), - types, - functions, - } - } - - fn translate_typedef(ty: &wit_parser::TypeDef) -> Type { - match &ty.kind { - TypeDefKind::Type(t) => Type::Primitive(translate_type(t)), - TypeDefKind::Record(r) => Type::Record { - fields: r - .fields - .iter() - .map(|f| (f.name.clone(), translate_type(&f.ty))) - .collect(), - }, - TypeDefKind::Tuple(t) => Type::Tuple { - types: t.types.iter().map(translate_type).collect(), - }, - TypeDefKind::Flags(r) => Type::Flags { - flags: r.flags.iter().map(|f| f.name.clone()).collect(), - }, - TypeDefKind::Enum(r) => Type::Enum { - cases: r.cases.iter().map(|f| f.name.clone()).collect(), - }, - TypeDefKind::Variant(v) => Type::Variant { - cases: v - .cases - .iter() - .map(|f| (f.name.clone(), translate_optional_type(f.ty.as_ref()))) - .collect(), - }, - TypeDefKind::Option(t) => Type::Option(translate_type(t)), - TypeDefKind::Result(r) => Type::Result { - ok: translate_optional_type(r.ok.as_ref()), - err: translate_optional_type(r.err.as_ref()), - }, - TypeDefKind::Future(t) => Type::Future(translate_optional_type(t.as_ref())), - TypeDefKind::Stream(s) => Type::Stream { - element: translate_optional_type(s.element.as_ref()), - end: translate_optional_type(s.end.as_ref()), - }, - TypeDefKind::List(ty) => Type::List(translate_type(ty)), - TypeDefKind::Union(u) => Type::Union { - cases: u.cases.iter().map(|c| translate_type(&c.ty)).collect(), - }, - } - } - - fn translate_type(ty: &wit_parser::Type) -> String { - use wit_parser::Type; - match ty { - Type::Bool => "bool".to_string(), - Type::U8 => "u8".to_string(), - Type::U16 => "u16".to_string(), - Type::U32 => "u32".to_string(), - Type::U64 => "u64".to_string(), - Type::S8 => "s8".to_string(), - Type::S16 => "s16".to_string(), - Type::S32 => "s32".to_string(), - Type::S64 => "s64".to_string(), - Type::Float32 => "float32".to_string(), - Type::Float64 => "float64".to_string(), - Type::Char => "char".to_string(), - Type::String => "string".to_string(), - Type::Id(id) => format!("type-{}", id.index()), - } - } - - fn translate_optional_type(ty: Option<&wit_parser::Type>) -> Option { - ty.map(translate_type) - } -} - -fn test_document(document: &Document) { - let mut sizes = SizeAlign::default(); - sizes.fill(document); -} diff --git a/crates/wit-parser/tests/ui/comments.wit.result b/crates/wit-parser/tests/ui/comments.wit.result deleted file mode 100644 index 7548639a38..0000000000 --- a/crates/wit-parser/tests/ui/comments.wit.result +++ /dev/null @@ -1,22 +0,0 @@ -{ - "interfaces": [ - { - "name": "foo", - "types": [ - "type-0", - "type-1" - ] - } - ], - "types": [ - { - "primitive": "u32" - }, - { - "stream": { - "element": "type-0", - "end": null - } - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/disambiguate-diamond/shared1.wit b/crates/wit-parser/tests/ui/disambiguate-diamond/shared1.wit new file mode 100644 index 0000000000..e65165ac2d --- /dev/null +++ b/crates/wit-parser/tests/ui/disambiguate-diamond/shared1.wit @@ -0,0 +1,3 @@ +default interface shared { + type the-type = u32 +} diff --git a/crates/wit-parser/tests/ui/disambiguate-diamond/shared2.wit b/crates/wit-parser/tests/ui/disambiguate-diamond/shared2.wit new file mode 100644 index 0000000000..e65165ac2d --- /dev/null +++ b/crates/wit-parser/tests/ui/disambiguate-diamond/shared2.wit @@ -0,0 +1,3 @@ +default interface shared { + type the-type = u32 +} diff --git a/crates/wit-parser/tests/ui/disambiguate-diamond/world.wit b/crates/wit-parser/tests/ui/disambiguate-diamond/world.wit new file mode 100644 index 0000000000..80ab9be490 --- /dev/null +++ b/crates/wit-parser/tests/ui/disambiguate-diamond/world.wit @@ -0,0 +1,11 @@ +world foo { + import foo: interface { + use pkg.shared1.{the-type} + } + import bar: interface { + use pkg.shared2.{the-type} + } + + import shared1: pkg.shared1 + import shared2: pkg.shared2 +} diff --git a/crates/wit-parser/tests/ui/embedded.wit.md.result b/crates/wit-parser/tests/ui/embedded.wit.md.result deleted file mode 100644 index 69871db16d..0000000000 --- a/crates/wit-parser/tests/ui/embedded.wit.md.result +++ /dev/null @@ -1,24 +0,0 @@ -{ - "interfaces": [ - { - "name": "foo", - "functions": [ - { - "name": "x", - "params": [], - "results": [] - }, - { - "name": "y", - "params": [], - "results": [] - }, - { - "name": "z", - "params": [], - "results": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/empty.wit.result b/crates/wit-parser/tests/ui/empty.wit.result deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/crates/wit-parser/tests/ui/empty.wit.result +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/another-pkg/other-doc.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/another-pkg/other-doc.wit new file mode 100644 index 0000000000..04933294d2 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/another-pkg/other-doc.wit @@ -0,0 +1 @@ +interface other-interface {} diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/corp/saas.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/corp/saas.wit new file mode 100644 index 0000000000..5a2b10d0d0 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/corp/saas.wit @@ -0,0 +1,2 @@ +default interface saas { +} diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/different-pkg/the-doc.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/different-pkg/the-doc.wit new file mode 100644 index 0000000000..f192837fb6 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/different-pkg/the-doc.wit @@ -0,0 +1 @@ +default interface i {} diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/foreign-pkg/the-doc.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/foreign-pkg/the-doc.wit new file mode 100644 index 0000000000..1fb9bad8de --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/foreign-pkg/the-doc.wit @@ -0,0 +1,3 @@ +default interface the-default { + type some-type = u32 +} diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/some-pkg/some-doc.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/some-pkg/some-doc.wit new file mode 100644 index 0000000000..8228c1d6f0 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/some-pkg/some-doc.wit @@ -0,0 +1,11 @@ +default interface the-default { + type from-default = string +} + +interface some-interface { + type another-type = u32 +} + +interface another-interface { + type yet-another-type = u8 +} diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/wasi/clocks.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/wasi/clocks.wit new file mode 100644 index 0000000000..18a3575649 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/wasi/clocks.wit @@ -0,0 +1,3 @@ +default interface wasi-clocks { + type timestamp = u64 +} diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/wasi/filesystem.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/wasi/filesystem.wit new file mode 100644 index 0000000000..b0a97a6cda --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/wasi/filesystem.wit @@ -0,0 +1,5 @@ +default interface wasi-filesystem { + record stat { + ino: u64 + } +} diff --git a/crates/wit-parser/tests/ui/foreign-deps/root.wit b/crates/wit-parser/tests/ui/foreign-deps/root.wit new file mode 100644 index 0000000000..d2325e855a --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/root.wit @@ -0,0 +1,31 @@ +interface foo { + use wasi.clocks.{timestamp} + use wasi.filesystem.{stat} +} + +world my-world { + import wasi-fs: wasi.filesystem + import wasi-clocks: wasi.clocks + + export saas: corp.saas +} + +interface bar { + use some-pkg.some-doc.{from-default} + use some-pkg.some-doc.some-interface.{another-type} + use some-pkg.some-doc.some-interface.{} + use some-pkg.some-doc.another-interface.{yet-another-type} + use different-pkg.the-doc.{} +} + +world bars-world { + import foo: some-pkg.some-doc + import bar: another-pkg.other-doc.other-interface +} + +interface use1 { + use foreign-pkg.the-doc.{some-type} +} +interface use2 { + use foreign-pkg.the-doc.{some-type} +} diff --git a/crates/wit-parser/tests/ui/functions.wit.result b/crates/wit-parser/tests/ui/functions.wit.result deleted file mode 100644 index 341cbaa9ba..0000000000 --- a/crates/wit-parser/tests/ui/functions.wit.result +++ /dev/null @@ -1,100 +0,0 @@ -{ - "interfaces": [ - { - "name": "functions", - "functions": [ - { - "name": "f1", - "params": [], - "results": [] - }, - { - "name": "f2", - "params": [ - "u32" - ], - "results": [] - }, - { - "name": "f3", - "params": [ - "u32" - ], - "results": [] - }, - { - "name": "f4", - "params": [], - "results": [ - "u32" - ] - }, - { - "name": "f6", - "params": [], - "results": [ - "type-0" - ] - }, - { - "name": "f7", - "params": [ - "float32", - "float32" - ], - "results": [ - "type-0" - ] - }, - { - "name": "f8", - "params": [ - "type-1" - ], - "results": [ - "type-2" - ] - }, - { - "name": "f9", - "params": [], - "results": [ - "u32", - "float32" - ] - }, - { - "name": "f10", - "params": [], - "results": [ - "u32" - ] - }, - { - "name": "f11", - "params": [], - "results": [] - } - ] - } - ], - "types": [ - { - "tuple": { - "types": [ - "u32", - "u32" - ] - } - }, - { - "option": "u32" - }, - { - "result": { - "ok": "u32", - "err": "float32" - } - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/multi-file/bar.wit b/crates/wit-parser/tests/ui/multi-file/bar.wit new file mode 100644 index 0000000000..9946d4ba33 --- /dev/null +++ b/crates/wit-parser/tests/ui/multi-file/bar.wit @@ -0,0 +1,19 @@ +default interface irrelevant-name { + record a-name {} +} + +interface depends-on-later-item { + use self.depend-on-me.{x} +} + +interface depend-on-me { + type x = u32 +} + +world more-depends-on-later-things { + import foo: self.later-interface + export bar: self.later-interface +} + +interface later-interface { +} diff --git a/crates/wit-parser/tests/ui/multi-file/foo.wit b/crates/wit-parser/tests/ui/multi-file/foo.wit new file mode 100644 index 0000000000..cf9e621b29 --- /dev/null +++ b/crates/wit-parser/tests/ui/multi-file/foo.wit @@ -0,0 +1,15 @@ +interface foo { + type x = u32 +} + +default interface something-else { + type y = u64 +} + +interface bar { + use self.foo.{x} + use pkg.foo.foo.{x as x2} + use pkg.foo.{y} + use self.something-else.{y as y2} + use pkg.bar.{a-name} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/alias-no-type.wit.result b/crates/wit-parser/tests/ui/parse-fail/alias-no-type.wit.result index fcbdb7de60..6b9c20d947 100644 --- a/crates/wit-parser/tests/ui/parse-fail/alias-no-type.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/alias-no-type.wit.result @@ -1,4 +1,4 @@ -no type named `bar` +type `bar` does not exist --> tests/ui/parse-fail/alias-no-type.wit:3:14 | 3 | type foo = bar diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-diamond.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-diamond.wit.result new file mode 100644 index 0000000000..42409143d5 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-diamond.wit.result @@ -0,0 +1,8 @@ +import of `shared` + .. which is depended on by import `b` +conflicts with a previously imported interface using the name `shared` + + --> tests/ui/parse-fail/bad-diamond/join.wit:3:10 + | + 3 | import b: pkg.b + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-diamond/a.wit b/crates/wit-parser/tests/ui/parse-fail/bad-diamond/a.wit new file mode 100644 index 0000000000..0c33f97761 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-diamond/a.wit @@ -0,0 +1,9 @@ +interface shared { + type foo = u32 +} + +default interface a { + use self.shared.{foo} + + a: func() -> foo +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-diamond/b.wit b/crates/wit-parser/tests/ui/parse-fail/bad-diamond/b.wit new file mode 100644 index 0000000000..3aeda1af16 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-diamond/b.wit @@ -0,0 +1,9 @@ +interface shared { + type foo = u32 +} + +default interface b { + use self.shared.{foo} + + a: func() -> foo +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-diamond/join.wit b/crates/wit-parser/tests/ui/parse-fail/bad-diamond/join.wit new file mode 100644 index 0000000000..289cf6df5a --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-diamond/join.wit @@ -0,0 +1,4 @@ +world foo { + import a: pkg.a + import b: pkg.b +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-function.wit b/crates/wit-parser/tests/ui/parse-fail/bad-function.wit new file mode 100644 index 0000000000..202abb5a72 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-function.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + x: func(param: nonexistent) +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result new file mode 100644 index 0000000000..a5c199de70 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result @@ -0,0 +1,5 @@ +name `nonexistent` is not defined + --> tests/ui/parse-fail/bad-function.wit:4:18 + | + 4 | x: func(param: nonexistent) + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit b/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit new file mode 100644 index 0000000000..c80e3e670f --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + x: func() -> nonexistent +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result new file mode 100644 index 0000000000..e2ea68da10 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result @@ -0,0 +1,5 @@ +name `nonexistent` is not defined + --> tests/ui/parse-fail/bad-function2.wit:4:16 + | + 4 | x: func() -> nonexistent + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result new file mode 100644 index 0000000000..b55b5e3631 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result @@ -0,0 +1,5 @@ +dependency on `nonexistent` doesn't exist at: tests/ui/parse-fail/bad-pkg1/deps/nonexistent + --> tests/ui/parse-fail/bad-pkg1/root.wit:2:7 + | + 2 | use nonexistent.foo.{} + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg1/root.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1/root.wit new file mode 100644 index 0000000000..7b0d8a0ff8 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1/root.wit @@ -0,0 +1,3 @@ +interface foo { + use nonexistent.foo.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg2.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg2.wit.result new file mode 100644 index 0000000000..9cd96bd756 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg2.wit.result @@ -0,0 +1,5 @@ +package `bar` does not define document `nonexistent` + --> tests/ui/parse-fail/bad-pkg2/root.wit:2:11 + | + 2 | use bar.nonexistent.{} + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg2/deps/bar/.gitkeep b/crates/wit-parser/tests/ui/parse-fail/bad-pkg2/deps/bar/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg2/root.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg2/root.wit new file mode 100644 index 0000000000..eee9664002 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg2/root.wit @@ -0,0 +1,3 @@ +interface foo { + use bar.nonexistent.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg3.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3.wit.result new file mode 100644 index 0000000000..432a7dffeb --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3.wit.result @@ -0,0 +1,5 @@ +default interface not specified in document + --> tests/ui/parse-fail/bad-pkg3/root.wit:2:11 + | + 2 | use bar.baz.{} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/deps/bar/.gitkeep b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/deps/bar/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/deps/bar/baz.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/deps/bar/baz.wit new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/root.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/root.wit new file mode 100644 index 0000000000..5bc31123f5 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/root.wit @@ -0,0 +1,3 @@ +interface foo { + use bar.baz.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg4.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4.wit.result new file mode 100644 index 0000000000..cdf2fba8c3 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4.wit.result @@ -0,0 +1,5 @@ +type not defined in interface + --> tests/ui/parse-fail/bad-pkg4/root.wit:2:16 + | + 2 | use bar.baz.{a-name} + | ^----- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/deps/bar/.gitkeep b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/deps/bar/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/deps/bar/baz.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/deps/bar/baz.wit new file mode 100644 index 0000000000..baa2185796 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/deps/bar/baz.wit @@ -0,0 +1,3 @@ +default interface irrelevant-name { + a-name: func() +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/root.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/root.wit new file mode 100644 index 0000000000..f1af5052ee --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/root.wit @@ -0,0 +1,3 @@ +interface foo { + use bar.baz.{a-name} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg5.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5.wit.result new file mode 100644 index 0000000000..da07d92785 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5.wit.result @@ -0,0 +1,5 @@ +type not defined in interface + --> tests/ui/parse-fail/bad-pkg5/root.wit:2:16 + | + 2 | use bar.baz.{nonexistent} + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/deps/bar/.gitkeep b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/deps/bar/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/deps/bar/baz.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/deps/bar/baz.wit new file mode 100644 index 0000000000..65ffc3ac20 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/deps/bar/baz.wit @@ -0,0 +1,2 @@ +default interface irrelevant-name { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/root.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/root.wit new file mode 100644 index 0000000000..0f19c0cce9 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/root.wit @@ -0,0 +1,3 @@ +interface foo { + use bar.baz.{nonexistent} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result new file mode 100644 index 0000000000..91c24db610 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result @@ -0,0 +1,5 @@ +interface not defined in document + --> tests/ui/parse-fail/bad-pkg6/root.wit:2:15 + | + 2 | use bar.baz.nonexistent.{} + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/deps/bar/.gitkeep b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/deps/bar/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/deps/bar/baz.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/deps/bar/baz.wit new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/root.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/root.wit new file mode 100644 index 0000000000..7c244a4d42 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/root.wit @@ -0,0 +1,3 @@ +interface foo { + use bar.baz.nonexistent.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/cycle.wit.result b/crates/wit-parser/tests/ui/parse-fail/cycle.wit.result index 7c273d7b1e..fbb1a6457a 100644 --- a/crates/wit-parser/tests/ui/parse-fail/cycle.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/cycle.wit.result @@ -1,5 +1,5 @@ -type can recursively refer to itself - --> tests/ui/parse-fail/cycle.wit:4:8 +type `foo` depends on itself + --> tests/ui/parse-fail/cycle.wit:4:14 | 4 | type foo = foo - | ^-- \ No newline at end of file + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/cycle2.wit.result b/crates/wit-parser/tests/ui/parse-fail/cycle2.wit.result index 5852e59ac0..946175ab6b 100644 --- a/crates/wit-parser/tests/ui/parse-fail/cycle2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/cycle2.wit.result @@ -1,5 +1,5 @@ -type can recursively refer to itself - --> tests/ui/parse-fail/cycle2.wit:4:8 +type `bar` depends on itself + --> tests/ui/parse-fail/cycle2.wit:4:14 | 4 | type foo = bar - | ^-- \ No newline at end of file + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/cycle3.wit.result b/crates/wit-parser/tests/ui/parse-fail/cycle3.wit.result index 308f29b091..facc6386e7 100644 --- a/crates/wit-parser/tests/ui/parse-fail/cycle3.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/cycle3.wit.result @@ -1,5 +1,5 @@ -type can recursively refer to itself - --> tests/ui/parse-fail/cycle3.wit:4:8 +type `bar` depends on itself + --> tests/ui/parse-fail/cycle3.wit:4:14 | 4 | type foo = bar - | ^-- \ No newline at end of file + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/cycle4.wit.result b/crates/wit-parser/tests/ui/parse-fail/cycle4.wit.result index 8ae0b3745c..45e9c17a0f 100644 --- a/crates/wit-parser/tests/ui/parse-fail/cycle4.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/cycle4.wit.result @@ -1,5 +1,5 @@ -type can recursively refer to itself - --> tests/ui/parse-fail/cycle4.wit:4:8 +type `bar` depends on itself + --> tests/ui/parse-fail/cycle4.wit:4:14 | 4 | type foo = bar - | ^-- \ No newline at end of file + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/cycle5.wit.result b/crates/wit-parser/tests/ui/parse-fail/cycle5.wit.result index 077957e5e3..0fd4f5c82b 100644 --- a/crates/wit-parser/tests/ui/parse-fail/cycle5.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/cycle5.wit.result @@ -1,5 +1,5 @@ -type can recursively refer to itself - --> tests/ui/parse-fail/cycle5.wit:4:8 +type `bar` depends on itself + --> tests/ui/parse-fail/cycle5.wit:4:14 | 4 | type foo = bar - | ^-- \ No newline at end of file + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/default-interface1.wit b/crates/wit-parser/tests/ui/parse-fail/default-interface1.wit new file mode 100644 index 0000000000..c6c4238802 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/default-interface1.wit @@ -0,0 +1,3 @@ +// parse-fail +default interface foo {} +default interface bar {} diff --git a/crates/wit-parser/tests/ui/parse-fail/default-interface1.wit.result b/crates/wit-parser/tests/ui/parse-fail/default-interface1.wit.result new file mode 100644 index 0000000000..a98ae7d335 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/default-interface1.wit.result @@ -0,0 +1,5 @@ +cannot specify more than one `default` interface + --> tests/ui/parse-fail/default-interface1.wit:3:19 + | + 3 | default interface bar {} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/default-world1.wit b/crates/wit-parser/tests/ui/parse-fail/default-world1.wit new file mode 100644 index 0000000000..df2d36e299 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/default-world1.wit @@ -0,0 +1,3 @@ +// parse-fail +default world foo {} +default world bar {} diff --git a/crates/wit-parser/tests/ui/parse-fail/default-world1.wit.result b/crates/wit-parser/tests/ui/parse-fail/default-world1.wit.result new file mode 100644 index 0000000000..e46b2538f2 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/default-world1.wit.result @@ -0,0 +1,5 @@ +cannot specify more than one `default` world + --> tests/ui/parse-fail/default-world1.wit:3:15 + | + 3 | default world bar {} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/duplicate-functions.wit.result b/crates/wit-parser/tests/ui/parse-fail/duplicate-functions.wit.result index 4b31f5dd84..c0ab243fa6 100644 --- a/crates/wit-parser/tests/ui/parse-fail/duplicate-functions.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/duplicate-functions.wit.result @@ -1,4 +1,4 @@ -`foo` is defined more than once +name `foo` is defined more than once --> tests/ui/parse-fail/duplicate-functions.wit:5:3 | 5 | foo: func() diff --git a/crates/wit-parser/tests/ui/parse-fail/duplicate-interface.wit.result b/crates/wit-parser/tests/ui/parse-fail/duplicate-interface.wit.result index c51063819e..293005fe64 100644 --- a/crates/wit-parser/tests/ui/parse-fail/duplicate-interface.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/duplicate-interface.wit.result @@ -1,4 +1,4 @@ -interface `foo` is defined more than once +name `foo` previously defined in document --> tests/ui/parse-fail/duplicate-interface.wit:4:11 | 4 | interface foo {} diff --git a/crates/wit-parser/tests/ui/parse-fail/duplicate-type.wit.result b/crates/wit-parser/tests/ui/parse-fail/duplicate-type.wit.result index 3b966ae586..9efd0d2c4f 100644 --- a/crates/wit-parser/tests/ui/parse-fail/duplicate-type.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/duplicate-type.wit.result @@ -1,4 +1,4 @@ -type `foo` is defined more than once +name `foo` is defined more than once --> tests/ui/parse-fail/duplicate-type.wit:5:8 | 5 | type foo = s32 diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit new file mode 100644 index 0000000000..da9286726d --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit @@ -0,0 +1,8 @@ +interface foo { + x: func() + + record foo { + // TODO: this should have a better error message referring to `x: func()` + a: x + } +} diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit.result b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit.result new file mode 100644 index 0000000000..1c872a1c05 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit.result @@ -0,0 +1,5 @@ +type `x` does not exist + --> tests/ui/parse-fail/invalid-type-reference.wit:6:8 + | + 6 | a: x + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit new file mode 100644 index 0000000000..c2f7393c61 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit @@ -0,0 +1,4 @@ +interface foo { + x: func() + y: func() -> x +} diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit.result b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit.result new file mode 100644 index 0000000000..6637a36a23 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit.result @@ -0,0 +1,5 @@ +cannot use function `x` as a type + --> tests/ui/parse-fail/invalid-type-reference2.wit:3:16 + | + 3 | y: func() -> x + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit b/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result b/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result new file mode 100644 index 0000000000..461cca9d53 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result @@ -0,0 +1,4 @@ +failed to start resolving path: tests/ui/parse-fail/invalid@filename.wit + +Caused by: + name of document isn't a valid WIT identifier `invalid@filename`: invalid character in identifier '@' \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle.wit.result b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle.wit.result new file mode 100644 index 0000000000..d7fbbe1104 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle.wit.result @@ -0,0 +1,5 @@ +package depends on itself + --> tests/ui/parse-fail/pkg-cycle/deps/a1/root.wit:2:7 + | + 2 | use a1.foo.{} + | ^- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle/deps/a1/root.wit b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle/deps/a1/root.wit new file mode 100644 index 0000000000..9a23dbfd35 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle/deps/a1/root.wit @@ -0,0 +1,3 @@ +interface foo { + use a1.foo.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle/root.wit b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle/root.wit new file mode 100644 index 0000000000..9a23dbfd35 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle/root.wit @@ -0,0 +1,3 @@ +interface foo { + use a1.foo.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2.wit.result b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2.wit.result new file mode 100644 index 0000000000..b7ccf5a080 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2.wit.result @@ -0,0 +1,5 @@ +package depends on itself + --> tests/ui/parse-fail/pkg-cycle2/deps/a2/root.wit:2:7 + | + 2 | use a1.foo.{} + | ^- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a1/root.wit b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a1/root.wit new file mode 100644 index 0000000000..9e8ae2bbfd --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a1/root.wit @@ -0,0 +1,3 @@ +interface foo { + use a2.foo.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a2/root.wit b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a2/root.wit new file mode 100644 index 0000000000..9a23dbfd35 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a2/root.wit @@ -0,0 +1,3 @@ +interface foo { + use a1.foo.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/root.wit b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/root.wit new file mode 100644 index 0000000000..9a23dbfd35 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/root.wit @@ -0,0 +1,3 @@ +interface foo { + use a1.foo.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/undefined-typed.wit.result b/crates/wit-parser/tests/ui/parse-fail/undefined-typed.wit.result index 25cde3f7c2..aba06b4bc2 100644 --- a/crates/wit-parser/tests/ui/parse-fail/undefined-typed.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/undefined-typed.wit.result @@ -1,4 +1,4 @@ -no type named `bar` +type `bar` does not exist --> tests/ui/parse-fail/undefined-typed.wit:4:14 | 4 | type foo = bar diff --git a/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit b/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit index a6c680b4e1..8fe521bfb5 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit +++ b/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit @@ -1,5 +1,5 @@ // parse-fail world foo { - import bar: bar + import bar: self.bar } diff --git a/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit.result b/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit.result index 592fc09f93..143852253c 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit.result @@ -1,5 +1,5 @@ -bar not defined - --> tests/ui/parse-fail/unknown-interface.wit:4:15 +interface does not exist + --> tests/ui/parse-fail/unknown-interface.wit:4:20 | - 4 | import bar: bar - | ^-- \ No newline at end of file + 4 | import bar: self.bar + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-default1.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface1.wit similarity index 55% rename from crates/wit-parser/tests/ui/parse-fail/world-default1.wit rename to crates/wit-parser/tests/ui/parse-fail/unresolved-interface1.wit index a20da5d4ba..cc1b6ae35f 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-default1.wit +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface1.wit @@ -1,5 +1,5 @@ // parse-fail world foo { - default export bar + import foo: self.foo } diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface1.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface1.wit.result new file mode 100644 index 0000000000..dc6cea3c5d --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface1.wit.result @@ -0,0 +1,5 @@ +name `foo` is defined as a world, not an interface + --> tests/ui/parse-fail/unresolved-interface1.wit:4:20 + | + 4 | import foo: self.foo + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit new file mode 100644 index 0000000000..8a67d92f00 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit @@ -0,0 +1,6 @@ +// parse-fail + +world foo { + import foo: self.bar +} + diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit.result new file mode 100644 index 0000000000..f230df6eb8 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit.result @@ -0,0 +1,5 @@ +interface does not exist + --> tests/ui/parse-fail/unresolved-interface2.wit:4:20 + | + 4 | import foo: self.bar + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit new file mode 100644 index 0000000000..61683dde44 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit @@ -0,0 +1,5 @@ +// parse-fail + +world foo { + import foo: pkg.foo.bar +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit.result new file mode 100644 index 0000000000..b7c4ba676c --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit.result @@ -0,0 +1,5 @@ +document `foo` does not exist + --> tests/ui/parse-fail/unresolved-interface3.wit:4:19 + | + 4 | import foo: pkg.foo.bar + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit new file mode 100644 index 0000000000..29817a23be --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit @@ -0,0 +1,5 @@ +// parse-fail + +world foo { + import foo: pkg.unresolved-interface4 +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit.result new file mode 100644 index 0000000000..e3f55f5ba0 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit.result @@ -0,0 +1,5 @@ +document does not specify a default interface + --> tests/ui/parse-fail/unresolved-interface4.wit:4:19 + | + 4 | import foo: pkg.unresolved-interface4 + | ^-------------------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit new file mode 100644 index 0000000000..fa943c2a59 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit @@ -0,0 +1,5 @@ +// parse-fail + +world foo { + import foo: pkg.unresolved-interface5.bar +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit.result new file mode 100644 index 0000000000..fc5a01ae26 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit.result @@ -0,0 +1,5 @@ +interface does not exist + --> tests/ui/parse-fail/unresolved-interface5.wit:4:41 + | + 4 | import foo: pkg.unresolved-interface5.bar + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit new file mode 100644 index 0000000000..f4b7dd5bef --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + use self.bar.{x} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result new file mode 100644 index 0000000000..33708bc6c9 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result @@ -0,0 +1,5 @@ +interface `bar` does not exist + --> tests/ui/parse-fail/unresolved-use1.wit:4:12 + | + 4 | use self.bar.{x} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result new file mode 100644 index 0000000000..bb1504e286 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result @@ -0,0 +1,8 @@ +failed to parse package: tests/ui/parse-fail/unresolved-use10 + +Caused by: + document does not specify a default interface + --> tests/ui/parse-fail/unresolved-use10/bar.wit:2:11 + | + 2 | use pkg.foo.{thing} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use10/bar.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10/bar.wit new file mode 100644 index 0000000000..82806f689e --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10/bar.wit @@ -0,0 +1,3 @@ +interface bar { + use pkg.foo.{thing} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use10/foo.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10/foo.wit new file mode 100644 index 0000000000..849a1eb3c9 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10/foo.wit @@ -0,0 +1,2 @@ +interface foo { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use11.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11.wit.result new file mode 100644 index 0000000000..afdbcf6f66 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11.wit.result @@ -0,0 +1,8 @@ +failed to parse package: tests/ui/parse-fail/unresolved-use11 + +Caused by: + name `thing` is not defined + --> tests/ui/parse-fail/unresolved-use11/bar.wit:2:16 + | + 2 | use pkg.foo.{thing} + | ^---- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use11/bar.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11/bar.wit new file mode 100644 index 0000000000..82806f689e --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11/bar.wit @@ -0,0 +1,3 @@ +interface bar { + use pkg.foo.{thing} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use11/foo.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11/foo.wit new file mode 100644 index 0000000000..3e6a7e9ced --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11/foo.wit @@ -0,0 +1,2 @@ +default interface foo { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit new file mode 100644 index 0000000000..402c72b61c --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit @@ -0,0 +1,8 @@ +// parse-fail + +interface foo { + use self.bar.{x} +} + +interface bar { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit.result new file mode 100644 index 0000000000..3a43a8feb3 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit.result @@ -0,0 +1,5 @@ +name `x` is not defined + --> tests/ui/parse-fail/unresolved-use2.wit:4:17 + | + 4 | use self.bar.{x} + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit new file mode 100644 index 0000000000..6b34d6cac4 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit @@ -0,0 +1,9 @@ +// parse-fail + +interface foo { + use self.bar.{x} +} + +interface bar { + x: func() +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit.result new file mode 100644 index 0000000000..3d82f08104 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit.result @@ -0,0 +1,5 @@ +cannot import function `x` + --> tests/ui/parse-fail/unresolved-use3.wit:4:17 + | + 4 | use self.bar.{x} + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit new file mode 100644 index 0000000000..a82a94b450 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + use pkg.something-that-does-not-exist.{x} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit.result new file mode 100644 index 0000000000..43bffddede --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit.result @@ -0,0 +1,5 @@ +document `something-that-does-not-exist` does not exist + --> tests/ui/parse-fail/unresolved-use4.wit:4:11 + | + 4 | use pkg.something-that-does-not-exist.{x} + | ^---------------------------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit new file mode 100644 index 0000000000..f349f39f0d --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit @@ -0,0 +1,6 @@ +// parse-fail + +interface foo { + use pkg.unresolved-use5.{x} +} + diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit.result new file mode 100644 index 0000000000..e361c989ae --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit.result @@ -0,0 +1,5 @@ +no `default` interface in document to use from + --> tests/ui/parse-fail/unresolved-use5.wit:4:11 + | + 4 | use pkg.unresolved-use5.{x} + | ^-------------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit new file mode 100644 index 0000000000..125b2e3dcf --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + use pkg.unresolved-use6.nonexistent.{x} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit.result new file mode 100644 index 0000000000..e174fa5426 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit.result @@ -0,0 +1,5 @@ +interface `nonexistent` does not exist + --> tests/ui/parse-fail/unresolved-use6.wit:4:27 + | + 4 | use pkg.unresolved-use6.nonexistent.{x} + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit new file mode 100644 index 0000000000..c08f490950 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit @@ -0,0 +1,8 @@ +// parse-fail + +interface foo { + use pkg.unresolved-use7.bar.{x} +} + +interface bar { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit.result new file mode 100644 index 0000000000..418dd27c65 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit.result @@ -0,0 +1,5 @@ +name `x` is not defined + --> tests/ui/parse-fail/unresolved-use7.wit:4:32 + | + 4 | use pkg.unresolved-use7.bar.{x} + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit new file mode 100644 index 0000000000..dad0d2eb28 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit @@ -0,0 +1,7 @@ +// parse-fail + +world foo { + import foo: interface { + use self.foo.{i32} + } +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit.result new file mode 100644 index 0000000000..8ed307231f --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit.result @@ -0,0 +1,5 @@ +name `foo` is defined as a world, not an interface + --> tests/ui/parse-fail/unresolved-use8.wit:5:14 + | + 5 | use self.foo.{i32} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit new file mode 100644 index 0000000000..83ba81ad7c --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit @@ -0,0 +1,7 @@ +// parse-fail + +world foo { + import foo: interface { + use self.bar.{i32} + } +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit.result new file mode 100644 index 0000000000..7bcfad2e5a --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit.result @@ -0,0 +1,5 @@ +interface does not exist + --> tests/ui/parse-fail/unresolved-use9.wit:5:14 + | + 5 | use self.bar.{i32} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-conflict.wit b/crates/wit-parser/tests/ui/parse-fail/use-conflict.wit new file mode 100644 index 0000000000..20e158e133 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-conflict.wit @@ -0,0 +1,9 @@ +// parse-fail + +interface foo { + type x = u32 +} + +interface bar { + use self.foo.{x, x} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-conflict.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-conflict.wit.result new file mode 100644 index 0000000000..2cbe95d2e2 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-conflict.wit.result @@ -0,0 +1,5 @@ +name `x` is defined more than once + --> tests/ui/parse-fail/use-conflict.wit:8:20 + | + 8 | use self.foo.{x, x} + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit b/crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit new file mode 100644 index 0000000000..11a16519bd --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit @@ -0,0 +1,11 @@ +// parse-fail + +interface foo { + type x = u32 +} + +interface bar { + use self.foo.{x} + + type x = s64 +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit.result new file mode 100644 index 0000000000..3c70f63445 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit.result @@ -0,0 +1,5 @@ +name `x` is defined more than once + --> tests/ui/parse-fail/use-conflict2.wit:10:8 + | + 10 | type x = s64 + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit b/crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit new file mode 100644 index 0000000000..53a85b0318 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit @@ -0,0 +1,11 @@ +// parse-fail + +interface foo { + type x = u32 +} + +interface bar { + use self.foo.{x} + + x: func() +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit.result new file mode 100644 index 0000000000..c9e7e41e21 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit.result @@ -0,0 +1,5 @@ +name `x` is defined more than once + --> tests/ui/parse-fail/use-conflict3.wit:10:3 + | + 10 | x: func() + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit b/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit new file mode 100644 index 0000000000..a1abaeca50 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + use self.foo.{bar} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit.result new file mode 100644 index 0000000000..8c7950ed91 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit.result @@ -0,0 +1,5 @@ +interface `foo` depends on itself + --> tests/ui/parse-fail/use-cycle1.wit:4:12 + | + 4 | use self.foo.{bar} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit b/crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit new file mode 100644 index 0000000000..7d416e30eb --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + use pkg.use-cycle2.foo.{bar} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit.result new file mode 100644 index 0000000000..8af25543a8 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit.result @@ -0,0 +1,5 @@ +interface `foo` depends on itself + --> tests/ui/parse-fail/use-cycle2.wit:4:22 + | + 4 | use pkg.use-cycle2.foo.{bar} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit b/crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit new file mode 100644 index 0000000000..7edf782278 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit @@ -0,0 +1,6 @@ +// parse-fail + +default interface foo { + // TODO: this could use a better error message + use pkg.use-cycle3.{bar} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit.result new file mode 100644 index 0000000000..ff6d459c48 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit.result @@ -0,0 +1,5 @@ +interface `foo` depends on itself + --> tests/ui/parse-fail/use-cycle3.wit:5:11 + | + 5 | use pkg.use-cycle3.{bar} + | ^--------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit b/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit new file mode 100644 index 0000000000..9fe1fbae34 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit @@ -0,0 +1,13 @@ +// parse-fail + +interface foo { + use self.bar.{y} + + type x = u32 +} + +interface bar { + use self.foo.{x} + + type y = u32 +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit.result new file mode 100644 index 0000000000..65b739418c --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit.result @@ -0,0 +1,5 @@ +interface `bar` depends on itself + --> tests/ui/parse-fail/use-cycle4.wit:4:12 + | + 4 | use self.bar.{y} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit new file mode 100644 index 0000000000..5fdf01a23e --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit @@ -0,0 +1,8 @@ +// parse-fail + +world foo { +} + +interface bar { + use pkg.use-from-package-world.foo.{x} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit.result new file mode 100644 index 0000000000..e1dc970fa6 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit.result @@ -0,0 +1,5 @@ +interface does not exist + --> tests/ui/parse-fail/use-from-package-world.wit:7:34 + | + 7 | use pkg.use-from-package-world.foo.{x} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2.wit.result new file mode 100644 index 0000000000..95608cfb58 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2.wit.result @@ -0,0 +1,8 @@ +failed to parse package: tests/ui/parse-fail/use-from-package-world2 + +Caused by: + cannot import from world `foo` + --> tests/ui/parse-fail/use-from-package-world2/bar.wit:2:15 + | + 2 | use pkg.foo.foo.{thing} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/bar.wit b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/bar.wit new file mode 100644 index 0000000000..078c0c348f --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/bar.wit @@ -0,0 +1,3 @@ +interface bar { + use pkg.foo.foo.{thing} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/foo.wit b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/foo.wit new file mode 100644 index 0000000000..c2e6c53e73 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/foo.wit @@ -0,0 +1,2 @@ +world foo { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-default1.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-default1.wit.result deleted file mode 100644 index c46b73ae56..0000000000 --- a/crates/wit-parser/tests/ui/parse-fail/world-default1.wit.result +++ /dev/null @@ -1,5 +0,0 @@ -bar not defined - --> tests/ui/parse-fail/world-default1.wit:4:18 - | - 4 | default export bar - | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-default2.wit b/crates/wit-parser/tests/ui/parse-fail/world-default2.wit deleted file mode 100644 index 202ff1f5e1..0000000000 --- a/crates/wit-parser/tests/ui/parse-fail/world-default2.wit +++ /dev/null @@ -1,6 +0,0 @@ -// parse-fail - -world foo { - default export interface {} - default export interface {} -} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-default2.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-default2.wit.result deleted file mode 100644 index c79e04163e..0000000000 --- a/crates/wit-parser/tests/ui/parse-fail/world-default2.wit.result +++ /dev/null @@ -1,5 +0,0 @@ -more than one default interface was defined - --> tests/ui/parse-fail/world-default2.wit:5:18 - | - 5 | default export interface {} - | ^-------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-default3.wit b/crates/wit-parser/tests/ui/parse-fail/world-default3.wit deleted file mode 100644 index 85751f90f9..0000000000 --- a/crates/wit-parser/tests/ui/parse-fail/world-default3.wit +++ /dev/null @@ -1,8 +0,0 @@ -// parse-fail - -interface foo {} - -world foo { - default export foo - default export foo -} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-default3.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-default3.wit.result deleted file mode 100644 index d6eae69175..0000000000 --- a/crates/wit-parser/tests/ui/parse-fail/world-default3.wit.result +++ /dev/null @@ -1,5 +0,0 @@ -more than one default interface was defined - --> tests/ui/parse-fail/world-default3.wit:7:18 - | - 7 | default export foo - | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit b/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit new file mode 100644 index 0000000000..cbaedc8bc9 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit @@ -0,0 +1,10 @@ +interface foo { + type a = u32 +} + +world the-world { + import bar: interface { + use self.foo.{a} + } + import foo: interface {} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit.result new file mode 100644 index 0000000000..e048165bb4 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit.result @@ -0,0 +1,6 @@ +import of `foo` conflicts with a previously imported interface using the name `foo` + + --> tests/ui/parse-fail/world-implicit-import1.wit:9:10 + | + 9 | import foo: interface {} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit b/crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit new file mode 100644 index 0000000000..edc358f041 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit @@ -0,0 +1,2 @@ +interface foo {} +world foo {} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit.result new file mode 100644 index 0000000000..396ada61ad --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit.result @@ -0,0 +1,5 @@ +name `foo` previously defined in document + --> tests/ui/parse-fail/world-interface-clash.wit:2:7 + | + 2 | world foo {} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit b/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit index c55e74ab88..8906f0d526 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit @@ -4,6 +4,6 @@ interface foo {} interface bar {} world a { - import foo: foo - import foo: bar + import foo: self.foo + import foo: self.bar } diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit.result index 0cdbf6248e..38f1a05b43 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit.result @@ -1,5 +1,5 @@ -duplicate import foo +name `foo` imported more than once --> tests/ui/parse-fail/world-same-fields.wit:8:10 | - 8 | import foo: bar + 8 | import foo: self.bar | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields2.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-fields2.wit.result index 14cfbec88c..b9ce13bd85 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-same-fields2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields2.wit.result @@ -1,4 +1,4 @@ -duplicate import foo +name `foo` imported more than once --> tests/ui/parse-fail/world-same-fields2.wit:5:10 | 5 | import foo: interface {} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit b/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit new file mode 100644 index 0000000000..e48a2aedb5 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit @@ -0,0 +1,6 @@ +// parse-fail + +world a { + export foo: interface {} + export foo: interface {} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit.result new file mode 100644 index 0000000000..5496909e55 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit.result @@ -0,0 +1,5 @@ +name `foo` cannot be exported more than once + --> tests/ui/parse-fail/world-same-fields3.wit:5:10 + | + 5 | export foo: interface {} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit b/crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit new file mode 100644 index 0000000000..766d29fbc3 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit @@ -0,0 +1,11 @@ +interface shared { + type a = u32 +} + +world foo { + import shared: interface {} + export a-name: interface { + use self.shared.{a} + } +} + diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit.result new file mode 100644 index 0000000000..0f6b9bb33a --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit.result @@ -0,0 +1,8 @@ +import of `shared` + .. which is depended on by export `a-name` +conflicts with a previously imported interface using the name `shared` + + --> tests/ui/parse-fail/world-same-fields4.wit:7:10 + | + 7 | export a-name: interface { + | ^----- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit b/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit new file mode 100644 index 0000000000..fedaaa76ca --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit @@ -0,0 +1,6 @@ +interface foo {} + +world bar { + import foo: self.foo + import bar: self.foo +} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit.result new file mode 100644 index 0000000000..e512366c6d --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit.result @@ -0,0 +1,5 @@ +interface `bar` imported more than once + --> tests/ui/parse-fail/world-same-import.wit:5:10 + | + 5 | import bar: self.foo + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit new file mode 100644 index 0000000000..7bf7e5fce9 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit @@ -0,0 +1,4 @@ +world foo { + import foo: func() + import foo: func() +} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit.result new file mode 100644 index 0000000000..3876a43df6 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit.result @@ -0,0 +1,5 @@ +name `foo` imported more than once + --> tests/ui/parse-fail/world-top-level-func.wit:3:10 + | + 3 | import foo: func() + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit new file mode 100644 index 0000000000..d872f06792 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit @@ -0,0 +1,3 @@ +world foo { + import foo: func(a: b) +} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result new file mode 100644 index 0000000000..8e845ca0c3 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result @@ -0,0 +1,5 @@ +name `b` is not defined + --> tests/ui/parse-fail/world-top-level-func2.wit:2:23 + | + 2 | import foo: func(a: b) + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit b/crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit new file mode 100644 index 0000000000..3faea41f1b --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit @@ -0,0 +1,16 @@ +interface i1 { + type t = u32 +} +interface i2 { + use self.i1.{t} +} +interface i3 { + use self.i2.{t} +} + +world test { + import i1: interface {} + + export i1: self.i1 + export i3: self.i3 +} diff --git a/crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit.result b/crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit.result new file mode 100644 index 0000000000..2e13df1a99 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit.result @@ -0,0 +1,9 @@ +import of `i1` + .. which is depended on by import `i2` + .. which is depended on by export `i3` +conflicts with a previously imported interface using the name `i1` + + --> tests/ui/parse-fail/worlds-same-fields5.wit:15:10 + | + 15 | export i3: self.i3 + | ^- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/shared-types.wit b/crates/wit-parser/tests/ui/shared-types.wit index 362e78719b..90caaddfa0 100644 --- a/crates/wit-parser/tests/ui/shared-types.wit +++ b/crates/wit-parser/tests/ui/shared-types.wit @@ -2,7 +2,7 @@ world foo { import foo: interface { a: func() -> list } - default export interface { + export foo: interface { a: func() -> tuple> } } diff --git a/crates/wit-parser/tests/ui/shared-types.wit.result b/crates/wit-parser/tests/ui/shared-types.wit.result deleted file mode 100644 index df992cf66c..0000000000 --- a/crates/wit-parser/tests/ui/shared-types.wit.result +++ /dev/null @@ -1,52 +0,0 @@ -{ - "worlds": [ - { - "name": "foo", - "default": "i1", - "imports": [ - [ - "foo", - "i0" - ] - ] - } - ], - "interfaces": [ - { - "name": "", - "functions": [ - { - "name": "a", - "params": [], - "results": [ - "type-0" - ] - } - ] - }, - { - "name": "", - "functions": [ - { - "name": "a", - "params": [], - "results": [ - "type-1" - ] - } - ] - } - ], - "types": [ - { - "list": "u8" - }, - { - "tuple": { - "types": [ - "type-0" - ] - } - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/type-then-eof.wit.result b/crates/wit-parser/tests/ui/type-then-eof.wit.result deleted file mode 100644 index 2817773224..0000000000 --- a/crates/wit-parser/tests/ui/type-then-eof.wit.result +++ /dev/null @@ -1,16 +0,0 @@ -{ - "interfaces": [ - { - "name": "foo", - "functions": [ - { - "name": "foo", - "params": [], - "results": [ - "string" - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/types.wit.result b/crates/wit-parser/tests/ui/types.wit.result deleted file mode 100644 index f412903d22..0000000000 --- a/crates/wit-parser/tests/ui/types.wit.result +++ /dev/null @@ -1,418 +0,0 @@ -{ - "interfaces": [ - { - "name": "types", - "types": [ - "type-0", - "type-1", - "type-2", - "type-3", - "type-4", - "type-5", - "type-6", - "type-7", - "type-8", - "type-9", - "type-10", - "type-11", - "type-12", - "type-13", - "type-14", - "type-15", - "type-16", - "type-17", - "type-18", - "type-19", - "type-20", - "type-21", - "type-22", - "type-23", - "type-24", - "type-25", - "type-26", - "type-27", - "type-28", - "type-29", - "type-30", - "type-31", - "type-32", - "type-33", - "type-34", - "type-35", - "type-36", - "type-37", - "type-38", - "type-39", - "type-40", - "type-41", - "type-42", - "type-43", - "type-44", - "type-45", - "type-46", - "type-47", - "type-48", - "type-49", - "type-50", - "type-51", - "type-52", - "type-53", - "type-54" - ] - } - ], - "types": [ - { - "primitive": "u8" - }, - { - "primitive": "u16" - }, - { - "primitive": "u32" - }, - { - "primitive": "u64" - }, - { - "primitive": "s8" - }, - { - "primitive": "s16" - }, - { - "primitive": "s32" - }, - { - "primitive": "s64" - }, - { - "primitive": "float32" - }, - { - "primitive": "float64" - }, - { - "primitive": "char" - }, - { - "list": "char" - }, - { - "primitive": "string" - }, - { - "option": "u32" - }, - { - "result": { - "ok": "u32", - "err": "u32" - } - }, - { - "result": { - "ok": null, - "err": "u32" - } - }, - { - "result": { - "ok": "u32", - "err": null - } - }, - { - "result": { - "ok": null, - "err": null - } - }, - { - "record": { - "fields": [] - } - }, - { - "record": { - "fields": [ - [ - "a", - "u32" - ] - ] - } - }, - { - "record": { - "fields": [ - [ - "a", - "u32" - ] - ] - } - }, - { - "record": { - "fields": [ - [ - "a", - "u32" - ], - [ - "b", - "u64" - ] - ] - } - }, - { - "record": { - "fields": [ - [ - "a", - "u32" - ], - [ - "b", - "u64" - ] - ] - } - }, - { - "record": { - "fields": [ - [ - "x", - "u32" - ] - ] - } - }, - { - "record": { - "fields": [] - } - }, - { - "tuple": { - "types": [] - } - }, - { - "tuple": { - "types": [ - "u32" - ] - } - }, - { - "tuple": { - "types": [ - "u32" - ] - } - }, - { - "tuple": { - "types": [ - "u32", - "u64" - ] - } - }, - { - "flags": { - "flags": [] - } - }, - { - "flags": { - "flags": [ - "a", - "b", - "c" - ] - } - }, - { - "flags": { - "flags": [ - "a", - "b", - "c" - ] - } - }, - { - "variant": { - "cases": [ - [ - "a", - null - ] - ] - } - }, - { - "variant": { - "cases": [ - [ - "a", - null - ], - [ - "b", - null - ] - ] - } - }, - { - "variant": { - "cases": [ - [ - "a", - null - ], - [ - "b", - null - ] - ] - } - }, - { - "variant": { - "cases": [ - [ - "a", - null - ], - [ - "b", - "u32" - ] - ] - } - }, - { - "variant": { - "cases": [ - [ - "a", - null - ], - [ - "b", - "type-55" - ] - ] - } - }, - { - "union": { - "cases": [ - "u32", - "u64" - ] - } - }, - { - "union": { - "cases": [ - "u32", - "type-55" - ] - } - }, - { - "union": { - "cases": [ - "u32", - "type-55" - ] - } - }, - { - "enum": { - "cases": [ - "a", - "b", - "c" - ] - } - }, - { - "enum": { - "cases": [ - "a", - "b", - "c" - ] - } - }, - { - "primitive": "bool" - }, - { - "primitive": "string" - }, - { - "list": "type-57" - }, - { - "primitive": "type-43" - }, - { - "primitive": "type-43" - }, - { - "stream": { - "element": "u32", - "end": "u32" - } - }, - { - "stream": { - "element": null, - "end": "u32" - } - }, - { - "stream": { - "element": "u32", - "end": null - } - }, - { - "stream": { - "element": null, - "end": null - } - }, - { - "future": "u32" - }, - { - "future": null - }, - { - "primitive": "type-54" - }, - { - "primitive": "u32" - }, - { - "option": "u32" - }, - { - "list": "type-31" - }, - { - "list": "type-56" - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/use.wit b/crates/wit-parser/tests/ui/use.wit new file mode 100644 index 0000000000..11d67325c4 --- /dev/null +++ b/crates/wit-parser/tests/ui/use.wit @@ -0,0 +1,31 @@ +interface foo { + use self.bar.{the-type} +} + +interface bar { + type the-type = u32 +} + +interface baz { + use self.foo.{the-type} + use self.bar.{the-type as test} +} + +interface empty { +} + +interface use-from-empty { + use self.empty.{} + use pkg.%use.empty.{} +} + + +interface use-multiple { + use self.baz.{the-type, test} + + some-function: func(x: the-type) -> test +} + +interface trailing-comma { + use self.foo.{the-type,} +} diff --git a/crates/wit-parser/tests/ui/wasi.wit.result b/crates/wit-parser/tests/ui/wasi.wit.result deleted file mode 100644 index 3357f47bb5..0000000000 --- a/crates/wit-parser/tests/ui/wasi.wit.result +++ /dev/null @@ -1,108 +0,0 @@ -{ - "interfaces": [ - { - "name": "wasi", - "types": [ - "type-0", - "type-1", - "type-2" - ] - } - ], - "types": [ - { - "enum": { - "cases": [ - "realtime", - "monotonic" - ] - } - }, - { - "primitive": "u64" - }, - { - "enum": { - "cases": [ - "success", - "toobig", - "access", - "addrinuse", - "addrnotavail", - "afnosupport", - "again", - "already", - "badf", - "badmsg", - "busy", - "canceled", - "child", - "connaborted", - "connrefused", - "connreset", - "deadlk", - "destaddrreq", - "dom", - "dquot", - "exist", - "fault", - "fbig", - "hostunreach", - "idrm", - "ilseq", - "inprogress", - "intr", - "inval", - "io", - "isconn", - "isdir", - "loop", - "mfile", - "mlink", - "msgsize", - "multihop", - "nametoolong", - "netdown", - "netreset", - "netunreach", - "nfile", - "nobufs", - "nodev", - "noent", - "noexec", - "nolck", - "nolink", - "nomem", - "nomsg", - "noprotoopt", - "nospc", - "nosys", - "notconn", - "notdir", - "notempty", - "notrecoverable", - "notsock", - "notsup", - "notty", - "nxio", - "overflow", - "ownerdead", - "perm", - "pipe", - "proto", - "protonosupport", - "prototype", - "range", - "rofs", - "spipe", - "srch", - "stale", - "timedout", - "txtbsy", - "xdev", - "notcapable" - ] - } - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/world-default.wit b/crates/wit-parser/tests/ui/world-default.wit deleted file mode 100644 index 0f15c22b62..0000000000 --- a/crates/wit-parser/tests/ui/world-default.wit +++ /dev/null @@ -1,7 +0,0 @@ -interface foo { - a: func() - } - -world bar { - default export foo -} diff --git a/crates/wit-parser/tests/ui/world-default.wit.result b/crates/wit-parser/tests/ui/world-default.wit.result deleted file mode 100644 index 67ccdedab4..0000000000 --- a/crates/wit-parser/tests/ui/world-default.wit.result +++ /dev/null @@ -1,20 +0,0 @@ -{ - "worlds": [ - { - "name": "bar", - "default": "i0" - } - ], - "interfaces": [ - { - "name": "foo", - "functions": [ - { - "name": "a", - "params": [], - "results": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/world-diamond.wit b/crates/wit-parser/tests/ui/world-diamond.wit new file mode 100644 index 0000000000..5de78b35c5 --- /dev/null +++ b/crates/wit-parser/tests/ui/world-diamond.wit @@ -0,0 +1,20 @@ +interface shared { + type foo = u32 +} + +interface i1 { + use self.shared.{foo} + + a: func() -> foo +} + +interface i2 { + use self.shared.{foo} + + a: func() -> foo +} + +world the-world { + import i1: self.i1 + import i2: self.i2 +} diff --git a/crates/wit-parser/tests/ui/world-top-level-funcs.wit b/crates/wit-parser/tests/ui/world-top-level-funcs.wit new file mode 100644 index 0000000000..9d89a682ed --- /dev/null +++ b/crates/wit-parser/tests/ui/world-top-level-funcs.wit @@ -0,0 +1,7 @@ +world foo { + import foo: func() + export foo: func() + + import bar: func(arg: list) + export some-name: func() -> list> +} diff --git a/crates/wit-parser/tests/ui/worlds.wit b/crates/wit-parser/tests/ui/worlds.wit index 61f61cd75c..3cd125117d 100644 --- a/crates/wit-parser/tests/ui/worlds.wit +++ b/crates/wit-parser/tests/ui/worlds.wit @@ -2,17 +2,39 @@ interface foo {} interface bar {} world the-world { - import foo: foo - import bar: bar + import foo: self.foo + import bar: self.bar import baz: interface { foo: func() } - export foo: foo - export bar: bar + export foo: self.foo + export bar: self.bar export baz: interface { foo: func() } +} - default export interface { - } +default world a-different-world { + import foo: self.foo +} + +interface i1 { + type t = u32 +} +interface i2 { + use self.i1.{t} +} +interface i3 { + use self.i2.{t} +} + +world test { + import i3: self.i3 + + export i1: self.i1 + + // This should insert an implicit dependency on `i2` as an import, and then + // i2's dependency on i1 should be wired up to i3's implicit imported + // dependency on i1. + export i3: self.i3 } diff --git a/crates/wit-parser/tests/ui/worlds.wit.result b/crates/wit-parser/tests/ui/worlds.wit.result deleted file mode 100644 index 560a74a7b7..0000000000 --- a/crates/wit-parser/tests/ui/worlds.wit.result +++ /dev/null @@ -1,67 +0,0 @@ -{ - "worlds": [ - { - "name": "the-world", - "default": "i4", - "imports": [ - [ - "foo", - "i0" - ], - [ - "bar", - "i1" - ], - [ - "baz", - "i2" - ] - ], - "exports": [ - [ - "foo", - "i0" - ], - [ - "bar", - "i1" - ], - [ - "baz", - "i3" - ] - ] - } - ], - "interfaces": [ - { - "name": "foo" - }, - { - "name": "bar" - }, - { - "name": "", - "functions": [ - { - "name": "foo", - "params": [], - "results": [] - } - ] - }, - { - "name": "", - "functions": [ - { - "name": "foo", - "params": [], - "results": [] - } - ] - }, - { - "name": "" - } - ] -} \ No newline at end of file diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index c1d9e28a7e..8c44d97d75 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,6 +21,8 @@ wasmprinter = { path = "../crates/wasmprinter" } wasmtime = { workspace = true, optional = true } wast = { path = "../crates/wast" } wat = { path = "../crates/wat" } +wit-parser = { path = "../crates/wit-parser" } +wit-component = { path = "../crates/wit-component" } [lib] test = false @@ -75,3 +77,9 @@ name = "no-traps" path = "fuzz_targets/no-traps.rs" test = false doc = false + +[[bin]] +name = "roundtrip-wit" +path = "fuzz_targets/roundtrip-wit.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/roundtrip-wit.rs b/fuzz/fuzz_targets/roundtrip-wit.rs new file mode 100644 index 0000000000..5bf6b9ef2d --- /dev/null +++ b/fuzz/fuzz_targets/roundtrip-wit.rs @@ -0,0 +1,724 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use std::collections::HashMap; +use std::path::Path; +use wit_component::*; +use wit_parser::{PackageId, Resolve, SourceMap}; + +fuzz_target!(|data: &[u8]| { + drop(env_logger::try_init()); + + let mut u = arbitrary::Unstructured::new(data); + let pkgs = match generate::packages(&mut u) { + Ok(doc) => doc, + Err(_) => return, + }; + let mut resolve = Resolve::default(); + let mut deps = HashMap::new(); + let mut last = None; + for pkg in pkgs { + let url = format!("my-scheme:/{}", pkg.name); + let unresolved = pkg.sources.parse(&pkg.name, Some(&url)).unwrap(); + let id = resolve.push(unresolved, &deps).unwrap(); + let prev = deps.insert(pkg.name, id); + assert!(prev.is_none()); + last = Some(id); + } + let pkg = last.unwrap(); + let wasm = roundtrip_through_printing("doc1", &resolve, pkg); + + let name = &resolve.packages[pkg].name; + let (resolve2, pkg2) = match wit_component::decode(&name, &wasm).unwrap() { + DecodedWasm::WitPackage(resolve, pkg) => (resolve, pkg), + DecodedWasm::Component(..) => unreachable!(), + }; + + let wasm2 = roundtrip_through_printing("doc2", &resolve2, pkg2); + + if wasm != wasm2 { + panic!("roundtrip wasm didn't match"); + } +}); + +fn roundtrip_through_printing(file: &str, resolve: &Resolve, pkg: PackageId) -> Vec { + // Encode `resolve` to wasm as the baseline expectation + let wasm = wit_component::encode(resolve, pkg).unwrap(); + write_file(&format!("{file}.wasm"), &wasm); + wasmparser::Validator::new_with_features(wasmparser::WasmFeatures { + component_model: true, + ..Default::default() + }) + .validate_all(&wasm) + .unwrap(); + + // For all packages in `resolve` print them all to a string, then re-parse + // them and insert them into a `new_resolve`. + let mut new_deps = HashMap::new(); + let mut new_resolve = Resolve::default(); + let mut last = None; + for (_, pkg) in resolve.packages.iter() { + let mut map = SourceMap::new(); + let pkg_name = &pkg.name; + for (name, doc) in pkg.documents.iter() { + let doc = DocumentPrinter::default().print(resolve, *doc).unwrap(); + write_file(&format!("{file}-{pkg_name}-{name}.wit"), &doc); + map.push(format!("{name}.wit").as_ref(), &name, doc); + } + let unresolved = map.parse(&pkg.name, pkg.url.as_deref()).unwrap(); + let id = new_resolve.push(unresolved, &new_deps).unwrap(); + new_deps.insert(pkg.name.clone(), id); + last = Some(id); + } + + // Finally encode the `new_resolve` which should be the exact same as + // before. + let wasm2 = wit_component::encode(&new_resolve, last.unwrap()).unwrap(); + write_file(&format!("{file}-reencoded.wasm"), &wasm2); + if wasm != wasm2 { + panic!("failed to roundtrip through text printing"); + } + + wasm +} + +fn write_file(path: &str, contents: impl AsRef<[u8]>) { + if !log::log_enabled!(log::Level::Debug) { + return; + } + log::debug!("writing file {path}"); + let contents = contents.as_ref(); + let path = Path::new(path); + std::fs::write(path, contents).unwrap(); + if path.extension().and_then(|s| s.to_str()) == Some("wasm") { + let path = path.with_extension("wat"); + log::debug!("writing file {}", path.display()); + std::fs::write(path, wasmprinter::print_bytes(&contents).unwrap()).unwrap(); + } +} + +mod generate { + use arbitrary::{Arbitrary, Result, Unstructured}; + use std::collections::HashSet; + use std::mem; + use std::str; + use wit_parser::*; + + const MAX_PARTS: usize = 5; + const MAX_PACKAGES: usize = 10; + const MAX_DOCUMENTS: usize = 10; + const MAX_DOC_ITEMS: usize = 10; + const MAX_WORLD_ITEMS: usize = 10; + const MAX_INTERFACE_ITEMS: usize = 10; + + #[derive(Default)] + struct Generator { + packages: PackageList, + documents: DocumentList, + interfaces: InterfaceList, + next_interface_id: u32, + } + + type TypeList = Vec<(String, usize)>; + type InterfaceList = Vec<(String, u32, bool, TypeList)>; + type DocumentList = Vec<(String, InterfaceList)>; + type PackageList = Vec<(String, DocumentList)>; + + struct InterfaceGenerator<'a> { + gen: &'a Generator, + unique_names: HashSet, + types_in_interface: Vec<(String, usize)>, + } + + pub struct Package { + pub name: String, + pub sources: SourceMap, + } + + pub fn packages(u: &mut Unstructured<'_>) -> Result> { + Generator::default().gen(u) + } + + impl Generator { + fn gen(&mut self, u: &mut Unstructured<'_>) -> Result> { + let mut packages = Vec::new(); + let mut names = HashSet::new(); + while packages.len() < MAX_PACKAGES && (packages.is_empty() || u.arbitrary()?) { + let name = gen_unique_name(u, &mut names)?; + let (sources, documents) = self.gen_package(&name, u)?; + self.packages.push((name.clone(), documents)); + packages.push(Package { name, sources }); + } + Ok(packages) + } + + fn gen_package( + &mut self, + pkg: &str, + u: &mut Unstructured<'_>, + ) -> Result<(SourceMap, DocumentList)> { + let mut map = SourceMap::new(); + let mut count = 0; + let mut names = HashSet::new(); + + while self.documents.len() < MAX_DOCUMENTS && (count == 0 || u.arbitrary()?) { + let name = gen_unique_name(u, &mut names)?; + let (doc, interfaces) = self.gen_document(u)?; + super::write_file(format!("orig-{pkg}-{name}.wit").as_ref(), &doc); + map.push(format!("{name}.wit").as_ref(), &name, doc); + count += 1; + self.documents.push((name, interfaces)); + } + + Ok((map, mem::take(&mut self.documents))) + } + + fn gen_document(&mut self, u: &mut Unstructured<'_>) -> Result<(String, InterfaceList)> { + #[derive(Arbitrary)] + enum Generate { + World, + Interface, + Done, + } + + let mut pieces = Vec::new(); + let mut has_default_interface = false; + let mut has_default_world = false; + let mut names = HashSet::new(); + while pieces.len() < MAX_DOC_ITEMS && !u.is_empty() { + let name = gen_unique_name(u, &mut names)?; + match u.arbitrary()? { + Generate::World => { + pieces.push(self.gen_world(u, &name, &mut has_default_world)?) + } + Generate::Interface => { + let id = self.next_interface_id; + self.next_interface_id += 1; + let (src, types) = + self.gen_interface(u, Some(&name), &mut has_default_interface)?; + self.interfaces + .push((name, id, src.starts_with("default"), types)); + pieces.push(src); + } + Generate::Done => break, + } + } + shuffle(u, &mut pieces)?; + let mut ret = String::new(); + for piece in pieces { + ret.push_str(&piece); + ret.push_str("\n\n"); + } + Ok((ret, mem::take(&mut self.interfaces))) + } + + fn gen_world( + &mut self, + u: &mut Unstructured<'_>, + name: &str, + has_default: &mut bool, + ) -> Result { + let mut ret = String::new(); + if !*has_default && u.arbitrary()? { + *has_default = true; + ret.push_str("default "); + } + ret.push_str("world %"); + ret.push_str(name); + ret.push_str(" {\n"); + + #[derive(Arbitrary)] + enum Direction { + Import, + Export, + } + + #[derive(Arbitrary)] + enum ItemKind { + Func, + Interface, + AnonInterface, + } + + let mut parts = Vec::new(); + let mut imports = HashSet::new(); + let mut exports = HashSet::new(); + let mut imported_interfaces = HashSet::new(); + let mut exported_interfaces = HashSet::new(); + + while parts.len() < MAX_WORLD_ITEMS && !u.is_empty() && u.arbitrary()? { + let mut part = String::new(); + let (desc, names, interfaces) = match u.arbitrary()? { + Direction::Import => ("import", &mut imports, &mut imported_interfaces), + Direction::Export => ("export", &mut exports, &mut exported_interfaces), + }; + part.push_str(desc); + part.push_str(" %"); + part.push_str(&gen_unique_name(u, names)?); + part.push_str(": "); + + match u.arbitrary()? { + ItemKind::Func => { + InterfaceGenerator::new(self).gen_func_sig(u, &mut part)?; + } + ItemKind::Interface => { + let id = match self.gen_path(u, &mut part)? { + Some((id, _types)) => id, + // If an interface couldn't be chosen or wasn't + // chosen then skip this import. A unique name was + // selecteed above but we just sort of leave that + // floating in the wild to get handled by some other + // test case. + None => continue, + }; + + // If this interface has already been imported or + // exported this document can't do so again. Throw out + // this item in that situation. + if !interfaces.insert(id) { + continue; + } + } + ItemKind::AnonInterface => { + let iface = InterfaceGenerator::new(self).gen(u, None, &mut true)?; + part.push_str(&iface); + } + } + parts.push(part); + } + + shuffle(u, &mut parts)?; + + for part in parts { + ret.push_str(&part); + ret.push_str("\n"); + } + + ret.push_str("}"); + + Ok(ret) + } + + fn gen_interface( + &mut self, + u: &mut Unstructured<'_>, + name: Option<&str>, + has_default: &mut bool, + ) -> Result<(String, TypeList)> { + let mut gen = InterfaceGenerator::new(self); + let ret = gen.gen(u, name, has_default)?; + Ok((ret, gen.types_in_interface)) + } + + fn gen_path( + &self, + u: &mut Unstructured<'_>, + dst: &mut String, + ) -> Result> { + Ok(if !self.interfaces.is_empty() && u.arbitrary()? { + dst.push_str("self."); + let (name, id, _default, types) = u.choose(&self.interfaces)?; + dst.push_str("%"); + dst.push_str(name); + Some((*id, types)) + } else if !self.documents.is_empty() && u.arbitrary()? { + dst.push_str("pkg."); + let (name, ifaces) = u.choose(&self.documents)?; + dst.push_str("%"); + dst.push_str(name); + let (name, id, default, types) = u.choose(ifaces)?; + if !*default || !u.arbitrary()? { + dst.push_str("."); + dst.push_str("%"); + dst.push_str(name); + } + Some((*id, types)) + } else if !self.packages.is_empty() && u.arbitrary()? { + let (name, docs) = u.choose(&self.packages)?; + dst.push_str("%"); + dst.push_str(name); + dst.push_str("."); + let (name, ifaces) = u.choose(docs)?; + dst.push_str("%"); + dst.push_str(name); + let (name, id, default, types) = u.choose(ifaces)?; + if !*default || !u.arbitrary()? { + dst.push_str("."); + dst.push_str("%"); + dst.push_str(name); + } + Some((*id, types)) + } else { + None + }) + } + } + + impl<'a> InterfaceGenerator<'a> { + fn new(gen: &'a mut Generator) -> InterfaceGenerator<'a> { + InterfaceGenerator { + gen, + types_in_interface: Vec::new(), + unique_names: HashSet::new(), + } + } + + fn gen( + &mut self, + u: &mut Unstructured<'_>, + name: Option<&str>, + has_default: &mut bool, + ) -> Result { + let mut ret = String::new(); + if !*has_default && u.arbitrary()? { + *has_default = true; + ret.push_str("default "); + } + ret.push_str("interface "); + if let Some(name) = name { + ret.push_str("%"); + ret.push_str(name); + ret.push_str(" "); + } + ret.push_str("{\n"); + + #[derive(Arbitrary)] + enum Generate { + Use, + Type, + Function, + } + + let mut parts = Vec::new(); + while parts.len() < MAX_INTERFACE_ITEMS && u.arbitrary()? { + match u.arbitrary()? { + Generate::Use => { + let mut path = String::new(); + let (_id, types) = match self.gen.gen_path(u, &mut path)? { + Some(types) => types, + None => continue, + }; + ret.push_str("use "); + ret.push_str(&path); + ret.push_str(".{"); + let (name, size) = u.choose(types)?; + ret.push_str("%"); + ret.push_str(name); + let name = if self.unique_names.contains(name) || u.arbitrary()? { + ret.push_str(" as %"); + let name = self.gen_unique_name(u)?; + ret.push_str(&name); + name + } else { + name.clone() + }; + self.types_in_interface.push((name, *size)); + ret.push_str("}"); + } + Generate::Type => { + let name = self.gen_unique_name(u)?; + let (size, typedef) = self.gen_typedef(u, &name)?; + parts.push(typedef); + self.types_in_interface.push((name, size)); + } + Generate::Function => { + parts.push(self.gen_func(u)?); + } + } + } + + shuffle(u, &mut parts)?; + for part in parts { + ret.push_str(&part); + ret.push_str("\n\n"); + } + + self.types_in_interface.clear(); + ret.push_str("}"); + Ok(ret) + } + + fn gen_typedef(&mut self, u: &mut Unstructured<'_>, name: &str) -> Result<(usize, String)> { + #[derive(Arbitrary)] + pub enum Kind { + Record, + Flags, + Variant, + Enum, + Union, + Anonymous, + } + + let mut size = 1; + let mut ret = String::new(); + match u.arbitrary()? { + Kind::Record => { + ret.push_str("record %"); + ret.push_str(name); + ret.push_str(" {\n"); + for _ in 0..u.int_in_range(0..=MAX_PARTS)? { + ret.push_str(" %"); + ret.push_str(&self.gen_unique_name(u)?); + ret.push_str(": "); + self.gen_type(u, &mut size, &mut ret)?; + ret.push_str(",\n"); + } + ret.push_str("}"); + } + Kind::Variant => { + ret.push_str("variant %"); + ret.push_str(name); + ret.push_str(" {\n"); + for _ in 0..u.int_in_range(1..=MAX_PARTS)? { + ret.push_str(" %"); + ret.push_str(&self.gen_unique_name(u)?); + if u.arbitrary()? { + ret.push_str("("); + self.gen_type(u, &mut size, &mut ret)?; + ret.push_str(")"); + } + ret.push_str(",\n"); + } + ret.push_str("}"); + } + Kind::Union => { + ret.push_str("union %"); + ret.push_str(name); + ret.push_str(" {\n"); + for _ in 0..u.int_in_range(1..=MAX_PARTS)? { + ret.push_str(" "); + self.gen_type(u, &mut size, &mut ret)?; + ret.push_str(",\n"); + } + ret.push_str("}"); + } + Kind::Enum => { + ret.push_str("enum %"); + ret.push_str(name); + ret.push_str(" {\n"); + for _ in 0..u.int_in_range(1..=MAX_PARTS)? { + ret.push_str(" %"); + ret.push_str(&self.gen_unique_name(u)?); + ret.push_str(",\n"); + } + ret.push_str("}"); + } + Kind::Flags => { + ret.push_str("flags %"); + ret.push_str(name); + ret.push_str(" {\n"); + for _ in 0..u.int_in_range(0..=MAX_PARTS)? { + ret.push_str(" %"); + ret.push_str(&self.gen_unique_name(u)?); + ret.push_str(",\n"); + } + ret.push_str("}"); + } + Kind::Anonymous => { + ret.push_str("type %"); + ret.push_str(name); + ret.push_str(" = "); + self.gen_type(u, &mut size, &mut ret)?; + } + } + + Ok((size, ret)) + } + + fn gen_type( + &mut self, + u: &mut Unstructured<'_>, + size: &mut usize, + dst: &mut String, + ) -> Result<()> { + const MAX_SIZE: usize = 100; + + #[derive(Arbitrary)] + enum Kind { + Bool, + U8, + U16, + U32, + U64, + S8, + S16, + S32, + S64, + Float32, + Float64, + Char, + String, + Id, + Tuple, + Option, + Result, + List, + } + + *size += 1; + if *size >= MAX_SIZE { + dst.push_str("bool"); + return Ok(()); + } + loop { + break match u.arbitrary()? { + Kind::Bool => dst.push_str("bool"), + Kind::U8 => dst.push_str("u8"), + Kind::S8 => dst.push_str("s8"), + Kind::U16 => dst.push_str("u16"), + Kind::S16 => dst.push_str("s16"), + Kind::U32 => dst.push_str("u32"), + Kind::S32 => dst.push_str("s32"), + Kind::U64 => dst.push_str("u64"), + Kind::S64 => dst.push_str("s64"), + Kind::Float32 => dst.push_str("float32"), + Kind::Float64 => dst.push_str("float64"), + Kind::Char => dst.push_str("char"), + Kind::String => dst.push_str("string"), + Kind::Id => { + if self.types_in_interface.is_empty() { + continue; + } + let (name, type_size) = u.choose(&self.types_in_interface)?; + if *size + *type_size > MAX_SIZE { + continue; + } + *size += *type_size; + dst.push_str("%"); + dst.push_str(name); + } + Kind::Tuple => { + dst.push_str("tuple<"); + for i in 0..u.int_in_range(0..=MAX_PARTS)? { + if i > 0 { + dst.push_str(", "); + } + self.gen_type(u, size, dst)?; + } + dst.push_str(">"); + } + Kind::Option => { + dst.push_str("option<"); + self.gen_type(u, size, dst)?; + dst.push_str(">"); + } + Kind::List => { + dst.push_str("list<"); + self.gen_type(u, size, dst)?; + dst.push_str(">"); + } + Kind::Result => { + dst.push_str("result"); + let ok = u.arbitrary()?; + let err = u.arbitrary()?; + match (ok, err) { + (true, true) => { + dst.push_str("<"); + self.gen_type(u, size, dst)?; + dst.push_str(", "); + self.gen_type(u, size, dst)?; + dst.push_str(">"); + } + (true, false) => { + dst.push_str("<"); + self.gen_type(u, size, dst)?; + dst.push_str(">"); + } + (false, true) => { + dst.push_str("<_, "); + self.gen_type(u, size, dst)?; + dst.push_str(">"); + } + (false, false) => {} + } + } + }; + } + + Ok(()) + } + + fn gen_func(&mut self, u: &mut Unstructured<'_>) -> Result { + let mut ret = "%".to_string(); + ret.push_str(&self.gen_unique_name(u)?); + ret.push_str(": "); + self.gen_func_sig(u, &mut ret)?; + Ok(ret) + } + + fn gen_func_sig(&mut self, u: &mut Unstructured<'_>, dst: &mut String) -> Result<()> { + dst.push_str("func"); + self.gen_params(u, dst)?; + if u.arbitrary()? { + dst.push_str(" -> "); + self.gen_params(u, dst)?; + } else if u.arbitrary()? { + dst.push_str(" -> "); + self.gen_type(u, &mut 1, dst)?; + } + Ok(()) + } + + fn gen_params(&mut self, u: &mut Unstructured<'_>, dst: &mut String) -> Result<()> { + dst.push_str("("); + for i in 0..u.int_in_range(0..=MAX_PARTS)? { + if i > 0 { + dst.push_str(", "); + } + dst.push_str("%"); + dst.push_str(&self.gen_unique_name(u)?); + dst.push_str(": "); + self.gen_type(u, &mut 1, dst)?; + } + dst.push_str(")"); + Ok(()) + } + + fn gen_unique_name(&mut self, u: &mut Unstructured<'_>) -> Result { + gen_unique_name(u, &mut self.unique_names) + } + } + + fn gen_unique_name(u: &mut Unstructured<'_>, set: &mut HashSet) -> Result { + use std::fmt::Write; + let mut name = gen_name(u)?; + while !set.insert(name.clone()) { + write!(&mut name, "{}", set.len()).unwrap(); + } + Ok(name) + } + + fn gen_name(u: &mut Unstructured<'_>) -> Result { + let size = u.arbitrary_len::()?; + let size = std::cmp::min(size, 20); + let name = match str::from_utf8(u.peek_bytes(size).unwrap()) { + Ok(s) => { + u.bytes(size).unwrap(); + s.to_string() + } + Err(e) => { + let i = e.valid_up_to(); + let valid = u.bytes(i).unwrap(); + str::from_utf8(valid).unwrap().to_string() + } + }; + let name = name + .chars() + .map(|x| if x.is_ascii_lowercase() { x } else { 'x' }) + .collect::(); + Ok(if name.is_empty() { + "name".to_string() + } else { + name + }) + } + + fn shuffle(u: &mut Unstructured<'_>, mut slice: &mut [T]) -> Result<()> { + while slice.len() > 0 { + let pos = u.int_in_range(0..=slice.len() - 1)?; + slice.swap(0, pos); + slice = &mut slice[1..]; + } + Ok(()) + } +} diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index 60d5022113..6f79f5f47d 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -1,17 +1,20 @@ //! The WebAssembly component tool command line interface. -use anyhow::{Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use clap::Parser; +use std::io::Read; use std::path::{Path, PathBuf}; +use wasm_encoder::{Encode, Section}; use wasm_tools::Output; -use wit_component::{decode_world, ComponentEncoder, DocumentPrinter, StringEncoding}; -use wit_parser::Document; +use wit_component::{ComponentEncoder, DecodedWasm, DocumentPrinter, StringEncoding}; +use wit_parser::{PackageId, Resolve, UnresolvedPackage}; /// WebAssembly wit-based component tooling. #[derive(Parser)] pub enum Opts { New(NewOpts), Wit(WitOpts), + Embed(EmbedOpts), } impl Opts { @@ -19,6 +22,7 @@ impl Opts { match self { Opts::New(new) => new.run(), Opts::Wit(wit) => wit.run(), + Opts::Embed(embed) => embed.run(), } } } @@ -48,13 +52,18 @@ fn parse_adapter(s: &str) -> Result<(String, Vec)> { /// WebAssembly component encoder from an input core wasm binary. /// /// This subcommand will create a new component `*.wasm` file from an input core -/// wasm binary. The input core wasm binary is expected to be compiled with -/// `wit-component` or derivative projects which encodes component-based type -/// information into the input core wasm binary's custom sections. The `--wit` -/// option can also be used to specify the interface manually too. +/// wasm binary. The input core wasm binary must have metadata embedded within +/// it about the component-types used during its compilation. This is done +/// automatically for `wit-bindgen`-based projects, for example, and can be +/// manually done through the `wasm-tools component embed` subcommand. +/// +/// This command will perform translation by collecting all type information +/// used during compilation of the core wasm module and will produce a component +/// with all of this type information resolved. #[derive(Parser)] pub struct NewOpts { - /// The path to an adapter module to satisfy imports. + /// The path to an adapter module to satisfy imports not otherwise bound to + /// WIT interfaces. /// /// An adapter module can be used to translate the `wasi_snapshot_preview1` /// ABI, for example, to one that uses the component model. The first @@ -71,19 +80,66 @@ pub struct NewOpts { #[clap(flatten)] io: wasm_tools::InputOutput, - /// The "world" that the input binary implements. - /// - /// This argument is a `*.wit` file which describes the imports and exports - /// of the core wasm module. Users of `wit-bindgen` don't need this as those - /// generators already embed this information into the input core wasm - /// binary. - #[clap(long, value_name = "PATH")] - wit: Option, - /// Skip validation of the output component. #[clap(long)] skip_validation: bool, + /// Print the output in the WebAssembly text format instead of binary. + #[clap(long, short = 't')] + wat: bool, +} + +impl NewOpts { + /// Executes the application. + fn run(self) -> Result<()> { + let wasm = self.io.parse_input_wasm()?; + let mut encoder = ComponentEncoder::default() + .validate(!self.skip_validation) + .module(&wasm)?; + + for (name, wasm) in self.adapters.iter() { + encoder = encoder.adapter(name, wasm)?; + } + + let bytes = encoder + .encode() + .context("failed to encode a component from module")?; + + self.io.output(Output::Wasm { + bytes: &bytes, + wat: self.wat, + })?; + + Ok(()) + } +} + +/// Embeds metadata for a component inside of a core wasm module. +/// +/// This subcommand is a convenience tool provided for producing core wasm +/// binaries which will get consumed by `wasm-tools component new`. This will +/// embed metadata for a component within a core wasm binary as a custom +/// section. +/// +/// This metadata describe the imports and exports of a core wasm module with a +/// WIT package's `world`. The metadata will be used when creating a full +/// component. +/// +/// Note that this subcommand may not be required most of the time since most +/// language tooling will already embed this metadata in the final wasm binary +/// for you. This is primarily intended for one-off testing or for developers +/// working with text format wasm. +#[derive(Parser)] +pub struct EmbedOpts { + /// The WIT package where the `world` that the core wasm module implements + /// lives. + /// + /// This can either be a directory or a path to a single `*.wit` file. + wit: PathBuf, + + #[clap(flatten)] + io: wasm_tools::InputOutput, + /// The expected string encoding format for the component. /// /// Supported values are: `utf8` (default), `utf16`, and `compact-utf16`. @@ -92,96 +148,343 @@ pub struct NewOpts { #[clap(long, value_name = "ENCODING")] encoding: Option, - /// Print the output in the WebAssembly text format instead of binary. - #[clap(long, short = 't')] - wat: bool, - - /// Generate a "types only" component which is a binary encoding of the - /// input wit file or the wit already encoded into the module. - #[clap(long, conflicts_with = "dummy")] - types_only: bool, + /// The world that the component uses. + /// + /// This is the path, within the `WIT` package provided as a positional + /// argument, to the `world` that the core wasm module works with. This can + /// either be a bare string which a document name that has a `default + /// world`, or it can be a `foo/bar` name where `foo` names a document and + /// `bar` names a world within that document. + #[clap(short, long)] + world: String, - /// Generate a "dummy" component which contains a core Wasm stub - /// implementation. - #[clap(long, conflicts_with = "types_only")] + /// Don't read a core wasm module as input, instead generating a "dummy" + /// module as a placeholder. + /// + /// This flag will generate a dummy core wasm module on the fly to match the + /// `WIT` argument provided. This dummy module will have the correct + /// imports/exports and the right signatures for the component model. This + /// can be useful to, perhaps, inspect a template module and what it looks + /// like to work with an interface in the component model. + #[clap(long)] dummy: bool, } -impl NewOpts { +impl EmbedOpts { /// Executes the application. fn run(self) -> Result<()> { - let wasm = if self.types_only || self.dummy { + let wasm = if self.dummy { self.io.init_logger(); None } else { Some(self.io.parse_input_wasm()?) }; - let mut encoder = ComponentEncoder::default() - .validate(!self.skip_validation) - .types_only(self.types_only); - - if let Some(wasm) = wasm { - encoder = encoder.module(&wasm)?; - } + let (resolve, id) = parse_wit(&self.wit)?; - if let Some(wit) = &self.wit { - let encoding = self.encoding.unwrap_or(StringEncoding::UTF8); - let doc = Document::parse_file(wit)?; - if self.dummy { - encoder = encoder.document_dummy(doc, encoding)?; - } else { - encoder = encoder.document(doc, encoding)?; - } - } + let mut parts = self.world.split('/'); + let doc = match parts.next() { + Some(name) => match resolve.packages[id].documents.get(name) { + Some(doc) => *doc, + None => bail!("no document named `{name}` in package"), + }, + None => bail!("invalid `--world` argument"), + }; + let world = match parts.next() { + Some(name) => match resolve.documents[doc].worlds.get(name) { + Some(world) => *world, + None => bail!("no world named `{name}` in document"), + }, + None => match resolve.documents[doc].default_world { + Some(world) => world, + None => bail!("no default world found in document"), + }, + }; - for (name, wasm) in self.adapters.iter() { - encoder = encoder.adapter(name, wasm)?; - } + let encoded = wit_component::metadata::encode( + &resolve, + world, + self.encoding.unwrap_or(StringEncoding::UTF8), + )?; - let bytes = encoder - .encode() - .with_context(|| format!("failed to encode a component from module ",))?; + let section = wasm_encoder::CustomSection { + name: "component-type", + data: &encoded, + }; + let mut wasm = wasm.unwrap_or_else(|| wit_component::dummy_module(&resolve, world)); + wasm.push(section.id()); + section.encode(&mut wasm); self.io.output(Output::Wasm { - bytes: &bytes, - wat: self.wat, + bytes: &wasm, + wat: false, })?; Ok(()) } } -/// WebAssembly interface printer. +/// Tool for working with the WIT text format for components. /// -/// Decodes a `*.wit` file from a binary WebAssembly component. +/// This subcommand can be used to inspect and debug the WIT text or binary +/// format with either WIT packages or binary components. Using this subcommand +/// a WIT package can be translated to binary, a WIT binary can be translated +/// back to text, and a WIT document can be extracted from a component binary to +/// inspect its interfaces. #[derive(Parser)] pub struct WitOpts { #[clap(flatten)] - io: wasm_tools::InputOutput, + verbosity: wasm_tools::Verbosity, - /// The name of the world to generate, inferred by default from the input - /// filename. + /// Input file or directory to process. + /// + /// The file specified can be a `*.wit` file parsed as a single-document + /// package. It can be a directory to be parsed as a WIT package. It can be + /// a `*.wat` or `*.wasm` file for either the binary representation of a WIT + /// package or a component itself to extract the interface from. The type of + /// input is inferred from the contents of the path specified. + /// + /// If not provided or if this is `-` then stdin is read entirely and + /// processed. + input: Option, + + #[clap(flatten)] + output: wasm_tools::OutputArg, + + /// If a WIT package is being parsed, then this is the optionally specified + /// name of the WIT package. If not specified this is automatically inferred + /// from the filename. #[clap(long)] name: Option, + + /// When printing a WIT package, the default mode, this option is used to + /// indicate which document is printed within the package if more than one + /// document is present. + #[clap(short, long, conflicts_with = "wasm", conflicts_with = "wat")] + document: Option, + + /// Emit a full WIT package into the specified directory when printing the + /// text form. + /// + /// This is incompatible with `-o`. + #[clap( + long, + conflicts_with = "output", + conflicts_with = "wasm", + conflicts_with = "wat", + conflicts_with = "document" + )] + out_dir: Option, + + /// Emit a WebAssembly binary representation instead of the WIT text format. + #[clap(short, long, conflicts_with = "wat")] + wasm: bool, + + /// Emit a WebAssembly textual representation instead of the WIT text + /// format. + #[clap(short = 't', long, conflicts_with = "wasm")] + wat: bool, + + /// Skips the validation performed when using the `--wasm` and `--wat` + /// options. + #[clap(long)] + skip_validation: bool, } impl WitOpts { /// Executes the application. fn run(self) -> Result<()> { - let bytes = self.io.parse_input_wasm()?; let name = match &self.name { Some(name) => name.as_str(), - None => match self.io.input_path() { + None => match &self.input { Some(path) => path.file_stem().unwrap().to_str().unwrap(), None => "component", }, }; - let (doc, _world) = decode_world(name, &bytes).context("failed to decode world")?; - let mut printer = DocumentPrinter::default(); - let output = printer.print(&doc)?; - self.io.output(Output::Wat(&output))?; + // First up determine the actual `DecodedWasm` as the input. This could + // come from a number of sources: + // + // * If a `*.wat` or `*.wasm` is specified, use `wit_component::decode` + // * If a directory is specified, parse it as a `Resolve`-oriented + // package with a `deps` directory optionally available. + // * If a file is specified then it's just a normal wit package where + // deps can't be resolved. + // * If no file is specified then parse the input as either `*.wat`, + // `*.wasm`, or `*.wit` and do as above. + // + // Eventually there will want to be more flags for things like + // specifying a directory but specifying the WIT dependencies are + // located elsewhere. This should be sufficient for now though. + let decoded = match &self.input { + Some(input) => match input.extension().and_then(|s| s.to_str()) { + Some("wat") | Some("wasm") => { + let bytes = wat::parse_file(&input)?; + wit_component::decode(name, &bytes).context("failed to decode WIT document")? + } + _ => { + let (resolve, id) = parse_wit(input)?; + DecodedWasm::WitPackage(resolve, id) + } + }, + None => { + let mut stdin = Vec::new(); + std::io::stdin() + .read_to_end(&mut stdin) + .context("failed to read ")?; + + if is_wasm(&stdin) { + let bytes = wat::parse_bytes(&stdin).map_err(|mut e| { + e.set_path(""); + e + })?; + + wit_component::decode(name, &bytes).context("failed to decode WIT document")? + } else { + let stdin = match std::str::from_utf8(&stdin) { + Ok(s) => s, + Err(_) => bail!("stdin was not valid utf-8"), + }; + let mut resolve = Resolve::default(); + let pkg = UnresolvedPackage::parse("".as_ref(), stdin)?; + let id = resolve.push(pkg, &Default::default())?; + DecodedWasm::WitPackage(resolve, id) + } + } + }; + + // Now that the WIT document has been decoded, it's time to emit it. + // This interprets all of the output options and performs such a task. + match &self.out_dir { + Some(dir) => { + assert!(self.output.output_path().is_none()); + assert!(!self.wasm && !self.wat); + assert!(self.document.is_none()); + let package = match &decoded { + DecodedWasm::WitPackage(_, package) => *package, + + DecodedWasm::Component(resolve, world) => { + let doc = resolve.worlds[*world].document; + resolve.documents[doc].package.unwrap() + } + }; + let resolve = decoded.resolve(); + std::fs::create_dir_all(&dir) + .with_context(|| format!("failed to create {dir:?}"))?; + for (name, doc) in resolve.packages[package].documents.iter() { + let output = DocumentPrinter::default().print(&resolve, *doc)?; + let path = dir.join(format!("{name}.wit")); + std::fs::write(&path, output) + .with_context(|| format!("failed to write {path:?}"))?; + } + } + None => { + if self.wasm || self.wat { + self.emit_wasm(&decoded)?; + } else { + self.emit_wit(&decoded)?; + } + } + } + Ok(()) + } + + fn emit_wasm(&self, decoded: &DecodedWasm) -> Result<()> { + assert!(self.wasm || self.wat); + assert!(self.out_dir.is_none()); + assert!(self.document.is_none()); + + let pkg = match decoded { + DecodedWasm::WitPackage(_resolve, pkg) => *pkg, + DecodedWasm::Component(resolve, world) => { + let doc = resolve.worlds[*world].document; + resolve.documents[doc].package.unwrap() + } + }; + + let resolve = decoded.resolve(); + let bytes = wit_component::encode(&resolve, pkg)?; + if !self.skip_validation { + wasmparser::Validator::new_with_features(wasmparser::WasmFeatures { + component_model: true, + ..Default::default() + }) + .validate_all(&bytes)?; + } + self.output.output(Output::Wasm { + bytes: &bytes, + wat: self.wat, + })?; + Ok(()) + } + + fn emit_wit(&self, decoded: &DecodedWasm) -> Result<()> { + assert!(!self.wasm && !self.wat); + assert!(self.out_dir.is_none()); + if self.wat { + bail!("the `--wat` option can only be combined with `--wasm`"); + } + + let doc = match decoded { + DecodedWasm::WitPackage(resolve, pkg) => { + let pkg = &resolve.packages[*pkg]; + match &self.document { + Some(name) => *pkg + .documents + .get(name) + .ok_or_else(|| anyhow!("no document named `{name}` found in package"))?, + None => match pkg.documents.len() { + 1 => *pkg.documents.iter().next().unwrap().1, + _ => bail!( + "more than document found in package, \ + specify which to print with `-d name`" + ), + }, + } + } + DecodedWasm::Component(resolve, world) => resolve.worlds[*world].document, + }; + let output = DocumentPrinter::default().print(decoded.resolve(), doc)?; + self.output.output(Output::Wat(&output))?; Ok(()) } } + +fn parse_wit(path: &Path) -> Result<(Resolve, PackageId)> { + let mut resolve = Resolve::default(); + let id = if path.is_dir() { + resolve.push_dir(&path)?.0 + } else { + let pkg = UnresolvedPackage::parse_file(&path)?; + resolve.push(pkg, &Default::default())? + }; + Ok((resolve, id)) +} + +/// Test to see if a string is probably a `*.wat` text syntax. +/// +/// This briefly lexes past whitespace and comments as a `*.wat` file to see if +/// we can find a left-paren. If that fails then it's probably `*.wit` instead. +fn is_wasm(bytes: &[u8]) -> bool { + use wast::lexer::{Lexer, Token}; + + if bytes.starts_with(b"\0asm") { + return true; + } + let text = match std::str::from_utf8(bytes) { + Ok(s) => s, + Err(_) => return true, + }; + + let mut lexer = Lexer::new(text); + + while let Some(next) = lexer.next() { + match next { + Ok(Token::Whitespace(_)) | Ok(Token::BlockComment(_)) | Ok(Token::LineComment(_)) => {} + Ok(Token::LParen(_)) => return true, + _ => break, + } + } + + false +} diff --git a/src/lib.rs b/src/lib.rs index afad2a5d9f..32a61f502f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,6 +146,10 @@ impl OutputArg { Ok(()) } + pub fn output_path(&self) -> Option<&Path> { + self.output.as_deref() + } + pub fn output_writer(&self) -> Result> { match &self.output { Some(output) => Ok(Box::new(BufWriter::new(File::create(&output)?))),