diff --git a/crates/wit-component/src/encoding/wit/v2.rs b/crates/wit-component/src/encoding/wit/v2.rs index 1803e03074..38b1b0fb94 100644 --- a/crates/wit-component/src/encoding/wit/v2.rs +++ b/crates/wit-component/src/encoding/wit/v2.rs @@ -229,8 +229,8 @@ impl InterfaceEncoder<'_> { Self::encode_instance_contents(self, interface)?; let iface = &self.resolve.interfaces[interface]; let mut instance = self.pop_instance(); - for (name, nest) in &iface.nested { - self.encode_nested(name, nest.id, &mut instance)?; + for nest in &iface.nested { + self.encode_nested(&self.interface_path(nest.id), nest.id, &mut instance)?; } let idx = self.outer.type_count(); @@ -240,6 +240,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 encode_nested<'a>( &mut self, name: &str, @@ -251,9 +268,13 @@ impl InterfaceEncoder<'_> { let ty = instance.ty(); let nested_instance = &mut inst.pop_instance(); let iface = &self.resolve.interfaces[iface_id]; - for (deep_name, deep_nest) in &iface.nested { + for deep_nest in &iface.nested { let mut inst = InterfaceEncoder::new(&self.resolve); - inst.encode_nested(deep_name, deep_nest.id, nested_instance)?; + inst.encode_nested( + &self.interface_path(deep_nest.id), + deep_nest.id, + nested_instance, + )?; } ty.instance(&nested_instance); instance.export(name, ComponentTypeRef::Instance(instance.type_count() - 1)); diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index e12e9653d0..b5949e8b32 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -135,7 +135,7 @@ 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 { + for nest in &interface.nested { self.print_stability(&nest.stability); self.print_docs(&nest.docs); self.output.push_str("nest "); 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 index 56fe82d994..19d50619e9 100644 --- a/crates/wit-component/tests/interfaces/nested.wat +++ b/crates/wit-component/tests/interfaces/nested.wat @@ -41,7 +41,7 @@ ) ) (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:{\22foo:nestee/things@1.0.0\22:{\22docs\22:{\22contents\22:\22nesting can be documented\22},\22stability\22:\22unknown\22},\22foo:nestee/more@1.0.0\22:{\22docs\22:{\22contents\22:null},\22stability\22:{\22stable\22:{\22since\22:\221.0.0\22}}}}}}}") + (@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-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 33954b6d3f..41a12a279b 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -561,6 +561,23 @@ struct Nest<'a> { 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> { attributes: Vec>, from: UsePath<'a>, @@ -998,14 +1015,7 @@ impl<'a> InterfaceItem<'a> { } Some((_span, Token::Use)) => Use::parse(tokens, attributes).map(InterfaceItem::Use), Some((_span, Token::Nest)) => { - tokens.eat(Token::Nest)?; - let path = UsePath::parse(tokens)?; - tokens.expect_semicolon()?; - Ok(InterfaceItem::Nest(Nest { - docs, - attributes, - from: path, - })) + 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/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index d2792908ce..aec1d090e7 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1,6 +1,5 @@ use super::{ParamList, ResultList, WorldOrInterface}; use crate::ast::toposort::toposort; -use crate::ast::UsePath; use crate::*; use anyhow::bail; use std::collections::{HashMap, HashSet}; @@ -312,7 +311,7 @@ impl<'a> Resolver<'a> { }); self.interfaces.alloc(Interface { name: None, - nested: IndexMap::new(), + nested: Vec::new(), types: IndexMap::new(), docs: Docs::default(), stability: Default::default(), @@ -842,54 +841,16 @@ impl<'a> Resolver<'a> { } ast::InterfaceItem::TypeDef(_) => {} ast::InterfaceItem::Nest(n) => { - if let ast::Nest { - from: - UsePath::Package { - id: package_id, - name, - }, - attributes, - docs, - } = n - { - let nest = self - .foreign_deps - .get(&PackageName { - namespace: package_id.namespace.name.to_string(), - name: package_id.name.name.to_string(), - version: package_id.clone().version.map(|v| v.1), - }) - .unwrap() - .get(&name.name) - .unwrap(); - let nested_id = if let AstItem::Interface(id) = nest { - id - } else { - bail!("Expected interface item") - }; - let stability = self.stability(attributes)?; - let docs = self.docs(&docs); - let full_name = if let Some(v) = &package_id.version { - format!( - "{}:{}/{}@{}", - package_id.namespace.name, - package_id.name.name, - name.name, - v.1.to_string() - ) - } else { - format!("{}/{}", package_id.package_name(), name.name) - }; + let (item, name, span) = self.resolve_ast_item_path(&n.from)?; + let nested_id = self.extract_iface_from_item(&item, &name, span)?; - self.interfaces[interface_id].nested.insert( - full_name.clone(), - crate::Nest { - id: *nested_id, - docs, - stability, - }, - ); - } + 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, + }); } } } diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index 8d4f89f9e3..088019cb1d 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}, @@ -739,93 +740,22 @@ impl WitPackageDecoder<'_> { let owner = TypeOwner::Interface(interface); for (name, ty) in ty.exports.iter() { 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)? } - types::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.insert( - name.to_string(), - Nest { - id: deep, - docs: Default::default(), - stability: Default::default(), - }, - ); - } - _ => bail!("expected interface name"), + 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"), + }, _ => {} } } @@ -854,76 +784,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. @@ -954,6 +817,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 @@ -999,7 +936,7 @@ impl WitPackageDecoder<'_> { self.resolve.interfaces.alloc(Interface { name: Some(name.interface().to_string()), docs: Default::default(), - nested: IndexMap::new(), + nested: Vec::new(), types: IndexMap::default(), functions: IndexMap::new(), package: None, @@ -1055,7 +992,7 @@ impl WitPackageDecoder<'_> { let mut interface = Interface { name: interface_name.clone(), docs: Default::default(), - nested: IndexMap::new(), + nested: Vec::new(), types: IndexMap::default(), functions: IndexMap::new(), package: None, @@ -1089,14 +1026,11 @@ impl WitPackageDecoder<'_> { ComponentNameKind::Interface(_) => { let ty = &self.types[inst]; let deep = self.register_export(&exp_name, &ty)?; - interface.nested.insert( - exp_name.to_string(), - Nest { - id: deep, - docs: Default::default(), - stability: Default::default(), - }, - ); + interface.nested.push(Nest { + id: deep, + docs: Default::default(), + stability: Default::default(), + }); } _ => bail!("expected interface name"), } diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index bef81c02bd..0a2ac386e8 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -433,8 +433,8 @@ pub struct Interface { pub name: Option, /// The nested interfaces in this interface. - #[serde(skip_serializing_if = "IndexMap::is_empty")] - pub nested: IndexMap, + #[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 diff --git a/crates/wit-parser/src/metadata.rs b/crates/wit-parser/src/metadata.rs index 46cee2f549..d08b55e934 100644 --- a/crates/wit-parser/src/metadata.rs +++ b/crates/wit-parser/src/metadata.rs @@ -547,7 +547,13 @@ impl InterfaceMetadata { let nested: IndexMap = interface .nested .iter() - .map(|n| (n.0.clone(), NestMetadata::extract(n.1.clone()))) + .map(|n| { + let iface = &resolve.interfaces[n.id]; + ( + iface.name.clone().unwrap(), + NestMetadata::extract(n.clone()), + ) + }) .collect(); Self { @@ -566,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 { @@ -578,10 +585,14 @@ impl InterfaceMetadata { } interface.stability = self.stability.clone(); for (name, data) in &self.nested { - let Some(n) = interface.nested.get_mut(name) else { + 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:?}"); - }; - data.inject(n)?; + } } Ok(()) } diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index d3e16dc304..71a249be29 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -2025,7 +2025,7 @@ impl Remap { iface.functions.insert(name, func); } } - for (_, nest) in &mut iface.nested { + for nest in &mut iface.nested { nest.id = self.interfaces[nest.id.index()].unwrap(); } 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