Commit 48056bd
committed
feat: Add support for implements syntax
DO NOT MERGE until wasm-tools release with
bytecodealliance/wasm-tools#2453
Points wasm-tools to PR branch `wasmparser-implements`
Add support for the component model `[implements=<I>]L`
(spec PR [#613](WebAssembly/component-model#613)),
which allows components to import/export the same
interface multiple times under different plain names.
A component can import the same interface twice under different labels,
each bound to a distinct host implementation:
```wit
import primary: wasi:keyvalue/store;
import secondary: wasi:keyvalue/store;
```
Guest code sees two separate namespaces with identical shapes:
```rust
let val = primary::get("my-key"); // calls the primary store
let val = secondary::get("my-key"); // calls the secondary store
```
From the host, wit-bindgen generates a separate Host trait per label:
```rust
impl primary::Host for MyState {
fn get(&mut self, key: String) -> String {
self.primary_db.get(&key).cloned().unwrap_or_default()
}
}
impl secondary::Host for MyState {
fn get(&mut self, key: String) -> String {
self.secondary_db.get(&key).cloned().unwrap_or_default()
}
}
primary::add_to_linker(&mut linker, |state| state)?;
secondary::add_to_linker(&mut linker, |state| state)?;
```
The linker also supports registering by plain label without knowing the annotation:
```rust
// Component imports [implements=<wasi:keyvalue/store>]primary
// but the host just registers "primary" — label fallback handles it
linker.root().instance("primary")?.func_wrap("get", /* ... */)?;
```
Users can also register to the linker with the full encoded `implements` name
```rust
let mut linker = Linker::<()>::new(engine);
linker
.root()
.instance("[implements=<wasi:keyvalue/store>]primary")?
.func_wrap("get", |_, (key,): (String,)| Ok((String::new(),)))?;
```
Semver matching works inside the implements annotation, just like regular interface imports:
```rust
// Host provides v1.0.1
linker
.root()
.instance("[implements=<wasi:keyvalue/store@1.0.1>]primary")?
.func_wrap("get", |_, (key,): (String,)| Ok((String::new(),)))?;
// Component requests v1.0.0, matches via semver
let component = Component::new(&engine, r#"(component
(type $store (instance
(export "get" (func (param "key" string) (result string)))
))
(import "[implements=<wasi:keyvalue/store@1.0.0>]primary" (instance (type $store)))
)"#)?;
linker.instantiate(&mut store, &component)?; // works, 1.0.1 is semver-compatible with 1.0.0
```
## Changes
### Runtime name resolution
- Add three-tier lookup in NameMap::get: exact → semver → label fallback
- Add implements_label_key() helper for extracting plain labels from `[implements=<I>]L`
- Add unit tests for all lookup tiers
### Code generation for multi-import/export
- Track first-seen implements imports/exports per `InterfaceId`
- Duplicate imports: re-export types via `pub use super::{first}::*`,
generate fresh Host trait + add_to_linker
- Duplicate exports: same pattern with fresh Guest/GuestIndices,
plus regenerate resource wrapper structs to reference the local Guest type
- Use `name_world_key_with_item` for export instance name lookups
- Guard `populate_world_and_interface_options` with `entry()` to avoid
overwriting link options for duplicate interfaces1 parent 4fd25c0 commit 48056bd
File tree
6 files changed
+1912
-1040
lines changed- crates
- environ/src/component
- wit-bindgen/src
- tests/all/component_model
6 files changed
+1912
-1040
lines changed
0 commit comments