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.

Comment on lines 416 to 418
* Validation requires that `[implements=<I>]` annotated `plainname`s only
occur on `instance` imports or exports and that `I` is a valid
`interfacename`.
Copy link
Member

Choose a reason for hiding this comment

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

I think the grammatical rules (the EBNF in Explainer.md) force I to be a valid interfacename without validation having to say so here.

Requiring [implements=<I>]L to only annotate instance-typed imports/exports makes sense, but if we do it here, then I think we should also require it for plain interfacename-named imports/exports too. This restriction actually makes sense and shouldn't break anyone b/c it matches what you can do in WIT today (thereby removing an expressivity gap from #614), so I'm in favor.

Comment on lines 2699 to 2702
Here, both imports implement `wasi:keyvalue/store` but have distinct plain
names `primary` and `secondary`. Bindings generators can use the
`[implements=<I>]` annotation to know which interface the instance implements,
enabling them to generate the same typed bindings for both imports.
Copy link
Member

Choose a reason for hiding this comment

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

The resource types (e.g. bucket) defined directly in wasi:keyvalue/store will be treated as different, so "generate the same typed bindings for both imports" isn't quite right. (And this makes sense: if we want to allow two totally different implementations of these two keyvalue stores, each implementation should be able to have its own implementation of bucket.) However I suppose bindings generators could/should share types for value types (like the key-response record), since they're not tied to the implementation; they just need predictable names. So maybe "enabling them to share value type bindings"?

Second, it might also be good to give some examples of how the interfacename helps hosts/clients of a component.

Comment on lines 2840 to 2842
* An `[implements=<I>]L` name and a bare `I` `interfacename` are always
strongly-unique (they are different name kinds: `plainname` vs
`interfacename`).
Copy link
Member

Choose a reason for hiding this comment

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

I might totally be forgetting a case, but I think we don't need this bullet at all; the "Otherwise" clause gives us the behavior we want.

Comment on lines 1423 to 1424
import primary: store;
import secondary: store;
Copy link
Member

Choose a reason for hiding this comment

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

Since you're using wasi:http/incoming-handler for the export, maybe use fully-qualified wasi:keyvalue/store for the imports (to avoid making a distinction that is beside the main point)?

instance type of the `store` interface. The plain name of the import is the
`id` before the colon (e.g., `primary`), not the interface name. This
contrasts with `import store;` (without the `id :` prefix), which would create
a single import using the full interface name `local:demo/store`.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe also speak to the export which has the name my-handler and how this is different than export wasi:http/incoming-handler (and perhaps we could switch to wasi:http/handler in anticipation of p3 :).

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 <mail@lukewagner.name>
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/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 interfaces
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