Skip to content

Commit a0879ad

Browse files
committed
The implements clause lets a world import or export the same interface
multiple times under different plain names: world runner { import primary: store; import backup: store; } No bindings-generator changes needed. wit-parser surfaces each label as a plain-name interface import/export, `generate_nominal_type_ids` gives each its own type identity, and the existing plain-name interface code path emits label-named modules and `wasm_import_module`s. The test covers Rust, Go, and WAT runners with Rust and Go tests, composed via `compose.wac` so the two labels resolve to two independent `store` instances. Temporary, until releases catch up: - `wac` is pointed at the `implements` branch; composition support for the clause is not yet in a released wac. - CI's wasmtime is pinned to the `dev` build; the clause has landed on wasmtime `main` but is not yet released. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
1 parent cfd7b77 commit a0879ad

10 files changed

Lines changed: 205 additions & 77 deletions

File tree

.github/actions/install-wasi-sdk/action.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,8 @@ runs:
3838
- name: Setup `wasmtime`
3939
uses: bytecodealliance/actions/wasmtime/setup@v1
4040
with:
41-
version: "44.0.0"
41+
# Pinned to the rolling `dev` build (main, which will become wasmtime
42+
# 47) because the component model `implements` clause used by the
43+
# `implements` runtime test has landed on `main` but is not in a
44+
# released wasmtime yet.
45+
version: "dev"

Cargo.lock

Lines changed: 38 additions & 76 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,11 @@ csharp = ['dep:wit-bindgen-csharp']
120120
csharp-mono = ['csharp']
121121
moonbit = ['dep:wit-bindgen-moonbit']
122122
async = []
123+
124+
# The component model `implements` clause requires composition support that is
125+
# not yet in a released `wac`. Until that ships on crates.io, point the `wac`
126+
# crates at the `implements` branch.
127+
[patch.crates-io]
128+
wac-parser = { git = "https://github.com/ricochet/wac", branch = "implements" }
129+
wac-types = { git = "https://github.com/ricochet/wac", branch = "implements" }
130+
wac-graph = { git = "https://github.com/ricochet/wac", branch = "implements" }
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package example:composition;
2+
3+
// Instantiate the `test` component twice to back the two labeled imports of the
4+
// runner. The component model `implements` clause lets the runner import the
5+
// same `store` interface under two different plain names (`primary` and
6+
// `backup`), each wired here to its own independent instance.
7+
let primary = new test:test { ... };
8+
let backup = new test:test { ... };
9+
10+
let runner = new test:runner {
11+
primary: primary.store,
12+
backup: backup.store,
13+
...
14+
};
15+
16+
export runner...;

tests/runtime/implements/runner.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package export_wit_world
2+
3+
import (
4+
"wit_component/backup"
5+
"wit_component/primary"
6+
7+
. "go.bytecodealliance.org/pkg/wit/types"
8+
)
9+
10+
func Run() {
11+
// Each labeled import is its own instance of `store`, so values set
12+
// through `primary` are independent from those set through `backup`.
13+
primary.Set("key", "from-primary")
14+
backup.Set("key", "from-backup")
15+
16+
assertSome(primary.Get("key"), "from-primary")
17+
assertSome(backup.Get("key"), "from-backup")
18+
19+
assertNone(primary.Get("missing"))
20+
assertNone(backup.Get("missing"))
21+
}
22+
23+
func assertSome(opt Option[string], expected string) {
24+
if opt.Tag() != OptionSome || opt.Some() != expected {
25+
panic("unexpected value")
26+
}
27+
}
28+
29+
func assertNone(opt Option[string]) {
30+
if opt.Tag() != OptionNone {
31+
panic("expected none")
32+
}
33+
}

tests/runtime/implements/runner.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
include!(env!("BINDINGS"));
2+
3+
struct Component;
4+
5+
export!(Component);
6+
7+
impl Guest for Component {
8+
fn run() {
9+
// Each labeled import is its own instance of `store`, so values set
10+
// through `primary` are independent from those set through `backup`.
11+
primary::set("key", "from-primary");
12+
backup::set("key", "from-backup");
13+
14+
assert_eq!(primary::get("key").as_deref(), Some("from-primary"));
15+
assert_eq!(backup::get("key").as_deref(), Some("from-backup"));
16+
17+
assert_eq!(primary::get("missing"), None);
18+
assert_eq!(backup::get("missing"), None);
19+
}
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
;; A `runner` written directly in WebAssembly text. It imports the same `store`
2+
;; interface twice under two different plain names (`primary` and `backup`) via
3+
;; the component model `implements` clause, so the two imports appear as distinct
4+
;; core wasm import modules.
5+
(module
6+
(import "primary" "set" (func $primary-set (param i32 i32 i32 i32)))
7+
(import "backup" "set" (func $backup-set (param i32 i32 i32 i32)))
8+
9+
(memory (export "memory") 1)
10+
11+
;; "key" @ 0 (len 3), "primary" @ 3 (len 7), "backup" @ 10 (len 6)
12+
(data (i32.const 0) "keyprimarybackup")
13+
14+
(func (export "run")
15+
;; primary::set("key", "primary")
16+
(call $primary-set (i32.const 0) (i32.const 3) (i32.const 3) (i32.const 7))
17+
;; backup::set("key", "backup")
18+
(call $backup-set (i32.const 0) (i32.const 3) (i32.const 10) (i32.const 6))
19+
)
20+
)

tests/runtime/implements/test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package export_test_implements_store
2+
3+
import (
4+
. "go.bytecodealliance.org/pkg/wit/types"
5+
)
6+
7+
var store = map[string]string{}
8+
9+
func Get(key string) Option[string] {
10+
if value, ok := store[key]; ok {
11+
return Some[string](value)
12+
}
13+
return None[string]()
14+
}
15+
16+
func Set(key string, value string) {
17+
store[key] = value
18+
}

0 commit comments

Comments
 (0)