diff --git a/crates/wit-component/src/encoding/wit.rs b/crates/wit-component/src/encoding/wit.rs index 8ee658960a..087bdf26ed 100644 --- a/crates/wit-component/src/encoding/wit.rs +++ b/crates/wit-component/src/encoding/wit.rs @@ -272,11 +272,15 @@ impl InterfaceEncoder<'_> { } } - fn encode_instance(&mut self, interface: InterfaceId) -> Result { + fn encode_instance_contents(&mut self, interface: InterfaceId) -> Result { self.push_instance(); let iface = &self.resolve.interfaces[interface]; let mut type_order = IndexSet::new(); for (_, id) in iface.types.iter() { + let ty = &self.resolve.types[*id]; + if let TypeOwner::Interface(iface_id) = ty.owner { + self.interface = Some(iface_id); + } self.encode_valtype(self.resolve, &Type::Id(*id))?; type_order.insert(*id); } @@ -311,7 +315,21 @@ impl InterfaceEncoder<'_> { .unwrap() .export(name, ComponentTypeRef::Func(ty)); } - let instance = self.pop_instance(); + let mut instance = self.pop_instance(); + for nest in &iface.nested { + let ty = instance.ty(); + ty.instance(&self.encode_instance_contents(nest.id)?); + instance.export( + &self.interface_path(nest.id), + ComponentTypeRef::Instance(instance.type_count() - 1), + ); + } + + Ok(instance) + } + + fn encode_instance(&mut self, interface: InterfaceId) -> Result { + let instance = self.encode_instance_contents(interface)?; let idx = self.outer.type_count(); self.outer.ty().instance(&instance); self.import_map.insert(interface, self.instances); @@ -319,6 +337,23 @@ impl InterfaceEncoder<'_> { Ok(idx) } + fn interface_path(&self, iface: InterfaceId) -> String { + let iface = &self.resolve.interfaces[iface]; + let pkg_id = iface.package.unwrap(); + let pkg = &self.resolve.packages[pkg_id]; + if let Some(v) = &pkg.name.version { + format!( + "{}:{}/{}@{}", + pkg.name.namespace, + pkg.name.name, + iface.name.as_ref().unwrap(), + v.to_string() + ) + } else { + format!("{}/{}", pkg.name.to_string(), iface.name.as_ref().unwrap()) + } + } + fn push_instance(&mut self) { assert!(self.ty.is_none()); assert!(self.saved_types.is_none()); diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 13e03ce0c6..a188fe3622 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -127,6 +127,13 @@ impl WitPrinter { fn print_interface(&mut self, resolve: &Resolve, id: InterfaceId) -> Result<()> { let prev_items = mem::replace(&mut self.any_items, false); let interface = &resolve.interfaces[id]; + for nest in &interface.nested { + self.print_stability(&nest.stability); + self.print_docs(&nest.docs); + self.output.push_str("nest "); + self.print_path_to_interface(resolve, nest.id, interface.package.unwrap())?; + self.output.push_str(";\n"); + } let mut resource_funcs = HashMap::new(); let mut freestanding = Vec::new(); diff --git a/crates/wit-component/tests/interfaces/multi-package-nest.wat b/crates/wit-component/tests/interfaces/multi-package-nest.wat new file mode 100644 index 0000000000..f0a99b48c4 --- /dev/null +++ b/crates/wit-component/tests/interfaces/multi-package-nest.wat @@ -0,0 +1,23 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) + (instance + (type (;0;) string) + (export (;1;) "t" (type (eq 0))) + ) + ) + (export (;0;) "foo:other/i" (instance (type 0))) + ) + ) + (export (;0;) "foo:bar/i1" (instance (type 0))) + ) + ) + (export (;1;) "i1" (type 0)) + (@custom "package-docs" "\00{}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/multi-package-nest.wit b/crates/wit-component/tests/interfaces/multi-package-nest.wit new file mode 100644 index 0000000000..98bf70f6c8 --- /dev/null +++ b/crates/wit-component/tests/interfaces/multi-package-nest.wit @@ -0,0 +1,11 @@ +package foo:bar; + +package foo:other { + interface i { + type t = string; + } +} + +interface i1 { + nest foo:other/i; +} \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/multi-package-nest.wit.print b/crates/wit-component/tests/interfaces/multi-package-nest.wit.print new file mode 100644 index 0000000000..e51308ef87 --- /dev/null +++ b/crates/wit-component/tests/interfaces/multi-package-nest.wit.print @@ -0,0 +1,6 @@ +package foo:bar; + +interface i1 { + nest foo:other/i; +} + diff --git a/crates/wit-component/tests/interfaces/nested.wat b/crates/wit-component/tests/interfaces/nested.wat new file mode 100644 index 0000000000..19d50619e9 --- /dev/null +++ b/crates/wit-component/tests/interfaces/nested.wat @@ -0,0 +1,48 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (record (field "foo" string))) + (export (;1;) "my-record" (type (eq 0))) + (type (;2;) (func (result string))) + (export (;0;) "hello" (func (type 2))) + (type (;3;) + (instance + (type (;0;) (record (field "foo" string))) + (export (;1;) "nest-record" (type (eq 0))) + (type (;2;) (func (result 1))) + (export (;0;) "goodbye" (func (type 2))) + (type (;3;) + (instance + (type (;0;) (record (field "foo" string))) + (export (;1;) "deep-record" (type (eq 0))) + (type (;2;) string) + (export (;3;) "anything" (type (eq 2))) + ) + ) + (export (;0;) "foo:nestnest/deep" (instance (type 3))) + ) + ) + (export (;0;) "foo:nestee/things@1.0.0" (instance (type 3))) + (type (;4;) + (instance + (export (;0;) "foo" (type (sub resource))) + (type (;1;) (borrow 0)) + (type (;2;) (option string)) + (type (;3;) (func (param "self" 1) (result 2))) + (export (;0;) "[method]foo.bar" (func (type 3))) + ) + ) + (export (;1;) "foo:nestee/more@1.0.0" (instance (type 4))) + ) + ) + (export (;0;) "foo:thing/something@1.0.0" (instance (type 0))) + ) + ) + (export (;1;) "something" (type 0)) + (@custom "package-docs" "\01{\22interfaces\22:{\22something\22:{\22types\22:{\22my-record\22:{\22stability\22:{\22stable\22:{\22since\22:\221.0.0\22}}}},\22nested\22:{\22things\22:{\22docs\22:{\22contents\22:\22nesting can be documented\22},\22stability\22:\22unknown\22},\22more\22:{\22docs\22:{\22contents\22:null},\22stability\22:{\22stable\22:{\22since\22:\221.0.0\22}}}}}}}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/nested/deps/nestee.wit b/crates/wit-component/tests/interfaces/nested/deps/nestee.wit new file mode 100644 index 0000000000..ebdad9e17e --- /dev/null +++ b/crates/wit-component/tests/interfaces/nested/deps/nestee.wit @@ -0,0 +1,17 @@ +package foo:nestee@1.0.0; + +interface things { + //nesting can be documented + @since(version = 1.0.0) + nest foo:nestnest/deep; + record nest-record { + foo: string + } + goodbye: func() -> nest-record; +} + +interface more { + resource foo { + bar: func() -> option; + } +} \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/nested/deps/nestnest.wit b/crates/wit-component/tests/interfaces/nested/deps/nestnest.wit new file mode 100644 index 0000000000..3c7c33e728 --- /dev/null +++ b/crates/wit-component/tests/interfaces/nested/deps/nestnest.wit @@ -0,0 +1,8 @@ +package foo:nestnest; + +interface deep { + record deep-record { + foo: string + } + type anything = string; +} \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/nested/nested.wit b/crates/wit-component/tests/interfaces/nested/nested.wit new file mode 100644 index 0000000000..909444299e --- /dev/null +++ b/crates/wit-component/tests/interfaces/nested/nested.wit @@ -0,0 +1,14 @@ +package foo:thing@1.0.0; + +interface something { + //nesting can be documented + nest foo:nestee/things@1.0.0; + @since(version = 1.0.0) + nest foo:nestee/more@1.0.0; + @since(version = 1.0.0) + record my-record { + foo: string + } + + hello: func() -> string; +} diff --git a/crates/wit-component/tests/interfaces/nested/thing.wit.print b/crates/wit-component/tests/interfaces/nested/thing.wit.print new file mode 100644 index 0000000000..b50745503e --- /dev/null +++ b/crates/wit-component/tests/interfaces/nested/thing.wit.print @@ -0,0 +1,15 @@ +package foo:thing@1.0.0; + +interface something { + /// nesting can be documented + nest foo:nestee/things@1.0.0; + @since(version = 1.0.0) + nest foo:nestee/more@1.0.0; + @since(version = 1.0.0) + record my-record { + foo: string, + } + + hello: func() -> string; +} + diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 8b49f2a760..ac6627562a 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -205,6 +205,9 @@ impl<'a> DeclList<'a> { Some(&u.names), WorldOrInterface::Interface, )?, + InterfaceItem::Nest(n) => { + f(Some(&i.name), &n.from, None, WorldOrInterface::Interface)? + } _ => {} } } @@ -549,6 +552,30 @@ enum InterfaceItem<'a> { TypeDef(TypeDef<'a>), Func(NamedFunc<'a>), Use(Use<'a>), + Nest(Nest<'a>), +} + +struct Nest<'a> { + docs: Docs<'a>, + attributes: Vec>, + from: UsePath<'a>, +} + +impl<'a> Nest<'a> { + fn parse( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result { + tokens.eat(Token::Nest)?; + let path = UsePath::parse(tokens)?; + tokens.expect_semicolon()?; + Ok(Self { + docs, + attributes, + from: path, + }) + } } struct Use<'a> { @@ -987,6 +1014,9 @@ impl<'a> InterfaceItem<'a> { NamedFunc::parse(tokens, docs, attributes).map(InterfaceItem::Func) } Some((_span, Token::Use)) => Use::parse(tokens, attributes).map(InterfaceItem::Use), + Some((_span, Token::Nest)) => { + Nest::parse(tokens, docs, attributes).map(InterfaceItem::Nest) + } other => Err(err_expected(tokens, "`type`, `resource` or `func`", other).into()), } } diff --git a/crates/wit-parser/src/ast/lex.rs b/crates/wit-parser/src/ast/lex.rs index a3a59cb945..78ad99713d 100644 --- a/crates/wit-parser/src/ast/lex.rs +++ b/crates/wit-parser/src/ast/lex.rs @@ -83,6 +83,7 @@ pub enum Token { As, From_, Static, + Nest, Interface, Tuple, Import, @@ -302,6 +303,7 @@ impl<'a> Tokenizer<'a> { "as" => As, "from" => From_, "static" => Static, + "nest" => Nest, "interface" => Interface, "tuple" => Tuple, "world" => World, @@ -563,6 +565,7 @@ impl Token { As => "keyword `as`", From_ => "keyword `from`", Static => "keyword `static`", + Nest => "keyword `nest`", Interface => "keyword `interface`", Tuple => "keyword `tuple`", Import => "keyword `import`", diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index f32b502331..7ba8ecca6d 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -311,6 +311,7 @@ impl<'a> Resolver<'a> { }); self.interfaces.alloc(Interface { name: None, + nested: Vec::new(), types: IndexMap::new(), docs: Docs::default(), stability: Default::default(), @@ -792,7 +793,7 @@ impl<'a> Resolver<'a> { fields.iter().filter_map(|i| match i { ast::InterfaceItem::Use(u) => Some(TypeItem::Use(u)), ast::InterfaceItem::TypeDef(t) => Some(TypeItem::Def(t)), - ast::InterfaceItem::Func(_) => None, + ast::InterfaceItem::Func(_) | ast::InterfaceItem::Nest(_) => None, }), )?; @@ -839,6 +840,18 @@ impl<'a> Resolver<'a> { } } ast::InterfaceItem::TypeDef(_) => {} + ast::InterfaceItem::Nest(n) => { + let (item, name, span) = self.resolve_ast_item_path(&n.from)?; + let nested_id = self.extract_iface_from_item(&item, &name, span)?; + + let stability = self.stability(&n.attributes)?; + let docs = self.docs(&n.docs); + self.interfaces[interface_id].nested.push(crate::Nest { + id: nested_id, + docs, + stability, + }); + } } } for func in funcs { @@ -1369,7 +1382,7 @@ impl<'a> Resolver<'a> { Type::Id(*id) } - fn docs(&mut self, doc: &super::Docs<'_>) -> Docs { + fn docs(&self, doc: &super::Docs<'_>) -> Docs { let mut docs = vec![]; for doc in doc.docs.iter() { if let Some(doc) = doc.strip_prefix("/**") { @@ -1402,7 +1415,7 @@ impl<'a> Resolver<'a> { Docs { contents } } - fn stability(&mut self, attrs: &[ast::Attribute<'_>]) -> Result { + fn stability(&self, attrs: &[ast::Attribute<'_>]) -> Result { match attrs { [] => Ok(Stability::Unknown), diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index 36524e52ba..e47627c35d 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -3,6 +3,7 @@ use anyhow::{anyhow, bail}; use indexmap::IndexSet; use std::mem; use std::{collections::HashMap, io::Read}; +use wasmparser::types::ComponentEntityType; use wasmparser::Chunk; use wasmparser::{ names::{ComponentName, ComponentNameKind}, @@ -726,6 +727,40 @@ impl WitPackageDecoder<'_> { Ok(()) } + fn register_export( + &mut self, + name: &str, + ty: &types::ComponentInstanceType, + ) -> Result { + let (is_local, interface) = match self.named_interfaces.get(name) { + Some(id) => (true, *id), + None => (false, self.extract_dep_interface(name)?), + }; + let owner = TypeOwner::Interface(interface); + for (name, ty) in ty.exports.iter() { + match *ty { + ComponentEntityType::Type { .. } => { + self.register_type(name, is_local, interface, owner, ty)? + } + ComponentEntityType::Instance(i) => match self.parse_component_name(name)?.kind() { + ComponentNameKind::Interface(_) => { + let ty = &self.types[i]; + let deep = self.register_export(name, ty)?; + let iface = &mut self.resolve.interfaces[interface]; + iface.nested.push(Nest { + id: deep, + docs: Default::default(), + stability: Default::default(), + }); + } + _ => bail!("expected interface name"), + }, + _ => {} + } + } + Ok(interface) + } + /// Registers that the `name` provided is either imported interface from a /// foreign package or referencing a previously defined interface in this /// package. @@ -748,76 +783,9 @@ impl WitPackageDecoder<'_> { for (name, ty) in ty.exports.iter() { log::debug!("decoding import instance export `{name}`"); match *ty { - types::ComponentEntityType::Type { - referenced, - created, - } => { - match self.resolve.interfaces[interface] - .types - .get(name.as_str()) - .copied() - { - // If this name is already defined as a type in the - // specified interface then that's ok. For package-local - // interfaces that's expected since the interface was - // fully defined. For remote interfaces it means we're - // using something that was already used elsewhere. In - // both cases continue along. - // - // Notably for the remotely defined case this will also - // walk over the structure of the type and register - // internal wasmparser ids with wit-parser ids. This is - // necessary to ensure that anonymous types like - // `list` defined in original definitions are - // unified with anonymous types when duplicated inside - // of worlds. Overall this prevents, for example, extra - // `list` types from popping up when decoding. This - // is not strictly necessary but assists with - // roundtripping assertions during fuzzing. - Some(id) => { - log::debug!("type already exist"); - match referenced { - types::ComponentAnyTypeId::Defined(ty) => { - self.register_defined(id, &self.types[ty])?; - } - types::ComponentAnyTypeId::Resource(_) => {} - _ => unreachable!(), - } - let prev = self.type_map.insert(created, id); - assert!(prev.is_none()); - } - - // If the name is not defined, however, then there's two - // possibilities: - // - // * For package-local interfaces this is an error - // because the package-local interface defined - // everything already and this is referencing - // something that isn't defined. - // - // * For remote interfaces they're never fully declared - // so it's lazily filled in here. This means that the - // view of remote interfaces ends up being the minimal - // slice needed for this resolve, which is what's - // intended. - None => { - if is_local { - bail!("instance type export `{name}` not defined in interface"); - } - let id = self.register_type_export( - name.as_str(), - owner, - referenced, - created, - )?; - let prev = self.resolve.interfaces[interface] - .types - .insert(name.to_string(), id); - assert!(prev.is_none()); - } - } + ComponentEntityType::Type { .. } => { + self.register_type(name, is_local, interface, owner, ty)? } - // This has similar logic to types above where we lazily fill in // functions for remote dependencies and otherwise assert // they're already defined for local dependencies. @@ -848,6 +816,80 @@ impl WitPackageDecoder<'_> { Ok(interface) } + fn register_type( + &mut self, + name: &str, + is_local: bool, + interface: Id, + owner: TypeOwner, + ty: &ComponentEntityType, + ) -> Result<()> { + if let types::ComponentEntityType::Type { + referenced, + created, + } = *ty + { + match self.resolve.interfaces[interface].types.get(name).copied() { + // If this name is already defined as a type in the + // specified interface then that's ok. For package-local + // interfaces that's expected since the interface was + // fully defined. For remote interfaces it means we're + // using something that was already used elsewhere. In + // both cases continue along. + // + // Notably for the remotely defined case this will also + // walk over the structure of the type and register + // internal wasmparser ids with wit-parser ids. This is + // necessary to ensure that anonymous types like + // `list` defined in original definitions are + // unified with anonymous types when duplicated inside + // of worlds. Overall this prevents, for example, extra + // `list` types from popping up when decoding. This + // is not strictly necessary but assists with + // roundtripping assertions during fuzzing. + Some(id) => { + log::debug!("type already exist"); + match referenced { + types::ComponentAnyTypeId::Defined(ty) => { + self.register_defined(id, &self.types[ty])?; + } + types::ComponentAnyTypeId::Resource(_) => {} + _ => unreachable!(), + } + let prev = self.type_map.insert(created, id); + assert!(prev.is_none()); + } + + // If the name is not defined, however, then there's two + // possibilities: + // + // * For package-local interfaces this is an error + // because the package-local interface defined + // everything already and this is referencing + // something that isn't defined. + // + // * For remote interfaces they're never fully declared + // so it's lazily filled in here. This means that the + // view of remote interfaces ends up being the minimal + // slice needed for this resolve, which is what's + // intended. + None => { + if is_local { + bail!("instance type export `{name}` not defined in interface"); + } + let id = self.register_type_export(name, owner, referenced, created)?; + let prev = self.resolve.interfaces[interface] + .types + .insert(name.to_string(), id); + assert!(prev.is_none()); + } + } + } else { + bail!("instance type export `{name}` is not a type") + } + Ok(()) + } + fn find_alias(&self, id: types::ComponentAnyTypeId) -> Option { // Consult `type_map` for `referenced` or anything in its // chain of aliases to determine what it maps to. This may @@ -893,6 +935,7 @@ impl WitPackageDecoder<'_> { self.resolve.interfaces.alloc(Interface { name: Some(name.interface().to_string()), docs: Default::default(), + nested: Vec::new(), types: IndexMap::default(), functions: IndexMap::new(), package: None, @@ -948,6 +991,7 @@ impl WitPackageDecoder<'_> { let mut interface = Interface { name: interface_name.clone(), docs: Default::default(), + nested: Vec::new(), types: IndexMap::default(), functions: IndexMap::new(), package: None, @@ -955,28 +999,42 @@ impl WitPackageDecoder<'_> { }; let owner = TypeOwner::Interface(self.resolve.interfaces.next_id()); - for (name, ty) in ty.exports.iter() { + for (exp_name, ty) in ty.exports.iter() { match *ty { types::ComponentEntityType::Type { referenced, created, } => { let ty = self - .register_type_export(name.as_str(), owner, referenced, created) - .with_context(|| format!("failed to register type export '{name}'"))?; - let prev = interface.types.insert(name.to_string(), ty); + .register_type_export(exp_name.as_str(), owner, referenced, created) + .with_context(|| format!("failed to register type export '{exp_name}'"))?; + let prev = interface.types.insert(exp_name.to_string(), ty); assert!(prev.is_none()); } types::ComponentEntityType::Func(ty) => { let ty = &self.types[ty]; let func = self - .convert_function(name.as_str(), ty, owner) - .with_context(|| format!("failed to convert function '{name}'"))?; - let prev = interface.functions.insert(name.to_string(), func); + .convert_function(exp_name.as_str(), ty, owner) + .with_context(|| format!("failed to convert function '{exp_name}'"))?; + let prev = interface.functions.insert(exp_name.to_string(), func); assert!(prev.is_none()); } - _ => bail!("instance type export `{name}` is not a type or function"), + types::ComponentEntityType::Instance(inst) => { + match self.parse_component_name(exp_name)?.kind() { + ComponentNameKind::Interface(_) => { + let ty = &self.types[inst]; + let deep = self.register_export(&exp_name, &ty)?; + interface.nested.push(Nest { + id: deep, + docs: Default::default(), + stability: Default::default(), + }); + } + _ => bail!("expected interface name"), + } + } + _ => bail!("instance type export `{name}` is not a type, function or instance"), }; } let id = self.resolve.interfaces.alloc(interface); diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index f26b87f01f..989e273a57 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -26,7 +26,7 @@ mod live; pub use live::{LiveTypes, TypeIdVisitor}; #[cfg(feature = "serde")] -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; #[cfg(feature = "serde")] mod serde_; #[cfg(feature = "serde")] @@ -411,6 +411,19 @@ impl WorldItem { } } +#[cfg_attr(feature = "serde", derive(Serialize))] +#[derive(Debug, Clone)] +pub struct Nest { + #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_id"))] + pub id: InterfaceId, + pub docs: Docs, + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Stability::is_unknown") + )] + pub stability: Stability, +} + #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct Interface { @@ -419,6 +432,9 @@ pub struct Interface { /// This is `None` for inline interfaces in worlds. pub name: Option, + /// The nested interfaces in this interface. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty",))] + pub nested: Vec, /// Exported types from this interface. /// /// Export names are listed within the types themselves. Note that the @@ -695,7 +711,7 @@ pub struct Stream { } #[derive(Clone, Default, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Docs { pub contents: Option, } diff --git a/crates/wit-parser/src/metadata.rs b/crates/wit-parser/src/metadata.rs index 103e3d03af..d08b55e934 100644 --- a/crates/wit-parser/src/metadata.rs +++ b/crates/wit-parser/src/metadata.rs @@ -15,7 +15,7 @@ //! format to store this information inline. use crate::{ - Docs, Function, InterfaceId, PackageId, Resolve, Stability, TypeDefKind, TypeId, WorldId, + Docs, Function, InterfaceId, Nest, PackageId, Resolve, Stability, TypeDefKind, TypeId, WorldId, WorldItem, WorldKey, }; use anyhow::{bail, Result}; @@ -521,6 +521,11 @@ struct InterfaceMetadata { serde(default, skip_serializing_if = "StringMap::is_empty") )] types: StringMap, + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "StringMap::is_empty") + )] + nested: StringMap, } impl InterfaceMetadata { @@ -539,12 +544,24 @@ impl InterfaceMetadata { .map(|(name, id)| (name.to_string(), TypeMetadata::extract(resolve, *id))) .filter(|(_, item)| !item.is_empty()) .collect(); + let nested: IndexMap = interface + .nested + .iter() + .map(|n| { + let iface = &resolve.interfaces[n.id]; + ( + iface.name.clone().unwrap(), + NestMetadata::extract(n.clone()), + ) + }) + .collect(); Self { docs: interface.docs.contents.clone(), stability: interface.stability.clone(), funcs, types, + nested, } } @@ -555,6 +572,7 @@ impl InterfaceMetadata { }; data.inject(resolve, id)?; } + let clone = resolve.interfaces.clone(); let interface = &mut resolve.interfaces[id]; for (name, data) in &self.funcs { let Some(f) = interface.functions.get_mut(name) else { @@ -566,6 +584,16 @@ impl InterfaceMetadata { interface.docs.contents = Some(docs.to_string()); } interface.stability = self.stability.clone(); + for (name, data) in &self.nested { + if let Some(n) = interface.nested.iter_mut().find(|n| { + let iface = &clone[n.id]; + iface.name.as_ref().unwrap() == name + }) { + data.inject(n)?; + } else { + bail!("missing nested item {name:?}"); + } + } Ok(()) } @@ -654,6 +682,26 @@ impl FunctionMetadata { } } +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +struct NestMetadata { + docs: Docs, + stability: Stability, +} + +impl NestMetadata { + fn extract(nest: Nest) -> Self { + Self { + docs: nest.docs, + stability: nest.stability, + } + } + fn inject(&self, nest: &mut Nest) -> Result<()> { + nest.stability = self.stability.clone(); + nest.docs = self.docs.clone(); + Ok(()) + } +} #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] struct TypeMetadata { diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 975e86222e..716e59176e 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -2263,6 +2263,9 @@ impl Remap { iface.functions.insert(name, func); } } + for nest in &mut iface.nested { + nest.id = self.interfaces[nest.id.index()].unwrap(); + } Ok(()) } 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..df8b567212 100644 --- a/crates/wit-parser/tests/ui/multi-package-deps.wit.json +++ b/crates/wit-parser/tests/ui/multi-package-deps.wit.json @@ -144,7 +144,7 @@ "worlds": {} }, { - "name": "foo:nest", + "name": "foo:nested", "interfaces": { "nesty": 2 }, diff --git a/crates/wit-parser/tests/ui/multi-package-deps/root.wit b/crates/wit-parser/tests/ui/multi-package-deps/root.wit index 66e89030d0..dc4b91fd96 100644 --- a/crates/wit-parser/tests/ui/multi-package-deps/root.wit +++ b/crates/wit-parser/tests/ui/multi-package-deps/root.wit @@ -1,6 +1,6 @@ package foo:root; -package foo:nest { +package foo:nested { interface nesty { use foo:dep1/i1.{o}; type l = list; @@ -9,7 +9,7 @@ package foo:nest { interface i0 { use foo:dep1/i1.{o}; - use foo:nest/nesty.{l}; + use foo:nested/nesty.{l}; type r = result; type r1 = result; 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..8e0f74f89b 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 @@ -131,7 +131,7 @@ ], "packages": [ { - "name": "foo:nest", + "name": "foo:nested", "interfaces": { "types": 0 }, diff --git a/crates/wit-parser/tests/ui/multi-package-transitive-deps/deps/dep2/types.wit b/crates/wit-parser/tests/ui/multi-package-transitive-deps/deps/dep2/types.wit index d47d01f658..7502a17863 100644 --- a/crates/wit-parser/tests/ui/multi-package-transitive-deps/deps/dep2/types.wit +++ b/crates/wit-parser/tests/ui/multi-package-transitive-deps/deps/dep2/types.wit @@ -1,13 +1,13 @@ package foo:dep2; interface types { - use foo:nest/types.{t2}; + use foo:nested/types.{t2}; record a { foo: t2 } } -package foo:nest { +package foo:nested { interface types { type t2 = string; } diff --git a/crates/wit-parser/tests/ui/parse-fail/nest-world.wit b/crates/wit-parser/tests/ui/parse-fail/nest-world.wit new file mode 100644 index 0000000000..275385cd96 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/nest-world.wit @@ -0,0 +1,11 @@ +package foo:bar; + +package foo:other { + interface i { + type t = string; + } +} + +world w { + nest foo:other/i; +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/nest-world.wit.result b/crates/wit-parser/tests/ui/parse-fail/nest-world.wit.result new file mode 100644 index 0000000000..b37ca40e2b --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/nest-world.wit.result @@ -0,0 +1,5 @@ +expected `import`, `export`, `include`, `use`, or type definition, found keyword `nest` + --> tests/ui/parse-fail/nest-world.wit:10:3 + | + 10 | nest foo:other/i; + | ^--- \ No newline at end of file