diff --git a/Cargo.lock b/Cargo.lock index ca4f71f..0d5e5e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4070,7 +4070,7 @@ dependencies = [ "warg-client", "warg-protocol", "wasmprinter 0.245.1", - "wat", + "wat 1.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", "wit-component", "wit-parser", ] @@ -4090,11 +4090,11 @@ dependencies = [ "serde_json", "thiserror", "wac-types", - "wasm-encoder 0.245.1", + "wasm-encoder 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", "wasm-metadata", - "wasmparser 0.245.1", + "wasmparser 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", "wasmprinter 0.245.1", - "wat", + "wat 1.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", "wit-component", "wit-parser", ] @@ -4120,9 +4120,9 @@ dependencies = [ "tokio", "wac-graph", "wac-resolver", - "wasm-encoder 0.245.1", + "wasm-encoder 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", "wasm-metadata", - "wasmparser 0.245.1", + "wasmparser 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", "wasmprinter 0.245.1", ] @@ -4150,7 +4150,7 @@ dependencies = [ "warg-protocol", "warg-server", "wasmprinter 0.245.1", - "wat", + "wat 1.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", "wit-component", "wit-parser", ] @@ -4164,8 +4164,9 @@ dependencies = [ "indexmap 2.13.0", "semver", "serde 1.0.228", - "wasm-encoder 0.245.1", - "wasmparser 0.245.1", + "wasm-encoder 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", + "wasmparser 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", + "wat 1.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", ] [[package]] @@ -4452,7 +4453,7 @@ dependencies = [ "smallvec", "wasm-encoder 0.41.2", "wasmparser 0.121.2", - "wat", + "wat 1.245.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4472,14 +4473,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" dependencies = [ "leb128fmt", - "wasmparser 0.245.1", + "wasmparser 0.245.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-encoder" +version = "0.245.1" +source = "git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements#3c7046d2129d650f10a7c84ee286a55dc81dce12" +dependencies = [ + "leb128fmt", + "wasmparser 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", ] [[package]] name = "wasm-metadata" version = "0.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da55e60097e8b37b475a0fa35c3420dd71d9eb7bd66109978ab55faf56a57efb" +source = "git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements#3c7046d2129d650f10a7c84ee286a55dc81dce12" dependencies = [ "anyhow", "auditable-serde", @@ -4490,8 +4499,8 @@ dependencies = [ "serde_json", "spdx", "url", - "wasm-encoder 0.245.1", - "wasmparser 0.245.1", + "wasm-encoder 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", + "wasmparser 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", ] [[package]] @@ -4523,6 +4532,16 @@ name = "wasmparser" version = "0.245.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" +dependencies = [ + "bitflags 2.5.0", + "indexmap 2.13.0", + "semver", +] + +[[package]] +name = "wasmparser" +version = "0.245.1" +source = "git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements#3c7046d2129d650f10a7c84ee286a55dc81dce12" dependencies = [ "bitflags 2.5.0", "hashbrown 0.16.1", @@ -4544,12 +4563,11 @@ dependencies = [ [[package]] name = "wasmprinter" version = "0.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41517a3716fbb8ccf46daa9c1325f760fcbff5168e75c7392288e410b91ac8" +source = "git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements#3c7046d2129d650f10a7c84ee286a55dc81dce12" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.245.1", + "wasmparser 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", ] [[package]] @@ -4562,7 +4580,19 @@ dependencies = [ "leb128fmt", "memchr", "unicode-width 0.2.0", - "wasm-encoder 0.245.1", + "wasm-encoder 0.245.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wast" +version = "245.0.1" +source = "git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements#3c7046d2129d650f10a7c84ee286a55dc81dce12" +dependencies = [ + "bumpalo", + "leb128fmt", + "memchr", + "unicode-width 0.2.0", + "wasm-encoder 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", ] [[package]] @@ -4571,7 +4601,15 @@ version = "1.245.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd48d1679b6858988cb96b154dda0ec5bbb09275b71db46057be37332d5477be" dependencies = [ - "wast", + "wast 245.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wat" +version = "1.245.1" +source = "git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements#3c7046d2129d650f10a7c84ee286a55dc81dce12" +dependencies = [ + "wast 245.0.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", ] [[package]] @@ -4844,8 +4882,7 @@ dependencies = [ [[package]] name = "wit-component" version = "0.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4894f10d2d5cbc17c77e91f86a1e48e191a788da4425293b55c98b44ba3fcac9" +source = "git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements#3c7046d2129d650f10a7c84ee286a55dc81dce12" dependencies = [ "anyhow", "bitflags 2.5.0", @@ -4854,18 +4891,17 @@ dependencies = [ "serde 1.0.228", "serde_derive", "serde_json", - "wasm-encoder 0.245.1", + "wasm-encoder 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", "wasm-metadata", - "wasmparser 0.245.1", - "wat", + "wasmparser 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", + "wat 1.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", "wit-parser", ] [[package]] name = "wit-parser" version = "0.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330698718e82983499419494dd1e3d7811a457a9bf9f69734e8c5f07a2547929" +source = "git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements#3c7046d2129d650f10a7c84ee286a55dc81dce12" dependencies = [ "anyhow", "hashbrown 0.16.1", @@ -4877,7 +4913,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.245.1", + "wasmparser 0.245.1 (git+https://github.com/ricochet/wasm-tools?branch=wasmparser-implements)", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index deb4dc1..6adb711 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,13 +63,13 @@ wac-parser = { path = "crates/wac-parser", version = "0.10.0-dev", default-featu wac-resolver = { path = "crates/wac-resolver", version = "0.10.0-dev", default-features = false } wac-graph = { path = "crates/wac-graph", version = "0.10.0-dev" } wac-types = { path = "crates/wac-types", version = "0.10.0-dev" } -wit-parser = "0.245.1" -wasmparser = "0.245.1" -wit-component = "0.245.1" -wasm-encoder = "0.245.1" -wasmprinter = "0.245.1" -wasm-metadata = "0.245.1" -wat = "1.245.1" +wit-parser = { git = "https://github.com/ricochet/wasm-tools", branch = "wasmparser-implements" } +wasmparser = { git = "https://github.com/ricochet/wasm-tools", branch = "wasmparser-implements" } +wit-component = { git = "https://github.com/ricochet/wasm-tools", branch = "wasmparser-implements" } +wasm-encoder = { git = "https://github.com/ricochet/wasm-tools", branch = "wasmparser-implements" } +wasmprinter = { git = "https://github.com/ricochet/wasm-tools", branch = "wasmparser-implements" } +wasm-metadata = { git = "https://github.com/ricochet/wasm-tools", branch = "wasmparser-implements" } +wat = { git = "https://github.com/ricochet/wasm-tools", branch = "wasmparser-implements" } anyhow = "1.0.81" clap = { version = "4.5.4", features = ["derive"] } semver = { version = "1.0.22", features = ["serde"] } diff --git a/crates/wac-graph/src/encoding.rs b/crates/wac-graph/src/encoding.rs index a120b3e..7b5d86c 100644 --- a/crates/wac-graph/src/encoding.rs +++ b/crates/wac-graph/src/encoding.rs @@ -690,7 +690,11 @@ impl<'a> TypeEncoder<'a> { .import_type(name, ComponentTypeRef::Instance(index)); if let Some(iid) = &self.0[id].id { log::debug!("instance index {import_index} ({iid}) is available for aliasing"); - state.current.instances.insert(iid.clone(), import_index); + state + .current + .instances + .entry(iid.clone()) + .or_insert(import_index); } } _ => panic!("expected only types, functions, and instance types"), diff --git a/crates/wac-graph/src/graph.rs b/crates/wac-graph/src/graph.rs index 7948e5b..5298228 100644 --- a/crates/wac-graph/src/graph.rs +++ b/crates/wac-graph/src/graph.rs @@ -1690,11 +1690,15 @@ impl<'a> CompositionGraphEncoder<'a> { fn import(&self, state: &mut State, name: &str, types: &Types, kind: ItemKind) -> u32 { // Check to see if this is an import of an interface that's already been - // imported; this can happen based on importing of shared dependencies + // imported; this can happen based on importing of shared dependencies. + // Skip deduplication for implements names, as they can import the same + // interface multiple times under different labels. if let ItemKind::Instance(id) = kind { if let Some(id) = &types[id].id { - if let Some(index) = state.current.instances.get(id) { - return *index; + if !name.starts_with("[implements=<") { + if let Some(index) = state.current.instances.get(id) { + return *index; + } } } } @@ -1739,7 +1743,7 @@ impl<'a> CompositionGraphEncoder<'a> { log::debug!( "interface `{id}` is available for aliasing as instance index {index}" ); - state.current.instances.insert(id.clone(), index); + state.current.instances.entry(id.clone()).or_insert(index); } } _ => {} diff --git a/crates/wac-graph/tests/graphs/implements-imports/consumer.wat b/crates/wac-graph/tests/graphs/implements-imports/consumer.wat new file mode 100644 index 0000000..c6488bf --- /dev/null +++ b/crates/wac-graph/tests/graphs/implements-imports/consumer.wat @@ -0,0 +1,10 @@ +(component + (type (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "do-something" (func (type 0))) + ) + ) + (import "[implements=]primary" (instance (;0;) (type 0))) + (import "[implements=]backup" (instance (;1;) (type 0))) +) diff --git a/crates/wac-graph/tests/graphs/implements-imports/encoded.wat b/crates/wac-graph/tests/graphs/implements-imports/encoded.wat new file mode 100644 index 0000000..1ab2b88 --- /dev/null +++ b/crates/wac-graph/tests/graphs/implements-imports/encoded.wat @@ -0,0 +1,28 @@ +(component + (type (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "do-something" (func (type 0))) + ) + ) + (import "[implements=]primary" (instance (;0;) (type 0))) + (import "[implements=]backup" (instance (;1;) (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "do-something" (func (type 0))) + ) + ) + (import "[implements=]primary" (instance (;0;) (type 0))) + (import "[implements=]backup" (instance (;1;) (type 0))) + ) + ) + (import "unlocked-dep=" (component (;0;) (type 1))) + (instance (;2;) (instantiate 0 + (with "[implements=]primary" (instance 0)) + (with "[implements=]backup" (instance 1)) + ) + ) +) diff --git a/crates/wac-graph/tests/graphs/implements-imports/graph.json b/crates/wac-graph/tests/graphs/implements-imports/graph.json new file mode 100644 index 0000000..3fc8117 --- /dev/null +++ b/crates/wac-graph/tests/graphs/implements-imports/graph.json @@ -0,0 +1,14 @@ +{ + "packages": [ + { + "name": "test:consumer", + "path": "consumer.wat" + } + ], + "nodes": [ + { + "type": "instantiation", + "package": 0 + } + ] +} diff --git a/crates/wac-types/Cargo.toml b/crates/wac-types/Cargo.toml index bc49722..98a4461 100644 --- a/crates/wac-types/Cargo.toml +++ b/crates/wac-types/Cargo.toml @@ -18,5 +18,8 @@ serde = { workspace = true, optional = true } wasmparser = { workspace = true } wasm-encoder = { workspace = true } +[dev-dependencies] +wat = { workspace = true } + [features] serde = ["dep:serde"] diff --git a/crates/wac-types/src/package.rs b/crates/wac-types/src/package.rs index da062e3..8e261d0 100644 --- a/crates/wac-types/src/package.rs +++ b/crates/wac-types/src/package.rs @@ -567,9 +567,19 @@ impl<'a> TypeConverter<'a> { wasm::ComponentEntityType::Func(id) => { Ok(ItemKind::Func(self.component_func_type(id)?)) } - wasm::ComponentEntityType::Instance(id) => Ok(ItemKind::Instance( - self.component_instance_type(Some(name), id)?, - )), + wasm::ComponentEntityType::Instance(id) => { + let implements_iface = + ComponentName::new(name, 0) + .ok() + .and_then(|cn| match cn.kind() { + ComponentNameKind::Implements(i) => Some(i.interface().to_owned()), + _ => None, + }); + let effective_name = implements_iface.as_deref().unwrap_or(name); + Ok(ItemKind::Instance( + self.component_instance_type(Some(effective_name), id)?, + )) + } wasm::ComponentEntityType::Component(id) => { Ok(ItemKind::Component(self.component_type(Some(name), id)?)) } diff --git a/crates/wac-types/tests/implements.rs b/crates/wac-types/tests/implements.rs new file mode 100644 index 0000000..5674e8b --- /dev/null +++ b/crates/wac-types/tests/implements.rs @@ -0,0 +1,89 @@ +use wac_types::{ItemKind, Package, Types}; + +/// Test that Package::from_bytes correctly parses components with implements imports. +/// The Interface.id should be the interface name (e.g., "test:test/iface"), +/// not the raw binary name (e.g., "[implements=]primary"). +#[test] +fn implements_import_interface_id() { + let wat = r#"(component + (type (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "do-something" (func (type 0))) + ) + ) + (import "[implements=]primary" (instance (;0;) (type 0))) + (import "[implements=]backup" (instance (;1;) (type 0))) + )"#; + + let bytes = wat::parse_str(wat).expect("failed to parse WAT"); + let mut types = Types::default(); + let package = Package::from_bytes("test:consumer", None, bytes, &mut types) + .expect("failed to parse component"); + + let world = &types[package.ty()]; + + // Both implements imports should be present in the world's imports + assert!( + world + .imports + .contains_key("[implements=]primary"), + "expected primary implements import" + ); + assert!( + world + .imports + .contains_key("[implements=]backup"), + "expected backup implements import" + ); + + // Both should be instance types with Interface.id set to the interface name, + // not the raw binary name. Both imports share the same underlying wasmparser + // type, so they resolve to the same InterfaceId due to caching. + for name in [ + "[implements=]primary", + "[implements=]backup", + ] { + let ItemKind::Instance(id) = world.imports[name] else { + panic!("expected instance kind for import `{name}`"); + }; + assert_eq!( + types[id].id.as_deref(), + Some("test:test/iface"), + "implements import `{name}` should have interface id 'test:test/iface'" + ); + } +} + +/// Test that a component with both a regular interface import and an implements +/// import of the same interface produces correct Interface.id values. +#[test] +fn implements_with_regular_import() { + let wat = r#"(component + (type (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "do-something" (func (type 0))) + ) + ) + (import "test:test/iface" (instance (;0;) (type 0))) + (import "[implements=]secondary" (instance (;1;) (type 0))) + )"#; + + let bytes = wat::parse_str(wat).expect("failed to parse WAT"); + let mut types = Types::default(); + let package = Package::from_bytes("test:consumer", None, bytes, &mut types) + .expect("failed to parse component"); + + let world = &types[package.ty()]; + + // Both imports should be present with correct interface id + for name in ["test:test/iface", "[implements=]secondary"] { + let ItemKind::Instance(id) = *world.imports.get(name).unwrap_or_else(|| { + panic!("expected import `{name}` to be present"); + }) else { + panic!("expected instance kind for import `{name}`"); + }; + assert_eq!(types[id].id.as_deref(), Some("test:test/iface")); + } +}