Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add [implements=<I>]L plainname for multiple imports#613

Open
ricochet wants to merge 1 commit intomainfrom
implements
Open

Add [implements=<I>]L plainname for multiple imports#613
ricochet wants to merge 1 commit intomainfrom
implements

Conversation

@ricochet
Copy link
Contributor

@ricochet ricochet commented Feb 25, 2026

Add [implements=<I>]L plainname for multiple imports of the same interface

Extend the WIT extern-type grammar to allow use-path as a third case,
enabling import id: use-path and export id: use-path to create
plain-named imports/exports whose instance type matches a named interface.

This allows importing the same interface multiple times under different
plain names (e.g., import primary: store; import secondary: store;),
encoded using the [implements=<interfacename>]label annotation pattern.

Fixes #287

Copy link
Member

@lukewagner lukewagner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks for writing this up! The PR looks great; just minor nits. I think what'll be important for this change is seeing how it "plays out" in the producer and consumer tooling, so I think we'll want to keep this PR open until we get some experience from tool implementations, but so far so good from my perspective.

ricochet added a commit to ricochet/wasm-tools that referenced this pull request Feb 27, 2026
Add parsing, validation, and uniqueness rules for the new
`[implements=<interface>]label` extern name form from the component
model `implements` proposal. An implements name labels an instance
import/export that implements a named interface.

Uniqueness: `[implements=<I>]L` conflicts with bare label `L` and with
`[method]L.L` / `[static]L.L` (the existing l.l edge case), but is
strongly unique from interface names, constructors, and normal
method/static names.

See implements feature:
WebAssembly/component-model#613
Extend the WIT `extern-type` grammar to allow `use-path` as a third case,
enabling `import id: use-path` and `export id: use-path` to create
plain-named imports/exports whose instance type matches a named interface.

This allows importing the same interface multiple times under different
plain names (e.g., `import primary: store; import secondary: store;`),
encoded using the `[implements=<interfacename>]label` annotation pattern.

Fixes #287

Co-authored-by: Luke Wagner <[email protected]>
ricochet added a commit to ricochet/wasmtime that referenced this pull request Feb 28, 2026
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 [bytecodealliance#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/[email protected]>]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/[email protected]>]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 interfaces
ricochet added a commit to ricochet/wasmtime that referenced this pull request Mar 1, 2026
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 [bytecodealliance#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
```

Host Import-side codegen: shared trait + label-parameterized add_to_linker

For imports, wit-bindgen generates one Host trait per interface (not per
label). The add_to_linker function takes a name: &str parameter so the
same trait implementation can be registered under different instance labels.
Duplicate implements imports don't generate separate modules — only the
first import produces bindings.

```rust
struct PrimaryBackend;
impl primary::Host for PrimaryBackend {
    fn get(&mut self, key: String) -> String {
        self.primary_db.get(&key).cloned().unwrap_or_default()
    }
}

struct SecondaryBackend;
impl primary::Host for SecondaryBackend {
    fn get(&mut self, key: String) -> String {
        self.secondary_db.get(&key).cloned().unwrap_or_default()
    }
}

// Same add_to_linker, different labels and host_getter closures
primary::add_to_linker(&mut linker, "primary", |s| &mut s.primary)?;
primary::add_to_linker(&mut linker, "secondary", |s| &mut s.secondary)?;
```

Export-side codegen: per-label modules with shared types

For exports, each label gets its own module with fresh Guest/GuestIndices
types but re-exports shared interface types from the first module via
`pub use super::{first}::*`.

Runtime name resolution

The linker 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:

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/[email protected]>]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/[email protected]>]primary" (instance (type $store)))
)"#)?;
linker.instantiate(&mut store, &component)?; // works, 1.0.1 is semver-compatible with 1.0.0
```

- 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

- Track first-seen implements imports per `InterfaceId`
- One `Host` trait per interface; `generate_add_to_linker` takes
  `named: bool` — when true, emits `name: &str` parameter instead of
  hardcoding the instance name
- Duplicate `implements` imports: just record the label in
  `implements_labels`, no module generation
- `world_add_to_linker`: iterate over `implements_labels` to emit one
  `add_to_linker` call per label, passing label as name argument
- Guard `populate_world_and_interface_options` with `entry()` to avoid
  overwriting link options for duplicate interfaces

- Duplicate exports: re-export types via `pub use super::{first}::*`,
  generate 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
Copy link
Member

@lukewagner lukewagner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@@ -0,0 +1,78 @@
;; RUN: wast --assert default --snapshot tests/snapshots %
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
;; RUN: wast --assert default --snapshot tests/snapshots %

Since test is new and not wasm-tools-specific, could you move it to a new directory test/validation/implements.wast?

))
)

;; Invalid: [implements=<...>] on func (must be instance)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also add a failing test for attempting to export value type and a resource type? And second: could you also add copies of these tests for interfacenames (given that recent addition to the validation rules)?

@lukewagner
Copy link
Member

Oh, I forgot: we probably should have an emoji-gate for this associated with the new rules in the grammar in Explainer.md and the descriptions in WIT.md.

}

world extended {
include base with { cache as my-cache }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking more about this example/rule in the context of duplicate-name-conflicts: could you expand this example to have two includes that contain a conflict for the plain-name cache and explicitly mention that this would be an error without the with (b/c we can't de-duplicate plain names like we can with pure interface names).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support multiple imports of the same interface with different names

2 participants