diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index b47fba2fb8..e4e141345e 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -713,7 +713,7 @@ impl<'a> EncodingState<'a> { self.component .alias_export(instance, name, ComponentExportKind::Type) } - TypeOwner::None => panic!("resources must have an owner"), + TypeOwner::Package(_) | TypeOwner::None => panic!("resources must have an owner"), } } diff --git a/crates/wit-component/src/encoding/types.rs b/crates/wit-component/src/encoding/types.rs index 6b43e5a84e..74cb2c209b 100644 --- a/crates/wit-component/src/encoding/types.rs +++ b/crates/wit-component/src/encoding/types.rs @@ -74,7 +74,7 @@ impl<'a> TypeEncodingMaps<'a> { } /// Inserts that `id` was encoded as `index`. - fn insert(&mut self, resolve: &'a Resolve, id: TypeId, index: u32) { + pub(crate) fn insert(&mut self, resolve: &'a Resolve, id: TypeId, index: u32) { // Always record the id=>index mapping. self.id_to_index.insert(id, index); @@ -121,6 +121,15 @@ pub trait ValtypeEncoder<'a> { /// imported type into this index space. fn import_type(&mut self, interface: InterfaceId, id: TypeId) -> u32; + /// Imports the package-scope type `id`, returning the index of the imported + /// type into this index space. + /// + /// Returns `None` when this encoder defines package-scope types itself + /// rather than depending on them. + fn import_package_type(&mut self, _resolve: &'a Resolve, _id: TypeId) -> Result> { + Ok(None) + } + /// Returns the identifier of the interface that generation is for. fn interface(&self) -> Option; @@ -193,72 +202,27 @@ pub trait ValtypeEncoder<'a> { let ty = &resolve.types[id]; - // If this type is imported from another interface then return - // it as it was bound here with an alias. + // If this type is imported from another interface, or from + // package scope, then return it as it was bound here with an + // alias. log::trace!("encode type name={:?} {:?}", ty.name, &ty.kind); - if let Some(index) = self.maybe_import_type(resolve, id) { + if let Some(index) = self.maybe_import_type(resolve, id)? { self.type_encoding_maps().insert(resolve, id, index); return Ok(ComponentValType::Type(index)); } + // Resources are named by the export that creates them rather + // than by a structural definition, so they don't participate in + // the define-then-name dance below. + if let TypeDefKind::Resource = &ty.kind { + let name = ty.name.as_ref().expect("resources must be named"); + let index = self.export_resource(extern_name(name, ty.external_id.as_deref())); + self.type_encoding_maps().id_to_index.insert(id, index); + return Ok(ComponentValType::Type(index)); + } + // ... and failing all that insert the type export. - let mut encoded = match &ty.kind { - 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(resolve, v)?, - 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(resolve, ty)?; - let (index, encoder) = self.defined_type(); - encoder.list(ty); - ComponentValType::Type(index) - } - TypeDefKind::Map(key_ty, value_ty) => { - let key = self.encode_valtype(resolve, key_ty)?; - let value = self.encode_valtype(resolve, value_ty)?; - let (index, encoder) = self.defined_type(); - encoder.map(key, value); - ComponentValType::Type(index) - } - TypeDefKind::FixedLengthList(ty, elements) => { - let ty = self.encode_valtype(resolve, ty)?; - let (index, encoder) = self.defined_type(); - encoder.fixed_length_list(ty, *elements); - ComponentValType::Type(index) - } - TypeDefKind::Type(ty) => self.encode_valtype(resolve, ty)?, - TypeDefKind::Future(ty) => self.encode_future(resolve, ty)?, - TypeDefKind::Stream(ty) => self.encode_stream(resolve, ty)?, - TypeDefKind::Unknown => unreachable!(), - TypeDefKind::Resource => { - let name = ty.name.as_ref().expect("resources must be named"); - let index = - self.export_resource(extern_name(name, ty.external_id.as_deref())); - self.type_encoding_maps().id_to_index.insert(id, index); - return Ok(ComponentValType::Type(index)); - } - TypeDefKind::Handle(Handle::Own(id)) => { - let ty = match self.encode_valtype(resolve, &Type::Id(*id))? { - ComponentValType::Type(index) => index, - _ => panic!("must be an indexed type"), - }; - let (index, encoder) = self.defined_type(); - encoder.own(ty); - ComponentValType::Type(index) - } - TypeDefKind::Handle(Handle::Borrow(id)) => { - let ty = match self.encode_valtype(resolve, &Type::Id(*id))? { - ComponentValType::Type(index) => index, - _ => panic!("must be an indexed type"), - }; - let (index, encoder) = self.defined_type(); - encoder.borrow(ty); - ComponentValType::Type(index) - } - }; + let mut encoded = self.encode_typedef_structure(resolve, id)?; if let Some(name) = &ty.name { let index = match encoded { @@ -287,20 +251,83 @@ pub trait ValtypeEncoder<'a> { }) } - /// Optionally imports `id` from a different interface, returning the index - /// of the imported type into this index space. + /// Encodes the structure of `id`'s definition into this index space. + /// + /// Unlike [`ValtypeEncoder::encode_valtype`] this never names the result, so + /// it's suitable for restating a definition that is going to be named some + /// other way, such as by the `import` that binds a package-scope type. + fn encode_typedef_structure( + &mut self, + resolve: &'a Resolve, + id: TypeId, + ) -> Result { + Ok(match &resolve.types[id].kind { + 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(resolve, v)?, + 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(resolve, ty)?; + let (index, encoder) = self.defined_type(); + encoder.list(ty); + ComponentValType::Type(index) + } + TypeDefKind::Map(key_ty, value_ty) => { + let key = self.encode_valtype(resolve, key_ty)?; + let value = self.encode_valtype(resolve, value_ty)?; + let (index, encoder) = self.defined_type(); + encoder.map(key, value); + ComponentValType::Type(index) + } + TypeDefKind::FixedLengthList(ty, elements) => { + let ty = self.encode_valtype(resolve, ty)?; + let (index, encoder) = self.defined_type(); + encoder.fixed_length_list(ty, *elements); + ComponentValType::Type(index) + } + TypeDefKind::Type(ty) => self.encode_valtype(resolve, ty)?, + TypeDefKind::Future(ty) => self.encode_future(resolve, ty)?, + TypeDefKind::Stream(ty) => self.encode_stream(resolve, ty)?, + TypeDefKind::Handle(Handle::Own(id)) => { + let ty = match self.encode_valtype(resolve, &Type::Id(*id))? { + ComponentValType::Type(index) => index, + _ => panic!("must be an indexed type"), + }; + let (index, encoder) = self.defined_type(); + encoder.own(ty); + ComponentValType::Type(index) + } + TypeDefKind::Handle(Handle::Borrow(id)) => { + let ty = match self.encode_valtype(resolve, &Type::Id(*id))? { + ComponentValType::Type(index) => index, + _ => panic!("must be an indexed type"), + }; + let (index, encoder) = self.defined_type(); + encoder.borrow(ty); + ComponentValType::Type(index) + } + TypeDefKind::Resource => unreachable!("resources have no structure to encode"), + TypeDefKind::Unknown => unreachable!(), + }) + } + + /// Optionally imports `id` from a different interface or from package scope, + /// returning the index of the imported type into this index space. /// /// Returns `None` if `id` can't be imported. - fn maybe_import_type(&mut self, resolve: &Resolve, id: TypeId) -> Option { - let ty = &resolve.types[id]; - let owner = match ty.owner { + fn maybe_import_type(&mut self, resolve: &'a Resolve, id: TypeId) -> Result> { + let owner = match resolve.types[id].owner { TypeOwner::Interface(i) => i, - _ => return None, + TypeOwner::Package(_) => return self.import_package_type(resolve, id), + TypeOwner::World(_) | TypeOwner::None => return Ok(None), }; if Some(owner) == self.interface() { - return None; + return Ok(None); } - Some(self.import_type(owner, id)) + Ok(Some(self.import_type(owner, id))) } fn encode_optional_valtype( diff --git a/crates/wit-component/src/encoding/wit.rs b/crates/wit-component/src/encoding/wit.rs index a70f90cc38..9b877036a3 100644 --- a/crates/wit-component/src/encoding/wit.rs +++ b/crates/wit-component/src/encoding/wit.rs @@ -142,6 +142,46 @@ struct Encoder<'a> { impl Encoder<'_> { fn run(&mut self) -> Result<()> { + // Encode package-scope types first so V2 sniffing still sees a Label + // as the first export name when types are present. + { + // Decoding recovers the package name from the fully-qualified + // export inside an interface's or world's component type. A + // package without either has no such export, so each of its types + // is additionally bound with a self-referential `eq` import whose + // fully-qualified name carries the package name. Later types then + // reference the imported index so that every import stays valid + // per the component model's named-type rules. + let pkg = &self.resolve.packages[self.package]; + let self_describe = pkg.interfaces.is_empty() && pkg.worlds.is_empty(); + + let mut encoder = PackageTypeEncoder { + component: &mut self.component, + package: self.package, + type_encoding_maps: Default::default(), + foreign_type_maps: Default::default(), + foreign_declared: Default::default(), + }; + for (_, &id) in self.resolve.packages[self.package].types.iter() { + encoder.encode_valtype(self.resolve, &Type::Id(id))?; + + if self_describe { + let index = encoder.type_encoding_maps.id_to_index[&id]; + let ty = &self.resolve.types[id]; + let imported = encoder.component.import( + ComponentExternName { + name: package_type_name(self.resolve, id).into(), + implements: None, + version_suffix: None, + external_id: ty.external_id.clone().map(Into::into), + }, + ComponentTypeRef::Type(TypeBounds::Eq(index)), + ); + encoder.type_encoding_maps.id_to_index.insert(id, imported); + } + } + } + // Encode all interfaces as component types and then export them. for (name, &id) in self.resolve.packages[self.package].interfaces.iter() { let component_ty = self.encode_interface(id)?; @@ -240,6 +280,10 @@ struct InterfaceEncoder<'a> { ty: Option, type_encoding_maps: TypeEncodingMaps<'a>, saved_maps: Option>, + /// Encoding maps for the definitions restated in `outer` to bind + /// package-scope type imports, kept separate from the maps of whichever + /// scope is currently being encoded. + package_type_maps: TypeEncodingMaps<'a>, import_map: HashMap, outer_type_map: HashMap, instances: u32, @@ -247,13 +291,14 @@ struct InterfaceEncoder<'a> { interface: Option, } -impl InterfaceEncoder<'_> { +impl<'a> InterfaceEncoder<'a> { fn new(resolve: &Resolve) -> InterfaceEncoder<'_> { InterfaceEncoder { resolve, outer: ComponentType::new(), ty: None, type_encoding_maps: Default::default(), + package_type_maps: Default::default(), import_map: Default::default(), outer_type_map: Default::default(), instances: 0, @@ -263,6 +308,18 @@ impl InterfaceEncoder<'_> { } } + /// Declares the package-scope type `id` in `outer`, returning the index of + /// the `import` that binds it there. + fn declare_package_type(&mut self, resolve: &'a Resolve, id: TypeId) -> Result { + PackageTypeImporter { + resolve, + outer: &mut self.outer, + type_encoding_maps: &mut self.package_type_maps, + declared: &mut self.outer_type_map, + } + .declare(id) + } + fn encode_instance(&mut self, interface: InterfaceId) -> Result { self.push_instance(); let iface = &self.resolve.interfaces[interface]; @@ -392,6 +449,18 @@ impl<'a> ValtypeEncoder<'a> for InterfaceEncoder<'a> { }); ret }); + self.alias_outer_type(outer_idx) + } + fn import_package_type(&mut self, resolve: &'a Resolve, id: TypeId) -> Result> { + let outer_idx = self.declare_package_type(resolve, id)?; + Ok(Some(self.alias_outer_type(outer_idx))) + } +} + +impl InterfaceEncoder<'_> { + /// Brings `outer_idx`, an index in the wrapping component-type, into the + /// instance type currently being built, if any. + fn alias_outer_type(&mut self, outer_idx: u32) -> u32 { match &mut self.ty { Some(ty) => { let ret = ty.type_count(); @@ -406,3 +475,186 @@ impl<'a> ValtypeEncoder<'a> for InterfaceEncoder<'a> { } } } + +/// An index space that a restated package-scope type definition, and the +/// `import` naming it, can be written into. +trait PackageTypeScope { + fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>); + fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>); + /// Imports a type bound with `eq` to `index`, returning the index of the + /// imported type. + fn import_eq(&mut self, name: ComponentExternName<'_>, index: u32) -> u32; +} + +impl PackageTypeScope for ComponentType { + fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { + (self.type_count(), self.ty().defined_type()) + } + fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { + (self.type_count(), self.ty().function()) + } + fn import_eq(&mut self, name: ComponentExternName<'_>, index: u32) -> u32 { + let ret = self.type_count(); + self.import(name, ComponentTypeRef::Type(TypeBounds::Eq(index))); + ret + } +} + +impl PackageTypeScope for ComponentBuilder { + fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { + self.type_defined(None) + } + fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { + self.type_function(None) + } + fn import_eq(&mut self, name: ComponentExternName<'_>, index: u32) -> u32 { + self.import(name, ComponentTypeRef::Type(TypeBounds::Eq(index))) + } +} + +/// Declares package-scope types that something depends on. +/// +/// A package-scope type is encoded like a `use` of an `interface`, just without +/// the wrapping `instance` type: the definition is restated locally and then +/// imported under its fully-qualified `namespace:package/name`, bound with `eq` +/// so that nothing has to be supplied to satisfy the import. +struct PackageTypeImporter<'b, 'a, S> { + resolve: &'a Resolve, + outer: &'b mut S, + type_encoding_maps: &'b mut TypeEncodingMaps<'a>, + declared: &'b mut HashMap, +} + +impl<'a, S: PackageTypeScope> PackageTypeImporter<'_, 'a, S> { + fn declare(&mut self, id: TypeId) -> Result { + if let Some(index) = self.declared.get(&id) { + return Ok(*index); + } + + let resolve = self.resolve; + let structure = match self.encode_typedef_structure(resolve, id)? { + ComponentValType::Type(index) => index, + // A named primitive still needs an entry in the type section for + // the import's bound to refer to. + ComponentValType::Primitive(ty) => { + let (index, encoder) = self.defined_type(); + encoder.primitive(ty); + index + } + }; + + let ty = &resolve.types[id]; + let index = self.outer.import_eq( + ComponentExternName { + name: package_type_name(resolve, id).into(), + implements: None, + version_suffix: None, + external_id: ty.external_id.clone().map(Into::into), + }, + structure, + ); + + self.declared.insert(id, index); + self.type_encoding_maps.insert(resolve, id, index); + Ok(index) + } +} + +impl<'a, S: PackageTypeScope> ValtypeEncoder<'a> for PackageTypeImporter<'_, 'a, S> { + fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { + self.outer.defined_type() + } + fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { + self.outer.define_function_type() + } + fn export_type(&mut self, _index: u32, _name: ComponentExternName<'a>) -> Option { + // Definitions restated here are named by the `import` that binds them, + // so the index of the definition itself is what callers want. + None + } + fn export_resource(&mut self, _name: ComponentExternName<'a>) -> u32 { + unreachable!("package-scope resources are not allowed") + } + fn type_encoding_maps(&mut self) -> &mut TypeEncodingMaps<'a> { + self.type_encoding_maps + } + fn interface(&self) -> Option { + None + } + fn import_type(&mut self, _owner: InterfaceId, _id: TypeId) -> u32 { + unreachable!("package-scope types cannot refer to interface types") + } + fn import_package_type(&mut self, _resolve: &'a Resolve, id: TypeId) -> Result> { + self.declare(id).map(Some) + } +} + +/// The fully-qualified `namespace:package/name` of a package-scope type. +fn package_type_name(resolve: &Resolve, id: TypeId) -> String { + let ty = &resolve.types[id]; + let package = match ty.owner { + TypeOwner::Package(package) => package, + _ => unreachable!("not a package-scope type"), + }; + let name = ty.name.as_ref().expect("package-scope types are named"); + resolve.packages[package].name.interface_id(name) +} + +/// Encodes a package's own package-scope types as type exports of the +/// package's component. +struct PackageTypeEncoder<'b, 'a> { + component: &'b mut ComponentBuilder, + package: PackageId, + type_encoding_maps: TypeEncodingMaps<'a>, + /// Encoding maps for the definitions restated to bind imports of + /// package-scope types from other packages, kept separate from the maps of + /// the definitions this package exports. + foreign_type_maps: TypeEncodingMaps<'a>, + foreign_declared: HashMap, +} + +impl<'a> ValtypeEncoder<'a> for PackageTypeEncoder<'_, 'a> { + fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { + self.component.type_defined(None) + } + fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { + self.component.type_function(None) + } + fn export_type(&mut self, index: u32, name: ComponentExternName<'a>) -> Option { + Some( + self.component + .export(name, ComponentExportKind::Type, index, None), + ) + } + fn export_resource(&mut self, _name: ComponentExternName<'a>) -> u32 { + unreachable!("package-scope resources are not allowed") + } + fn type_encoding_maps(&mut self) -> &mut TypeEncodingMaps<'a> { + &mut self.type_encoding_maps + } + fn interface(&self) -> Option { + None + } + fn import_type(&mut self, _owner: InterfaceId, _id: TypeId) -> u32 { + unreachable!("package-scope types do not import from interfaces") + } + fn import_package_type(&mut self, resolve: &'a Resolve, id: TypeId) -> Result> { + // This encoder is defining the package's own types, so those are + // defined and exported rather than imported. + if resolve.types[id].owner == TypeOwner::Package(self.package) { + return Ok(None); + } + + // A definition from another package has no wrapping component-type to + // hold the `import` naming it, so it goes on the package's component + // directly. + PackageTypeImporter { + resolve, + outer: &mut *self.component, + type_encoding_maps: &mut self.foreign_type_maps, + declared: &mut self.foreign_declared, + } + .declare(id) + .map(Some) + } +} diff --git a/crates/wit-component/src/lib.rs b/crates/wit-component/src/lib.rs index 11f57201e5..319daf47d8 100644 --- a/crates/wit-component/src/lib.rs +++ b/crates/wit-component/src/lib.rs @@ -111,10 +111,10 @@ pub fn embed_component_metadata( #[cfg(test)] mod tests { use anyhow::Result; - use wasmparser::Payload; + use wasmparser::{Payload, WasmFeatures}; use wit_parser::Resolve; - use super::{StringEncoding, embed_component_metadata}; + use super::{StringEncoding, embed_component_metadata, encode}; const MODULE_WAT: &str = r#" (module @@ -175,4 +175,554 @@ world test-world {} Ok(()) } + + #[test] + fn package_scope_foreign_type_encodes_as_qualified_import() -> Result<()> { + let mut resolve = Resolve::new(); + resolve.push_str( + "types.wit", + r#" +package local:types; + +record point { + x: u32, + y: u32, +} + +interface unused {} +"#, + )?; + let pkg = resolve.push_str( + "consumer.wit", + r#" +package local:consumer; + +use local:types/point; + +interface api { + move-to: func(p: point); +} + +world w { + export api; +} +"#, + )?; + + let wasm = encode(&resolve, pkg)?; + let wat = wasmprinter::print_bytes(&wasm)?; + // The definition is restated locally and the import names where it came + // from, just as it is for a type projected out of a foreign interface. + assert!( + wat.contains("(record (field \"x\" u32) (field \"y\" u32))"), + "expected the foreign definition to be restated:\n{wat}" + ); + assert!( + wat.contains("(import \"local:types/point\" (type"), + "expected a qualified import for the foreign package type:\n{wat}" + ); + assert!( + !wat.contains("(import \"local:types/point\" (instance"), + "a package-scope type must not be wrapped in an instance:\n{wat}" + ); + wasmparser::Validator::new_with_features(WasmFeatures::all()).validate_all(&wasm)?; + + // Text print of the resolved package should emit a toplevel use. + let mut printer = crate::WitPrinter::default(); + printer.print(&resolve, pkg, &[])?; + let printed = printer.output.to_string(); + assert!( + printed.contains("use local:types/point;"), + "expected toplevel use printback:\n{printed}" + ); + + Ok(()) + } + + #[test] + fn package_scope_foreign_type_as_printback() -> Result<()> { + let mut resolve = Resolve::new(); + resolve.push_str( + "types.wit", + r#" +package local:types; + +record point { + x: u32, + y: u32, +} + +interface unused {} +"#, + )?; + let pkg = resolve.push_str( + "consumer.wit", + r#" +package local:consumer; + +use local:types/point as pt; + +interface api { + move-to: func(p: pt); +} + +world w { + export api; +} +"#, + )?; + + let mut printer = crate::WitPrinter::default(); + printer.print(&resolve, pkg, &[])?; + let printed = printer.output.to_string(); + // Printer canonicalizes to the original package type name rather than + // preserving the local `as` alias. + assert!( + printed.contains("use local:types/point"), + "expected toplevel use printback:\n{printed}" + ); + assert!( + printed.contains("func(p: point)") || printed.contains("func(p: pt)"), + "expected func to reference the foreign type:\n{printed}" + ); + + let wasm = encode(&resolve, pkg)?; + wasmparser::Validator::new_with_features(WasmFeatures::all()).validate_all(&wasm)?; + Ok(()) + } + + #[test] + fn package_scope_nested_package_encodes() -> Result<()> { + let mut resolve = Resolve::new(); + resolve.push_str( + "nested.wit", + r#" +package local:root; + +package local:nested { + record point { + x: u32, + y: u32, + } + + interface api { + move-to: func(p: point); + } + + world w { + export api; + } +} +"#, + )?; + let nested = *resolve + .package_names + .iter() + .find(|(name, _)| name.name == "nested") + .map(|(_, id)| id) + .expect("nested package"); + + let wasm = encode(&resolve, nested)?; + let wat = wasmprinter::print_bytes(&wasm)?; + assert!( + wat.contains("(export (;1;) \"point\" (type 0))"), + "expected package-scope point export:\n{wat}" + ); + assert!( + wat.contains("(import \"local:nested/point\" (type"), + "expected the interface to import the package-scope type:\n{wat}" + ); + wasmparser::Validator::new_with_features(WasmFeatures::all()).validate_all(&wasm)?; + + let decoded = crate::decode(&wasm)?; + let mut printer = crate::WitPrinter::default(); + printer.print(decoded.resolve(), decoded.package(), &[])?; + let printed = printer.output.to_string(); + assert!(printed.contains("record point"), "{printed}"); + assert!(printed.contains("interface api"), "{printed}"); + + Ok(()) + } + + /// The approved WIT.md "a package-scope type may refer to another one" + /// example: since an exported type may only refer to types named by an + /// import or export, the referenced definition is exported under its own id + /// first and the dependent one refers back to that export. + #[test] + fn package_scope_type_referring_to_another_exports_before_use() -> Result<()> { + let mut resolve = Resolve::new(); + let pkg = resolve.push_str( + "demo.wit", + r#" +package local:demo; + +record point { + x: u32, + y: u32, +} + +type point-list = list; +"#, + )?; + + let wasm = encode(&resolve, pkg)?; + let wat = wasmprinter::print_bytes(&wasm)?; + // `point` is exported first as a record and, since this package has + // no interfaces or worlds, re-imported under its fully-qualified name + // so the package name is recoverable... + assert!( + wat.contains("(record (field \"x\" u32) (field \"y\" u32))") + && wat.contains("\"point\" (type 0)") + && wat.contains("(import \"local:demo/point\" (type (;2;) (eq 1)))"), + "expected `point` to be exported and self-imported:\n{wat}" + ); + // ...and `point-list` is a `list` of the imported `point`, exported + // next. + assert!( + wat.contains("(type (;3;) (list 2))") && wat.contains("\"point-list\" (type 3)"), + "expected `point-list` to reference the imported `point`:\n{wat}" + ); + wasmparser::Validator::new_with_features(WasmFeatures::all()).validate_all(&wasm)?; + Ok(()) + } + + /// A package without interfaces or worlds has no fully-qualified export + /// to recover the package name from, so each of its types is additionally + /// bound with a self-referential `eq` import named by the type's + /// fully-qualified name, and decoding recovers the package name from + /// those imports. + #[test] + fn package_scope_type_only_package_round_trips() -> Result<()> { + let mut resolve = Resolve::new(); + let pkg = resolve.push_str( + "types-only.wit", + r#" +package local:types; + +record point { + x: u32, + y: u32, +} + +type path = list; +"#, + )?; + + let wasm = encode(&resolve, pkg)?; + wasmparser::Validator::new_with_features(WasmFeatures::all()).validate_all(&wasm)?; + + let wat = wasmprinter::print_bytes(&wasm)?; + assert!( + wat.contains("(import \"local:types/point\""), + "expected a self-import binding `point`:\n{wat}" + ); + assert!( + wat.contains("(import \"local:types/path\""), + "expected a self-import binding `path`:\n{wat}" + ); + + let decoded = crate::decode(&wasm)?; + let resolve = decoded.resolve(); + let package = &resolve.packages[decoded.package()]; + assert_eq!(package.name.to_string(), "local:types"); + assert_eq!(package.types.len(), 2); + assert!(package.types.contains_key("point")); + assert!(package.types.contains_key("path")); + assert!(package.interfaces.is_empty()); + assert!(package.worlds.is_empty()); + Ok(()) + } + + /// Same as above, but the types-only package also depends on a foreign + /// package-scope type: the self-imports and the foreign import coexist at + /// the package root and decoding tells them apart. + #[test] + fn package_scope_type_only_package_with_foreign_dep_round_trips() -> Result<()> { + let mut resolve = Resolve::new(); + resolve.push_str( + "types.wit", + r#" +package local:types; + +record point { + x: u32, + y: u32, +} +"#, + )?; + let pkg = resolve.push_str( + "consumer.wit", + r#" +package local:consumer; + +use local:types/point; + +record bin { + p: point, +} +"#, + )?; + + let wasm = encode(&resolve, pkg)?; + wasmparser::Validator::new_with_features(WasmFeatures::all()).validate_all(&wasm)?; + + let decoded = crate::decode(&wasm)?; + let resolve = decoded.resolve(); + let package = &resolve.packages[decoded.package()]; + assert_eq!(package.name.to_string(), "local:consumer"); + assert!(package.types.contains_key("bin")); + + // The foreign `point` stays owned by `local:types`. + let types_pkg = resolve + .packages + .iter() + .find(|(_, p)| p.name.to_string() == "local:types") + .map(|(_, p)| p) + .expect("foreign package should be present"); + assert!(types_pkg.types.contains_key("point")); + Ok(()) + } + + /// An `interface` or `world` names a foreign package-scope type with an + /// `import` on its wrapping component-type. A package-scope definition has + /// no such wrapper, so its import goes on the package's component itself. + #[test] + fn package_scope_type_depending_on_foreign_package_type() -> Result<()> { + let mut resolve = Resolve::new(); + resolve.push_str( + "types.wit", + r#" +package local:types; + +record point { + x: u32, + y: u32, +} + +interface unused {} +"#, + )?; + let pkg = resolve.push_str( + "consumer.wit", + r#" +package local:consumer; + +use local:types/point; + +record bin { + p: point, +} + +interface api { + wrap: func(b: bin); +} +"#, + )?; + + let wasm = encode(&resolve, pkg)?; + let wat = wasmprinter::print_bytes(&wasm)?; + assert!( + wat.contains("(import \"local:types/point\" (type"), + "expected a qualified import on the package itself:\n{wat}" + ); + wasmparser::Validator::new_with_features(WasmFeatures::all()).validate_all(&wasm)?; + + // The import must not be mistaken for a concrete component's, and + // `point` must stay owned by `local:types` rather than being claimed by + // the consumer. + let decoded = crate::decode(&wasm)?; + let mut printer = crate::WitPrinter::default(); + printer.print(decoded.resolve(), decoded.package(), &[])?; + let printed = printer.output.to_string(); + assert!(printed.contains("use local:types/point;"), "{printed}"); + assert!(printed.contains("record bin {"), "{printed}"); + assert!( + !printed.contains("record point"), + "the foreign definition must not be restated as our own:\n{printed}" + ); + + Ok(()) + } + + /// A foreign package-scope type carries its version in the fully-qualified + /// import name and that version survives a decode round-trip. + #[test] + fn package_scope_foreign_type_preserves_version() -> Result<()> { + let mut resolve = Resolve::new(); + resolve.push_str( + "types.wit", + r#" +package local:types@1.2.3; + +record point { + x: u32, + y: u32, +} + +interface unused {} +"#, + )?; + let pkg = resolve.push_str( + "consumer.wit", + r#" +package local:consumer@0.1.0; + +use local:types/point@1.2.3; + +interface api { + move-to: func(p: point); +} + +world w { + export api; +} +"#, + )?; + + let wasm = encode(&resolve, pkg)?; + let wat = wasmprinter::print_bytes(&wasm)?; + assert!( + wat.contains("local:types/point@1.2.3"), + "expected the import name to carry the version:\n{wat}" + ); + wasmparser::Validator::new_with_features(WasmFeatures::all()).validate_all(&wasm)?; + + let decoded = crate::decode(&wasm)?; + let mut printer = crate::WitPrinter::default(); + printer.print(decoded.resolve(), decoded.package(), &[])?; + let printed = printer.output.to_string(); + assert!( + printed.contains("use local:types/point@1.2.3;"), + "expected the decoded use to keep the version:\n{printed}" + ); + + Ok(()) + } + + /// `resource` is excluded from package scope: its bound is abstract, so it + /// can't be restated and bound with `eq`. A component that nonetheless names + /// a resource where a package-scope type would go must be rejected rather + /// than silently decoded. + #[test] + fn package_scope_resource_import_is_rejected() -> Result<()> { + let wasm = wat::parse_str( + r#" +(component + (import "local:types/thing" (type (;0;) (sub resource))) + (type (;1;) (record (field "x" u32))) + (export (;2;) "bin" (type 1)) +) +"#, + )?; + + let err = match crate::decode(&wasm) { + Ok(_) => panic!("resource at package scope must fail to decode"), + Err(err) => format!("{err:#}"), + }; + assert!( + err.contains("not a structural type"), + "unexpected error: {err}" + ); + Ok(()) + } +} + +#[cfg(all(test, feature = "dummy-module"))] +mod component_tests { + use crate::{ + ComponentEncoder, DecodedWasm, StringEncoding, dummy_module, embed_component_metadata, + }; + use anyhow::Result; + use wit_parser::{ManglingAndAbi, Resolve}; + + // Note: `wit_parser::{Type, TypeOwner, WorldItem}` are referenced via full + // paths in the assertions below to keep this module's imports minimal. + + /// Building a real component from a world that uses a package-scope type + /// must not fabricate an interface just to hold the type: the export is the + /// interface itself. This is the outcome motivating the feature (issue + /// #694). The produced artifact must also decode as a component, not be + /// mistaken for a WIT package now that packages may carry type imports. + #[test] + fn package_scope_component_has_no_invented_interface() -> Result<()> { + let mut resolve = Resolve::new(); + let pkg = resolve.push_str( + "demo.wit", + r#" +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface api { + move-to: func(p: point); +} + +world w { + export api; +} +"#, + )?; + let world = resolve.select_world(&[pkg], Some("w"))?; + + let mut module = dummy_module(&resolve, world, ManglingAndAbi::Standard32); + embed_component_metadata(&mut module, &resolve, world, StringEncoding::UTF8)?; + let component = ComponentEncoder::default() + .module(&module)? + .validate(true) + .encode()?; + + let decoded = crate::decode(&component)?; + let (resolve, world) = match &decoded { + DecodedWasm::Component(resolve, world) => (resolve, *world), + DecodedWasm::WitPackage(..) => { + panic!("a real component must not be sniffed as a WIT package") + } + }; + + // The world exports the interface directly, with no import fabricated + // to carry the package-scope type. + assert!( + resolve.worlds[world].imports.is_empty(), + "no import should be invented to hold the package-scope type" + ); + let exports: Vec<_> = resolve.worlds[world].exports.values().collect(); + assert_eq!( + exports.len(), + 1, + "world should export exactly the interface" + ); + let iface = match exports[0] { + wit_parser::WorldItem::Interface { id, .. } => *id, + other => panic!("expected an interface export, got {other:?}"), + }; + + // That interface still carries `move-to`, and its parameter is the + // package-scope `point`, owned by a package rather than by the + // interface. + let func = resolve.interfaces[iface] + .functions + .get("move-to") + .expect("api interface should keep its function"); + let point = match func.params.as_slice() { + [param] => match param.ty { + wit_parser::Type::Id(id) => id, + other => panic!("expected a named type param, got {other:?}"), + }, + params => panic!("expected one param, got {}", params.len()), + }; + // Decoding a component doesn't reconstruct package scope, but the type + // must still resolve to the sole `api` interface rather than to any + // separate interface fabricated to hold it. + assert_eq!( + resolve.types[point].owner, + wit_parser::TypeOwner::Interface(iface), + "point should belong to the api interface, not an invented one" + ); + assert_eq!(resolve.types[point].name.as_deref(), Some("point")); + + Ok(()) + } } diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index 361facbc78..0e933deb29 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -82,6 +82,7 @@ impl Default for Bindgen { docs: Default::default(), interfaces: Default::default(), worlds: Default::default(), + types: Default::default(), }); let world = resolve.worlds.alloc(World { name: "root".to_string(), diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index a9381465a9..9b49e15e55 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -1,6 +1,6 @@ use anyhow::{Result, anyhow, bail}; use std::borrow::Cow; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::fmt::Display; use std::mem; use std::ops::Deref; @@ -68,8 +68,8 @@ impl WitPrinter { pkg: PackageId, is_main: bool, ) -> Result<()> { - let pkg = &resolve.packages[pkg]; - self.print_package_outer(pkg)?; + let package = &resolve.packages[pkg]; + self.print_package_outer(package)?; if is_main { self.output.semicolon(); @@ -78,7 +78,21 @@ impl WitPrinter { self.output.indent_start(); } - for (name, id) in pkg.interfaces.iter() { + self.print_foreign_package_type_uses(resolve, pkg)?; + + for (name, id) in package.types.iter() { + let ty = &resolve.types[*id]; + self.print_docs(&ty.docs); + self.print_stability(&ty.stability); + self.print_external_id(ty.external_id.as_deref()); + self.declare_type(resolve, &Type::Id(*id))?; + let _ = name; + if is_main { + self.output.newline(); + } + } + + for (name, id) in package.interfaces.iter() { self.print_interface_outer(resolve, *id, name)?; self.output.indent_start(); self.print_interface(resolve, *id)?; @@ -88,7 +102,7 @@ impl WitPrinter { } } - for (name, id) in pkg.worlds.iter() { + for (name, id) in package.worlds.iter() { self.print_docs(&resolve.worlds[*id].docs); self.print_stability(&resolve.worlds[*id].stability); self.output.keyword("world"); @@ -104,6 +118,53 @@ impl WitPrinter { Ok(()) } + /// Emit toplevel `use ns:pkg/name;` for foreign package-scope types that + /// this package's types, interfaces, or worlds refer to. + fn print_foreign_package_type_uses(&mut self, resolve: &Resolve, pkg: PackageId) -> Result<()> { + let mut live = LiveTypes::default(); + for (_, &id) in resolve.packages[pkg].types.iter() { + live.add_type_id(resolve, id); + } + for (_, &id) in resolve.packages[pkg].interfaces.iter() { + live.add_interface(resolve, id); + } + for (_, &id) in resolve.packages[pkg].worlds.iter() { + live.add_world(resolve, id); + } + + // Sort for stable output: (package name string, type name). + let mut uses = BTreeMap::new(); + for id in live.iter() { + let ty = &resolve.types[id]; + let TypeOwner::Package(owner) = ty.owner else { + continue; + }; + if owner == pkg { + continue; + } + let Some(name) = ty.name.as_deref() else { + continue; + }; + uses.insert( + ( + resolve.packages[owner].name.to_string(), + name.to_string(), + owner, + ), + (), + ); + } + + for ((_, name, owner), _) in uses { + self.output.keyword("use"); + self.output.str(" "); + self.print_path_to_package_type(resolve, owner, &name, pkg)?; + self.output.semicolon(); + self.output.newline(); + } + Ok(()) + } + /// Print the specified package without its content. /// Does not print the semicolon nor starts the indentation. pub fn print_package_outer(&mut self, pkg: &Package) -> Result<()> { @@ -241,6 +302,7 @@ impl WitPrinter { let my_pkg = match owner { TypeOwner::Interface(id) => resolve.interfaces[id].package.unwrap(), TypeOwner::World(id) => resolve.worlds[id].package.unwrap(), + TypeOwner::Package(id) => id, TypeOwner::None => unreachable!(), }; for (owner, ty, tys) in types_to_import { @@ -249,29 +311,42 @@ impl WitPrinter { self.print_external_id(ty.external_id.as_deref()); self.output.keyword("use"); self.output.str(" "); - let id = match owner { - TypeOwner::Interface(id) => id, - // it's only possible to import types from interfaces at - // this time. - _ => unreachable!(), - }; - self.print_path_to_interface(resolve, id, my_pkg)?; - self.output.str(".{"); // Note: not changing the indentation. - for (i, (my_name, other_name)) in tys.into_iter().enumerate() { - if i > 0 { - self.output.str(", "); + match owner { + TypeOwner::Interface(id) => { + self.print_path_to_interface(resolve, id, my_pkg)?; + self.output.str(".{"); // Note: not changing the indentation. + for (i, (my_name, other_name)) in tys.into_iter().enumerate() { + if i > 0 { + self.output.str(", "); + } + if my_name == other_name { + self.print_name_type(my_name, TypeKind::TypeImport); + } else { + self.print_name_type(other_name, TypeKind::TypeImport); + self.output.str(" "); + self.output.keyword("as"); + self.output.str(" "); + self.print_name_type(my_name, TypeKind::TypeAlias); + } + } + self.output.str("}"); // Note: not changing the indentation. } - if my_name == other_name { - self.print_name_type(my_name, TypeKind::TypeImport); - } else { - self.print_name_type(other_name, TypeKind::TypeImport); - self.output.str(" "); - self.output.keyword("as"); - self.output.str(" "); - self.print_name_type(my_name, TypeKind::TypeAlias); + TypeOwner::Package(pkg) => { + // Package-scope types are imported with toplevel `use`. + assert_eq!(tys.len(), 1); + let (my_name, other_name) = tys[0]; + self.print_path_to_package_type(resolve, pkg, other_name, my_pkg)?; + if my_name != other_name { + self.output.str(" "); + self.output.keyword("as"); + self.output.str(" "); + self.print_name_type(my_name, TypeKind::TypeAlias); + } } + // it's only possible to import types from interfaces or + // packages at this time. + _ => unreachable!(), } - self.output.str("}"); // Note: not changing the indentation. self.output.semicolon(); } @@ -556,6 +631,29 @@ impl WitPrinter { Ok(()) } + fn print_path_to_package_type( + &mut self, + resolve: &Resolve, + package: PackageId, + type_name: &str, + cur_pkg: PackageId, + ) -> Result<()> { + if package == cur_pkg { + self.print_name_type(type_name, TypeKind::TypeImport); + } else { + let pkg = &resolve.packages[package].name; + self.print_name_type(&pkg.namespace, TypeKind::NamespacePath); + self.output.str(":"); + self.print_name_type(&pkg.name, TypeKind::PackageNamePath); + self.output.str("/"); + self.print_name_type(type_name, TypeKind::TypeImport); + if let Some(version) = &pkg.version { + self.print_name_type(&format!("@{version}"), TypeKind::VersionPath); + } + } + Ok(()) + } + /// Print the name of type `ty`. pub fn print_type_name(&mut self, resolve: &Resolve, ty: &Type) -> Result<()> { match ty { diff --git a/crates/wit-component/tests/interfaces/package-scope-docs.wat b/crates/wit-component/tests/interfaces/package-scope-docs.wat new file mode 100644 index 0000000000..3aa061775d --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-docs.wat @@ -0,0 +1,43 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (type 0)) + (type (;2;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;1;) (eq 0))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (type (;1;) (func (param "p" 0))) + (export (;0;) "move-to" (func (type 1))) + ) + ) + (export (;0;) "local:demo/api" (instance (type 2))) + ) + ) + (export (;3;) "api" (type 2)) + (type (;4;) + (component + (type (;0;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;1;) (eq 0))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (type (;1;) (func (param "p" 0))) + (export (;0;) "move-to" (func (type 1))) + ) + ) + (export (;0;) "local:demo/api" (instance (type 2))) + ) + ) + (export (;0;) "local:demo/w" (component (type 0))) + ) + ) + (export (;5;) "w" (type 4)) + (@custom "package-docs" "\01{\22interfaces\22:{\22api\22:{\22funcs\22:{\22move-to\22:{\22docs\22:\22Move to a package-scope point.\22}}}},\22types\22:{\22point\22:{\22docs\22:\22A shared point type at package scope.\22}}}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-docs.wit b/crates/wit-component/tests/interfaces/package-scope-docs.wit new file mode 100644 index 0000000000..457ad34c16 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-docs.wit @@ -0,0 +1,16 @@ +package local:demo; + +/// A shared point type at package scope. +record point { + x: u32, + y: u32, +} + +interface api { + /// Move to a package-scope point. + move-to: func(p: point); +} + +world w { + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-docs.wit.print b/crates/wit-component/tests/interfaces/package-scope-docs.wit.print new file mode 100644 index 0000000000..457ad34c16 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-docs.wit.print @@ -0,0 +1,16 @@ +package local:demo; + +/// A shared point type at package scope. +record point { + x: u32, + y: u32, +} + +interface api { + /// Move to a package-scope point. + move-to: func(p: point); +} + +world w { + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-external-id.wat b/crates/wit-component/tests/interfaces/package-scope-external-id.wat new file mode 100644 index 0000000000..f89be1d382 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-external-id.wat @@ -0,0 +1,55 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (external-id "pkg-point") (type 0)) + (type (;2;) (list 1)) + (export (;3;) "path" (external-id "pkg-path") (type 2)) + (type (;4;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (external-id "pkg-point") (type (;1;) (eq 0))) + (type (;2;) (list 1)) + (import "local:demo/path" (external-id "pkg-path") (type (;3;) (eq 2))) + (type (;4;) + (instance + (alias outer 1 1 (type (;0;))) + (type (;1;) (func (param "p" 0))) + (export (;0;) "move-to" (func (type 1))) + (alias outer 1 3 (type (;2;))) + (type (;3;) (func (result 2))) + (export (;1;) "trail" (func (type 3))) + ) + ) + (export (;0;) "local:demo/api" (instance (type 4))) + ) + ) + (export (;5;) "api" (type 4)) + (type (;6;) + (component + (type (;0;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (external-id "pkg-point") (type (;1;) (eq 0))) + (type (;2;) (list 1)) + (import "local:demo/path" (external-id "pkg-path") (type (;3;) (eq 2))) + (type (;4;) + (instance + (alias outer 1 1 (type (;0;))) + (type (;1;) (func (param "p" 0))) + (export (;0;) "move-to" (func (type 1))) + (alias outer 1 3 (type (;2;))) + (type (;3;) (func (result 2))) + (export (;1;) "trail" (func (type 3))) + ) + ) + (export (;0;) "local:demo/api" (instance (type 4))) + ) + ) + (export (;0;) "local:demo/w" (component (type 0))) + ) + ) + (export (;7;) "w" (type 6)) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-external-id.wit b/crates/wit-component/tests/interfaces/package-scope-external-id.wit new file mode 100644 index 0000000000..668a90b086 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-external-id.wit @@ -0,0 +1,19 @@ +package local:demo; + +@external-id("pkg-point") +record point { + x: u32, + y: u32, +} + +@external-id("pkg-path") +type path = list; + +interface api { + move-to: func(p: point); + trail: func() -> path; +} + +world w { + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-external-id.wit.print b/crates/wit-component/tests/interfaces/package-scope-external-id.wit.print new file mode 100644 index 0000000000..be8c1bf7c7 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-external-id.wit.print @@ -0,0 +1,20 @@ +package local:demo; + +@external-id("pkg-point") +record point { + x: u32, + y: u32, +} + +@external-id("pkg-path") +type path = list; + +interface api { + move-to: func(p: point); + + trail: func() -> path; +} + +world w { + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-field-docs.wat b/crates/wit-component/tests/interfaces/package-scope-field-docs.wat new file mode 100644 index 0000000000..a8fd041f4a --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-field-docs.wat @@ -0,0 +1,69 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (type 0)) + (type (;2;) (variant (case "circle") (case "rect" 1))) + (export (;3;) "shape" (type 2)) + (type (;4;) (flags "bold" "italic")) + (export (;5;) "style" (type 4)) + (type (;6;) (enum "north" "south")) + (export (;7;) "direction" (type 6)) + (type (;8;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;1;) (eq 0))) + (type (;2;) (variant (case "circle") (case "rect" 1))) + (import "local:demo/shape" (type (;3;) (eq 2))) + (type (;4;) (flags "bold" "italic")) + (import "local:demo/style" (type (;5;) (eq 4))) + (type (;6;) (enum "north" "south")) + (import "local:demo/direction" (type (;7;) (eq 6))) + (type (;8;) + (instance + (alias outer 1 3 (type (;0;))) + (alias outer 1 5 (type (;1;))) + (type (;2;) (func (param "s" 0) (param "style" 1))) + (export (;0;) "draw" (func (type 2))) + (alias outer 1 7 (type (;3;))) + (type (;4;) (func (result 3))) + (export (;1;) "heading" (func (type 4))) + ) + ) + (export (;0;) "local:demo/api" (instance (type 8))) + ) + ) + (export (;9;) "api" (type 8)) + (type (;10;) + (component + (type (;0;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;1;) (eq 0))) + (type (;2;) (variant (case "circle") (case "rect" 1))) + (import "local:demo/shape" (type (;3;) (eq 2))) + (type (;4;) (flags "bold" "italic")) + (import "local:demo/style" (type (;5;) (eq 4))) + (type (;6;) (enum "north" "south")) + (import "local:demo/direction" (type (;7;) (eq 6))) + (type (;8;) + (instance + (alias outer 1 3 (type (;0;))) + (alias outer 1 5 (type (;1;))) + (type (;2;) (func (param "s" 0) (param "style" 1))) + (export (;0;) "draw" (func (type 2))) + (alias outer 1 7 (type (;3;))) + (type (;4;) (func (result 3))) + (export (;1;) "heading" (func (type 4))) + ) + ) + (export (;0;) "local:demo/api" (instance (type 8))) + ) + ) + (export (;0;) "local:demo/w" (component (type 0))) + ) + ) + (export (;11;) "w" (type 10)) + (@custom "package-docs" "\01{\22types\22:{\22point\22:{\22docs\22:\22A shared point type at package scope.\22,\22items\22:{\22x\22:\22X coordinate\22,\22y\22:\22Y coordinate\22}},\22shape\22:{\22docs\22:\22Geometric shape using package-scope point.\22,\22items\22:{\22circle\22:\22Unit circle case\22,\22rect\22:\22Rectangle backed by a point\22}},\22style\22:{\22items\22:{\22bold\22:\22Bold text\22}},\22direction\22:{\22items\22:{\22north\22:\22Facing north\22}}}}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-field-docs.wit b/crates/wit-component/tests/interfaces/package-scope-field-docs.wit new file mode 100644 index 0000000000..b873f7dcb7 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-field-docs.wit @@ -0,0 +1,38 @@ +package local:demo; + +/// A shared point type at package scope. +record point { + /// X coordinate + x: u32, + /// Y coordinate + y: u32, +} + +/// Geometric shape using package-scope point. +variant shape { + /// Unit circle case + circle, + /// Rectangle backed by a point + rect(point), +} + +flags style { + /// Bold text + bold, + italic, +} + +enum direction { + /// Facing north + north, + south, +} + +interface api { + draw: func(s: shape, style: style); + heading: func() -> direction; +} + +world w { + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-field-docs.wit.print b/crates/wit-component/tests/interfaces/package-scope-field-docs.wit.print new file mode 100644 index 0000000000..207faf9dbc --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-field-docs.wit.print @@ -0,0 +1,39 @@ +package local:demo; + +/// A shared point type at package scope. +record point { + /// X coordinate + x: u32, + /// Y coordinate + y: u32, +} + +/// Geometric shape using package-scope point. +variant shape { + /// Unit circle case + circle, + /// Rectangle backed by a point + rect(point), +} + +flags style { + /// Bold text + bold, + italic, +} + +enum direction { + /// Facing north + north, + south, +} + +interface api { + draw: func(s: shape, style: style); + + heading: func() -> direction; +} + +world w { + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-foreign-use.wat b/crates/wit-component/tests/interfaces/package-scope-foreign-use.wat new file mode 100644 index 0000000000..1d3ea3320d --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-foreign-use.wat @@ -0,0 +1,55 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:types/point" (type (;1;) (eq 0))) + (type (;2;) (record (field "p" 1))) + (export (;3;) "bin" (type 2)) + (type (;4;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:types/point" (type (;1;) (eq 0))) + (type (;2;) (record (field "p" 1))) + (import "local:consumer/bin" (type (;3;) (eq 2))) + (type (;4;) + (instance + (alias outer 1 3 (type (;0;))) + (type (;1;) (func (param "b" 0))) + (export (;0;) "wrap" (func (type 1))) + (alias outer 1 1 (type (;2;))) + (type (;3;) (func (param "p" 2))) + (export (;1;) "move-to" (func (type 3))) + ) + ) + (export (;0;) "local:consumer/api" (instance (type 4))) + ) + ) + (export (;5;) "api" (type 4)) + (type (;6;) + (component + (type (;0;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:types/point" (type (;1;) (eq 0))) + (type (;2;) (record (field "p" 1))) + (import "local:consumer/bin" (type (;3;) (eq 2))) + (type (;4;) + (instance + (alias outer 1 3 (type (;0;))) + (type (;1;) (func (param "b" 0))) + (export (;0;) "wrap" (func (type 1))) + (alias outer 1 1 (type (;2;))) + (type (;3;) (func (param "p" 2))) + (export (;1;) "move-to" (func (type 3))) + ) + ) + (export (;0;) "local:consumer/api" (instance (type 4))) + ) + ) + (export (;0;) "local:consumer/w" (component (type 0))) + ) + ) + (export (;7;) "w" (type 6)) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-foreign-use/consumer.wit b/crates/wit-component/tests/interfaces/package-scope-foreign-use/consumer.wit new file mode 100644 index 0000000000..a1703d92f3 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-foreign-use/consumer.wit @@ -0,0 +1,16 @@ +package local:consumer; + +use local:types/point; + +record bin { + p: point, +} + +interface api { + wrap: func(b: bin); + move-to: func(p: point); +} + +world w { + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-foreign-use/consumer.wit.print b/crates/wit-component/tests/interfaces/package-scope-foreign-use/consumer.wit.print new file mode 100644 index 0000000000..b267a94634 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-foreign-use/consumer.wit.print @@ -0,0 +1,17 @@ +package local:consumer; + +use local:types/point; + +record bin { + p: point, +} + +interface api { + wrap: func(b: bin); + + move-to: func(p: point); +} + +world w { + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-foreign-use/deps/types/types.wit b/crates/wit-component/tests/interfaces/package-scope-foreign-use/deps/types/types.wit new file mode 100644 index 0000000000..d25c1fd54a --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-foreign-use/deps/types/types.wit @@ -0,0 +1,10 @@ +package local:types; + +/// Shared package-scope point. +record point { + x: u32, + y: u32, +} + +interface unused { +} diff --git a/crates/wit-component/tests/interfaces/package-scope-implements-external-id.wat b/crates/wit-component/tests/interfaces/package-scope-implements-external-id.wat new file mode 100644 index 0000000000..3239b38598 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-implements-external-id.wat @@ -0,0 +1,52 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (external-id "pkg-point") (type 0)) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + ) + ) + (export (;0;) "local:demo/store" (instance (type 0))) + ) + ) + (export (;3;) "store" (type 2)) + (type (;4;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + ) + ) + (import "primary" (implements "local:demo/store") (instance (;0;) (type 0))) + (type (;1;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + ) + ) + (import "backup" (implements "local:demo/store") (instance (;1;) (type 1))) + (type (;2;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (external-id "pkg-point") (type (;3;) (eq 2))) + (type (;4;) (func (param "p" 3))) + (import "move-to" (external-id "world-move") (func (;0;) (type 4))) + (export (;1;) "place" (func (type 4))) + ) + ) + (export (;0;) "local:demo/w" (component (type 0))) + ) + ) + (export (;5;) "w" (type 4)) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-implements-external-id.wit b/crates/wit-component/tests/interfaces/package-scope-implements-external-id.wit new file mode 100644 index 0000000000..b8b12df22e --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-implements-external-id.wit @@ -0,0 +1,21 @@ +package local:demo; + +@external-id("pkg-point") +record point { + x: u32, + y: u32, +} + +interface store { + get: func(key: string) -> option; +} + +world w { + import primary: store; + import backup: store; + + @external-id("world-move") + import move-to: func(p: point); + + export place: func(p: point); +} diff --git a/crates/wit-component/tests/interfaces/package-scope-implements-external-id.wit.print b/crates/wit-component/tests/interfaces/package-scope-implements-external-id.wit.print new file mode 100644 index 0000000000..ec4dbbb69c --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-implements-external-id.wit.print @@ -0,0 +1,20 @@ +package local:demo; + +@external-id("pkg-point") +record point { + x: u32, + y: u32, +} + +interface store { + get: func(key: string) -> option; +} + +world w { + import primary: store; + import backup: store; + @external-id("world-move") + import move-to: func(p: point); + + export place: func(p: point); +} diff --git a/crates/wit-component/tests/interfaces/package-scope-implements.wat b/crates/wit-component/tests/interfaces/package-scope-implements.wat new file mode 100644 index 0000000000..07d433185c --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-implements.wat @@ -0,0 +1,112 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + (type (;2;) (func (param "key" string) (param "value" string))) + (export (;1;) "set" (func (type 2))) + ) + ) + (export (;0;) "local:demo/store" (instance (type 0))) + ) + ) + (export (;3;) "store" (type 2)) + (type (;4;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;1;) (eq 0))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (type (;1;) (func (param "p" 0))) + (export (;0;) "move-to" (func (type 1))) + ) + ) + (export (;0;) "local:demo/api" (instance (type 2))) + ) + ) + (export (;5;) "api" (type 4)) + (type (;6;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + (type (;2;) (func (param "key" string) (param "value" string))) + (export (;1;) "set" (func (type 2))) + ) + ) + (import "primary" (implements "local:demo/store") (instance (;0;) (type 0))) + (type (;1;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + (type (;2;) (func (param "key" string) (param "value" string))) + (export (;1;) "set" (func (type 2))) + ) + ) + (import "backup" (implements "local:demo/store") (instance (;1;) (type 1))) + (type (;2;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;3;) (eq 2))) + (type (;4;) (func (param "p" 3))) + (import "move-to" (func (;0;) (type 4))) + ) + ) + (export (;0;) "local:demo/multi-import" (component (type 0))) + ) + ) + (export (;7;) "multi-import" (type 6)) + (type (;8;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + (type (;2;) (func (param "key" string) (param "value" string))) + (export (;1;) "set" (func (type 2))) + ) + ) + (import "local:demo/store" (instance (;0;) (type 0))) + (type (;1;) + (instance + (type (;0;) (option string)) + (type (;1;) (func (param "key" string) (result 0))) + (export (;0;) "get" (func (type 1))) + (type (;2;) (func (param "key" string) (param "value" string))) + (export (;1;) "set" (func (type 2))) + ) + ) + (import "cache" (implements "local:demo/store") (instance (;1;) (type 1))) + (type (;2;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;3;) (eq 2))) + (type (;4;) + (instance + (alias outer 1 3 (type (;0;))) + (type (;1;) (func (param "p" 0))) + (export (;0;) "move-to" (func (type 1))) + ) + ) + (export (;2;) "local:demo/api" (instance (type 4))) + ) + ) + (export (;0;) "local:demo/mixed" (component (type 0))) + ) + ) + (export (;9;) "mixed" (type 8)) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-implements.wit b/crates/wit-component/tests/interfaces/package-scope-implements.wit new file mode 100644 index 0000000000..2ef35cc494 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-implements.wit @@ -0,0 +1,27 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface store { + get: func(key: string) -> option; + set: func(key: string, value: string); +} + +interface api { + move-to: func(p: point); +} + +world multi-import { + import primary: store; + import backup: store; + import move-to: func(p: point); +} + +world mixed { + import store; + import cache: store; + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-implements.wit.print b/crates/wit-component/tests/interfaces/package-scope-implements.wit.print new file mode 100644 index 0000000000..029a99c8fd --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-implements.wit.print @@ -0,0 +1,28 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface store { + get: func(key: string) -> option; + + set: func(key: string, value: string); +} + +interface api { + move-to: func(p: point); +} + +world multi-import { + import primary: store; + import backup: store; + import move-to: func(p: point); +} +world mixed { + import store; + import cache: store; + + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-mixed-use.wat b/crates/wit-component/tests/interfaces/package-scope-mixed-use.wat new file mode 100644 index 0000000000..bd5b55dd5e --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-mixed-use.wat @@ -0,0 +1,77 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (enum "info" "debug")) + (export (;1;) "level" (type (eq 0))) + ) + ) + (export (;0;) "local:consumer/levels" (instance (type 0))) + ) + ) + (export (;1;) "levels" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) (enum "info" "debug")) + (export (;1;) "level" (type (eq 0))) + ) + ) + (import "local:consumer/levels" (instance (;0;) (type 0))) + (alias export 0 "level" (type (;1;))) + (type (;2;) (record (field "x" u32) (field "y" u32))) + (import "local:types/point" (type (;3;) (eq 2))) + (type (;4;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "level" (type (eq 0))) + (alias outer 1 3 (type (;2;))) + (type (;3;) (func (param "p" 2))) + (export (;0;) "move-to" (func (type 3))) + (type (;4;) (func (param "level" 1) (param "msg" string))) + (export (;1;) "log" (func (type 4))) + ) + ) + (export (;1;) "local:consumer/api" (instance (type 4))) + ) + ) + (export (;3;) "api" (type 2)) + (type (;4;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (enum "info" "debug")) + (export (;1;) "level" (type (eq 0))) + ) + ) + (import "local:consumer/levels" (instance (;0;) (type 0))) + (alias export 0 "level" (type (;1;))) + (type (;2;) (record (field "x" u32) (field "y" u32))) + (import "local:types/point" (type (;3;) (eq 2))) + (type (;4;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "level" (type (eq 0))) + (alias outer 1 3 (type (;2;))) + (type (;3;) (func (param "p" 2))) + (export (;0;) "move-to" (func (type 3))) + (type (;4;) (func (param "level" 1) (param "msg" string))) + (export (;1;) "log" (func (type 4))) + ) + ) + (export (;1;) "local:consumer/api" (instance (type 4))) + ) + ) + (export (;0;) "local:consumer/w" (component (type 0))) + ) + ) + (export (;5;) "w" (type 4)) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-mixed-use/consumer.wit b/crates/wit-component/tests/interfaces/package-scope-mixed-use/consumer.wit new file mode 100644 index 0000000000..6d31b1ce22 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-mixed-use/consumer.wit @@ -0,0 +1,21 @@ +package local:consumer; + +use local:types/point; + +interface levels { + enum level { + info, + debug, + } +} + +interface api { + use levels.{level}; + + move-to: func(p: point); + log: func(level: level, msg: string); +} + +world w { + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-mixed-use/consumer.wit.print b/crates/wit-component/tests/interfaces/package-scope-mixed-use/consumer.wit.print new file mode 100644 index 0000000000..4ab94f7b07 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-mixed-use/consumer.wit.print @@ -0,0 +1,24 @@ +package local:consumer; + +use local:types/point; + +interface levels { + enum level { + info, + debug, + } +} + +interface api { + use levels.{level}; + + move-to: func(p: point); + + log: func(level: level, msg: string); +} + +world w { + import levels; + + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-mixed-use/deps/types/types.wit b/crates/wit-component/tests/interfaces/package-scope-mixed-use/deps/types/types.wit new file mode 100644 index 0000000000..a15e70a05a --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-mixed-use/deps/types/types.wit @@ -0,0 +1,6 @@ +package local:types; + +record point { + x: u32, + y: u32, +} diff --git a/crates/wit-component/tests/interfaces/package-scope-nested.wat b/crates/wit-component/tests/interfaces/package-scope-nested.wat new file mode 100644 index 0000000000..9da09ac215 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-nested.wat @@ -0,0 +1,43 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (type 0)) + (type (;2;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:nested/point" (type (;1;) (eq 0))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (type (;1;) (func (param "p" 0))) + (export (;0;) "move-to" (func (type 1))) + ) + ) + (export (;0;) "local:nested/api" (instance (type 2))) + ) + ) + (export (;3;) "api" (type 2)) + (type (;4;) + (component + (type (;0;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:nested/point" (type (;1;) (eq 0))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (type (;1;) (func (param "p" 0))) + (export (;0;) "move-to" (func (type 1))) + ) + ) + (export (;0;) "local:nested/api" (instance (type 2))) + ) + ) + (export (;0;) "local:nested/w" (component (type 0))) + ) + ) + (export (;5;) "w" (type 4)) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-nested.wit b/crates/wit-component/tests/interfaces/package-scope-nested.wit new file mode 100644 index 0000000000..6bf8a55f65 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-nested.wit @@ -0,0 +1,14 @@ +package local:nested; + +record point { + x: u32, + y: u32, +} + +interface api { + move-to: func(p: point); +} + +world w { + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-nested.wit.print b/crates/wit-component/tests/interfaces/package-scope-nested.wit.print new file mode 100644 index 0000000000..6bf8a55f65 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-nested.wit.print @@ -0,0 +1,14 @@ +package local:nested; + +record point { + x: u32, + y: u32, +} + +interface api { + move-to: func(p: point); +} + +world w { + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-spec-interface.wat b/crates/wit-component/tests/interfaces/package-scope-spec-interface.wat new file mode 100644 index 0000000000..ee088f5cf2 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-spec-interface.wat @@ -0,0 +1,23 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (type 0)) + (type (;2;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;1;) (eq 0))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (type (;1;) (func (param "p" 0))) + (export (;0;) "move-to" (func (type 1))) + ) + ) + (export (;0;) "local:demo/api" (instance (type 2))) + ) + ) + (export (;3;) "api" (type 2)) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-spec-interface.wit b/crates/wit-component/tests/interfaces/package-scope-spec-interface.wit new file mode 100644 index 0000000000..65d3074983 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-spec-interface.wit @@ -0,0 +1,7 @@ +package local:demo; + +record point { x: u32, y: u32 } + +interface api { + move-to: func(p: point); +} diff --git a/crates/wit-component/tests/interfaces/package-scope-spec-interface.wit.print b/crates/wit-component/tests/interfaces/package-scope-spec-interface.wit.print new file mode 100644 index 0000000000..365af4dfc2 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-spec-interface.wit.print @@ -0,0 +1,11 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface api { + move-to: func(p: point); +} + diff --git a/crates/wit-component/tests/interfaces/package-scope-spec-world.wat b/crates/wit-component/tests/interfaces/package-scope-spec-world.wat new file mode 100644 index 0000000000..35904c346d --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-spec-world.wat @@ -0,0 +1,22 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (type 0)) + (type (;2;) + (component + (type (;0;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;1;) (eq 0))) + (type (;2;) (func (param "p" 1))) + (export (;0;) "move-to" (func (type 2))) + ) + ) + (export (;0;) "local:demo/the-world" (component (type 0))) + ) + ) + (export (;3;) "the-world" (type 2)) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-spec-world.wit b/crates/wit-component/tests/interfaces/package-scope-spec-world.wit new file mode 100644 index 0000000000..db7f6be522 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-spec-world.wit @@ -0,0 +1,7 @@ +package local:demo; + +record point { x: u32, y: u32 } + +world the-world { + export move-to: func(p: point); +} diff --git a/crates/wit-component/tests/interfaces/package-scope-spec-world.wit.print b/crates/wit-component/tests/interfaces/package-scope-spec-world.wit.print new file mode 100644 index 0000000000..3c61d751e9 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-spec-world.wit.print @@ -0,0 +1,10 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +world the-world { + export move-to: func(p: point); +} diff --git a/crates/wit-component/tests/interfaces/package-scope-types-only-external-id.wat b/crates/wit-component/tests/interfaces/package-scope-types-only-external-id.wat new file mode 100644 index 0000000000..4f9c1deb55 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-types-only-external-id.wat @@ -0,0 +1,12 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (external-id "pkg-point") (type 0)) + (import "local:demo/point" (external-id "pkg-point") (type (;2;) (eq 1))) + (type (;3;) (list 2)) + (export (;4;) "path" (external-id "pkg-path") (type 3)) + (import "local:demo/path" (external-id "pkg-path") (type (;5;) (eq 4))) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-types-only-external-id.wit b/crates/wit-component/tests/interfaces/package-scope-types-only-external-id.wit new file mode 100644 index 0000000000..77ce56c8b1 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-types-only-external-id.wit @@ -0,0 +1,10 @@ +package local:demo; + +@external-id("pkg-point") +record point { + x: u32, + y: u32, +} + +@external-id("pkg-path") +type path = list; diff --git a/crates/wit-component/tests/interfaces/package-scope-types-only-external-id.wit.print b/crates/wit-component/tests/interfaces/package-scope-types-only-external-id.wit.print new file mode 100644 index 0000000000..a6dccd50eb --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-types-only-external-id.wit.print @@ -0,0 +1,11 @@ +package local:demo; + +@external-id("pkg-point") +record point { + x: u32, + y: u32, +} + +@external-id("pkg-path") +type path = list; + diff --git a/crates/wit-component/tests/interfaces/package-scope-types-only.wat b/crates/wit-component/tests/interfaces/package-scope-types-only.wat new file mode 100644 index 0000000000..d6d562f167 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-types-only.wat @@ -0,0 +1,15 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (type 0)) + (import "local:demo/point" (type (;2;) (eq 1))) + (type (;3;) (enum "north" "south" "east" "west")) + (export (;4;) "direction" (type 3)) + (import "local:demo/direction" (type (;5;) (eq 4))) + (type (;6;) (list 2)) + (export (;7;) "path" (type 6)) + (import "local:demo/path" (type (;8;) (eq 7))) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-types-only.wit b/crates/wit-component/tests/interfaces/package-scope-types-only.wit new file mode 100644 index 0000000000..cb16bf27f6 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-types-only.wit @@ -0,0 +1,15 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +enum direction { + north, + south, + east, + west, +} + +type path = list; diff --git a/crates/wit-component/tests/interfaces/package-scope-types-only.wit.print b/crates/wit-component/tests/interfaces/package-scope-types-only.wit.print new file mode 100644 index 0000000000..9188689e15 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-types-only.wit.print @@ -0,0 +1,16 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +enum direction { + north, + south, + east, + west, +} + +type path = list; + diff --git a/crates/wit-component/tests/interfaces/package-scope-types.wat b/crates/wit-component/tests/interfaces/package-scope-types.wat new file mode 100644 index 0000000000..a0d3af5e11 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-types.wat @@ -0,0 +1,95 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (type 0)) + (type (;2;) (variant (case "circle" u32) (case "rect" 1))) + (export (;3;) "shape" (type 2)) + (type (;4;) (flags "bold" "italic")) + (export (;5;) "style" (type 4)) + (type (;6;) (enum "north" "south" "east" "west")) + (export (;7;) "direction" (type 6)) + (type (;8;) (list 1)) + (export (;9;) "path" (type 8)) + (type (;10;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;1;) (eq 0))) + (type (;2;) (enum "north" "south" "east" "west")) + (import "local:demo/direction" (type (;3;) (eq 2))) + (type (;4;) (list 1)) + (import "local:demo/path" (type (;5;) (eq 4))) + (type (;6;) (variant (case "circle" u32) (case "rect" 1))) + (import "local:demo/shape" (type (;7;) (eq 6))) + (type (;8;) (flags "bold" "italic")) + (import "local:demo/style" (type (;9;) (eq 8))) + (type (;10;) + (instance + (alias outer 1 1 (type (;0;))) + (type (;1;) (func (param "p" 0))) + (export (;0;) "move-to" (func (type 1))) + (alias outer 1 3 (type (;2;))) + (type (;3;) (func (result 2))) + (export (;1;) "heading" (func (type 3))) + (alias outer 1 5 (type (;4;))) + (type (;5;) (func (result 4))) + (export (;2;) "trail" (func (type 5))) + (alias outer 1 7 (type (;6;))) + (alias outer 1 9 (type (;7;))) + (type (;8;) (func (param "s" 6) (param "style" 7))) + (export (;3;) "draw" (func (type 8))) + ) + ) + (export (;0;) "local:demo/api" (instance (type 10))) + ) + ) + (export (;11;) "api" (type 10)) + (type (;12;) + (component + (type (;0;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;1;) (eq 0))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (type (;1;) (func (param "p" 0))) + (export (;0;) "place" (func (type 1))) + ) + ) + (import "host" (instance (;0;) (type 2))) + (type (;3;) (enum "north" "south" "east" "west")) + (import "local:demo/direction" (type (;4;) (eq 3))) + (type (;5;) (list 1)) + (import "local:demo/path" (type (;6;) (eq 5))) + (type (;7;) (variant (case "circle" u32) (case "rect" 1))) + (import "local:demo/shape" (type (;8;) (eq 7))) + (type (;9;) (flags "bold" "italic")) + (import "local:demo/style" (type (;10;) (eq 9))) + (type (;11;) + (instance + (alias outer 1 1 (type (;0;))) + (type (;1;) (func (param "p" 0))) + (export (;0;) "move-to" (func (type 1))) + (alias outer 1 4 (type (;2;))) + (type (;3;) (func (result 2))) + (export (;1;) "heading" (func (type 3))) + (alias outer 1 6 (type (;4;))) + (type (;5;) (func (result 4))) + (export (;2;) "trail" (func (type 5))) + (alias outer 1 8 (type (;6;))) + (alias outer 1 10 (type (;7;))) + (type (;8;) (func (param "s" 6) (param "style" 7))) + (export (;3;) "draw" (func (type 8))) + ) + ) + (export (;1;) "local:demo/api" (instance (type 11))) + ) + ) + (export (;0;) "local:demo/w" (component (type 0))) + ) + ) + (export (;13;) "w" (type 12)) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-types.wit b/crates/wit-component/tests/interfaces/package-scope-types.wit new file mode 100644 index 0000000000..9cfceb3d33 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-types.wit @@ -0,0 +1,39 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +variant shape { + circle(u32), + rect(point), +} + +flags style { + bold, + italic, +} + +enum direction { + north, + south, + east, + west, +} + +type path = list; + +interface api { + move-to: func(p: point); + heading: func() -> direction; + trail: func() -> path; + draw: func(s: shape, style: style); +} + +world w { + import host: interface { + place: func(p: point); + } + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-types.wit.print b/crates/wit-component/tests/interfaces/package-scope-types.wit.print new file mode 100644 index 0000000000..bba812babf --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-types.wit.print @@ -0,0 +1,43 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +variant shape { + circle(u32), + rect(point), +} + +flags style { + bold, + italic, +} + +enum direction { + north, + south, + east, + west, +} + +type path = list; + +interface api { + move-to: func(p: point); + + heading: func() -> direction; + + trail: func() -> path; + + draw: func(s: shape, style: style); +} + +world w { + import host: interface { + place: func(p: point); + } + + export api; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-with-iface-use.wat b/crates/wit-component/tests/interfaces/package-scope-with-iface-use.wat new file mode 100644 index 0000000000..8059a11f02 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-with-iface-use.wat @@ -0,0 +1,47 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) (enum "info" "debug")) + (export (;1;) "level" (type (eq 0))) + ) + ) + (export (;0;) "local:demo/types" (instance (type 0))) + ) + ) + (export (;3;) "types" (type 2)) + (type (;4;) + (component + (type (;0;) + (instance + (type (;0;) (enum "info" "debug")) + (export (;1;) "level" (type (eq 0))) + ) + ) + (import "local:demo/types" (instance (;0;) (type 0))) + (alias export 0 "level" (type (;1;))) + (type (;2;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;3;) (eq 2))) + (type (;4;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "level" (type (eq 0))) + (alias outer 1 3 (type (;2;))) + (type (;3;) (func (param "p" 2))) + (export (;0;) "move-to" (func (type 3))) + (type (;4;) (func (param "level" 1) (param "msg" string))) + (export (;1;) "log" (func (type 4))) + ) + ) + (export (;1;) "local:demo/api" (instance (type 4))) + ) + ) + (export (;5;) "api" (type 4)) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-with-iface-use.wit b/crates/wit-component/tests/interfaces/package-scope-with-iface-use.wit new file mode 100644 index 0000000000..2d5007b177 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-with-iface-use.wit @@ -0,0 +1,20 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface types { + enum level { + info, + debug, + } +} + +interface api { + use types.{level}; + + move-to: func(p: point); + log: func(level: level, msg: string); +} diff --git a/crates/wit-component/tests/interfaces/package-scope-with-iface-use.wit.print b/crates/wit-component/tests/interfaces/package-scope-with-iface-use.wit.print new file mode 100644 index 0000000000..e178ceb1ba --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-with-iface-use.wit.print @@ -0,0 +1,22 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface types { + enum level { + info, + debug, + } +} + +interface api { + use types.{level}; + + move-to: func(p: point); + + log: func(level: level, msg: string); +} + diff --git a/crates/wit-component/tests/interfaces/package-scope-with-resource-use.wat b/crates/wit-component/tests/interfaces/package-scope-with-resource-use.wat new file mode 100644 index 0000000000..4702f19cab --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-with-resource-use.wat @@ -0,0 +1,49 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance + (export (;0;) "file" (type (sub resource))) + (type (;1;) (borrow 0)) + (type (;2;) (func (param "self" 1) (result string))) + (export (;0;) "[method]file.path" (func (type 2))) + ) + ) + (export (;0;) "local:demo/files" (instance (type 0))) + ) + ) + (export (;3;) "files" (type 2)) + (type (;4;) + (component + (type (;0;) + (instance + (export (;0;) "file" (type (sub resource))) + ) + ) + (import "local:demo/files" (instance (;0;) (type 0))) + (alias export 0 "file" (type (;1;))) + (type (;2;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;3;) (eq 2))) + (type (;4;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "file" (type (eq 0))) + (alias outer 1 3 (type (;2;))) + (type (;3;) (func (param "p" 2))) + (export (;0;) "move-to" (func (type 3))) + (type (;4;) (own 1)) + (type (;5;) (func (result 4))) + (export (;1;) "open" (func (type 5))) + ) + ) + (export (;1;) "local:demo/api" (instance (type 4))) + ) + ) + (export (;5;) "api" (type 4)) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-with-resource-use.wit b/crates/wit-component/tests/interfaces/package-scope-with-resource-use.wit new file mode 100644 index 0000000000..4fd036649e --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-with-resource-use.wit @@ -0,0 +1,19 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface files { + resource file { + path: func() -> string; + } +} + +interface api { + use files.{file}; + + move-to: func(p: point); + open: func() -> file; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-with-resource-use.wit.print b/crates/wit-component/tests/interfaces/package-scope-with-resource-use.wit.print new file mode 100644 index 0000000000..a243b3fab6 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-with-resource-use.wit.print @@ -0,0 +1,21 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface files { + resource file { + path: func() -> string; + } +} + +interface api { + use files.{file}; + + move-to: func(p: point); + + open: func() -> file; +} + diff --git a/crates/wit-component/tests/interfaces/package-scope-with-world-use.wat b/crates/wit-component/tests/interfaces/package-scope-with-world-use.wat new file mode 100644 index 0000000000..e5c2716ad5 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-with-world-use.wat @@ -0,0 +1,45 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) string) + (export (;1;) "tag" (type (eq 0))) + ) + ) + (export (;0;) "local:demo/types" (instance (type 0))) + ) + ) + (export (;3;) "types" (type 2)) + (type (;4;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) string) + (export (;1;) "tag" (type (eq 0))) + ) + ) + (import "local:demo/types" (instance (;0;) (type 0))) + (alias export 0 "tag" (type (;1;))) + (import "tag" (type (;2;) (eq 1))) + (type (;3;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;4;) (eq 3))) + (type (;5;) (func (param "p" 4))) + (import "move-to" (func (;0;) (type 5))) + (type (;6;) (func (param "t" 2) (result 4))) + (export (;1;) "label" (func (type 6))) + ) + ) + (export (;0;) "local:demo/w" (component (type 0))) + ) + ) + (export (;5;) "w" (type 4)) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-with-world-use.wit b/crates/wit-component/tests/interfaces/package-scope-with-world-use.wit new file mode 100644 index 0000000000..0b62a14cb7 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-with-world-use.wit @@ -0,0 +1,17 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface types { + type tag = string; +} + +world w { + use types.{tag}; + + import move-to: func(p: point); + export label: func(t: tag) -> point; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-with-world-use.wit.print b/crates/wit-component/tests/interfaces/package-scope-with-world-use.wit.print new file mode 100644 index 0000000000..5090f92087 --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-with-world-use.wit.print @@ -0,0 +1,18 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface types { + type tag = string; +} + +world w { + import types; + use types.{tag}; + import move-to: func(p: point); + + export label: func(t: tag) -> point; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-world-only.wat b/crates/wit-component/tests/interfaces/package-scope-world-only.wat new file mode 100644 index 0000000000..1e36cdb79e --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-world-only.wat @@ -0,0 +1,26 @@ +(component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (export (;1;) "point" (type 0)) + (type (;2;) + (component + (type (;0;) + (component + (type (;0;) (record (field "x" u32) (field "y" u32))) + (import "local:demo/point" (type (;1;) (eq 0))) + (type (;2;) (func (param "p" 1))) + (import "move-to" (func (;0;) (type 2))) + (export (;1;) "place" (func (type 2))) + (type (;3;) (list 1)) + (type (;4;) (func (result 3))) + (export (;2;) "trail" (func (type 4))) + ) + ) + (export (;0;) "local:demo/w" (component (type 0))) + ) + ) + (export (;3;) "w" (type 2)) + (@custom "package-docs" "\01{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/package-scope-world-only.wit b/crates/wit-component/tests/interfaces/package-scope-world-only.wit new file mode 100644 index 0000000000..3a7b29968d --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-world-only.wit @@ -0,0 +1,12 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +world w { + import move-to: func(p: point); + export place: func(p: point); + export trail: func() -> list; +} diff --git a/crates/wit-component/tests/interfaces/package-scope-world-only.wit.print b/crates/wit-component/tests/interfaces/package-scope-world-only.wit.print new file mode 100644 index 0000000000..3b58727f3e --- /dev/null +++ b/crates/wit-component/tests/interfaces/package-scope-world-only.wit.print @@ -0,0 +1,13 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +world w { + import move-to: func(p: point); + + export place: func(p: point); + export trail: func() -> list; +} diff --git a/crates/wit-component/tests/package-scope-world-merge.rs b/crates/wit-component/tests/package-scope-world-merge.rs new file mode 100644 index 0000000000..25d36018b1 --- /dev/null +++ b/crates/wit-component/tests/package-scope-world-merge.rs @@ -0,0 +1,41 @@ +//! Encoding of a world merged from two packages that each declare a +//! package-scope type under the same local name. + +use anyhow::Result; +use wasmparser::WasmFeatures; +use wit_parser::{CloneMaps, Resolve}; + +/// A package-scope name is unique within its package, so merging the worlds +/// keeps the two `r` types distinct. Each is encoded as its own `eq` import +/// named by its fully-qualified name rather than being unified into one. +#[test] +fn same_local_type_name_keeps_qualified_imports() -> Result<()> { + let mut resolve = Resolve::new(); + let (pkg1, _) = resolve.push_dir("tests/package-scope-world-merge")?; + let pkg2 = resolve + .packages + .iter() + .find_map(|(id, pkg)| (pkg.name.namespace == "a" && pkg.name.name == "b2").then_some(id)) + .unwrap(); + + let w1 = resolve.packages[pkg1].worlds["w1"]; + let w2 = resolve.packages[pkg2].worlds["w2"]; + resolve.merge_worlds(w2, w1, &mut CloneMaps::default())?; + + let wasm = wit_component::encode(&resolve, pkg1)?; + wasmparser::Validator::new_with_features(WasmFeatures::all()).validate_all(&wasm)?; + let wat = wasmprinter::print_bytes(&wasm)?; + assert!( + wat.contains("(import \"a:b1/r\"") && wat.contains("(record (field \"a\" u32))"), + "expected `a:b1/r` as a u32 record:\n{wat}" + ); + assert!( + wat.contains("(import \"a:b2/r\"") && wat.contains("(record (field \"a\" f32))"), + "expected `a:b2/r` as an f32 record:\n{wat}" + ); + assert!( + wat.contains("\"f1\"") && wat.contains("\"f2\""), + "merged world should export both functions:\n{wat}" + ); + Ok(()) +} diff --git a/crates/wit-component/tests/package-scope-world-merge/b1.wit b/crates/wit-component/tests/package-scope-world-merge/b1.wit new file mode 100644 index 0000000000..6bfe906942 --- /dev/null +++ b/crates/wit-component/tests/package-scope-world-merge/b1.wit @@ -0,0 +1,9 @@ +package a:b1; + +record r { + a: u32, +} + +world w1 { + export f1: func() -> r; +} diff --git a/crates/wit-component/tests/package-scope-world-merge/deps/b2/b2.wit b/crates/wit-component/tests/package-scope-world-merge/deps/b2/b2.wit new file mode 100644 index 0000000000..386eecfca1 --- /dev/null +++ b/crates/wit-component/tests/package-scope-world-merge/deps/b2/b2.wit @@ -0,0 +1,9 @@ +package a:b2; + +record r { + a: f32, +} + +world w2 { + export f2: func() -> r; +} diff --git a/crates/wit-dylib/tests/roundtrip.rs b/crates/wit-dylib/tests/roundtrip.rs index 59a40adc3e..f2e89744cf 100644 --- a/crates/wit-dylib/tests/roundtrip.rs +++ b/crates/wit-dylib/tests/roundtrip.rs @@ -105,6 +105,7 @@ fn run_one(u: &mut Unstructured<'_>) -> Result<()> { }, interfaces: Default::default(), worlds: Default::default(), + types: Default::default(), docs: Default::default(), }); diff --git a/crates/wit-encoder/src/from_parser.rs b/crates/wit-encoder/src/from_parser.rs index a757d17841..aef28d05d1 100644 --- a/crates/wit-encoder/src/from_parser.rs +++ b/crates/wit-encoder/src/from_parser.rs @@ -30,6 +30,33 @@ impl<'a> Converter<'a> { fn convert_package(&self, package_id: PackageId, package: &wit_parser::Package) -> Package { let mut output = Package::new(self.convert_package_name(&package.name)); + // Emit toplevel `use` for foreign package-scope types referenced by + // this package's types, interfaces, and worlds. + let mut foreign_uses = Vec::new(); + for (_, id) in &package.types { + self.collect_foreign_package_type_uses(package_id, *id, &mut foreign_uses); + } + for (_, id) in &package.interfaces { + let interface = self.resolve.interfaces.get(*id).unwrap(); + self.collect_foreign_package_type_uses_from_interface( + package_id, + interface, + &mut foreign_uses, + ); + } + for (_, id) in &package.worlds { + let world = self.resolve.worlds.get(*id).unwrap(); + self.collect_foreign_package_type_uses_from_world(package_id, world, &mut foreign_uses); + } + for path in foreign_uses { + output.use_type(path, None); + } + for (_, id) in &package.types { + let type_def = self.resolve.types.get(*id).unwrap(); + if let Some(converted) = self.convert_type_def(type_def, *id) { + output.type_def(converted); + } + } for (_, id) in &package.interfaces { let interface = self.resolve.interfaces.get(*id).unwrap(); output.interface(self.convert_interface( @@ -384,7 +411,9 @@ impl<'a> Converter<'a> { wit_parser::TypeOwner::Interface(id) => { &self.resolve.interfaces.get(*id).unwrap().functions } - wit_parser::TypeOwner::None => panic!("Resource has to have an owner"), + wit_parser::TypeOwner::Package(_) | wit_parser::TypeOwner::None => { + panic!("Resource has to have an owner") + } }; let mut output = Resource::empty(); @@ -578,6 +607,154 @@ impl<'a> Converter<'a> { None } } + + fn package_type_path(&self, package_id: PackageId, type_name: &str) -> Ident { + let package = self.resolve.packages.get(package_id).unwrap(); + Ident::new(format!( + "{}:{}/{}{}", + package.name.namespace, + package.name.name, + type_name, + package + .name + .version + .as_ref() + .map(|version| format!("@{version}")) + .unwrap_or_default() + )) + } + + fn collect_foreign_package_type_uses( + &self, + package_id: PackageId, + type_id: wit_parser::TypeId, + uses: &mut Vec, + ) { + let type_def = self.resolve.types.get(type_id).unwrap(); + match &type_def.kind { + wit_parser::TypeDefKind::Type(ty) + | wit_parser::TypeDefKind::List(ty) + | wit_parser::TypeDefKind::FixedLengthList(ty, _) + | wit_parser::TypeDefKind::Option(ty) + | wit_parser::TypeDefKind::Future(Some(ty)) + | wit_parser::TypeDefKind::Stream(Some(ty)) => { + self.collect_foreign_package_type_uses_from_type(package_id, ty, uses); + } + wit_parser::TypeDefKind::Map(k, v) => { + self.collect_foreign_package_type_uses_from_type(package_id, k, uses); + self.collect_foreign_package_type_uses_from_type(package_id, v, uses); + } + wit_parser::TypeDefKind::Result(r) => { + if let Some(ty) = &r.ok { + self.collect_foreign_package_type_uses_from_type(package_id, ty, uses); + } + if let Some(ty) = &r.err { + self.collect_foreign_package_type_uses_from_type(package_id, ty, uses); + } + } + wit_parser::TypeDefKind::Tuple(t) => { + for ty in &t.types { + self.collect_foreign_package_type_uses_from_type(package_id, ty, uses); + } + } + wit_parser::TypeDefKind::Record(r) => { + for field in &r.fields { + self.collect_foreign_package_type_uses_from_type(package_id, &field.ty, uses); + } + } + wit_parser::TypeDefKind::Variant(v) => { + for case in &v.cases { + if let Some(ty) = &case.ty { + self.collect_foreign_package_type_uses_from_type(package_id, ty, uses); + } + } + } + wit_parser::TypeDefKind::Handle(h) => { + let id = match h { + wit_parser::Handle::Own(id) | wit_parser::Handle::Borrow(id) => *id, + }; + self.collect_foreign_package_type_uses(package_id, id, uses); + } + wit_parser::TypeDefKind::Enum(_) + | wit_parser::TypeDefKind::Flags(_) + | wit_parser::TypeDefKind::Resource + | wit_parser::TypeDefKind::Future(None) + | wit_parser::TypeDefKind::Stream(None) + | wit_parser::TypeDefKind::Unknown => {} + } + } + + fn collect_foreign_package_type_uses_from_type( + &self, + package_id: PackageId, + ty: &wit_parser::Type, + uses: &mut Vec, + ) { + let wit_parser::Type::Id(id) = ty else { + return; + }; + let type_def = self.resolve.types.get(*id).unwrap(); + if let wit_parser::TypeOwner::Package(owner) = type_def.owner { + if owner != package_id { + let name = type_def + .name + .as_deref() + .expect("package type must be named"); + let path = self.package_type_path(owner, name); + if !uses.contains(&path) { + uses.push(path); + } + return; + } + } + self.collect_foreign_package_type_uses(package_id, *id, uses); + } + + fn collect_foreign_package_type_uses_from_interface( + &self, + package_id: PackageId, + interface: &wit_parser::Interface, + uses: &mut Vec, + ) { + for (_, type_id) in &interface.types { + self.collect_foreign_package_type_uses(package_id, *type_id, uses); + } + for (_, func) in &interface.functions { + for ty in func.parameter_and_result_types() { + self.collect_foreign_package_type_uses_from_type(package_id, &ty, uses); + } + } + } + + fn collect_foreign_package_type_uses_from_world( + &self, + package_id: PackageId, + world: &wit_parser::World, + uses: &mut Vec, + ) { + for (_, item) in world.imports.iter().chain(world.exports.iter()) { + match item { + wit_parser::WorldItem::Function(func) => { + for ty in func.parameter_and_result_types() { + self.collect_foreign_package_type_uses_from_type(package_id, &ty, uses); + } + } + wit_parser::WorldItem::Type { id, .. } => { + self.collect_foreign_package_type_uses(package_id, *id, uses); + } + wit_parser::WorldItem::Interface { id, .. } => { + let interface = self.resolve.interfaces.get(*id).unwrap(); + // Named interfaces from other packages are imported by name; + // only scan anonymous/inline interfaces owned here. + if interface.package == Some(package_id) && interface.name.is_none() { + self.collect_foreign_package_type_uses_from_interface( + package_id, interface, uses, + ); + } + } + } + } + } } fn clean_func_name(resource_name: &str, method_name: &str) -> String { diff --git a/crates/wit-encoder/src/package.rs b/crates/wit-encoder/src/package.rs index c6606cf09f..37f18c19f9 100644 --- a/crates/wit-encoder/src/package.rs +++ b/crates/wit-encoder/src/package.rs @@ -3,7 +3,7 @@ use std::ops::{Deref, DerefMut}; use semver::Version; -use crate::{Interface, Render, RenderOpts, World, ident::Ident}; +use crate::{Interface, Render, RenderOpts, TypeDef, World, ident::Ident}; /// A WIT package. /// @@ -48,6 +48,17 @@ impl Package { self.items.push(PackageItem::World(world)) } + /// Add a package-scope type to the package + pub fn type_def(&mut self, type_def: TypeDef) { + self.items.push(PackageItem::Type(type_def)) + } + + /// Add a toplevel `use` (e.g. `use ns:pkg/name;`) for a foreign package-scope type. + pub fn use_type(&mut self, path: impl Into, alias: Option) { + self.items + .push(PackageItem::Use(ToplevelUse::new(path, alias))); + } + pub fn item(&mut self, item: impl Into) { self.items.push(item.into()); } @@ -82,6 +93,12 @@ impl Package { PackageItem::World(world) => { world.render(f, opts)?; } + PackageItem::Type(type_def) => { + type_def.render(f, opts)?; + } + PackageItem::Use(use_) => { + use_.render(f, opts)?; + } } } Ok(()) @@ -147,6 +164,45 @@ impl fmt::Display for NestedPackage { pub enum PackageItem { Interface(Interface), World(World), + Type(TypeDef), + /// Toplevel `use ns:pkg/name;` for a foreign package-scope type. + Use(ToplevelUse), +} + +/// A toplevel WIT `use` of a package-scope type (`use ns:pkg/name [as alias];`). +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub struct ToplevelUse { + path: Ident, + alias: Option, +} + +impl ToplevelUse { + pub fn new(path: impl Into, alias: Option) -> Self { + Self { + path: path.into(), + alias, + } + } + + pub fn path(&self) -> &Ident { + &self.path + } + + pub fn alias(&self) -> Option<&Ident> { + self.alias.as_ref() + } +} + +impl Render for ToplevelUse { + fn render(&self, f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + write!(f, "{}use {}", opts.spaces(), self.path)?; + if let Some(alias) = &self.alias { + write!(f, " as {alias}")?; + } + write!(f, ";\n") + } } /// A structure used to keep track of the name of a package, containing optional diff --git a/crates/wit-encoder/tests/package-scope-types.rs b/crates/wit-encoder/tests/package-scope-types.rs new file mode 100644 index 0000000000..2a8906b004 --- /dev/null +++ b/crates/wit-encoder/tests/package-scope-types.rs @@ -0,0 +1,302 @@ +use pretty_assertions::assert_eq; +use wit_encoder::packages_from_parsed; + +const WIT: &str = r#"package local:demo; + +/// Shared point. +record point { + /// X coordinate + x: u32, + y: u32, +} + +flags style { + bold, + italic, +} + +interface api { + move-to: func(p: point); + style: func() -> style; +} + +world w { + export api; +} +"#; + +#[test] +fn package_scope_types_round_trip() { + let mut resolve = wit_parser::Resolve::new(); + resolve.push_str("demo.wit", WIT).unwrap(); + let packages = packages_from_parsed(&resolve); + assert_eq!(packages.len(), 1); + let rendered = packages[0].to_string(); + assert!( + rendered.contains("record point"), + "expected package-scope record in encoder output:\n{rendered}" + ); + assert!( + rendered.contains("flags style"), + "expected package-scope flags in encoder output:\n{rendered}" + ); + + let mut resolve2 = wit_parser::Resolve::new(); + resolve2.push_str("demo.wit", &rendered).unwrap(); + let packages2 = packages_from_parsed(&resolve2); + assert_eq!(packages[0].to_string(), packages2[0].to_string()); +} + +#[test] +fn package_scope_types_order() { + let mut resolve = wit_parser::Resolve::new(); + resolve.push_str("demo.wit", WIT).unwrap(); + let packages = packages_from_parsed(&resolve); + let rendered = packages[0].to_string(); + + let point = rendered.find("record point").expect("missing point"); + let style = rendered.find("flags style").expect("missing style"); + let api = rendered.find("interface api").expect("missing api"); + let world = rendered.find("world w").expect("missing world"); + assert!( + point < style && style < api && api < world, + "expected package types before interfaces/worlds:\n{rendered}" + ); +} + +#[test] +fn package_scope_foreign_compose_round_trip() { + let mut resolve = wit_parser::Resolve::new(); + resolve + .push_str( + "types.wit", + r#" +package local:types; + +record point { + x: u32, + y: u32, +} + +interface unused {} +"#, + ) + .unwrap(); + resolve + .push_str( + "consumer.wit", + r#" +package local:consumer; + +use local:types/point; + +record bin { + p: point, +} + +interface api { + wrap: func(b: bin); +} + +world w { + export api; +} +"#, + ) + .unwrap(); + + let packages = packages_from_parsed(&resolve); + let consumer = packages + .iter() + .find(|p| p.name().to_string() == "local:consumer") + .expect("consumer package"); + let rendered = consumer.to_string(); + assert!( + rendered.contains("use local:types/point;"), + "expected toplevel use of foreign package type:\n{rendered}" + ); + assert!( + rendered.contains("record bin"), + "expected consumer package-scope type:\n{rendered}" + ); + + // Push deps first, then consumer. + let mut resolve2 = wit_parser::Resolve::new(); + let types = packages + .iter() + .find(|p| p.name().to_string() == "local:types") + .unwrap(); + resolve2.push_str("types.wit", &types.to_string()).unwrap(); + resolve2.push_str("consumer.wit", &rendered).unwrap(); + let packages2 = packages_from_parsed(&resolve2); + let rendered2 = packages2 + .iter() + .find(|p| p.name().to_string() == "local:consumer") + .unwrap() + .to_string(); + assert_eq!(rendered, rendered2); +} + +#[test] +fn package_scope_nested_round_trip() { + let wit = r#"package local:nested; + +record point { + x: u32, + y: u32, +} + +interface api { + move-to: func(p: point); +} + +world w { + export api; +} +"#; + let mut resolve = wit_parser::Resolve::new(); + resolve.push_str("nested.wit", wit).unwrap(); + let packages = packages_from_parsed(&resolve); + assert_eq!(packages.len(), 1); + let rendered = packages[0].to_string(); + assert!( + rendered.contains("record point"), + "expected nested package-scope type:\n{rendered}" + ); + + let mut resolve2 = wit_parser::Resolve::new(); + resolve2.push_str("nested.wit", &rendered).unwrap(); + let packages2 = packages_from_parsed(&resolve2); + assert_eq!(packages[0].to_string(), packages2[0].to_string()); +} + +#[test] +fn package_scope_foreign_iface_only_round_trip() { + let mut resolve = wit_parser::Resolve::new(); + resolve + .push_str( + "types.wit", + r#" +package local:types; + +record point { + x: u32, + y: u32, +} + +interface unused {} +"#, + ) + .unwrap(); + resolve + .push_str( + "consumer.wit", + r#" +package local:consumer; + +use local:types/point; + +interface api { + move-to: func(p: point); +} + +world w { + export api; +} +"#, + ) + .unwrap(); + + let packages = packages_from_parsed(&resolve); + let consumer = packages + .iter() + .find(|p| p.name().to_string() == "local:consumer") + .expect("consumer package"); + let rendered = consumer.to_string(); + assert!( + rendered.contains("use local:types/point;"), + "iface-only foreign refs must still emit toplevel use:\n{rendered}" + ); + assert!( + !rendered.contains("record point"), + "consumer should not redefine foreign package type:\n{rendered}" + ); + + let mut resolve2 = wit_parser::Resolve::new(); + let types = packages + .iter() + .find(|p| p.name().to_string() == "local:types") + .unwrap(); + resolve2.push_str("types.wit", &types.to_string()).unwrap(); + resolve2.push_str("consumer.wit", &rendered).unwrap(); + let packages2 = packages_from_parsed(&resolve2); + let rendered2 = packages2 + .iter() + .find(|p| p.name().to_string() == "local:consumer") + .unwrap() + .to_string(); + assert_eq!(rendered, rendered2); +} + +#[test] +fn package_scope_foreign_world_only_round_trip() { + let mut resolve = wit_parser::Resolve::new(); + resolve + .push_str( + "types.wit", + r#" +package local:types; + +record point { + x: u32, + y: u32, +} + +interface unused {} +"#, + ) + .unwrap(); + resolve + .push_str( + "consumer.wit", + r#" +package local:consumer; + +use local:types/point; + +world w { + import move-to: func(p: point); + export place: func(p: point); +} +"#, + ) + .unwrap(); + + let packages = packages_from_parsed(&resolve); + let consumer = packages + .iter() + .find(|p| p.name().to_string() == "local:consumer") + .expect("consumer package"); + let rendered = consumer.to_string(); + assert!( + rendered.contains("use local:types/point;"), + "world-only foreign refs must still emit toplevel use:\n{rendered}" + ); + + let mut resolve2 = wit_parser::Resolve::new(); + let types = packages + .iter() + .find(|p| p.name().to_string() == "local:types") + .unwrap(); + resolve2.push_str("types.wit", &types.to_string()).unwrap(); + resolve2.push_str("consumer.wit", &rendered).unwrap(); + let packages2 = packages_from_parsed(&resolve2); + assert_eq!( + consumer.to_string(), + packages2 + .iter() + .find(|p| p.name().to_string() == "local:consumer") + .unwrap() + .to_string() + ); +} diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 10ed687411..ea8272865b 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -1,6 +1,6 @@ use crate::ast::error::ParseError; use crate::{ParseResult, UnresolvedPackage, UnresolvedPackageGroup}; -use alloc::borrow::Cow; +use alloc::borrow::{Cow, ToOwned}; use alloc::boxed::Box; use alloc::format; use alloc::string::{String, ToString}; @@ -259,6 +259,9 @@ impl<'a> DeclList<'a> { )?; } + // Package-scope types only reference names, not package paths. + AstItem::Type(_) => {} + AstItem::Package(pkg) => pkg.decl_list.for_each_path(f)?, } } @@ -271,6 +274,7 @@ enum AstItem<'a> { World(World<'a>), Use(ToplevelUse<'a>), Package(PackageFile<'a>), + Type(TypeDef<'a>), } impl<'a> AstItem<'a> { @@ -285,7 +289,29 @@ impl<'a> AstItem<'a> { Some((_span, Token::Package)) => { PackageFile::parse_nested(tokens, docs, attributes, depth).map(Self::Package) } - other => Err(err_expected(tokens, "`world`, `interface` or `use`", other).into()), + Some((_span, Token::Type)) => TypeDef::parse(tokens, docs, attributes).map(Self::Type), + Some((_span, Token::Flags)) => { + TypeDef::parse_flags(tokens, docs, attributes).map(Self::Type) + } + Some((_span, Token::Record)) => { + TypeDef::parse_record(tokens, docs, attributes).map(Self::Type) + } + Some((_span, Token::Variant)) => { + TypeDef::parse_variant(tokens, docs, attributes).map(Self::Type) + } + Some((_span, Token::Enum)) => { + TypeDef::parse_enum(tokens, docs, attributes).map(Self::Type) + } + Some((span, Token::Resource)) => Err(ParseError::new_syntax( + span, + "resources cannot be declared at package scope".to_owned(), + )), + other => Err(err_expected( + tokens, + "`world`, `interface`, `use`, or type definition", + other, + ) + .into()), } } } diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 3ac115a27d..b993d59914 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -37,10 +37,23 @@ pub struct Resolver<'a> { /// handling things like `use` at the top level. ast_items: Vec>, + /// Per-file aliases from toplevel `use` that resolve (or may resolve) to + /// package-scope types. + ast_type_items: Vec>, + /// A map for the entire package being created of all names defined within, /// along with the ID they're mapping to. package_items: IndexMap<&'a str, AstItem>, + /// Package-scope types keyed by name. Owners are [`TypeOwner::None`] until + /// merge into a [`Resolve`]. + package_types: IndexMap<&'a str, TypeId>, + + /// Names of package-scope types discovered before they are allocated. + /// Used so top-level `use` of those names can be deferred until after + /// `resolve_package_types`. + pending_package_type_names: HashSet<&'a str>, + /// A per-interface map of name to item-in-the-interface. This is the same /// length as `self.types` and is pushed to whenever `self.types` is pushed /// to. @@ -54,6 +67,10 @@ pub struct Resolver<'a> { /// of its imports. foreign_deps: IndexMap)>>, + /// Dual stubs for toplevel `use ns:pkg/name` whose target may be an + /// interface or a package-scope type. + foreign_unknown: IndexMap<(PackageName, String), (InterfaceId, TypeId)>, + /// All interfaces that are present within `self.foreign_deps`. foreign_interfaces: HashSet, @@ -174,8 +191,14 @@ impl<'a> Resolver<'a> { // all interfaces in the package to visit. let decl_lists = mem::take(&mut self.decl_lists); self.populate_foreign_deps(&decl_lists); - let (iface_order, world_order) = self.populate_ast_items(&decl_lists)?; + let (iface_order, world_order, mut ids) = self.populate_ast_items(&decl_lists)?; + // File namespaces for interfaces/worlds must exist before foreign type + // stubs are created (resolve_ast_item_path). Package-scope types are + // registered afterwards so Unknown stubs stay a prefix of the arena. + self.finish_ast_items(&decl_lists, &ids)?; self.populate_foreign_types(&decl_lists)?; + self.resolve_package_types(&decl_lists, &mut ids)?; + self.register_package_types_in_ns(&decl_lists, &ids)?; // Use the topological ordering of all interfaces to resolve all // interfaces in-order. Note that a reverse-mapping from ID to AST is @@ -188,18 +211,18 @@ impl<'a> Resolver<'a> { ast::AstItem::Interface(iface) => { let id = match self.ast_items[i][iface.name.name] { AstItem::Interface(id) => id, - AstItem::World(_) => unreachable!(), + AstItem::World(_) | AstItem::Type(_) => unreachable!(), }; iface_id_to_ast.insert(id, (iface, i)); } ast::AstItem::World(world) => { let id = match self.ast_items[i][world.name.name] { AstItem::World(id) => id, - AstItem::Interface(_) => unreachable!(), + AstItem::Interface(_) | AstItem::Type(_) => unreachable!(), }; world_id_to_ast.insert(id, (world, i)); } - ast::AstItem::Use(_) => {} + ast::AstItem::Use(_) | ast::AstItem::Type(_) => {} ast::AstItem::Package(_) => unreachable!(), } } @@ -225,6 +248,12 @@ impl<'a> Resolver<'a> { worlds: mem::take(&mut self.worlds), types: mem::take(&mut self.types), interfaces: mem::take(&mut self.interfaces), + package_types: self + .package_types + .iter() + .map(|(name, id)| (name.to_string(), *id)) + .collect(), + foreign_unknown: mem::take(&mut self.foreign_unknown), foreign_deps: self .foreign_deps .iter() @@ -253,6 +282,8 @@ impl<'a> Resolver<'a> { let mut foreign_deps = mem::take(&mut self.foreign_deps); let mut foreign_interfaces = mem::take(&mut self.foreign_interfaces); let mut foreign_worlds = mem::take(&mut self.foreign_worlds); + let mut foreign_unknown = mem::take(&mut self.foreign_unknown); + let self_pkg = self.package_name.as_ref().map(|(n, _)| n.clone()); for decl_list in decl_lists { decl_list .for_each_path(&mut |_, attrs, path, _names, world_or_iface| { @@ -261,41 +292,74 @@ impl<'a> Resolver<'a> { _ => return Ok(()), }; + // Same-package toplevel `use` of a name that may be a + // package-scope type is resolved locally later. Interface + // and world package paths still register (including self) + // so dependency cycles are detected. + if self_pkg.as_ref() == Some(&id.package_name()) + && matches!(world_or_iface, WorldOrInterface::Unknown) + { + return Ok(()); + } + let stability = self.stability(attrs)?; + let pkg_name = id.package_name(); - let deps = foreign_deps.entry(id.package_name()).or_insert_with(|| { + let deps = foreign_deps.entry(pkg_name.clone()).or_insert_with(|| { self.foreign_dep_spans.push(id.span); IndexMap::default() }); - let (id, stabilities) = deps.entry(name.name).or_insert_with(|| { - let id = match world_or_iface { + let (ast_item, stabilities) = deps.entry(name.name).or_insert_with(|| { + let ast_item = match world_or_iface { WorldOrInterface::World => { log::trace!( "creating a world for foreign dep: {}/{}", - id.package_name(), + pkg_name, name.name ); AstItem::World(self.alloc_world(name.span)) } - WorldOrInterface::Interface | WorldOrInterface::Unknown => { - // Currently top-level `use` always assumes an interface, so the - // `Unknown` case is the same as `Interface`. + WorldOrInterface::Interface => { log::trace!( "creating an interface for foreign dep: {}/{}", - id.package_name(), + pkg_name, name.name ); AstItem::Interface(self.alloc_interface(name.span)) } + WorldOrInterface::Unknown => { + // Dual stubs: may resolve to either an interface + // or a package-scope type once the dep is known. + log::trace!( + "creating dual stubs for foreign dep: {}/{}", + pkg_name, + name.name + ); + let iface = self.alloc_interface(name.span); + let ty = self.types.alloc(TypeDef { + docs: Docs::default(), + stability: Stability::Unknown, + kind: TypeDefKind::Unknown, + name: Some(name.name.to_string()), + owner: TypeOwner::None, + span: name.span, + external_id: None, + }); + self.unknown_type_spans.push(name.span); + foreign_unknown + .insert((pkg_name.clone(), name.name.to_string()), (iface, ty)); + AstItem::Interface(iface) + } }; - (id, Vec::new()) + (ast_item, Vec::new()) }); stabilities.push(stability); - let _ = match *id { + let _ = match *ast_item { AstItem::Interface(id) => foreign_interfaces.insert(id), AstItem::World(id) => foreign_worlds.insert(id), + AstItem::Type(_) => false, }; Ok(()) @@ -305,6 +369,7 @@ impl<'a> Resolver<'a> { self.foreign_deps = foreign_deps; self.foreign_interfaces = foreign_interfaces; self.foreign_worlds = foreign_worlds; + self.foreign_unknown = foreign_unknown; } fn alloc_interface(&mut self, span: Span) -> InterfaceId { @@ -340,7 +405,7 @@ impl<'a> Resolver<'a> { fn populate_ast_items( &mut self, decl_lists: &[ast::DeclList<'a>], - ) -> ParseResult<(Vec, Vec)> { + ) -> ParseResult<(Vec, Vec, IndexMap<&'a str, AstItem>)> { let mut package_items = IndexMap::default(); // Validate that all worlds and interfaces have unique names within this @@ -380,6 +445,21 @@ impl<'a> Resolver<'a> { let prev = names.insert(w.name.name, item); assert!(prev.is_none()); } + ast::AstItem::Type(t) => { + if package_items.insert(t.name.name, t.name.span).is_some() { + return Err(ParseError::new_syntax( + t.name.span, + format!("duplicate item named `{}`", t.name.name), + )); + } + let prev = decl_list_ns.insert(t.name.name, ()); + assert!(prev.is_none()); + // Package-scope types are not part of the iface/world + // dependency order graph. + let prev = names.insert(t.name.name, item); + assert!(prev.is_none()); + self.pending_package_type_names.insert(t.name.name); + } // These are processed down below. ast::AstItem::Use(_) => {} @@ -416,6 +496,7 @@ impl<'a> Resolver<'a> { } ast::AstItem::Interface(i) => (&i.name, ItemSource::Local(i.name.clone())), ast::AstItem::World(w) => (&w.name, ItemSource::Local(w.name.clone())), + ast::AstItem::Type(t) => (&t.name, ItemSource::Local(t.name.clone())), ast::AstItem::Package(_) => unreachable!(), }; if decl_list_ns.insert(name.name, (name.span, src)).is_some() { @@ -443,10 +524,16 @@ impl<'a> Resolver<'a> { match decl_list_ns.get(used_name.name) { Some((_, ItemSource::Foreign)) => return Ok(()), Some((_, ItemSource::Local(id))) => { + if matches!(names.get(id.name), Some(ast::AstItem::Type(_))) { + return Ok(()); + } order[iface.name].push(id.clone()); } None => match package_items.get(used_name.name) { Some(_) => { + if matches!(names.get(used_name.name), Some(ast::AstItem::Type(_))) { + return Ok(()); + } order[iface.name].push(used_name.clone()); } None => { @@ -488,11 +575,23 @@ impl<'a> Resolver<'a> { assert!(prev.is_none()); world_id_order.push(id); } - ast::AstItem::Use(_) | ast::AstItem::Package(_) => unreachable!(), + ast::AstItem::Type(_) | ast::AstItem::Use(_) | ast::AstItem::Package(_) => { + unreachable!() + } }; } + Ok((iface_id_order, world_id_order, ids)) + } + + fn finish_ast_items( + &mut self, + decl_lists: &[ast::DeclList<'a>], + ids: &IndexMap<&'a str, AstItem>, + ) -> ParseResult<()> { + let self_pkg = self.package_name.as_ref().map(|(n, _)| n.clone()); for decl_list in decl_lists { let mut items = IndexMap::default(); + let mut type_items = IndexMap::default(); for item in decl_list.items.iter() { let (name, ast_item) = match item { ast::AstItem::Use(u) => { @@ -502,21 +601,63 @@ impl<'a> Resolver<'a> { format!("attributes not allowed on top-level use"), )); } - let name = u.as_.as_ref().unwrap_or(u.item.name()); + let alias = u.as_.as_ref().unwrap_or(u.item.name()); let item = match &u.item { - ast::UsePath::Id(name) => *ids.get(name.name).ok_or_else(|| { - ParseError::from(ParseErrorKind::ItemNotFound { - span: name.span, - name: name.name.to_string(), - kind: "interface or world".to_owned(), - hint: None, - }) - })?, + ast::UsePath::Id(name) => { + match ids.get(name.name) { + Some(item) => *item, + None if self.pending_package_type_names.contains(name.name) => { + // Package-scope type; registered after + // resolve_package_types. + continue; + } + None => { + return Err(ParseError::from( + ParseErrorKind::ItemNotFound { + span: name.span, + name: name.name.to_string(), + kind: "interface, world, or type".to_owned(), + hint: None, + }, + )); + } + } + } ast::UsePath::Package { id, name } => { - self.foreign_deps[&id.package_name()][name.name].0 + if self_pkg.as_ref() == Some(&id.package_name()) { + match ids.get(name.name) { + Some(item) => *item, + None if self + .pending_package_type_names + .contains(name.name) => + { + continue; + } + None => { + return Err(ParseError::from( + ParseErrorKind::ItemNotFound { + span: name.span, + name: name.name.to_string(), + kind: "interface, world, or type".to_owned(), + hint: None, + }, + )); + } + } + } else { + let pkg = id.package_name(); + let key = (pkg.clone(), name.name.to_string()); + if let Some((_iface, ty)) = self.foreign_unknown.get(&key) { + type_items.insert(alias.name, *ty); + } + self.foreign_deps[&pkg][name.name].0 + } } }; - (name.name, item) + if let AstItem::Type(ty) = item { + type_items.insert(alias.name, ty); + } + (alias.name, item) } ast::AstItem::Interface(i) => { let iface_item = ids[i.name.name]; @@ -528,6 +669,7 @@ impl<'a> Resolver<'a> { assert!(matches!(world_item, AstItem::World(_))); (w.name.name, world_item) } + ast::AstItem::Type(_) => continue, ast::AstItem::Package(_) => unreachable!(), }; let prev = items.insert(name, ast_item); @@ -541,8 +683,144 @@ impl<'a> Resolver<'a> { } } self.ast_items.push(items); + self.ast_type_items.push(type_items); + } + Ok(()) + } + + /// Resolve all package-scope typedefs into `self.types` / `self.package_types`. + fn resolve_package_types( + &mut self, + decl_lists: &[ast::DeclList<'a>], + ids: &mut IndexMap<&'a str, AstItem>, + ) -> ParseResult<()> { + let mut type_defs = IndexMap::default(); + let mut type_files = IndexMap::default(); + for (i, decl_list) in decl_lists.iter().enumerate() { + for item in decl_list.items.iter() { + let ast::AstItem::Type(t) = item else { + continue; + }; + let prev = type_defs.insert(t.name.name, t); + assert!(prev.is_none()); + type_files.insert(t.name.name, i); + } + } + + let mut type_deps = IndexMap::default(); + for (name, def) in type_defs.iter() { + let mut deps = Vec::new(); + collect_deps(&def.ty, &mut deps); + // Only other package-scope types participate in topo ordering. + // Same-file foreign `use` aliases are already allocated stubs and + // are resolved via `ast_type_items` with `cur_ast_index` set below. + deps.retain(|d| type_defs.contains_key(d.name)); + type_deps.insert(*name, deps); + } + + let order = toposort("type", &type_deps)?; + for name in order { + // Allow same-file toplevel `use` aliases (e.g. foreign package + // types). Cross-file uses remain invisible because `use` is + // file-scoped. + self.cur_ast_index = type_files[name]; + let def = type_defs[name]; + let docs = self.docs(&def.docs); + let stability = self.stability(&def.attributes)?; + let external_id = self.external_id(&def.attributes)?; + let kind = self.resolve_type_def(&def.ty, &stability)?; + let id = self.types.alloc(TypeDef { + docs, + stability, + kind, + name: Some(def.name.name.to_string()), + owner: TypeOwner::None, + span: def.name.span, + external_id, + }); + let prev = self.package_types.insert(def.name.name, id); + assert!(prev.is_none()); + let prev = ids.insert(def.name.name, AstItem::Type(id)); + assert!(prev.is_none()); + } + Ok(()) + } + + /// After package-scope types are allocated, register them in package and + /// per-file namespaces, and finish deferred top-level `use` aliases. + fn register_package_types_in_ns( + &mut self, + decl_lists: &[ast::DeclList<'a>], + ids: &IndexMap<&'a str, AstItem>, + ) -> ParseResult<()> { + let self_pkg = self.package_name.as_ref().map(|(n, _)| n.clone()); + for (i, decl_list) in decl_lists.iter().enumerate() { + for item in decl_list.items.iter() { + match item { + ast::AstItem::Type(t) => { + let type_item = ids[t.name.name]; + assert!(matches!(type_item, AstItem::Type(_))); + if let AstItem::Type(ty) = type_item { + self.ast_type_items[i].insert(t.name.name, ty); + } + let prev = self.ast_items[i].insert(t.name.name, type_item); + assert!(prev.is_none()); + let prev = self.package_items.insert(t.name.name, type_item); + assert!(prev.is_none()); + } + ast::AstItem::Use(u) => { + let alias = u.as_.as_ref().unwrap_or(u.item.name()); + if self.ast_items[i].contains_key(alias.name) { + continue; + } + let item = match &u.item { + ast::UsePath::Id(name) => match ids.get(name.name) { + Some(AstItem::Type(ty)) => { + self.ast_type_items[i].insert(alias.name, *ty); + AstItem::Type(*ty) + } + Some(_) => continue, + None => { + return Err(ParseError::from(ParseErrorKind::ItemNotFound { + span: name.span, + name: name.name.to_string(), + kind: "interface, world, or type".to_owned(), + hint: None, + })); + } + }, + ast::UsePath::Package { id, name } => { + if self_pkg.as_ref() != Some(&id.package_name()) { + continue; + } + match ids.get(name.name) { + Some(AstItem::Type(ty)) => { + self.ast_type_items[i].insert(alias.name, *ty); + AstItem::Type(*ty) + } + Some(_) => continue, + None => { + return Err(ParseError::from( + ParseErrorKind::ItemNotFound { + span: name.span, + name: name.name.to_string(), + kind: "interface, world, or type".to_owned(), + hint: None, + }, + )); + } + } + } + }; + let prev = self.ast_items[i].insert(alias.name, item); + assert!(prev.is_none()); + } + _ => {} + } + } } - Ok((iface_id_order, world_id_order)) + self.pending_package_type_names.clear(); + Ok(()) } /// Generate a `Type::Unknown` entry for all types imported from foreign @@ -850,8 +1128,10 @@ impl<'a> Resolver<'a> { for field in fields { match field { ast::InterfaceItem::Func(f) => { - self.define_interface_name(&f.name, TypeOrItem::Item("function"))?; - funcs.push(self.resolve_function( + // Resolve the signature before reserving the function name so + // package-scope (and local) types with the same name remain + // visible to params/results. + let func = self.resolve_function( &f.docs, &f.attributes, &f.name.name, @@ -862,7 +1142,9 @@ impl<'a> Resolver<'a> { } else { FunctionKind::Freestanding }, - )?); + )?; + self.define_interface_name(&f.name, TypeOrItem::Item("function"))?; + funcs.push(func); } ast::InterfaceItem::Use(_) => {} ast::InterfaceItem::TypeDef(ast::TypeDef { @@ -939,6 +1221,32 @@ impl<'a> Resolver<'a> { } } } + + // Package-scope and file-level type aliases are visible without being + // in `type_lookup`. Add phantom entries so toposort does not fail when + // local defs reference them. + let mut phantoms = Vec::new(); + for deps in type_deps.values() { + for dep in deps { + if type_deps.contains_key(dep.name) { + continue; + } + let is_package = self.package_types.contains_key(dep.name); + let is_file = self + .ast_type_items + .get(self.cur_ast_index) + .map(|m| m.contains_key(dep.name)) + .unwrap_or(false); + if is_package || is_file { + phantoms.push(dep.name); + } + } + } + for name in phantoms { + type_deps.insert(name, Vec::new()); + type_defs.insert(name, None); + } + let order = toposort("type", &type_deps).map_err(attach_old_float_type_context)?; for ty in order { let def = match type_defs.swap_remove(&ty).unwrap() { @@ -1124,21 +1432,41 @@ impl<'a> Resolver<'a> { .or_else(|| self.package_items.get(id.name)); match item { Some(item) => Ok((*item, id.name.into(), id.span)), - None => { - return Err(ParseError::from(ParseErrorKind::ItemNotFound { - span: id.span, - name: id.name.to_string(), - kind: "interface or world".to_owned(), - hint: None, - })); + None if self.pending_package_type_names.contains(id.name) => { + Err(ParseError::new_syntax( + id.span, + format!("name `{}` is defined as a type, not an interface", id.name), + )) } + None => Err(ParseError::from(ParseErrorKind::ItemNotFound { + span: id.span, + name: id.name.to_string(), + kind: "interface or world".to_owned(), + hint: None, + })), } } - ast::UsePath::Package { id, name } => Ok(( - self.foreign_deps[&id.package_name()][name.name].0, - name.name.into(), - name.span, - )), + ast::UsePath::Package { id, name } => { + let pkg = id.package_name(); + if let Some((item, _)) = self + .foreign_deps + .get(&pkg) + .and_then(|deps| deps.get(name.name)) + { + return Ok((*item, name.name.into(), name.span)); + } + if self.package_name.as_ref().map(|(n, _)| n) == Some(&pkg) { + if let Some(item) = self.package_items.get(name.name) { + return Ok((*item, name.name.into(), name.span)); + } + } + Err(ParseError::from(ParseErrorKind::ItemNotFound { + span: name.span, + name: name.name.to_string(), + kind: "interface, world, or type".to_owned(), + hint: None, + })) + } } } @@ -1156,6 +1484,12 @@ impl<'a> Resolver<'a> { format!("name `{name}` is defined as a world, not an interface"), )); } + AstItem::Type(_) => { + return Err(ParseError::new_syntax( + span, + format!("name `{name}` is defined as a type, not an interface"), + )); + } } } @@ -1173,6 +1507,12 @@ impl<'a> Resolver<'a> { format!("name `{name}` is defined as an interface, not a world"), )); } + AstItem::Type(_) => { + return Err(ParseError::new_syntax( + span, + format!("name `{name}` is defined as a type, not a world"), + )); + } } } @@ -1375,6 +1715,16 @@ impl<'a> Resolver<'a> { )); } None => { + if let Some(id) = self.package_types.get(name.name) { + return Ok(*id); + } + if let Some(id) = self + .ast_type_items + .get(self.cur_ast_index) + .and_then(|m| m.get(name.name)) + { + return Ok(*id); + } return Err(ParseError::from(ParseErrorKind::ItemNotFound { span: name.span, name: name.name.to_string(), diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index 6b6f0498e0..b692dd24a7 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -145,22 +145,27 @@ impl ComponentInfo { } fn is_wit_package(&self) -> Option { - // all wit package exports must be component types, and there must be at - // least one + // all wit package exports must be types (component or defined), and + // there must be at least one if self.externs.is_empty() { return None; } - if !self.externs.iter().all(|(_, item)| { - let export = match item { - Extern::Export(e) => e, - _ => return false, - }; - match export.ty { - ComponentEntityType::Type { created, .. } => { - matches!(created, ComponentAnyTypeId::Component(_)) - } + if !self.externs.iter().all(|(name, item)| match item { + Extern::Export(export) => match export.ty { + ComponentEntityType::Type { created, .. } => matches!( + created, + ComponentAnyTypeId::Component(_) | ComponentAnyTypeId::Defined(_) + ), _ => false, + }, + + // A package's definitions are all exported, with one exception: a + // package-scope type defined in terms of one from another package + // has no wrapping component-type to hold the `import` naming where + // that definition came from, so the import lands here instead. + Extern::Import(import) => { + matches!(import.ty, ComponentEntityType::Type { .. }) && is_qualified_name(name) } }) { return None; @@ -170,7 +175,11 @@ impl ComponentInfo { // strings for each component. The v1 format uses ":/wit" as the name // for the top-level exports, while the v2 format uses the unqualified name of the encoded // entity. - match ComponentName::new(&self.externs[0].0, 0).ok()?.kind() { + let (first_export, _) = self + .externs + .iter() + .find(|(_, item)| matches!(item, Extern::Export(_)))?; + match ComponentName::new(first_export, 0).ok()?.kind() { ComponentNameKind::Interface(name) if name.interface().as_str() == "wit" => { Some(WitEncodingVersion::V1) } @@ -214,61 +223,109 @@ impl ComponentInfo { let mut interfaces = IndexMap::default(); let mut worlds = IndexMap::default(); + let mut types = IndexMap::default(); let mut fields = PackageFields { interfaces: &mut interfaces, worlds: &mut worlds, + types: &mut types, }; - for (_, item) in self.externs.iter() { + for (export_name, item) in self.externs.iter() { let export = match item { Extern::Export(e) => e, - _ => unreachable!(), + + // An import here either binds one of this package's own + // exported types under its fully-qualified name, which is how + // the package name of a package without interfaces or worlds + // is recovered, or it names a package-scope type from another + // package that this package's own definitions depend on. + Extern::Import(import) => { + if let Some(owner) = + decoder.match_self_package_type_import(export_name, import, &fields)? + { + if let Some(prev) = pkg_name.as_ref() { + if *prev != owner { + bail!("item defined with mismatched package name") + } + } else { + pkg_name = Some(owner); + } + continue; + } + decoder + .register_package_type_import(export_name, import, None, &mut fields) + .with_context(|| format!("failed to process import `{export_name}`"))?; + continue; + } }; - let component = match export.ty { + match export.ty { + ComponentEntityType::Type { + created: ComponentAnyTypeId::Defined(_), + .. + } => { + let name = { + let parsed = ComponentName::new(export_name, 0) + .with_context(|| format!("invalid export name `{export_name}`"))?; + match parsed.kind() { + ComponentNameKind::Label(label) => label.to_string(), + _ => bail!( + "package-scope type export `{export_name}` must be a kebab name" + ), + } + }; + let id = decoder.register_type_export(&name, export, TypeOwner::None)?; + let prev = fields.types.insert(name, id); + if prev.is_some() { + bail!("duplicate package-scope type export"); + } + } ComponentEntityType::Type { created: ComponentAnyTypeId::Component(id), .. - } => &self.types[id], - _ => unreachable!(), - }; - - // The single export of this component will determine if it's a world or an interface: - // worlds export a component, while interfaces export an instance. - if component.exports.len() != 1 { - bail!( - "Expected a single export, but found {} instead", - component.exports.len() - ); - } + } => { + let component = &self.types[id]; + + // The single export of this component will determine if it's a world or an interface: + // worlds export a component, while interfaces export an instance. + if component.exports.len() != 1 { + bail!( + "Expected a single export, but found {} instead", + component.exports.len() + ); + } - let name = component.exports.keys().nth(0).unwrap(); + let name = component.exports.keys().nth(0).unwrap(); - let item = &component.exports[name]; - let name = match item.ty { - ComponentEntityType::Component(_) => { - let package_name = decoder.decode_world(name.as_str(), item, &mut fields)?; - package_name - } - ComponentEntityType::Instance(_) => { - let package_name = decoder.decode_interface( - name.as_str(), - &component.imports, - item, - &mut fields, - )?; - package_name + let item = &component.exports[name]; + let name = match item.ty { + ComponentEntityType::Component(_) => { + let package_name = + decoder.decode_world(name.as_str(), item, &mut fields)?; + package_name + } + ComponentEntityType::Instance(_) => { + let package_name = decoder.decode_interface( + name.as_str(), + &component.imports, + item, + &mut fields, + )?; + package_name + } + _ => unreachable!(), + }; + + if let Some(pkg_name) = pkg_name.as_ref() { + // TODO: when we have fully switched to the v2 format, we should switch to parsing + // multiple wit documents instead of bailing. + if pkg_name != &name { + bail!("item defined with mismatched package name") + } + } else { + pkg_name.replace(name); + } } _ => unreachable!(), - }; - - if let Some(pkg_name) = pkg_name.as_ref() { - // TODO: when we have fully switched to the v2 format, we should switch to parsing - // multiple wit documents instead of bailing. - if pkg_name != &name { - bail!("item defined with mismatched package name") - } - } else { - pkg_name.replace(name); } } @@ -278,7 +335,17 @@ impl ComponentInfo { docs: Docs::default(), interfaces, worlds, + types, } + } else if !types.is_empty() { + // A package encoded with self-describing type imports never lands + // here; this is only reachable for binaries produced before those + // imports were emitted for packages without interfaces or worlds. + bail!( + "cannot recover the package name of this WIT package: it contains \ + only package-scope types and no fully-qualified names; re-encode \ + it with a newer version of wit-component" + ); } else { bail!("no exported component type found"); }; @@ -319,11 +386,13 @@ impl ComponentInfo { docs: Default::default(), worlds: [(world_name.to_string(), world)].into_iter().collect(), interfaces: Default::default(), + types: Default::default(), }; let mut fields = PackageFields { worlds: &mut package.worlds, interfaces: &mut package.interfaces, + types: &mut package.types, }; for (name, item) in self.externs.iter() { @@ -467,6 +536,7 @@ pub fn decode_world(wasm: &[u8]) -> Result<(Resolve, WorldId)> { let mut decoder = WitPackageDecoder::new(types); let mut interfaces = IndexMap::default(); let mut worlds = IndexMap::default(); + let mut pkg_types = IndexMap::default(); let ty = &types[world]; assert_eq!(ty.imports.len(), 0); assert_eq!(ty.exports.len(), 1); @@ -477,12 +547,14 @@ pub fn decode_world(wasm: &[u8]) -> Result<(Resolve, WorldId)> { &mut PackageFields { interfaces: &mut interfaces, worlds: &mut worlds, + types: &mut pkg_types, }, )?; let (resolve, pkg) = decoder.finish(Package { name, interfaces, worlds, + types: pkg_types, docs: Default::default(), }); // The package decoded here should only have a single world so extract that @@ -494,6 +566,7 @@ pub fn decode_world(wasm: &[u8]) -> Result<(Resolve, WorldId)> { struct PackageFields<'a> { interfaces: &'a mut IndexMap, worlds: &'a mut IndexMap, + types: &'a mut IndexMap, } struct WitPackageDecoder<'a> { @@ -538,13 +611,6 @@ impl WitPackageDecoder<'_> { _ => unreachable!(), }; - // Process all imports for this package first, where imports are - // importing from remote packages. - for (name, item) in ty.imports.iter() { - self.register_import(name, item) - .with_context(|| format!("failed to process import `{name}`"))?; - } - let mut package = Package { // The name encoded for packages must be of the form `foo:bar/wit` // where "wit" is just a placeholder for now. The package name in @@ -558,13 +624,23 @@ impl WitPackageDecoder<'_> { docs: Default::default(), interfaces: Default::default(), worlds: Default::default(), + types: Default::default(), }; + let package_name = package.name.clone(); let mut fields = PackageFields { interfaces: &mut package.interfaces, worlds: &mut package.worlds, + types: &mut package.types, }; + // Process all imports for this package first, where imports are + // importing from remote packages. + for (name, item) in ty.imports.iter() { + self.register_component_type_import(name, item, &package_name, &mut fields) + .with_context(|| format!("failed to process import `{name}`"))?; + } + for (name, ty) in ty.exports.iter() { match ty.ty { ComponentEntityType::Instance(_) => { @@ -573,10 +649,20 @@ impl WitPackageDecoder<'_> { } ComponentEntityType::Component(idx) => { let ty = &self.types[idx]; - self.register_world(name.as_str(), ty, &mut fields) + self.register_world(name.as_str(), ty, &package_name, &mut fields) .with_context(|| format!("failed to process export `{name}`"))?; } - _ => bail!("component export `{name}` is not an instance or component"), + ComponentEntityType::Type { + created: ComponentAnyTypeId::Defined(_), + .. + } => { + let id = self.register_type_export(name.as_str(), ty, TypeOwner::None)?; + let prev = fields.types.insert(name.to_string(), id); + if prev.is_some() { + bail!("duplicate package-scope type export `{name}`"); + } + } + _ => bail!("component export `{name}` is not an instance, component, or type"), } } Ok(package) @@ -598,9 +684,9 @@ impl WitPackageDecoder<'_> { _ => bail!("expected world name to be fully qualified"), }; - for (name, ty) in imports.iter() { - self.register_import(name, ty) - .with_context(|| format!("failed to process import `{name}`"))?; + for (import_name, ty) in imports.iter() { + self.register_component_type_import(import_name, ty, &package, fields) + .with_context(|| format!("failed to process import `{import_name}`"))?; } let _ = self.register_interface(name, item, fields)?; @@ -608,6 +694,157 @@ impl WitPackageDecoder<'_> { Ok(package) } + /// Processes one import of a wrapping component-type, which is either an + /// instance for a `use`d interface or a type for a package-scope type. + fn register_component_type_import( + &mut self, + name: &str, + item: &ComponentItem, + package: &PackageName, + fields: &mut PackageFields<'_>, + ) -> Result<()> { + match item.ty { + ComponentEntityType::Type { .. } => { + self.register_package_type_import(name, item, Some(package), fields)?; + } + _ => { + self.register_import(name, item)?; + } + } + Ok(()) + } + + /// Registers a dependency on the package-scope type named by `name`, a + /// fully-qualified `namespace:package/name`. + /// + /// Package-scope types are structural, so the importer restates the + /// definition and binds it with an `eq` import rather than supplying an + /// instance to satisfy it. The restated definition is unified with the type + /// already decoded for `package`, when the name belongs to it, or with a + /// lazily created type otherwise. `package` is `None` where no local + /// definition can be in scope yet, which makes the name necessarily foreign. + /// Detects a self-import: a root-level type import bound with `eq` to one + /// of this package's own exported types, named by its fully-qualified + /// `namespace:package/name`. + /// + /// Such an import is emitted for packages without interfaces or worlds, + /// where it is the only place the package name appears in the binary. + /// Returns the package name recovered from the import, or `None` when the + /// import is a foreign package-scope dependency instead. + fn match_self_package_type_import( + &mut self, + name: &str, + item: &ComponentItem, + fields: &PackageFields<'_>, + ) -> Result> { + let parsed = self.parse_component_name(name)?; + let owner_name = match parsed.kind() { + ComponentNameKind::Interface(id) => id, + _ => return Ok(None), + }; + let type_name = owner_name.interface().to_string(); + + let (referenced, created) = match item.ty { + ComponentEntityType::Type { + referenced, + created, + } => (referenced, created), + _ => return Ok(None), + }; + + // The import is a self-import only when its `eq` bound resolves to the + // same type that this package exported under the import's unqualified + // label. A foreign dependency's bound is a restated definition that no + // local export refers to, so it never matches. + let Some(prev) = self.find_alias(referenced) else { + return Ok(None); + }; + if fields.types.get(&type_name) != Some(&prev) { + return Ok(None); + } + + let owner = owner_name.to_package_name(item)?; + self.type_map.insert(created, prev); + Ok(Some(owner)) + } + + fn register_package_type_import( + &mut self, + name: &str, + item: &ComponentItem, + package: Option<&PackageName>, + fields: &mut PackageFields<'_>, + ) -> Result { + let parsed = self.parse_component_name(name)?; + let owner_name = match parsed.kind() { + ComponentNameKind::Interface(id) => id, + _ => bail!("expected type import `{name}` to be fully qualified"), + }; + let owner = owner_name.to_package_name(item)?; + let type_name = owner_name.interface().to_string(); + + let (referenced, created) = match item.ty { + ComponentEntityType::Type { + referenced, + created, + } => (referenced, created), + _ => unreachable!(), + }; + let referenced = match referenced { + ComponentAnyTypeId::Defined(ty) => ty, + // `resource` is excluded from package scope precisely because its + // bound is abstract and so can't be restated like this. + _ => bail!("package-scope type `{name}` is not a structural type"), + }; + + let existing = if package == Some(&owner) { + fields.types.get(&type_name).copied() + } else { + None + } + .or_else(|| { + self.foreign_packages + .get(&owner.to_string()) + .and_then(|pkg| pkg.types.get(&type_name).copied()) + }); + + let id = match existing { + Some(id) => { + // Walk the restated definition to unify its anonymous inner + // types with those of the original definition, the same way the + // types of a `use`d interface are unified. + let types = self.types; + self.register_defined(id, &types[referenced])?; + let prev = self.type_map.insert(created, id); + assert!(prev.is_none()); + id + } + + // Nothing has declared this definition yet, so fill it in here. + // This is the only view available when a definition is decoded on + // its own, such as a `world` out of a `component-type` custom + // section, and it leaves the owning package with just the slice of + // types this resolve needs. + None => { + let id = self.register_type_export(&type_name, item, TypeOwner::None)?; + let package = self + .foreign_packages + .entry(owner.to_string()) + .or_insert_with(|| Package { + name: owner.clone(), + docs: Default::default(), + interfaces: Default::default(), + worlds: Default::default(), + types: Default::default(), + }); + package.types.insert(type_name, id); + id + } + }; + + Ok(id) + } + fn decode_world<'a>( &mut self, name: &str, @@ -627,7 +864,7 @@ impl WitPackageDecoder<'_> { _ => bail!("expected world name to be fully qualified"), }; - let _ = self.register_world(name, ty, fields)?; + let _ = self.register_world(name, ty, &package, fields)?; Ok(package) } @@ -893,6 +1130,7 @@ impl WitPackageDecoder<'_> { docs: Default::default(), interfaces: Default::default(), worlds: Default::default(), + types: Default::default(), }); let interface = *package .interfaces @@ -1100,6 +1338,7 @@ impl WitPackageDecoder<'_> { &mut self, name: &str, ty: &ComponentType, + package_name: &PackageName, package: &mut PackageFields<'a>, ) -> Result { let name = self @@ -1122,6 +1361,13 @@ impl WitPackageDecoder<'_> { ComponentEntityType::Instance(_) => { self.decode_world_instance(name, item, package)? } + // A fully-qualified name here is a package-scope type the world + // depends on, which is not an item of the world itself. A plain + // name is a type the world defines. + ComponentEntityType::Type { .. } if is_qualified_name(name) => { + self.register_package_type_import(name, item, Some(package_name), package)?; + continue; + } ComponentEntityType::Type { .. } => { let ty = self.register_type_export(name.as_str(), item, owner)?; ( @@ -1505,6 +1751,7 @@ impl WitPackageDecoder<'_> { name, interfaces, worlds, + types, docs, } = package; @@ -1523,6 +1770,7 @@ impl WitPackageDecoder<'_> { name: name.clone(), interfaces: Default::default(), worlds: Default::default(), + types: Default::default(), docs, }); let prev = self.resolve.package_names.insert(name, id); @@ -1553,6 +1801,12 @@ impl WitPackageDecoder<'_> { } } + for (name, id) in types { + let prev = self.resolve.packages[pkg].types.insert(name, id); + assert!(prev.is_none()); + self.resolve.types[id].owner = TypeOwner::Package(pkg); + } + pkg } @@ -1875,3 +2129,12 @@ impl InterfaceNameExt for wasmparser::names::InterfaceName<'_> { }) } } + +/// Whether `name` is a fully-qualified `namespace:package/name` rather than a +/// plain kebab name. +fn is_qualified_name(name: &str) -> bool { + matches!( + ComponentName::new(name, 0).map(|n| matches!(n.kind(), ComponentNameKind::Interface(_))), + Ok(true) + ) +} diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index d1aece5a81..1ce7bdb598 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -163,6 +163,18 @@ pub struct UnresolvedPackage { /// Doc comments for this package. pub docs: Docs, + /// Named types declared at package scope, keyed by kebab-name. + /// + /// These types temporarily use [`TypeOwner::None`] until they are merged + /// into a [`Resolve`], at which point their owner becomes + /// [`TypeOwner::Package`]. + pub package_types: IndexMap, + + /// Foreign toplevel `use ns:pkg/name` entries whose target may be either an + /// interface or a package-scope type. Each entry records the dual stubs + /// allocated before the dependency is known. + pub foreign_unknown: IndexMap<(PackageName, String), (InterfaceId, TypeId)>, + #[cfg_attr(not(feature = "std"), allow(dead_code))] package_name_span: Span, unknown_type_spans: Vec, @@ -225,6 +237,9 @@ pub enum AstItem { Interface(InterfaceId), #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_id"))] World(WorldId), + /// A package-scope type declaration. + #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_id"))] + Type(TypeId), } /// A structure used to keep track of the name of a package, containing optional @@ -730,6 +745,9 @@ pub enum TypeOwner { /// This type was defined within an `interface` block. #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_id"))] Interface(InterfaceId), + /// This type was defined at package scope. + #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_id"))] + Package(PackageId), /// This type wasn't inherently defined anywhere, such as a `list`, which /// doesn't need an owner. #[cfg_attr(feature = "serde", serde(untagged, serialize_with = "serialize_none"))] diff --git a/crates/wit-parser/src/metadata.rs b/crates/wit-parser/src/metadata.rs index f1972f7a3a..f73c1f5d53 100644 --- a/crates/wit-parser/src/metadata.rs +++ b/crates/wit-parser/src/metadata.rs @@ -68,6 +68,11 @@ pub struct PackageMetadata { serde(default, skip_serializing_if = "StringMap::is_empty") )] interfaces: StringMap, + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "StringMap::is_empty") + )] + types: StringMap, } impl PackageMetadata { @@ -89,11 +94,18 @@ impl PackageMetadata { .map(|(name, id)| (name.to_string(), InterfaceMetadata::extract(resolve, *id))) .filter(|(_, item)| !item.is_empty()) .collect(); + let types = package + .types + .iter() + .map(|(name, id)| (name.to_string(), TypeMetadata::extract(resolve, *id))) + .filter(|(_, item)| !item.is_empty()) + .collect(); Self { docs: package.docs.contents.as_deref().map(Into::into), worlds, interfaces, + types, } } @@ -113,6 +125,12 @@ impl PackageMetadata { }; docs.inject(resolve, id)?; } + for (name, docs) in &self.types { + let Some(&id) = resolve.packages[package].types.get(name) else { + bail!("missing package type {name:?}"); + }; + docs.inject(resolve, id)?; + } if let Some(docs) = &self.docs { resolve.packages[package].docs.contents = Some(docs.to_string()); } diff --git a/crates/wit-parser/src/resolve/clone.rs b/crates/wit-parser/src/resolve/clone.rs index 9e2df3d2fe..11e1b92a6e 100644 --- a/crates/wit-parser/src/resolve/clone.rs +++ b/crates/wit-parser/src/resolve/clone.rs @@ -109,6 +109,17 @@ impl<'a> Cloner<'a> { } fn type_id(&mut self, ty: &mut TypeId) { + let owner = self.resolve.types[*ty].owner; + // Package-scope types (and other types not owned by the world/interface + // being cloned) are shared by reference across includes. Only types + // owned by `prev_owner` (or anonymous `None` types) are deep-cloned. + if owner != TypeOwner::None && owner != self.prev_owner { + if let Some(new_id) = self.maps.types.get(ty) { + *ty = *new_id; + } + return; + } + if !self.maps.types.contains_key(ty) { let mut new = self.resolve.types[*ty].clone(); self.type_def(&mut new); @@ -226,6 +237,7 @@ impl<'a> Cloner<'a> { new.package = Some(self.new_package.unwrap_or_else(|| match self.new_owner { TypeOwner::Interface(id) => self.resolve.interfaces[id].package.unwrap(), TypeOwner::World(id) => self.resolve.worlds[id].package.unwrap(), + TypeOwner::Package(id) => id, TypeOwner::None => unreachable!(), })); new.clone_of = Some(old_id); diff --git a/crates/wit-parser/src/resolve/mod.rs b/crates/wit-parser/src/resolve/mod.rs index 388e2767b7..f9f8be83d9 100644 --- a/crates/wit-parser/src/resolve/mod.rs +++ b/crates/wit-parser/src/resolve/mod.rs @@ -108,9 +108,9 @@ pub struct Resolve { /// A WIT package within a `Resolve`. /// -/// A package is a collection of interfaces and worlds. Packages additionally -/// have a unique identifier that affects generated components and uniquely -/// identifiers this particular package. +/// A package is a collection of interfaces, worlds, and package-scope types. +/// Packages additionally have a unique identifier that affects generated +/// components and uniquely identifiers this particular package. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct Package { @@ -129,6 +129,13 @@ pub struct Package { /// All worlds contained in this package, keyed by the world's name. #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_id_map"))] pub worlds: IndexMap, + + /// Types declared at package scope, keyed by the type's kebab-name. + /// + /// These share the same top-level name space as [`Self::interfaces`] and + /// [`Self::worlds`]. + #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_id_map"))] + pub types: IndexMap, } pub type PackageId = Id; @@ -522,6 +529,7 @@ impl Resolve { world_map, interfaces_to_add, worlds_to_add, + types_to_add, .. } = map; @@ -700,6 +708,9 @@ impl Resolve { for (_, id) in pkg.worlds.iter_mut() { *id = remap.map_world(*id, Default::default())?; } + for (_, id) in pkg.types.iter_mut() { + *id = remap.map_type(*id, Default::default())?; + } self.packages.alloc(pkg) } }; @@ -741,6 +752,7 @@ impl Resolve { match &mut self.types[id].owner { TypeOwner::Interface(id) => *id = remap.map_interface(*id, Default::default())?, TypeOwner::World(id) => *id = remap.map_world(*id, Default::default())?, + TypeOwner::Package(id) => *id = remap.packages[id.index()], TypeOwner::None => {} } } @@ -761,6 +773,12 @@ impl Resolve { .insert(name, remap.map_world(world, Default::default())?); assert!(prev.is_none()); } + for (name, pkg, ty) in types_to_add { + let prev = self.packages[pkg] + .types + .insert(name, remap.map_type(ty, Default::default())?); + assert!(prev.is_none()); + } log::trace!("now have {} packages", self.packages.len()); @@ -1783,6 +1801,11 @@ impl Resolve { assert!(self.worlds.get(id).is_some()); assert!(world_types[id.index()].contains(&ty_id)); } + TypeOwner::Package(id) => { + assert!(self.packages.get(id).is_some()); + let name = ty.name.as_ref().expect("package type must be named"); + assert_eq!(self.packages[id].types.get(name), Some(&ty_id)); + } TypeOwner::None => {} } } @@ -1938,6 +1961,11 @@ impl Resolve { visitor.visit_type_def(self, ty); for ty in visitor.1 { let ty = &self.types[ty]; + // Package-scope types are visible without being imported into the + // world (they are not world-owned names). + if matches!(ty.owner, TypeOwner::Package(_)) { + continue; + } let Some(name) = ty.name.clone() else { continue; }; @@ -3488,6 +3516,7 @@ impl Remap { docs: unresolved.docs.clone(), interfaces: Default::default(), worlds: Default::default(), + types: Default::default(), }); assert!( !resolve.package_names.contains_key(&unresolved.name), @@ -3500,6 +3529,7 @@ impl Remap { let foreign_types = self.types.len(); let foreign_interfaces = self.interfaces.len(); let foreign_worlds = self.worlds.len(); + let package_types = unresolved.package_types.clone(); // Copy over all types first, updating any intra-type references. Note // that types are sorted topologically which means this iteration @@ -3514,6 +3544,11 @@ impl Remap { } self.update_typedef(resolve, &mut ty, span)?; + // Package-scope types are tracked on the unresolved package and get + // their owner rewritten here once PackageId is known. + if package_types.values().any(|tid| *tid == id) { + ty.owner = TypeOwner::Package(pkgid); + } let new_id = resolve.types.alloc(ty); assert_eq!(self.types.len(), id.index()); @@ -3567,7 +3602,7 @@ impl Remap { TypeOwner::Interface(iface_id) => { *iface_id = self.map_interface_for_type(*iface_id, span)?; } - TypeOwner::World(_) | TypeOwner::None => {} + TypeOwner::World(_) | TypeOwner::Package(_) | TypeOwner::None => {} } } @@ -3602,7 +3637,7 @@ impl Remap { TypeOwner::World(world_id) => { *world_id = self.map_world_for_type(*world_id, span)?; } - TypeOwner::Interface(_) | TypeOwner::None => {} + TypeOwner::Interface(_) | TypeOwner::Package(_) | TypeOwner::None => {} } } @@ -3657,6 +3692,13 @@ impl Remap { .insert(world.name.clone(), id); assert!(prev.is_none()); } + for (name, old_id) in package_types { + // Note that errors here mean the type was filtered by stability. + if let Ok(new_id) = self.map_type(old_id, Default::default()) { + let prev = resolve.packages[pkgid].types.insert(name, new_id); + assert!(prev.is_none()); + } + } Ok(pkgid) } @@ -3687,6 +3729,7 @@ impl Remap { ); assert!(prev.is_none()); } + AstItem::Type(_) => {} } } } @@ -3781,15 +3824,63 @@ impl Remap { continue; } - let iface_id = pkg.interfaces.get(interface).copied().ok_or_else(|| { - ResolveError::from(ResolveErrorKind::InterfaceNotFound { - span: iface_span, - requested: interface.to_string(), - package: pkg.name.clone(), - }) - })?; - assert_eq!(self.interfaces.len(), unresolved_iface_id.index()); - self.interfaces.push(Some(iface_id)); + let key = (pkg_name.clone(), interface.clone()); + let is_unknown = unresolved.foreign_unknown.contains_key(&key); + + let iface_id = pkg.interfaces.get(interface).copied(); + let type_id = pkg.types.get(interface).copied(); + let world_id = pkg.worlds.get(interface).copied(); + + if is_unknown { + match (iface_id, type_id, world_id) { + (Some(iface_id), None, None) => { + assert_eq!(self.interfaces.len(), unresolved_iface_id.index()); + self.interfaces.push(Some(iface_id)); + // Type stub abandoned in process_foreign_types. + } + (None, Some(_), None) => { + // Interface stub abandoned; type mapped in process_foreign_types. + self.interfaces.push(None); + } + (None, None, Some(_)) => { + return Err(ResolveError::new_semantic( + iface_span, + format!( + "`{interface}` is a world; top-level use expects an interface or type" + ), + )); + } + (None, None, None) => { + return Err(ResolveError::new_semantic( + iface_span, + format!( + "interface or type `{interface}` not found in package `{}`", + pkg.name + ), + )); + } + _ => unreachable!("provider package enforces a shared top-level name space"), + } + } else { + let iface_id = match iface_id { + Some(id) => id, + None if type_id.is_some() => { + return Err(ResolveError::new_semantic( + iface_span, + format!("name `{interface}` is defined as a type, not an interface"), + )); + } + None => { + return Err(ResolveError::from(ResolveErrorKind::InterfaceNotFound { + span: iface_span, + requested: interface.to_string(), + package: pkg.name.clone(), + })); + } + }; + assert_eq!(self.interfaces.len(), unresolved_iface_id.index()); + self.interfaces.push(Some(iface_id)); + } } for (id, _) in unresolved.interfaces.iter().skip(self.interfaces.len()) { assert!( @@ -3883,24 +3974,57 @@ impl Remap { continue; } - let unresolved_iface_id = match unresolved_ty.owner { - TypeOwner::Interface(id) => id, - _ => unreachable!(), - }; - let iface_id = self.map_interface(unresolved_iface_id, Default::default())?; - 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(|| { - ResolveError::new_semantic( - span, - format!("type `{name}` not defined in interface"), - ) - })?; - assert_eq!(self.types.len(), unresolved_type_id.index()); - self.types.push(Some(type_id)); + match unresolved_ty.owner { + TypeOwner::Interface(unresolved_iface_id) => { + let iface_id = self.map_interface(unresolved_iface_id, Default::default())?; + 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(|| { + ResolveError::new_semantic( + span, + format!("type `{name}` not defined in interface"), + ) + })?; + assert_eq!(self.types.len(), unresolved_type_id.index()); + self.types.push(Some(type_id)); + } + TypeOwner::None => { + // Dual-stub Unknown types from toplevel `use` of a package-scope type. + let name = unresolved_ty.name.as_ref().unwrap(); + let span = unresolved.unknown_type_spans[unresolved_type_id.index()]; + let mut mapped = None; + for ((pkg_name, item_name), (_iface, ty_stub)) in + unresolved.foreign_unknown.iter() + { + if *ty_stub != unresolved_type_id || item_name != name { + continue; + } + let dep_pkg = resolve.package_names[pkg_name]; + if let Some(type_id) = resolve.packages[dep_pkg].types.get(name).copied() { + mapped = Some(type_id); + } else { + // Resolved as an interface — abandon this type stub. + mapped = None; + } + break; + } + assert_eq!(self.types.len(), unresolved_type_id.index()); + match mapped { + Some(type_id) => self.types.push(Some(type_id)), + None => { + // Either abandoned (resolved as interface) or missing. + // Missing is already reported when processing interfaces. + self.types.push(None); + let _ = span; + } + } + } + TypeOwner::World(_) | TypeOwner::Package(_) => unreachable!(), + } } for (_, ty) in unresolved.types.iter().skip(self.types.len()) { if let TypeDefKind::Unknown = ty.kind { @@ -4468,6 +4592,7 @@ struct MergeMap<'a> { /// * The ID within `from` of the item being added. interfaces_to_add: Vec<(String, PackageId, InterfaceId)>, worlds_to_add: Vec<(String, PackageId, WorldId)>, + types_to_add: Vec<(String, PackageId, TypeId)>, /// Which `Resolve` is being merged from. from: &'a Resolve, @@ -4485,6 +4610,7 @@ impl<'a> MergeMap<'a> { world_map: Default::default(), interfaces_to_add: Default::default(), worlds_to_add: Default::default(), + types_to_add: Default::default(), from, into, } @@ -4527,6 +4653,12 @@ impl<'a> MergeMap<'a> { let into_interface_id = match into.interfaces.get(name) { Some(id) => *id, None => { + if into.types.contains_key(name) || into.worlds.contains_key(name) { + bail!( + "failed to add interface `{name}`: package already has a \ + type or world with that name" + ); + } log::trace!("adding unique interface {name}"); self.interfaces_to_add .push((name.clone(), into_id, *from_interface_id)); @@ -4543,6 +4675,12 @@ impl<'a> MergeMap<'a> { let into_world_id = match into.worlds.get(name) { Some(id) => *id, None => { + if into.types.contains_key(name) || into.interfaces.contains_key(name) { + bail!( + "failed to add world `{name}`: package already has a \ + type or interface with that name" + ); + } log::trace!("adding unique world {name}"); self.worlds_to_add .push((name.clone(), into_id, *from_world_id)); @@ -4555,6 +4693,30 @@ impl<'a> MergeMap<'a> { .with_context(|| format!("failed to merge world `{name}`"))?; } + for (name, from_type_id) in from.types.iter() { + let into_type_id = match into.types.get(name) { + Some(id) => *id, + None => { + if into.interfaces.contains_key(name) || into.worlds.contains_key(name) { + bail!( + "failed to add package type `{name}`: package already has an \ + interface or world with that name" + ); + } + log::trace!("adding unique package type {name}"); + self.types_to_add + .push((name.clone(), into_id, *from_type_id)); + continue; + } + }; + + log::trace!("merging duplicate package types {name}"); + let prev = self.type_map.insert(*from_type_id, into_type_id); + assert!(prev.is_none()); + self.build_type_id(*from_type_id, into_type_id) + .with_context(|| format!("failed to merge package type `{name}`"))?; + } + Ok(()) } diff --git a/crates/wit-parser/tests/package-scope-merge.rs b/crates/wit-parser/tests/package-scope-merge.rs new file mode 100644 index 0000000000..32d7161d5a --- /dev/null +++ b/crates/wit-parser/tests/package-scope-merge.rs @@ -0,0 +1,360 @@ +use wit_parser::{CloneMaps, Resolve, TypeOwner}; + +#[test] +fn package_scope_types_survive_merge() { + let mut a = Resolve::new(); + let pkg_a = a + .push_str( + "a.wit", + r#" +package local:a; + +record point { + x: u32, + y: u32, +} + +interface api { + move-to: func(p: point); +} + +world w { + export api; +} +"#, + ) + .unwrap(); + + let mut b = Resolve::new(); + let pkg_b = b + .push_str( + "b.wit", + r#" +package local:b; + +enum direction { + north, + south, +} + +interface api { + heading: func() -> direction; +} + +world w { + export api; +} +"#, + ) + .unwrap(); + + let remap = a.merge(b).unwrap(); + let pkg_b = remap.packages[pkg_b.index()]; + + assert_eq!(a.packages[pkg_a].types.len(), 1); + assert!(a.packages[pkg_a].types.contains_key("point")); + assert_eq!(a.packages[pkg_b].types.len(), 1); + assert!(a.packages[pkg_b].types.contains_key("direction")); + + for (name, &id) in a.packages[pkg_a].types.iter() { + assert_eq!(a.types[id].owner, TypeOwner::Package(pkg_a)); + assert_eq!(a.types[id].name.as_deref(), Some(name.as_str())); + } + for (name, &id) in a.packages[pkg_b].types.iter() { + assert_eq!(a.types[id].owner, TypeOwner::Package(pkg_b)); + assert_eq!(a.types[id].name.as_deref(), Some(name.as_str())); + } + + a.assert_valid(); +} + +#[test] +fn package_scope_types_merge_into_same_package() { + let mut into = Resolve::new(); + into.push_str( + "into.wit", + r#" +package local:shared; + +record point { + x: u32, + y: u32, +} + +interface api { + move-to: func(p: point); +} + +world w { + export api; +} +"#, + ) + .unwrap(); + + let mut from = Resolve::new(); + from.push_str( + "from.wit", + r#" +package local:shared; + +record point { + x: u32, + y: u32, +} + +flags style { + bold, + italic, +} + +interface api { + move-to: func(p: point); + style: func() -> style; +} + +world w { + export api; +} +"#, + ) + .unwrap(); + + into.merge(from).unwrap(); + let pkg = *into.package_names.values().next().unwrap(); + assert!(into.packages[pkg].types.contains_key("point")); + assert!(into.packages[pkg].types.contains_key("style")); + into.assert_valid(); +} + +#[test] +fn package_scope_types_survive_merge_worlds() { + let mut resolve = Resolve::new(); + let pkg = resolve + .push_str( + "demo.wit", + r#" +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface a { + move-to: func(p: point); +} + +interface b { + place: func(p: point); +} + +world wa { + export a; +} + +world wb { + export b; +} +"#, + ) + .unwrap(); + + let wa = resolve.packages[pkg].worlds["wa"]; + let wb = resolve.packages[pkg].worlds["wb"]; + let point = resolve.packages[pkg].types["point"]; + + resolve + .merge_worlds(wb, wa, &mut CloneMaps::default()) + .unwrap(); + + assert_eq!(resolve.packages[pkg].types["point"], point); + assert_eq!(resolve.types[point].owner, TypeOwner::Package(pkg)); + assert_eq!(resolve.packages[pkg].types.len(), 1); + resolve.assert_valid(); +} + +/// Two packages may each declare a package-scope type under the same local +/// name. Merging their worlds does not unify those types: a package-scope name +/// is unique within its package, so the two stay distinct and keep their own +/// owners. +#[test] +fn package_scope_same_local_name_survives_merge_worlds() { + let mut resolve = Resolve::new(); + let pkg1 = resolve + .push_str( + "b1.wit", + r#" +package a:b1; + +record r { + a: u32, +} + +world w1 { + export f1: func() -> r; +} +"#, + ) + .unwrap(); + let pkg2 = resolve + .push_str( + "b2.wit", + r#" +package a:b2; + +record r { + a: f32, +} + +world w2 { + export f2: func() -> r; +} +"#, + ) + .unwrap(); + + let w1 = resolve.packages[pkg1].worlds["w1"]; + let w2 = resolve.packages[pkg2].worlds["w2"]; + let r1 = resolve.packages[pkg1].types["r"]; + let r2 = resolve.packages[pkg2].types["r"]; + assert_ne!(r1, r2); + + resolve + .merge_worlds(w2, w1, &mut CloneMaps::default()) + .unwrap(); + + assert_eq!(resolve.packages[pkg1].types["r"], r1); + assert_eq!(resolve.packages[pkg2].types["r"], r2); + assert_eq!(resolve.types[r1].owner, TypeOwner::Package(pkg1)); + assert_eq!(resolve.types[r2].owner, TypeOwner::Package(pkg2)); + + let f1 = match &resolve.worlds[w1].exports[&wit_parser::WorldKey::Name("f1".into())] { + wit_parser::WorldItem::Function(f) => f, + other => panic!("expected f1 to be a function, got {other:?}"), + }; + let f2 = match &resolve.worlds[w1].exports[&wit_parser::WorldKey::Name("f2".into())] { + wit_parser::WorldItem::Function(f) => f, + other => panic!("expected f2 to be a function, got {other:?}"), + }; + assert_eq!(f1.result, Some(wit_parser::Type::Id(r1))); + assert_eq!(f2.result, Some(wit_parser::Type::Id(r2))); + resolve.assert_valid(); +} + +/// When both worlds export the same kebab name, the shared item is merged +/// rather than added, and `MergeMap::build_type_id` does not compare type +/// structure (see its FIXME). So two `export f: func() -> r` worlds whose `r` +/// differs merge without error and `into`'s type wins. This is pre-existing +/// behavior for any named type, not specific to package scope; the test pins it +/// down so a future structural check is a deliberate change. +#[test] +fn package_scope_same_export_name_keeps_into_type_on_merge_worlds() { + let mut resolve = Resolve::new(); + let pkg1 = resolve + .push_str( + "b1.wit", + r#" +package a:b1; + +record r { + a: u32, +} + +world w1 { + export f: func() -> r; +} +"#, + ) + .unwrap(); + let pkg2 = resolve + .push_str( + "b2.wit", + r#" +package a:b2; + +record r { + a: f32, +} + +world w2 { + export f: func() -> r; +} +"#, + ) + .unwrap(); + + let w1 = resolve.packages[pkg1].worlds["w1"]; + let w2 = resolve.packages[pkg2].worlds["w2"]; + let r1 = resolve.packages[pkg1].types["r"]; + let r2 = resolve.packages[pkg2].types["r"]; + + resolve + .merge_worlds(w2, w1, &mut CloneMaps::default()) + .unwrap(); + + // `f` already existed in `w1`, so it was merged, not added, and kept + // pointing at `a:b1/r`. Both package-scope types still exist separately. + let f = match &resolve.worlds[w1].exports[&wit_parser::WorldKey::Name("f".into())] { + wit_parser::WorldItem::Function(f) => f, + other => panic!("expected f to be a function, got {other:?}"), + }; + assert_eq!(f.result, Some(wit_parser::Type::Id(r1))); + assert_eq!(resolve.packages[pkg1].types["r"], r1); + assert_eq!(resolve.packages[pkg2].types["r"], r2); + assert_eq!(resolve.types[r1].owner, TypeOwner::Package(pkg1)); + assert_eq!(resolve.types[r2].owner, TypeOwner::Package(pkg2)); + resolve.assert_valid(); +} + +#[test] +fn package_scope_type_vs_interface_merge_clash() { + let mut into = Resolve::new(); + into.push_str( + "into.wit", + r#" +package local:shared; + +record point { + x: u32, + y: u32, +} + +interface api { + move-to: func(p: point); +} + +world w { + export api; +} +"#, + ) + .unwrap(); + + let mut from = Resolve::new(); + from.push_str( + "from.wit", + r#" +package local:shared; + +interface point { + get: func() -> u32; +} + +world w { + export point; +} +"#, + ) + .unwrap(); + + let err = match into.merge(from) { + Ok(_) => panic!("expected merge to fail on type vs interface name clash"), + Err(e) => e, + }; + let msg = format!("{err:#}"); + assert!( + msg.contains("point"), + "expected NS clash mentioning `point`, got: {msg}" + ); +} diff --git a/crates/wit-parser/tests/ui/async.wit.json b/crates/wit-parser/tests/ui/async.wit.json index fd009db69b..591be0e9af 100644 --- a/crates/wit-parser/tests/ui/async.wit.json +++ b/crates/wit-parser/tests/ui/async.wit.json @@ -145,7 +145,8 @@ }, "worlds": { "y": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/comments.wit.json b/crates/wit-parser/tests/ui/comments.wit.json index b15b1200f7..f09acee90c 100644 --- a/crates/wit-parser/tests/ui/comments.wit.json +++ b/crates/wit-parser/tests/ui/comments.wit.json @@ -40,7 +40,8 @@ "interfaces": { "foo": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/complex-include.wit.json b/crates/wit-parser/tests/ui/complex-include.wit.json index f9b17f2792..cddf35f7f8 100644 --- a/crates/wit-parser/tests/ui/complex-include.wit.json +++ b/crates/wit-parser/tests/ui/complex-include.wit.json @@ -171,7 +171,8 @@ }, "worlds": { "bar-a": 0 - } + }, + "types": {} }, { "name": "foo:baz", @@ -181,7 +182,8 @@ }, "worlds": { "baz-a": 1 - } + }, + "types": {} }, { "name": "foo:root", @@ -194,7 +196,8 @@ "b": 3, "c": 4, "union-world": 5 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/cross-package-resource.wit.json b/crates/wit-parser/tests/ui/cross-package-resource.wit.json index 51ab92795d..c4b337c882 100644 --- a/crates/wit-parser/tests/ui/cross-package-resource.wit.json +++ b/crates/wit-parser/tests/ui/cross-package-resource.wit.json @@ -54,14 +54,16 @@ "interfaces": { "foo": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:bar", "interfaces": { "foo": 1 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/diamond1.wit.json b/crates/wit-parser/tests/ui/diamond1.wit.json index 54e87e7b04..260a82a7f3 100644 --- a/crates/wit-parser/tests/ui/diamond1.wit.json +++ b/crates/wit-parser/tests/ui/diamond1.wit.json @@ -39,21 +39,24 @@ "interfaces": { "types": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:dep2", "interfaces": { "types": 1 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:foo", "interfaces": {}, "worlds": { "foo": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/disambiguate-diamond.wit.json b/crates/wit-parser/tests/ui/disambiguate-diamond.wit.json index 34f675da51..336a141aae 100644 --- a/crates/wit-parser/tests/ui/disambiguate-diamond.wit.json +++ b/crates/wit-parser/tests/ui/disambiguate-diamond.wit.json @@ -109,7 +109,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/empty.wit.json b/crates/wit-parser/tests/ui/empty.wit.json index 092abe5e1d..e46bf51e0d 100644 --- a/crates/wit-parser/tests/ui/empty.wit.json +++ b/crates/wit-parser/tests/ui/empty.wit.json @@ -6,7 +6,8 @@ { "name": "foo:empty", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/error-context.wit.json b/crates/wit-parser/tests/ui/error-context.wit.json index 617e37d162..f2408b1423 100644 --- a/crates/wit-parser/tests/ui/error-context.wit.json +++ b/crates/wit-parser/tests/ui/error-context.wit.json @@ -53,7 +53,8 @@ "interfaces": { "error-contexts": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/feature-gates.wit.json b/crates/wit-parser/tests/ui/feature-gates.wit.json index e959720d9a..4020a830a3 100644 --- a/crates/wit-parser/tests/ui/feature-gates.wit.json +++ b/crates/wit-parser/tests/ui/feature-gates.wit.json @@ -295,7 +295,8 @@ "worlds": { "ungated-world": 0, "mixed-world": 1 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/feature-types.wit.json b/crates/wit-parser/tests/ui/feature-types.wit.json index 929b3982c9..e76b714f51 100644 --- a/crates/wit-parser/tests/ui/feature-types.wit.json +++ b/crates/wit-parser/tests/ui/feature-types.wit.json @@ -60,7 +60,8 @@ "interfaces": { "foo": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/foreign-deps-union.wit.json b/crates/wit-parser/tests/ui/foreign-deps-union.wit.json index a6b2d44acd..8f38e1f930 100644 --- a/crates/wit-parser/tests/ui/foreign-deps-union.wit.json +++ b/crates/wit-parser/tests/ui/foreign-deps-union.wit.json @@ -349,28 +349,32 @@ "interfaces": { "other-interface": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:corp", "interfaces": { "saas": 1 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:different-pkg", "interfaces": { "i": 2 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:foreign-pkg", "interfaces": { "the-default": 3 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:wasi", @@ -380,7 +384,8 @@ }, "worlds": { "wasi": 0 - } + }, + "types": {} }, { "name": "foo:some-pkg", @@ -389,7 +394,8 @@ "some-interface": 7, "another-interface": 8 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:root", @@ -404,7 +410,8 @@ "my-world2": 2, "bars-world": 3, "unionw-world": 4 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/foreign-deps.wit.json b/crates/wit-parser/tests/ui/foreign-deps.wit.json index f24404d86e..f5818bba54 100644 --- a/crates/wit-parser/tests/ui/foreign-deps.wit.json +++ b/crates/wit-parser/tests/ui/foreign-deps.wit.json @@ -304,28 +304,32 @@ "interfaces": { "other-interface": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:corp", "interfaces": { "saas": 1 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:different-pkg", "interfaces": { "i": 2 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:foreign-pkg", "interfaces": { "the-default": 3 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:wasi", @@ -333,7 +337,8 @@ "clocks": 4, "filesystem": 5 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:some-pkg", @@ -342,7 +347,8 @@ "some-interface": 7, "another-interface": 8 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:root", @@ -356,7 +362,8 @@ "my-world": 0, "my-world2": 1, "bars-world": 2 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/foreign-interface-dep-gated.wit.json b/crates/wit-parser/tests/ui/foreign-interface-dep-gated.wit.json index 58e43977ea..9399dec23d 100644 --- a/crates/wit-parser/tests/ui/foreign-interface-dep-gated.wit.json +++ b/crates/wit-parser/tests/ui/foreign-interface-dep-gated.wit.json @@ -19,21 +19,24 @@ { "name": "a:b3", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:b2", "interfaces": {}, "worlds": { "the-world": 0 - } + }, + "types": {} }, { "name": "a:b1", "interfaces": {}, "worlds": { "the-world": 1 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/foreign-world-dep-gated.wit.json b/crates/wit-parser/tests/ui/foreign-world-dep-gated.wit.json index 58e43977ea..9399dec23d 100644 --- a/crates/wit-parser/tests/ui/foreign-world-dep-gated.wit.json +++ b/crates/wit-parser/tests/ui/foreign-world-dep-gated.wit.json @@ -19,21 +19,24 @@ { "name": "a:b3", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:b2", "interfaces": {}, "worlds": { "the-world": 0 - } + }, + "types": {} }, { "name": "a:b1", "interfaces": {}, "worlds": { "the-world": 1 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/functions.wit.json b/crates/wit-parser/tests/ui/functions.wit.json index b6eb38fdc6..b336608b22 100644 --- a/crates/wit-parser/tests/ui/functions.wit.json +++ b/crates/wit-parser/tests/ui/functions.wit.json @@ -109,7 +109,8 @@ "interfaces": { "functions": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/gated-include.wit.json b/crates/wit-parser/tests/ui/gated-include.wit.json index 5f00420fe1..60d8adb3f0 100644 --- a/crates/wit-parser/tests/ui/gated-include.wit.json +++ b/crates/wit-parser/tests/ui/gated-include.wit.json @@ -340,7 +340,8 @@ "dup-include-in-package": 5, "dup-use-package": 6, "dup-use-package-ordered": 7 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/gated-use.wit.json b/crates/wit-parser/tests/ui/gated-use.wit.json index 7a823b83a4..1b6284ddfb 100644 --- a/crates/wit-parser/tests/ui/gated-use.wit.json +++ b/crates/wit-parser/tests/ui/gated-use.wit.json @@ -21,14 +21,16 @@ "interfaces": { "stable": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "wasmtime:test", "interfaces": { "types": 1 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/ignore-files-deps.wit.json b/crates/wit-parser/tests/ui/ignore-files-deps.wit.json index 7a812dbecf..7178008af3 100644 --- a/crates/wit-parser/tests/ui/ignore-files-deps.wit.json +++ b/crates/wit-parser/tests/ui/ignore-files-deps.wit.json @@ -28,14 +28,16 @@ "interfaces": { "types": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:foo", "interfaces": {}, "worlds": { "foo": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/import-export-overlap1.wit.json b/crates/wit-parser/tests/ui/import-export-overlap1.wit.json index 40fa899882..51f2f8138a 100644 --- a/crates/wit-parser/tests/ui/import-export-overlap1.wit.json +++ b/crates/wit-parser/tests/ui/import-export-overlap1.wit.json @@ -31,7 +31,8 @@ "interfaces": {}, "worlds": { "foo": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/import-export-overlap2.wit.json b/crates/wit-parser/tests/ui/import-export-overlap2.wit.json index 1c0e597444..0fdcc2a3da 100644 --- a/crates/wit-parser/tests/ui/import-export-overlap2.wit.json +++ b/crates/wit-parser/tests/ui/import-export-overlap2.wit.json @@ -36,7 +36,8 @@ "interfaces": {}, "worlds": { "foo": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/include-reps.wit.json b/crates/wit-parser/tests/ui/include-reps.wit.json index 21debef2fe..a6a5760f9a 100644 --- a/crates/wit-parser/tests/ui/include-reps.wit.json +++ b/crates/wit-parser/tests/ui/include-reps.wit.json @@ -62,7 +62,8 @@ "worlds": { "bar": 0, "foo": 1 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/kebab-name-include-with.wit.json b/crates/wit-parser/tests/ui/kebab-name-include-with.wit.json index f940be853b..badd5a6b13 100644 --- a/crates/wit-parser/tests/ui/kebab-name-include-with.wit.json +++ b/crates/wit-parser/tests/ui/kebab-name-include-with.wit.json @@ -60,7 +60,8 @@ "foo": 0, "bar": 1, "baz": 2 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/kinds-of-deps.wit.json b/crates/wit-parser/tests/ui/kinds-of-deps.wit.json index f13afe6301..a71fd72472 100644 --- a/crates/wit-parser/tests/ui/kinds-of-deps.wit.json +++ b/crates/wit-parser/tests/ui/kinds-of-deps.wit.json @@ -61,35 +61,40 @@ "interfaces": { "d": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "e:e", "interfaces": { "e": 1 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "b:b", "interfaces": { "b": 2 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "c:c", "interfaces": { "c": 3 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:a", "interfaces": {}, "worlds": { "a": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/many-names.wit.json b/crates/wit-parser/tests/ui/many-names.wit.json index 590c68471a..bea73c3e52 100644 --- a/crates/wit-parser/tests/ui/many-names.wit.json +++ b/crates/wit-parser/tests/ui/many-names.wit.json @@ -36,7 +36,8 @@ }, "worlds": { "name": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/maps.wit.json b/crates/wit-parser/tests/ui/maps.wit.json index 33f5065f12..9a5fa1e79f 100644 --- a/crates/wit-parser/tests/ui/maps.wit.json +++ b/crates/wit-parser/tests/ui/maps.wit.json @@ -249,7 +249,8 @@ }, "worlds": { "maps-world": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/multi-file-multi-package.wit.json b/crates/wit-parser/tests/ui/multi-file-multi-package.wit.json index 2d0132c82e..6f3529115a 100644 --- a/crates/wit-parser/tests/ui/multi-file-multi-package.wit.json +++ b/crates/wit-parser/tests/ui/multi-file-multi-package.wit.json @@ -217,7 +217,8 @@ }, "worlds": { "w2": 0 - } + }, + "types": {} }, { "name": "baz:name", @@ -226,12 +227,14 @@ }, "worlds": { "w3": 1 - } + }, + "types": {} }, { "name": "foo:main", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:name", @@ -240,7 +243,8 @@ }, "worlds": { "w1": 2 - } + }, + "types": {} }, { "name": "qux:name", @@ -249,7 +253,8 @@ }, "worlds": { "w4": 3 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/multi-file.wit.json b/crates/wit-parser/tests/ui/multi-file.wit.json index 53bb14cb82..d954242876 100644 --- a/crates/wit-parser/tests/ui/multi-file.wit.json +++ b/crates/wit-parser/tests/ui/multi-file.wit.json @@ -294,7 +294,8 @@ "worlds": { "more-depends-on-later-things": 0, "the-world": 1 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/multi-package-deps.wit.json b/crates/wit-parser/tests/ui/multi-package-deps.wit.json index 427c1bdf35..9fa272f36b 100644 --- a/crates/wit-parser/tests/ui/multi-package-deps.wit.json +++ b/crates/wit-parser/tests/ui/multi-package-deps.wit.json @@ -134,28 +134,32 @@ "interfaces": { "i2": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:dep1", "interfaces": { "i1": 1 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:nest", "interfaces": { "nesty": 2 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:root", "interfaces": { "i0": 3 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/multi-package-gated-include.wit.json b/crates/wit-parser/tests/ui/multi-package-gated-include.wit.json index 8beca272e7..3703581aeb 100644 --- a/crates/wit-parser/tests/ui/multi-package-gated-include.wit.json +++ b/crates/wit-parser/tests/ui/multi-package-gated-include.wit.json @@ -638,7 +638,8 @@ }, "worlds": { "imports": 0 - } + }, + "types": {} }, { "name": "wasi:dep-unversioned", @@ -647,7 +648,8 @@ }, "worlds": { "imports": 1 - } + }, + "types": {} }, { "name": "wasi:dep2@0.2.3", @@ -656,14 +658,16 @@ }, "worlds": { "imports": 2 - } + }, + "types": {} }, { "name": "wasi:foo@0.2.3", "interfaces": {}, "worlds": { "imports": 3 - } + }, + "types": {} }, { "name": "wasi:someother@0.2.3", @@ -672,14 +676,16 @@ }, "worlds": { "imports": 4 - } + }, + "types": {} }, { "name": "wasi:unstable@0.2.3", "interfaces": {}, "worlds": { "imports": 5 - } + }, + "types": {} }, { "name": "wasmtime:test", @@ -691,7 +697,8 @@ "test-only-stable": 9, "test-only-stable-with-feature": 10, "test-only-stable-with-in-active-feature": 11 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/multi-package-shared-deps.wit.json b/crates/wit-parser/tests/ui/multi-package-shared-deps.wit.json index 8076d19598..540fd190ee 100644 --- a/crates/wit-parser/tests/ui/multi-package-shared-deps.wit.json +++ b/crates/wit-parser/tests/ui/multi-package-shared-deps.wit.json @@ -223,14 +223,16 @@ "interfaces": { "types": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:dep2", "interfaces": { "types": 1 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:bar", @@ -239,7 +241,8 @@ }, "worlds": { "w-bar": 0 - } + }, + "types": {} }, { "name": "foo:qux", @@ -248,14 +251,16 @@ }, "worlds": { "w-qux": 1 - } + }, + "types": {} }, { "name": "foo:root", "interfaces": { "root": 4 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/multi-package-transitive-deps.wit.json b/crates/wit-parser/tests/ui/multi-package-transitive-deps.wit.json index ed79fcc057..c06543f034 100644 --- a/crates/wit-parser/tests/ui/multi-package-transitive-deps.wit.json +++ b/crates/wit-parser/tests/ui/multi-package-transitive-deps.wit.json @@ -135,40 +135,46 @@ "interfaces": { "types": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:dep2", "interfaces": { "types": 1 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:dep1", "interfaces": { "types": 2 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:bar", "interfaces": {}, "worlds": { "w-bar": 0 - } + }, + "types": {} }, { "name": "foo:qux", "interfaces": {}, "worlds": { "w-qux": 1 - } + }, + "types": {} }, { "name": "foo:root", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/name-both-resource-and-type.wit.json b/crates/wit-parser/tests/ui/name-both-resource-and-type.wit.json index 1dad5120b5..8ea69f5b81 100644 --- a/crates/wit-parser/tests/ui/name-both-resource-and-type.wit.json +++ b/crates/wit-parser/tests/ui/name-both-resource-and-type.wit.json @@ -76,14 +76,16 @@ "interfaces": { "foo": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:bar", "interfaces": { "foo": 1 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-alias-chain.wit b/crates/wit-parser/tests/ui/package-scope-alias-chain.wit new file mode 100644 index 0000000000..0b3b4a4bad --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-alias-chain.wit @@ -0,0 +1,13 @@ +package local:demo; + +type c = u32; +type b = list; +type a = option; + +interface api { + make: func() -> a; +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-alias-chain.wit.json b/crates/wit-parser/tests/ui/package-scope-alias-chain.wit.json new file mode 100644 index 0000000000..7193743300 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-alias-chain.wit.json @@ -0,0 +1,76 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "make": { + "name": "make", + "kind": "freestanding", + "params": [], + "result": 2 + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "c", + "kind": { + "type": "u32" + }, + "owner": { + "package": 0 + } + }, + { + "name": "b", + "kind": { + "list": 0 + }, + "owner": { + "package": 0 + } + }, + { + "name": "a", + "kind": { + "option": 1 + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": { + "api": 0 + }, + "worlds": { + "w": 0 + }, + "types": { + "c": 0, + "b": 1, + "a": 2 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-all-gated.wit b/crates/wit-parser/tests/ui/package-scope-all-gated.wit new file mode 100644 index 0000000000..64400b308b --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-all-gated.wit @@ -0,0 +1,19 @@ +package local:demo; + +@unstable(feature = inactive) +record hidden { + x: u32, +} + +@unstable(feature = inactive) +flags style { + bold, +} + +interface api { + ping: func(); +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-all-gated.wit.json b/crates/wit-parser/tests/ui/package-scope-all-gated.wit.json new file mode 100644 index 0000000000..2168b039e0 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-all-gated.wit.json @@ -0,0 +1,43 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "ping": { + "name": "ping", + "kind": "freestanding", + "params": [] + } + }, + "package": 0 + } + ], + "types": [], + "packages": [ + { + "name": "local:demo", + "interfaces": { + "api": 0 + }, + "worlds": { + "w": 0 + }, + "types": {} + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-deprecated.wit b/crates/wit-parser/tests/ui/package-scope-deprecated.wit new file mode 100644 index 0000000000..9d3bf3b654 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-deprecated.wit @@ -0,0 +1,15 @@ +package local:demo@1.0.0; + +@since(version = 0.1.0) +@deprecated(version = 0.9.0) +record old { + x: u32, +} + +interface api { + f: func(o: old); +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-deprecated.wit.json b/crates/wit-parser/tests/ui/package-scope-deprecated.wit.json new file mode 100644 index 0000000000..a9516e1623 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-deprecated.wit.json @@ -0,0 +1,73 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "f": { + "name": "f", + "kind": "freestanding", + "params": [ + { + "name": "o", + "type": 0 + } + ] + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "old", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + }, + "stability": { + "stable": { + "since": "0.1.0", + "deprecated": "0.9.0" + } + } + } + ], + "packages": [ + { + "name": "local:demo@1.0.0", + "interfaces": { + "api": 0 + }, + "worlds": { + "w": 0 + }, + "types": { + "old": 0 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-external-id.wit b/crates/wit-parser/tests/ui/package-scope-external-id.wit new file mode 100644 index 0000000000..a0e2cfccad --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-external-id.wit @@ -0,0 +1,11 @@ +package local:demo; + +@external-id("pkg-point") +record point { + x: u32, + y: u32, +} + +interface api { + move-to: func(p: point); +} diff --git a/crates/wit-parser/tests/ui/package-scope-external-id.wit.json b/crates/wit-parser/tests/ui/package-scope-external-id.wit.json new file mode 100644 index 0000000000..104eb49328 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-external-id.wit.json @@ -0,0 +1,57 @@ +{ + "worlds": [], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "move-to": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + }, + "external_id": "pkg-point" + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": { + "api": 0 + }, + "worlds": {}, + "types": { + "point": 0 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-foreign-as.wit.json b/crates/wit-parser/tests/ui/package-scope-foreign-as.wit.json new file mode 100644 index 0000000000..64c4c612b5 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-foreign-as.wit.json @@ -0,0 +1,85 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-1": { + "interface": { + "id": 1 + } + } + }, + "package": 1 + } + ], + "interfaces": [ + { + "name": "unused", + "types": {}, + "functions": {}, + "package": 0 + }, + { + "name": "api", + "types": {}, + "functions": { + "move-to": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + }, + "package": 1 + } + ], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:types", + "interfaces": { + "unused": 0 + }, + "worlds": {}, + "types": { + "point": 0 + } + }, + { + "name": "local:consumer", + "interfaces": { + "api": 1 + }, + "worlds": { + "w": 0 + }, + "types": {} + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-foreign-as/deps/types.wit b/crates/wit-parser/tests/ui/package-scope-foreign-as/deps/types.wit new file mode 100644 index 0000000000..a2135e771c --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-foreign-as/deps/types.wit @@ -0,0 +1,9 @@ +package local:types; + +record point { + x: u32, + y: u32, +} + +interface unused { +} diff --git a/crates/wit-parser/tests/ui/package-scope-foreign-as/root.wit b/crates/wit-parser/tests/ui/package-scope-foreign-as/root.wit new file mode 100644 index 0000000000..282f0da1da --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-foreign-as/root.wit @@ -0,0 +1,11 @@ +package local:consumer; + +use local:types/point as pt; + +interface api { + move-to: func(p: pt); +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-foreign-compose.wit.json b/crates/wit-parser/tests/ui/package-scope-foreign-compose.wit.json new file mode 100644 index 0000000000..301a2fc7a9 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-foreign-compose.wit.json @@ -0,0 +1,103 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-1": { + "interface": { + "id": 1 + } + } + }, + "package": 1 + } + ], + "interfaces": [ + { + "name": "unused", + "types": {}, + "functions": {}, + "package": 0 + }, + { + "name": "api", + "types": {}, + "functions": { + "wrap": { + "name": "wrap", + "kind": "freestanding", + "params": [ + { + "name": "b", + "type": 1 + } + ] + } + }, + "package": 1 + } + ], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + }, + { + "name": "bin", + "kind": { + "record": { + "fields": [ + { + "name": "p", + "type": 0 + } + ] + } + }, + "owner": { + "package": 1 + } + } + ], + "packages": [ + { + "name": "local:types", + "interfaces": { + "unused": 0 + }, + "worlds": {}, + "types": { + "point": 0 + } + }, + { + "name": "local:consumer", + "interfaces": { + "api": 1 + }, + "worlds": { + "w": 0 + }, + "types": { + "bin": 1 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-foreign-compose/deps/types.wit b/crates/wit-parser/tests/ui/package-scope-foreign-compose/deps/types.wit new file mode 100644 index 0000000000..a2135e771c --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-foreign-compose/deps/types.wit @@ -0,0 +1,9 @@ +package local:types; + +record point { + x: u32, + y: u32, +} + +interface unused { +} diff --git a/crates/wit-parser/tests/ui/package-scope-foreign-compose/root.wit b/crates/wit-parser/tests/ui/package-scope-foreign-compose/root.wit new file mode 100644 index 0000000000..a24b22e823 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-foreign-compose/root.wit @@ -0,0 +1,15 @@ +package local:consumer; + +use local:types/point; + +record bin { + p: point, +} + +interface api { + wrap: func(b: bin); +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-foreign-iface.wit.json b/crates/wit-parser/tests/ui/package-scope-foreign-iface.wit.json new file mode 100644 index 0000000000..30bb4b2002 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-foreign-iface.wit.json @@ -0,0 +1,77 @@ +{ + "worlds": [ + { + "name": "w", + "imports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "exports": {}, + "package": 1 + } + ], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "move-to": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:types", + "interfaces": { + "api": 0 + }, + "worlds": {}, + "types": { + "point": 0 + } + }, + { + "name": "local:consumer", + "interfaces": {}, + "worlds": { + "w": 0 + }, + "types": {} + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-foreign-iface/deps/types.wit b/crates/wit-parser/tests/ui/package-scope-foreign-iface/deps/types.wit new file mode 100644 index 0000000000..7b274e1ac1 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-foreign-iface/deps/types.wit @@ -0,0 +1,10 @@ +package local:types; + +record point { + x: u32, + y: u32, +} + +interface api { + move-to: func(p: point); +} diff --git a/crates/wit-parser/tests/ui/package-scope-foreign-iface/root.wit b/crates/wit-parser/tests/ui/package-scope-foreign-iface/root.wit new file mode 100644 index 0000000000..71dd65ac70 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-foreign-iface/root.wit @@ -0,0 +1,7 @@ +package local:consumer; + +use local:types/api; + +world w { + import api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-foreign-use.wit.json b/crates/wit-parser/tests/ui/package-scope-foreign-use.wit.json new file mode 100644 index 0000000000..64c4c612b5 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-foreign-use.wit.json @@ -0,0 +1,85 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-1": { + "interface": { + "id": 1 + } + } + }, + "package": 1 + } + ], + "interfaces": [ + { + "name": "unused", + "types": {}, + "functions": {}, + "package": 0 + }, + { + "name": "api", + "types": {}, + "functions": { + "move-to": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + }, + "package": 1 + } + ], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:types", + "interfaces": { + "unused": 0 + }, + "worlds": {}, + "types": { + "point": 0 + } + }, + { + "name": "local:consumer", + "interfaces": { + "api": 1 + }, + "worlds": { + "w": 0 + }, + "types": {} + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-foreign-use/deps/types.wit b/crates/wit-parser/tests/ui/package-scope-foreign-use/deps/types.wit new file mode 100644 index 0000000000..a2135e771c --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-foreign-use/deps/types.wit @@ -0,0 +1,9 @@ +package local:types; + +record point { + x: u32, + y: u32, +} + +interface unused { +} diff --git a/crates/wit-parser/tests/ui/package-scope-foreign-use/root.wit b/crates/wit-parser/tests/ui/package-scope-foreign-use/root.wit new file mode 100644 index 0000000000..2ffea3270d --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-foreign-use/root.wit @@ -0,0 +1,11 @@ +package local:consumer; + +use local:types/point; + +interface api { + move-to: func(p: point); +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-func-name-overlap.wit b/crates/wit-parser/tests/ui/package-scope-func-name-overlap.wit new file mode 100644 index 0000000000..ead31982ad --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-func-name-overlap.wit @@ -0,0 +1,16 @@ +package local:demo; + +flags style { + bold, + italic, +} + +interface api { + /// Function may share a name with a package-scope type; the return type + /// still resolves to the package type. + style: func() -> style; +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-func-name-overlap.wit.json b/crates/wit-parser/tests/ui/package-scope-func-name-overlap.wit.json new file mode 100644 index 0000000000..7c2e9f8213 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-func-name-overlap.wit.json @@ -0,0 +1,68 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "style": { + "name": "style", + "kind": "freestanding", + "params": [], + "result": 0, + "docs": { + "contents": "Function may share a name with a package-scope type; the return type\nstill resolves to the package type." + } + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "style", + "kind": { + "flags": { + "flags": [ + { + "name": "bold" + }, + { + "name": "italic" + } + ] + } + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": { + "api": 0 + }, + "worlds": { + "w": 0 + }, + "types": { + "style": 0 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-gated.wit b/crates/wit-parser/tests/ui/package-scope-gated.wit new file mode 100644 index 0000000000..a9e1fcc8ec --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-gated.wit @@ -0,0 +1,25 @@ +package local:demo; + +@unstable(feature = inactive) +record hidden { + x: u32, +} + +@unstable(feature = active) +record kept { + x: u32, +} + +record point { + x: u32, + y: u32, +} + +interface api { + move-to: func(p: point); + keep: func(k: kept); +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-gated.wit.json b/crates/wit-parser/tests/ui/package-scope-gated.wit.json new file mode 100644 index 0000000000..0c9904e7c8 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-gated.wit.json @@ -0,0 +1,103 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "move-to": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 1 + } + ] + }, + "keep": { + "name": "keep", + "kind": "freestanding", + "params": [ + { + "name": "k", + "type": 0 + } + ] + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "kept", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + }, + "stability": { + "unstable": { + "feature": "active" + } + } + }, + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": { + "api": 0 + }, + "worlds": { + "w": 0 + }, + "types": { + "kept": 0, + "point": 1 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-include-with.wit b/crates/wit-parser/tests/ui/package-scope-include-with.wit new file mode 100644 index 0000000000..822beb4a4e --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-include-with.wit @@ -0,0 +1,15 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +world base { + import move-to: func(p: point); +} + +world w { + include base with { move-to as place } + export trail: func() -> list; +} diff --git a/crates/wit-parser/tests/ui/package-scope-include-with.wit.json b/crates/wit-parser/tests/ui/package-scope-include-with.wit.json new file mode 100644 index 0000000000..0676624236 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-include-with.wit.json @@ -0,0 +1,94 @@ +{ + "worlds": [ + { + "name": "base", + "imports": { + "move-to": { + "function": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + } + }, + "exports": {}, + "package": 0 + }, + { + "name": "w", + "imports": { + "place": { + "function": { + "name": "place", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + } + }, + "exports": { + "trail": { + "function": { + "name": "trail", + "kind": "freestanding", + "params": [], + "result": 1 + } + } + }, + "package": 0 + } + ], + "interfaces": [], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + }, + { + "name": null, + "kind": { + "list": 0 + }, + "owner": null + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": {}, + "worlds": { + "base": 0, + "w": 1 + }, + "types": { + "point": 0 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-include.wit b/crates/wit-parser/tests/ui/package-scope-include.wit new file mode 100644 index 0000000000..b7e001d928 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-include.wit @@ -0,0 +1,15 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +world base { + import move-to: func(p: point); +} + +world w { + include base; + export place: func(p: point); +} diff --git a/crates/wit-parser/tests/ui/package-scope-include.wit.json b/crates/wit-parser/tests/ui/package-scope-include.wit.json new file mode 100644 index 0000000000..5b5087f3d6 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-include.wit.json @@ -0,0 +1,91 @@ +{ + "worlds": [ + { + "name": "base", + "imports": { + "move-to": { + "function": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + } + }, + "exports": {}, + "package": 0 + }, + { + "name": "w", + "imports": { + "move-to": { + "function": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + } + }, + "exports": { + "place": { + "function": { + "name": "place", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + } + }, + "package": 0 + } + ], + "interfaces": [], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": {}, + "worlds": { + "base": 0, + "w": 1 + }, + "types": { + "point": 0 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-local-use.wit b/crates/wit-parser/tests/ui/package-scope-local-use.wit new file mode 100644 index 0000000000..9cd602b4e5 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-local-use.wit @@ -0,0 +1,16 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +use point as pt; + +interface api { + move-to: func(p: pt); +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-local-use.wit.json b/crates/wit-parser/tests/ui/package-scope-local-use.wit.json new file mode 100644 index 0000000000..3cf57db27c --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-local-use.wit.json @@ -0,0 +1,71 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "move-to": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": { + "api": 0 + }, + "worlds": { + "w": 0 + }, + "types": { + "point": 0 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-multifile-world.wit.json b/crates/wit-parser/tests/ui/package-scope-multifile-world.wit.json new file mode 100644 index 0000000000..712545b77a --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-multifile-world.wit.json @@ -0,0 +1,74 @@ +{ + "worlds": [ + { + "name": "w", + "imports": { + "place": { + "function": { + "name": "place", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + } + }, + "exports": { + "trail": { + "function": { + "name": "trail", + "kind": "freestanding", + "params": [], + "result": 1 + } + } + }, + "package": 0 + } + ], + "interfaces": [], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + }, + { + "name": null, + "kind": { + "list": 0 + }, + "owner": null + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": {}, + "worlds": { + "w": 0 + }, + "types": { + "point": 0 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-multifile-world/types.wit b/crates/wit-parser/tests/ui/package-scope-multifile-world/types.wit new file mode 100644 index 0000000000..d58d3b8f68 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-multifile-world/types.wit @@ -0,0 +1,6 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} diff --git a/crates/wit-parser/tests/ui/package-scope-multifile-world/world.wit b/crates/wit-parser/tests/ui/package-scope-multifile-world/world.wit new file mode 100644 index 0000000000..bc9469b688 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-multifile-world/world.wit @@ -0,0 +1,6 @@ +package local:demo; + +world w { + import place: func(p: point); + export trail: func() -> list; +} diff --git a/crates/wit-parser/tests/ui/package-scope-multifile.wit.json b/crates/wit-parser/tests/ui/package-scope-multifile.wit.json new file mode 100644 index 0000000000..3cf57db27c --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-multifile.wit.json @@ -0,0 +1,71 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "move-to": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": { + "api": 0 + }, + "worlds": { + "w": 0 + }, + "types": { + "point": 0 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-multifile/api.wit b/crates/wit-parser/tests/ui/package-scope-multifile/api.wit new file mode 100644 index 0000000000..6c1d05d6c0 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-multifile/api.wit @@ -0,0 +1,9 @@ +package local:demo; + +interface api { + move-to: func(p: point); +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-multifile/types.wit b/crates/wit-parser/tests/ui/package-scope-multifile/types.wit new file mode 100644 index 0000000000..d58d3b8f68 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-multifile/types.wit @@ -0,0 +1,6 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} diff --git a/crates/wit-parser/tests/ui/package-scope-nested-use.wit b/crates/wit-parser/tests/ui/package-scope-nested-use.wit new file mode 100644 index 0000000000..f185fd07f2 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-nested-use.wit @@ -0,0 +1,23 @@ +package local:root; + +package local:nested { + record point { + x: u32, + y: u32, + } + + interface unused { + } +} + +package local:consumer { + use local:nested/point; + + interface api { + move-to: func(p: point); + } + + world w { + export api; + } +} diff --git a/crates/wit-parser/tests/ui/package-scope-nested-use.wit.json b/crates/wit-parser/tests/ui/package-scope-nested-use.wit.json new file mode 100644 index 0000000000..f0fb8d8d55 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-nested-use.wit.json @@ -0,0 +1,91 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-1": { + "interface": { + "id": 1 + } + } + }, + "package": 1 + } + ], + "interfaces": [ + { + "name": "unused", + "types": {}, + "functions": {}, + "package": 0 + }, + { + "name": "api", + "types": {}, + "functions": { + "move-to": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + }, + "package": 1 + } + ], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:nested", + "interfaces": { + "unused": 0 + }, + "worlds": {}, + "types": { + "point": 0 + } + }, + { + "name": "local:consumer", + "interfaces": { + "api": 1 + }, + "worlds": { + "w": 0 + }, + "types": {} + }, + { + "name": "local:root", + "interfaces": {}, + "worlds": {}, + "types": {} + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-nested.wit b/crates/wit-parser/tests/ui/package-scope-nested.wit new file mode 100644 index 0000000000..db0b7d7fec --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-nested.wit @@ -0,0 +1,16 @@ +package local:root; + +package local:nested { + record point { + x: u32, + y: u32, + } + + interface api { + move-to: func(p: point); + } + + world w { + export api; + } +} diff --git a/crates/wit-parser/tests/ui/package-scope-nested.wit.json b/crates/wit-parser/tests/ui/package-scope-nested.wit.json new file mode 100644 index 0000000000..ab08797961 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-nested.wit.json @@ -0,0 +1,77 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "move-to": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:nested", + "interfaces": { + "api": 0 + }, + "worlds": { + "w": 0 + }, + "types": { + "point": 0 + } + }, + { + "name": "local:root", + "interfaces": {}, + "worlds": {}, + "types": {} + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-rich-aliases.wit b/crates/wit-parser/tests/ui/package-scope-rich-aliases.wit new file mode 100644 index 0000000000..6b10550006 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-rich-aliases.wit @@ -0,0 +1,16 @@ +package local:demo; + +type pair = tuple; +type maybe = option; +type either = result; +type pipe = stream; +type job = future; +type table = map; + +interface api { + f: func() -> tuple; +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-rich-aliases.wit.json b/crates/wit-parser/tests/ui/package-scope-rich-aliases.wit.json new file mode 100644 index 0000000000..2bff097688 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-rich-aliases.wit.json @@ -0,0 +1,132 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "f": { + "name": "f", + "kind": "freestanding", + "params": [], + "result": 6 + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "pair", + "kind": { + "tuple": { + "types": [ + "u32", + "u32" + ] + } + }, + "owner": { + "package": 0 + } + }, + { + "name": "maybe", + "kind": { + "option": 0 + }, + "owner": { + "package": 0 + } + }, + { + "name": "either", + "kind": { + "result": { + "ok": 0, + "err": "string" + } + }, + "owner": { + "package": 0 + } + }, + { + "name": "pipe", + "kind": { + "stream": "u8" + }, + "owner": { + "package": 0 + } + }, + { + "name": "job", + "kind": { + "future": 0 + }, + "owner": { + "package": 0 + } + }, + { + "name": "table", + "kind": { + "map": [ + "string", + 0 + ] + }, + "owner": { + "package": 0 + } + }, + { + "name": null, + "kind": { + "tuple": { + "types": [ + 1, + 2, + 3, + 4, + 5 + ] + } + }, + "owner": null + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": { + "api": 0 + }, + "worlds": { + "w": 0 + }, + "types": { + "pair": 0, + "maybe": 1, + "either": 2, + "pipe": 3, + "job": 4, + "table": 5 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-self-use.wit b/crates/wit-parser/tests/ui/package-scope-self-use.wit new file mode 100644 index 0000000000..c470b3d372 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-self-use.wit @@ -0,0 +1,16 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +use local:demo/point as pt; + +interface api { + move-to: func(p: pt); +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-self-use.wit.json b/crates/wit-parser/tests/ui/package-scope-self-use.wit.json new file mode 100644 index 0000000000..3cf57db27c --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-self-use.wit.json @@ -0,0 +1,71 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "move-to": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": { + "api": 0 + }, + "worlds": { + "w": 0 + }, + "types": { + "point": 0 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-shadow.wit b/crates/wit-parser/tests/ui/package-scope-shadow.wit new file mode 100644 index 0000000000..1372ea5b12 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-shadow.wit @@ -0,0 +1,15 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface api { + type point = u64; + f: func(p: point); +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-shadow.wit.json b/crates/wit-parser/tests/ui/package-scope-shadow.wit.json new file mode 100644 index 0000000000..6573195c00 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-shadow.wit.json @@ -0,0 +1,82 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "api", + "types": { + "point": 1 + }, + "functions": { + "f": { + "name": "f", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 1 + } + ] + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + }, + { + "name": "point", + "kind": { + "type": "u64" + }, + "owner": { + "interface": 0 + } + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": { + "api": 0 + }, + "worlds": { + "w": 0 + }, + "types": { + "point": 0 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-since.wit b/crates/wit-parser/tests/ui/package-scope-since.wit new file mode 100644 index 0000000000..d2ac75b251 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-since.wit @@ -0,0 +1,20 @@ +package local:demo@1.0.0; + +@since(version = 0.2.0) +record kept { + x: u32, +} + +@since(version = 1.0.0) +record also-kept { + x: u32, +} + +interface api { + f: func(k: kept); + g: func(a: also-kept); +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-since.wit.json b/crates/wit-parser/tests/ui/package-scope-since.wit.json new file mode 100644 index 0000000000..54c46f34bf --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-since.wit.json @@ -0,0 +1,104 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "f": { + "name": "f", + "kind": "freestanding", + "params": [ + { + "name": "k", + "type": 0 + } + ] + }, + "g": { + "name": "g", + "kind": "freestanding", + "params": [ + { + "name": "a", + "type": 1 + } + ] + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "kept", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + }, + "stability": { + "stable": { + "since": "0.2.0" + } + } + }, + { + "name": "also-kept", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + }, + "stability": { + "stable": { + "since": "1.0.0" + } + } + } + ], + "packages": [ + { + "name": "local:demo@1.0.0", + "interfaces": { + "api": 0 + }, + "worlds": { + "w": 0 + }, + "types": { + "kept": 0, + "also-kept": 1 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-types.wit b/crates/wit-parser/tests/ui/package-scope-types.wit new file mode 100644 index 0000000000..9cfceb3d33 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-types.wit @@ -0,0 +1,39 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +variant shape { + circle(u32), + rect(point), +} + +flags style { + bold, + italic, +} + +enum direction { + north, + south, + east, + west, +} + +type path = list; + +interface api { + move-to: func(p: point); + heading: func() -> direction; + trail: func() -> path; + draw: func(s: shape, style: style); +} + +world w { + import host: interface { + place: func(p: point); + } + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-types.wit.json b/crates/wit-parser/tests/ui/package-scope-types.wit.json new file mode 100644 index 0000000000..a49f6f526c --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-types.wit.json @@ -0,0 +1,195 @@ +{ + "worlds": [ + { + "name": "w", + "imports": { + "host": { + "interface": { + "id": 1 + } + } + }, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "api", + "types": {}, + "functions": { + "move-to": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + }, + "heading": { + "name": "heading", + "kind": "freestanding", + "params": [], + "result": 3 + }, + "trail": { + "name": "trail", + "kind": "freestanding", + "params": [], + "result": 4 + }, + "draw": { + "name": "draw", + "kind": "freestanding", + "params": [ + { + "name": "s", + "type": 1 + }, + { + "name": "style", + "type": 2 + } + ] + } + }, + "package": 0 + }, + { + "name": null, + "types": {}, + "functions": { + "place": { + "name": "place", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + }, + { + "name": "shape", + "kind": { + "variant": { + "cases": [ + { + "name": "circle", + "type": "u32" + }, + { + "name": "rect", + "type": 0 + } + ] + } + }, + "owner": { + "package": 0 + } + }, + { + "name": "style", + "kind": { + "flags": { + "flags": [ + { + "name": "bold" + }, + { + "name": "italic" + } + ] + } + }, + "owner": { + "package": 0 + } + }, + { + "name": "direction", + "kind": { + "enum": { + "cases": [ + { + "name": "north" + }, + { + "name": "south" + }, + { + "name": "east" + }, + { + "name": "west" + } + ] + } + }, + "owner": { + "package": 0 + } + }, + { + "name": "path", + "kind": { + "list": 0 + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": { + "api": 0 + }, + "worlds": { + "w": 0 + }, + "types": { + "point": 0, + "shape": 1, + "style": 2, + "direction": 3, + "path": 4 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-versioned-use.wit.json b/crates/wit-parser/tests/ui/package-scope-versioned-use.wit.json new file mode 100644 index 0000000000..e52f52e2b4 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-versioned-use.wit.json @@ -0,0 +1,85 @@ +{ + "worlds": [ + { + "name": "w", + "imports": {}, + "exports": { + "interface-1": { + "interface": { + "id": 1 + } + } + }, + "package": 1 + } + ], + "interfaces": [ + { + "name": "unused", + "types": {}, + "functions": {}, + "package": 0 + }, + { + "name": "api", + "types": {}, + "functions": { + "move-to": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + }, + "package": 1 + } + ], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + } + ], + "packages": [ + { + "name": "local:types@1.0.0", + "interfaces": { + "unused": 0 + }, + "worlds": {}, + "types": { + "point": 0 + } + }, + { + "name": "local:consumer", + "interfaces": { + "api": 1 + }, + "worlds": { + "w": 0 + }, + "types": {} + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-scope-versioned-use/deps/types.wit b/crates/wit-parser/tests/ui/package-scope-versioned-use/deps/types.wit new file mode 100644 index 0000000000..ee15458642 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-versioned-use/deps/types.wit @@ -0,0 +1,9 @@ +package local:types@1.0.0; + +record point { + x: u32, + y: u32, +} + +interface unused { +} diff --git a/crates/wit-parser/tests/ui/package-scope-versioned-use/root.wit b/crates/wit-parser/tests/ui/package-scope-versioned-use/root.wit new file mode 100644 index 0000000000..6963614cb4 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-versioned-use/root.wit @@ -0,0 +1,11 @@ +package local:consumer; + +use local:types/point@1.0.0; + +interface api { + move-to: func(p: point); +} + +world w { + export api; +} diff --git a/crates/wit-parser/tests/ui/package-scope-world.wit b/crates/wit-parser/tests/ui/package-scope-world.wit new file mode 100644 index 0000000000..3a7b29968d --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-world.wit @@ -0,0 +1,12 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +world w { + import move-to: func(p: point); + export place: func(p: point); + export trail: func() -> list; +} diff --git a/crates/wit-parser/tests/ui/package-scope-world.wit.json b/crates/wit-parser/tests/ui/package-scope-world.wit.json new file mode 100644 index 0000000000..252cda7997 --- /dev/null +++ b/crates/wit-parser/tests/ui/package-scope-world.wit.json @@ -0,0 +1,86 @@ +{ + "worlds": [ + { + "name": "w", + "imports": { + "move-to": { + "function": { + "name": "move-to", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + } + }, + "exports": { + "place": { + "function": { + "name": "place", + "kind": "freestanding", + "params": [ + { + "name": "p", + "type": 0 + } + ] + } + }, + "trail": { + "function": { + "name": "trail", + "kind": "freestanding", + "params": [], + "result": 1 + } + } + }, + "package": 0 + } + ], + "interfaces": [], + "types": [ + { + "name": "point", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + }, + { + "name": "y", + "type": "u32" + } + ] + } + }, + "owner": { + "package": 0 + } + }, + { + "name": null, + "kind": { + "list": 0 + }, + "owner": null + } + ], + "packages": [ + { + "name": "local:demo", + "interfaces": {}, + "worlds": { + "w": 0 + }, + "types": { + "point": 0 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-syntax1.wit.json b/crates/wit-parser/tests/ui/package-syntax1.wit.json index 80af0cbb3b..028ca2a78c 100644 --- a/crates/wit-parser/tests/ui/package-syntax1.wit.json +++ b/crates/wit-parser/tests/ui/package-syntax1.wit.json @@ -6,7 +6,8 @@ { "name": "foo:foo", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-syntax3.wit.json b/crates/wit-parser/tests/ui/package-syntax3.wit.json index e0323c511b..3684a49107 100644 --- a/crates/wit-parser/tests/ui/package-syntax3.wit.json +++ b/crates/wit-parser/tests/ui/package-syntax3.wit.json @@ -6,7 +6,8 @@ { "name": "foo:bar", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/package-syntax4.wit.json b/crates/wit-parser/tests/ui/package-syntax4.wit.json index 7bacc86f03..109587e3b1 100644 --- a/crates/wit-parser/tests/ui/package-syntax4.wit.json +++ b/crates/wit-parser/tests/ui/package-syntax4.wit.json @@ -6,7 +6,8 @@ { "name": "foo:bar@2.0.0", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/packages-multiple-nested.wit.json b/crates/wit-parser/tests/ui/packages-multiple-nested.wit.json index f2c8d6b0ee..ac40eb7a5f 100644 --- a/crates/wit-parser/tests/ui/packages-multiple-nested.wit.json +++ b/crates/wit-parser/tests/ui/packages-multiple-nested.wit.json @@ -160,7 +160,8 @@ }, "worlds": { "w2": 0 - } + }, + "types": {} }, { "name": "foo:name", @@ -169,7 +170,8 @@ }, "worlds": { "w1": 1 - } + }, + "types": {} }, { "name": "foo:root", @@ -178,7 +180,8 @@ }, "worlds": { "w0": 2 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/packages-nested-colliding-decl-names.wit.json b/crates/wit-parser/tests/ui/packages-nested-colliding-decl-names.wit.json index 3a9dc3b079..4b0f98c0aa 100644 --- a/crates/wit-parser/tests/ui/packages-nested-colliding-decl-names.wit.json +++ b/crates/wit-parser/tests/ui/packages-nested-colliding-decl-names.wit.json @@ -115,7 +115,8 @@ }, "worlds": { "w": 0 - } + }, + "types": {} }, { "name": "foo:name", @@ -124,12 +125,14 @@ }, "worlds": { "w": 1 - } + }, + "types": {} }, { "name": "foo:root", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/packages-nested-internal-references.wit.json b/crates/wit-parser/tests/ui/packages-nested-internal-references.wit.json index d11a121536..b7ddf22e4d 100644 --- a/crates/wit-parser/tests/ui/packages-nested-internal-references.wit.json +++ b/crates/wit-parser/tests/ui/packages-nested-internal-references.wit.json @@ -73,19 +73,22 @@ "interfaces": { "i1": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "bar:name", "interfaces": {}, "worlds": { "w1": 0 - } + }, + "types": {} }, { "name": "foo:root", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/packages-nested-with-semver.wit.json b/crates/wit-parser/tests/ui/packages-nested-with-semver.wit.json index ecd0fe37ce..318135d497 100644 --- a/crates/wit-parser/tests/ui/packages-nested-with-semver.wit.json +++ b/crates/wit-parser/tests/ui/packages-nested-with-semver.wit.json @@ -115,7 +115,8 @@ }, "worlds": { "w1": 0 - } + }, + "types": {} }, { "name": "foo:name@1.0.1", @@ -124,12 +125,14 @@ }, "worlds": { "w1": 1 - } + }, + "types": {} }, { "name": "foo:root", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/packages-single-nested.wit.json b/crates/wit-parser/tests/ui/packages-single-nested.wit.json index c67c2a78f9..218917023f 100644 --- a/crates/wit-parser/tests/ui/packages-single-nested.wit.json +++ b/crates/wit-parser/tests/ui/packages-single-nested.wit.json @@ -64,12 +64,14 @@ }, "worlds": { "w1": 0 - } + }, + "types": {} }, { "name": "foo:root", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/no-access-to-sibling-use.wit.result b/crates/wit-parser/tests/ui/parse-fail/no-access-to-sibling-use.wit.result index 91c398043d..7c8f4c40c9 100644 --- a/crates/wit-parser/tests/ui/parse-fail/no-access-to-sibling-use.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/no-access-to-sibling-use.wit.result @@ -1,4 +1,4 @@ -failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/no-access-to-sibling-use]: failed to parse package: tests/ui/parse-fail/no-access-to-sibling-use: interface or world `bar-renamed` does not exist +failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/no-access-to-sibling-use]: failed to parse package: tests/ui/parse-fail/no-access-to-sibling-use: interface, world, or type `bar-renamed` does not exist --> tests/ui/parse-fail/no-access-to-sibling-use/foo.wit:3:5 | 3 | use bar-renamed; diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-dup-multifile.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-dup-multifile.wit.result new file mode 100644 index 0000000000..e7bc9f03ff --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-dup-multifile.wit.result @@ -0,0 +1,5 @@ +failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/package-scope-dup-multifile]: failed to parse package: tests/ui/parse-fail/package-scope-dup-multifile: duplicate item named `point` + --> tests/ui/parse-fail/package-scope-dup-multifile/types2.wit:3:8 + | + 3 | record point { + | ^---- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-dup-multifile/types1.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-dup-multifile/types1.wit new file mode 100644 index 0000000000..d58d3b8f68 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-dup-multifile/types1.wit @@ -0,0 +1,6 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-dup-multifile/types2.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-dup-multifile/types2.wit new file mode 100644 index 0000000000..d58d3b8f68 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-dup-multifile/types2.wit @@ -0,0 +1,6 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-gated-ref.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-gated-ref.wit new file mode 100644 index 0000000000..f07648fbfe --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-gated-ref.wit @@ -0,0 +1,10 @@ +package local:demo; + +@unstable(feature = inactive) +record hidden { + x: u32, +} + +interface api { + use-hidden: func(h: hidden); +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-gated-ref.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-gated-ref.wit.result new file mode 100644 index 0000000000..4d5334453e --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-gated-ref.wit.result @@ -0,0 +1,5 @@ +found a reference to a type which is excluded due to its feature not being activated + --> tests/ui/parse-fail/package-scope-gated-ref.wit:9:3 + | + 9 | use-hidden: func(h: hidden); + | ^--------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-multifile-ns-clash.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-multifile-ns-clash.wit.result new file mode 100644 index 0000000000..d83a86933f --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-multifile-ns-clash.wit.result @@ -0,0 +1,5 @@ +failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/package-scope-multifile-ns-clash]: failed to parse package: tests/ui/parse-fail/package-scope-multifile-ns-clash: duplicate item named `point` + --> tests/ui/parse-fail/package-scope-multifile-ns-clash/types.wit:3:8 + | + 3 | record point { + | ^---- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-multifile-ns-clash/api.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-multifile-ns-clash/api.wit new file mode 100644 index 0000000000..315362aca0 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-multifile-ns-clash/api.wit @@ -0,0 +1,5 @@ +package local:demo; + +interface point { + get: func() -> u32; +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-multifile-ns-clash/types.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-multifile-ns-clash/types.wit new file mode 100644 index 0000000000..d58d3b8f68 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-multifile-ns-clash/types.wit @@ -0,0 +1,6 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-nested-outer-leak.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-nested-outer-leak.wit new file mode 100644 index 0000000000..00737d5490 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-nested-outer-leak.wit @@ -0,0 +1,17 @@ +package local:root; + +package local:nested { + record point { + x: u32, + y: u32, + } + + interface api { + move-to: func(p: point); + } +} + +interface outer { + // Nested package types must not leak into the outer package without a use. + bad: func(p: point); +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-nested-outer-leak.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-nested-outer-leak.wit.result new file mode 100644 index 0000000000..a8d21e191e --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-nested-outer-leak.wit.result @@ -0,0 +1,5 @@ +name `point` does not exist + --> tests/ui/parse-fail/package-scope-nested-outer-leak.wit:16:16 + | + 16 | bad: func(p: point); + | ^---- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-resource.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-resource.wit new file mode 100644 index 0000000000..92c635ffec --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-resource.wit @@ -0,0 +1,4 @@ +package local:demo; + +resource r { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-resource.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-resource.wit.result new file mode 100644 index 0000000000..a22f3cd321 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-resource.wit.result @@ -0,0 +1,5 @@ +resources cannot be declared at package scope + --> tests/ui/parse-fail/package-scope-resource.wit:3:1 + | + 3 | resource r { + | ^------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-self-use-dup.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-self-use-dup.wit new file mode 100644 index 0000000000..b54444daad --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-self-use-dup.wit @@ -0,0 +1,14 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +// Package type `point` already occupies this file's namespace, so a plain +// self-use without `as` is a duplicate name (use `as` to alias instead). +use local:demo/point; + +interface api { + move-to: func(p: point); +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-self-use-dup.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-self-use-dup.wit.result new file mode 100644 index 0000000000..aab910b6c1 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-self-use-dup.wit.result @@ -0,0 +1,5 @@ +duplicate name `point` in this file + --> tests/ui/parse-fail/package-scope-self-use-dup.wit:10:16 + | + 10 | use local:demo/point; + | ^---- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-since-ref.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-since-ref.wit new file mode 100644 index 0000000000..f63f408b8a --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-since-ref.wit @@ -0,0 +1,10 @@ +package local:demo@1.0.0; + +@since(version = 1.1.0) +record upcoming { + x: u32, +} + +interface api { + f: func(k: upcoming); +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-since-ref.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-since-ref.wit.result new file mode 100644 index 0000000000..4e3667bbc9 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-since-ref.wit.result @@ -0,0 +1,5 @@ +feature gate cannot reference unreleased version 1.1.0 of package [local:demo@1.0.0] (current version 1.0.0) + --> tests/ui/parse-fail/package-scope-since-ref.wit:4:8 + | + 4 | record upcoming { + | ^------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-type-cycle.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-type-cycle.wit new file mode 100644 index 0000000000..9098e942e2 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-type-cycle.wit @@ -0,0 +1,4 @@ +package local:demo; + +type a = list; +type b = list; diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-type-cycle.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-type-cycle.wit.result new file mode 100644 index 0000000000..ee26c1c80c --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-type-cycle.wit.result @@ -0,0 +1,5 @@ +type `b` depends on itself + --> tests/ui/parse-fail/package-scope-type-cycle.wit:3:15 + | + 3 | type a = list; + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-type-iface-clash.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-type-iface-clash.wit new file mode 100644 index 0000000000..48f65322bc --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-type-iface-clash.wit @@ -0,0 +1,10 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface point { + get: func() -> u32; +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-type-iface-clash.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-type-iface-clash.wit.result new file mode 100644 index 0000000000..1a633dbc97 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-type-iface-clash.wit.result @@ -0,0 +1,5 @@ +duplicate item named `point` + --> tests/ui/parse-fail/package-scope-type-iface-clash.wit:8:11 + | + 8 | interface point { + | ^---- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-type-world-clash.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-type-world-clash.wit new file mode 100644 index 0000000000..1357646751 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-type-world-clash.wit @@ -0,0 +1,10 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +world point { + import foo: interface {} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-type-world-clash.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-type-world-clash.wit.result new file mode 100644 index 0000000000..add3c52612 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-type-world-clash.wit.result @@ -0,0 +1,5 @@ +duplicate item named `point` + --> tests/ui/parse-fail/package-scope-type-world-clash.wit:8:7 + | + 8 | world point { + | ^---- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-cross-file.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-cross-file.wit.result new file mode 100644 index 0000000000..55bd324086 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-cross-file.wit.result @@ -0,0 +1,5 @@ +failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/package-scope-use-cross-file]: failed to parse package: tests/ui/parse-fail/package-scope-use-cross-file: name `point` does not exist + --> tests/ui/parse-fail/package-scope-use-cross-file/types.wit:6:6 + | + 6 | p: point, + | ^---- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-cross-file/deps/foreign.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-cross-file/deps/foreign.wit new file mode 100644 index 0000000000..c565aec476 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-cross-file/deps/foreign.wit @@ -0,0 +1,9 @@ +package local:foreign; + +record point { + x: u32, + y: u32, +} + +interface unused { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-cross-file/types.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-cross-file/types.wit new file mode 100644 index 0000000000..b30dc8c78d --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-cross-file/types.wit @@ -0,0 +1,11 @@ +package local:demo; + +// `point` was imported in uses.wit; toplevel use is file-scoped, so this +// package-scope type must not see that alias. +record bin { + p: point, +} + +interface api { + wrap: func(b: bin); +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-cross-file/uses.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-cross-file/uses.wit new file mode 100644 index 0000000000..e87e567929 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-cross-file/uses.wit @@ -0,0 +1,3 @@ +package local:demo; + +use local:foreign/point; diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-missing.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-missing.wit.result new file mode 100644 index 0000000000..382c69a1b3 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-missing.wit.result @@ -0,0 +1,5 @@ +failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/package-scope-use-missing]: interface or type `missing` not found in package `local:types` + --> tests/ui/parse-fail/package-scope-use-missing/root.wit:3:17 + | + 3 | use local:types/missing; + | ^------ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-missing/deps/types.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-missing/deps/types.wit new file mode 100644 index 0000000000..de228f0084 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-missing/deps/types.wit @@ -0,0 +1,5 @@ +package local:types; + +interface api { + f: func(); +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-missing/root.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-missing/root.wit new file mode 100644 index 0000000000..865cd5e556 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-missing/root.wit @@ -0,0 +1,7 @@ +package local:consumer; + +use local:types/missing; + +interface api { + f: func(); +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface-foreign.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface-foreign.wit.result new file mode 100644 index 0000000000..de8b3f6e91 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface-foreign.wit.result @@ -0,0 +1,5 @@ +failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/package-scope-use-type-as-iface-foreign]: name `point` is defined as a type, not an interface + --> tests/ui/parse-fail/package-scope-use-type-as-iface-foreign/root.wit:4:19 + | + 4 | use local:types/point.{x}; + | ^---- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface-foreign/deps/types.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface-foreign/deps/types.wit new file mode 100644 index 0000000000..a2135e771c --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface-foreign/deps/types.wit @@ -0,0 +1,9 @@ +package local:types; + +record point { + x: u32, + y: u32, +} + +interface unused { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface-foreign/root.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface-foreign/root.wit new file mode 100644 index 0000000000..ead92cd5a8 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface-foreign/root.wit @@ -0,0 +1,6 @@ +package local:consumer; + +interface api { + use local:types/point.{x}; + f: func(); +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface.wit new file mode 100644 index 0000000000..bf9c43a79a --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface.wit @@ -0,0 +1,10 @@ +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface api { + use point.{x}; +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface.wit.result new file mode 100644 index 0000000000..4174a4b117 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-type-as-iface.wit.result @@ -0,0 +1,5 @@ +name `point` is defined as a type, not an interface + --> tests/ui/parse-fail/package-scope-use-type-as-iface.wit:9:7 + | + 9 | use point.{x}; + | ^---- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-world.wit.result b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-world.wit.result new file mode 100644 index 0000000000..2125434f1d --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-world.wit.result @@ -0,0 +1,5 @@ +failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/package-scope-use-world]: `point` is a world; top-level use expects an interface or type + --> tests/ui/parse-fail/package-scope-use-world/root.wit:3:17 + | + 3 | use local:types/point; + | ^---- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-world/deps/types.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-world/deps/types.wit new file mode 100644 index 0000000000..471becfa20 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-world/deps/types.wit @@ -0,0 +1,5 @@ +package local:types; + +world point { + import foo: interface {} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/package-scope-use-world/root.wit b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-world/root.wit new file mode 100644 index 0000000000..faecc806a0 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/package-scope-use-world/root.wit @@ -0,0 +1,7 @@ +package local:consumer; + +use local:types/point; + +interface api { + f: func(); +} 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 index b8515dd20e..58f5b66d06 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit.result @@ -1,4 +1,4 @@ -interface or world `bar` does not exist +interface, world, or type `bar` does not exist --> tests/ui/parse-fail/unresolved-interface3.wit:5:5 | 5 | use bar as foo; diff --git a/crates/wit-parser/tests/ui/parse-fail/use-world.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-world.wit.result index 975def98c5..b4e428ddd9 100644 --- a/crates/wit-parser/tests/ui/parse-fail/use-world.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/use-world.wit.result @@ -1,4 +1,4 @@ -failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/use-world]: interface 'bar' not found in package 'foo:baz' +failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/use-world]: `bar` is a world; top-level use expects an interface or type --> tests/ui/parse-fail/use-world/root.wit:3:13 | 3 | use foo:baz/bar; diff --git a/crates/wit-parser/tests/ui/random.wit.json b/crates/wit-parser/tests/ui/random.wit.json index dcf2178c1c..c07aa9a6d3 100644 --- a/crates/wit-parser/tests/ui/random.wit.json +++ b/crates/wit-parser/tests/ui/random.wit.json @@ -50,7 +50,8 @@ "interfaces": { "random": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/resources-empty.wit.json b/crates/wit-parser/tests/ui/resources-empty.wit.json index 4c16e4960e..16a76f206d 100644 --- a/crates/wit-parser/tests/ui/resources-empty.wit.json +++ b/crates/wit-parser/tests/ui/resources-empty.wit.json @@ -64,7 +64,8 @@ "interfaces": { "resources-empty": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/resources-multiple-returns-own.wit.json b/crates/wit-parser/tests/ui/resources-multiple-returns-own.wit.json index f4d50ced19..276fd352a8 100644 --- a/crates/wit-parser/tests/ui/resources-multiple-returns-own.wit.json +++ b/crates/wit-parser/tests/ui/resources-multiple-returns-own.wit.json @@ -79,7 +79,8 @@ "interfaces": { "resources1": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/resources-multiple.wit.json b/crates/wit-parser/tests/ui/resources-multiple.wit.json index 8b7969f2eb..c0006e595a 100644 --- a/crates/wit-parser/tests/ui/resources-multiple.wit.json +++ b/crates/wit-parser/tests/ui/resources-multiple.wit.json @@ -201,7 +201,8 @@ "interfaces": { "resources-multiple": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/resources-return-own.wit.json b/crates/wit-parser/tests/ui/resources-return-own.wit.json index 290611e53b..e1efd1057b 100644 --- a/crates/wit-parser/tests/ui/resources-return-own.wit.json +++ b/crates/wit-parser/tests/ui/resources-return-own.wit.json @@ -67,7 +67,8 @@ "interfaces": { "resources1": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/resources.wit.json b/crates/wit-parser/tests/ui/resources.wit.json index bf81cd6e4a..487d0e32b2 100644 --- a/crates/wit-parser/tests/ui/resources.wit.json +++ b/crates/wit-parser/tests/ui/resources.wit.json @@ -381,7 +381,8 @@ }, "worlds": { "w": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/resources1.wit.json b/crates/wit-parser/tests/ui/resources1.wit.json index 3bb1da4bbd..6144687e13 100644 --- a/crates/wit-parser/tests/ui/resources1.wit.json +++ b/crates/wit-parser/tests/ui/resources1.wit.json @@ -86,7 +86,8 @@ "interfaces": { "resources1": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/same-name-import-export.wit.json b/crates/wit-parser/tests/ui/same-name-import-export.wit.json index ac0647ee14..873a652216 100644 --- a/crates/wit-parser/tests/ui/same-name-import-export.wit.json +++ b/crates/wit-parser/tests/ui/same-name-import-export.wit.json @@ -33,7 +33,8 @@ "interfaces": {}, "worlds": { "greeter": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/shared-types.wit.json b/crates/wit-parser/tests/ui/shared-types.wit.json index 8f53710b99..96dd598fb7 100644 --- a/crates/wit-parser/tests/ui/shared-types.wit.json +++ b/crates/wit-parser/tests/ui/shared-types.wit.json @@ -73,7 +73,8 @@ "interfaces": {}, "worlds": { "foo": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/simple-wasm-text.wit.json b/crates/wit-parser/tests/ui/simple-wasm-text.wit.json index 969c13a506..e5db39d27a 100644 --- a/crates/wit-parser/tests/ui/simple-wasm-text.wit.json +++ b/crates/wit-parser/tests/ui/simple-wasm-text.wit.json @@ -15,7 +15,8 @@ "interfaces": { "x": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/since-and-unstable.wit.json b/crates/wit-parser/tests/ui/since-and-unstable.wit.json index 851694acba..cf12b4c8c5 100644 --- a/crates/wit-parser/tests/ui/since-and-unstable.wit.json +++ b/crates/wit-parser/tests/ui/since-and-unstable.wit.json @@ -602,7 +602,8 @@ "w1": 0, "w2": 1, "in-a-world": 2 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/streams-and-futures.wit.json b/crates/wit-parser/tests/ui/streams-and-futures.wit.json index 8a0b295308..75129cd366 100644 --- a/crates/wit-parser/tests/ui/streams-and-futures.wit.json +++ b/crates/wit-parser/tests/ui/streams-and-futures.wit.json @@ -199,7 +199,8 @@ "interfaces": { "streams-and-futures": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/stress-export-elaborate.wit.json b/crates/wit-parser/tests/ui/stress-export-elaborate.wit.json index d77812776b..01300c7e1e 100644 --- a/crates/wit-parser/tests/ui/stress-export-elaborate.wit.json +++ b/crates/wit-parser/tests/ui/stress-export-elaborate.wit.json @@ -1150,7 +1150,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/type-then-eof.wit.json b/crates/wit-parser/tests/ui/type-then-eof.wit.json index 54ecd5ed6d..7b1a694587 100644 --- a/crates/wit-parser/tests/ui/type-then-eof.wit.json +++ b/crates/wit-parser/tests/ui/type-then-eof.wit.json @@ -22,7 +22,8 @@ "interfaces": { "foo": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/types.wit.json b/crates/wit-parser/tests/ui/types.wit.json index baa6a74849..0584effb73 100644 --- a/crates/wit-parser/tests/ui/types.wit.json +++ b/crates/wit-parser/tests/ui/types.wit.json @@ -762,7 +762,8 @@ "interfaces": { "types": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/union-fuzz-1.wit.json b/crates/wit-parser/tests/ui/union-fuzz-1.wit.json index 31c7c82687..3b64b42619 100644 --- a/crates/wit-parser/tests/ui/union-fuzz-1.wit.json +++ b/crates/wit-parser/tests/ui/union-fuzz-1.wit.json @@ -29,7 +29,8 @@ "xo": 0, "name": 1, "x": 2 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/union-fuzz-2.wit.json b/crates/wit-parser/tests/ui/union-fuzz-2.wit.json index 36de6b23d5..587037fea0 100644 --- a/crates/wit-parser/tests/ui/union-fuzz-2.wit.json +++ b/crates/wit-parser/tests/ui/union-fuzz-2.wit.json @@ -56,7 +56,8 @@ "worlds": { "foo": 0, "bar": 1 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/unstable-resource.wit.json b/crates/wit-parser/tests/ui/unstable-resource.wit.json index 3d6677a486..32fd06d781 100644 --- a/crates/wit-parser/tests/ui/unstable-resource.wit.json +++ b/crates/wit-parser/tests/ui/unstable-resource.wit.json @@ -31,14 +31,16 @@ "interfaces": { "error": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:b@0.1.0", "interfaces": { "foo": 1 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/use-chain.wit.json b/crates/wit-parser/tests/ui/use-chain.wit.json index bd942bb8cd..746bca8377 100644 --- a/crates/wit-parser/tests/ui/use-chain.wit.json +++ b/crates/wit-parser/tests/ui/use-chain.wit.json @@ -47,7 +47,8 @@ "foo": 0, "name": 1 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/use.wit.json b/crates/wit-parser/tests/ui/use.wit.json index 704bcdaaff..238d75a169 100644 --- a/crates/wit-parser/tests/ui/use.wit.json +++ b/crates/wit-parser/tests/ui/use.wit.json @@ -162,7 +162,8 @@ "use-multiple": 5, "trailing-comma": 6 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/version-syntax.wit.json b/crates/wit-parser/tests/ui/version-syntax.wit.json index a31d71534b..f5c71ca01c 100644 --- a/crates/wit-parser/tests/ui/version-syntax.wit.json +++ b/crates/wit-parser/tests/ui/version-syntax.wit.json @@ -6,52 +6,62 @@ { "name": "a:b@1.0.0-11-a", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:b@1.0.0-11ab", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:b@1.0.0-a1.1-a", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:b@1.0.0", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:b@1.0.1-1+1", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:b@1.0.1--", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:b@1.0.1-1a+1a", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:b@1.0.1-a+a", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:b@1.0.1", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:root", "interfaces": {}, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/versions.wit.json b/crates/wit-parser/tests/ui/versions.wit.json index febd55be51..8d1e55c7d3 100644 --- a/crates/wit-parser/tests/ui/versions.wit.json +++ b/crates/wit-parser/tests/ui/versions.wit.json @@ -71,21 +71,24 @@ "interfaces": { "foo": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:a@2.0.0", "interfaces": { "foo": 1 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "foo:versions", "interfaces": { "foo": 2 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/wasi.wit.json b/crates/wit-parser/tests/ui/wasi.wit.json index e784fbd207..a33a8d8f95 100644 --- a/crates/wit-parser/tests/ui/wasi.wit.json +++ b/crates/wit-parser/tests/ui/wasi.wit.json @@ -533,7 +533,8 @@ "interfaces": { "wasi": 0 }, - "worlds": {} + "worlds": {}, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/with-resource-as.wit.json b/crates/wit-parser/tests/ui/with-resource-as.wit.json index 93cd6a4c90..4b35948330 100644 --- a/crates/wit-parser/tests/ui/with-resource-as.wit.json +++ b/crates/wit-parser/tests/ui/with-resource-as.wit.json @@ -215,7 +215,8 @@ "worlds": { "a": 0, "b": 1 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/world-diamond.wit.json b/crates/wit-parser/tests/ui/world-diamond.wit.json index 657c5b9c4a..cecf7a4fa4 100644 --- a/crates/wit-parser/tests/ui/world-diamond.wit.json +++ b/crates/wit-parser/tests/ui/world-diamond.wit.json @@ -109,7 +109,8 @@ }, "worlds": { "the-world": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/world-iface-no-collide.wit.json b/crates/wit-parser/tests/ui/world-iface-no-collide.wit.json index 4e86a4da8d..77469a38c0 100644 --- a/crates/wit-parser/tests/ui/world-iface-no-collide.wit.json +++ b/crates/wit-parser/tests/ui/world-iface-no-collide.wit.json @@ -61,7 +61,8 @@ }, "worlds": { "bar": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/world-implicit-import1.wit.json b/crates/wit-parser/tests/ui/world-implicit-import1.wit.json index 708c339b40..d903d58c82 100644 --- a/crates/wit-parser/tests/ui/world-implicit-import1.wit.json +++ b/crates/wit-parser/tests/ui/world-implicit-import1.wit.json @@ -75,7 +75,8 @@ }, "worlds": { "the-world": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/world-implicit-import2.wit.json b/crates/wit-parser/tests/ui/world-implicit-import2.wit.json index 25ab96a5b3..1617dee5eb 100644 --- a/crates/wit-parser/tests/ui/world-implicit-import2.wit.json +++ b/crates/wit-parser/tests/ui/world-implicit-import2.wit.json @@ -62,7 +62,8 @@ }, "worlds": { "w": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/world-implicit-import3.wit.json b/crates/wit-parser/tests/ui/world-implicit-import3.wit.json index 3036f69c74..54a4f74ad5 100644 --- a/crates/wit-parser/tests/ui/world-implicit-import3.wit.json +++ b/crates/wit-parser/tests/ui/world-implicit-import3.wit.json @@ -63,7 +63,8 @@ }, "worlds": { "w": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/world-same-fields4.wit.json b/crates/wit-parser/tests/ui/world-same-fields4.wit.json index 3be16acf9b..6ab2912fc1 100644 --- a/crates/wit-parser/tests/ui/world-same-fields4.wit.json +++ b/crates/wit-parser/tests/ui/world-same-fields4.wit.json @@ -76,7 +76,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/world-top-level-funcs.wit.json b/crates/wit-parser/tests/ui/world-top-level-funcs.wit.json index df491f2349..93c41e8dd4 100644 --- a/crates/wit-parser/tests/ui/world-top-level-funcs.wit.json +++ b/crates/wit-parser/tests/ui/world-top-level-funcs.wit.json @@ -73,7 +73,8 @@ "interfaces": {}, "worlds": { "foo": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/world-top-level-resources.wit.json b/crates/wit-parser/tests/ui/world-top-level-resources.wit.json index 0babd96f9b..7d60c35987 100644 --- a/crates/wit-parser/tests/ui/world-top-level-resources.wit.json +++ b/crates/wit-parser/tests/ui/world-top-level-resources.wit.json @@ -219,7 +219,8 @@ }, "worlds": { "proxy": 0 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/worlds-union-dedup.wit.json b/crates/wit-parser/tests/ui/worlds-union-dedup.wit.json index d1e24e97b6..f8c4409f6b 100644 --- a/crates/wit-parser/tests/ui/worlds-union-dedup.wit.json +++ b/crates/wit-parser/tests/ui/worlds-union-dedup.wit.json @@ -106,7 +106,8 @@ "my-world-a": 0, "my-world-b": 1, "union-my-world": 2 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/worlds-with-types.wit.json b/crates/wit-parser/tests/ui/worlds-with-types.wit.json index 19ccefac7d..ed3a5947b7 100644 --- a/crates/wit-parser/tests/ui/worlds-with-types.wit.json +++ b/crates/wit-parser/tests/ui/worlds-with-types.wit.json @@ -182,7 +182,8 @@ "foo": 0, "bar": 1, "the-test": 2 - } + }, + "types": {} } ] } \ No newline at end of file diff --git a/tests/cli/dummy-package-scope-implements.wit b/tests/cli/dummy-package-scope-implements.wit new file mode 100644 index 0000000000..4d34d4faef --- /dev/null +++ b/tests/cli/dummy-package-scope-implements.wit @@ -0,0 +1,20 @@ +// RUN: component embed % --dummy-names legacy | \ +// component new | \ +// validate -f cm-implements + +package local:demo; + +record point { + x: u32, + y: u32, +} + +interface store { + get: func(key: string) -> option; +} + +world w { + import primary: store; + import backup: store; + import move-to: func(p: point); +} diff --git a/tests/cli/nominal1.wit.stdout b/tests/cli/nominal1.wit.stdout index b9ff3e7604..1205589a10 100644 --- a/tests/cli/nominal1.wit.stdout +++ b/tests/cli/nominal1.wit.stdout @@ -97,7 +97,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } diff --git a/tests/cli/nominal10.wit.stdout b/tests/cli/nominal10.wit.stdout index 6aa93710b1..232e214ca6 100644 --- a/tests/cli/nominal10.wit.stdout +++ b/tests/cli/nominal10.wit.stdout @@ -91,7 +91,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } diff --git a/tests/cli/nominal11.wit.stdout b/tests/cli/nominal11.wit.stdout index da7aeb39ad..ce4da45535 100644 --- a/tests/cli/nominal11.wit.stdout +++ b/tests/cli/nominal11.wit.stdout @@ -138,7 +138,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } diff --git a/tests/cli/nominal12.wit.stdout b/tests/cli/nominal12.wit.stdout index 7e2717a5f5..8077ec6854 100644 --- a/tests/cli/nominal12.wit.stdout +++ b/tests/cli/nominal12.wit.stdout @@ -161,7 +161,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } diff --git a/tests/cli/nominal13.wit.stdout b/tests/cli/nominal13.wit.stdout index 600535dc1e..fa90342b23 100644 --- a/tests/cli/nominal13.wit.stdout +++ b/tests/cli/nominal13.wit.stdout @@ -58,7 +58,8 @@ }, "worlds": { "w": 0 - } + }, + "types": {} } ] } diff --git a/tests/cli/nominal2.wit.stdout b/tests/cli/nominal2.wit.stdout index 01060209ce..5d7775f3da 100644 --- a/tests/cli/nominal2.wit.stdout +++ b/tests/cli/nominal2.wit.stdout @@ -137,7 +137,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } diff --git a/tests/cli/nominal3.wit.stdout b/tests/cli/nominal3.wit.stdout index 15aaca488d..bf3b433645 100644 --- a/tests/cli/nominal3.wit.stdout +++ b/tests/cli/nominal3.wit.stdout @@ -97,7 +97,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } diff --git a/tests/cli/nominal4.wit.stdout b/tests/cli/nominal4.wit.stdout index f2a0db18fd..20cbf4754b 100644 --- a/tests/cli/nominal4.wit.stdout +++ b/tests/cli/nominal4.wit.stdout @@ -92,7 +92,8 @@ "interfaces": { "x": 0 }, - "worlds": {} + "worlds": {}, + "types": {} }, { "name": "a:b", @@ -102,7 +103,8 @@ "interfaces": {}, "worlds": { "foo": 0 - } + }, + "types": {} } ] } diff --git a/tests/cli/nominal5.wit.stdout b/tests/cli/nominal5.wit.stdout index fc7f0bd6cc..e63fdcd915 100644 --- a/tests/cli/nominal5.wit.stdout +++ b/tests/cli/nominal5.wit.stdout @@ -57,7 +57,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } diff --git a/tests/cli/nominal6.wit.stdout b/tests/cli/nominal6.wit.stdout index 82c375a627..4bbbcb93f0 100644 --- a/tests/cli/nominal6.wit.stdout +++ b/tests/cli/nominal6.wit.stdout @@ -58,7 +58,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } diff --git a/tests/cli/nominal7.wit.stdout b/tests/cli/nominal7.wit.stdout index 6ab64be37f..84eb2a87e4 100644 --- a/tests/cli/nominal7.wit.stdout +++ b/tests/cli/nominal7.wit.stdout @@ -58,7 +58,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } diff --git a/tests/cli/nominal8.wit.stdout b/tests/cli/nominal8.wit.stdout index e20f038f9d..55489cf8ef 100644 --- a/tests/cli/nominal8.wit.stdout +++ b/tests/cli/nominal8.wit.stdout @@ -58,7 +58,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } diff --git a/tests/cli/nominal9.wit.stdout b/tests/cli/nominal9.wit.stdout index 2a84af6a9d..a7ebd09862 100644 --- a/tests/cli/nominal9.wit.stdout +++ b/tests/cli/nominal9.wit.stdout @@ -57,7 +57,8 @@ }, "worlds": { "foo": 0 - } + }, + "types": {} } ] } diff --git a/tests/cli/roundtrip-package-scope-external-id.wit b/tests/cli/roundtrip-package-scope-external-id.wit new file mode 100644 index 0000000000..e642e940d6 --- /dev/null +++ b/tests/cli/roundtrip-package-scope-external-id.wit @@ -0,0 +1,25 @@ +// RUN: component wit % + +package local:demo; + +@external-id("pkg-point") +record point { + x: u32, + y: u32, +} + +@external-id("pkg-path") +type path = list; + +interface api { + @external-id("iface-move") + move-to: func(p: point); +} + +world w { + @external-id("world-api") + export api; + + @external-id("world-move") + import move-to: func(p: point); +} diff --git a/tests/cli/roundtrip-package-scope-external-id.wit.stdout b/tests/cli/roundtrip-package-scope-external-id.wit.stdout new file mode 100644 index 0000000000..53f94e03b2 --- /dev/null +++ b/tests/cli/roundtrip-package-scope-external-id.wit.stdout @@ -0,0 +1,24 @@ +/// RUN: component wit % +package local:demo; + +@external-id("pkg-point") +record point { + x: u32, + y: u32, +} + +@external-id("pkg-path") +type path = list; + +interface api { + @external-id("iface-move") + move-to: func(p: point); +} + +world w { + @external-id("world-move") + import move-to: func(p: point); + + @external-id("world-api") + export api; +}