Skip to content

Commit b6beda9

Browse files
ricochetlukewagner
andcommitted
Add [implements=<I>]L for multiple imports
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>
1 parent c7176a5 commit b6beda9

File tree

5 files changed

+240
-9
lines changed

5 files changed

+240
-9
lines changed

design/mvp/Binary.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -408,11 +408,15 @@ Notes:
408408
[text format](Explainer.md#import-and-export-definitions).
409409
* The `<importname>`s of a component must all be [strongly-unique]. Separately,
410410
the `<exportname>`s of a component must also all be [strongly-unique].
411-
* Validation requires that annotated `plainname`s only occur on `func` imports
412-
or exports and that the first label of a `[constructor]`, `[method]` or
413-
`[static]` matches the `plainname` of a preceding `resource` import or
414-
export, respectively, in the same scope (component, component type or
415-
instance type).
411+
* Validation requires that `[constructor]`, `[method]` and `[static]` annotated
412+
`plainname`s only occur on `func` imports or exports and that the first label
413+
of a `[constructor]`, `[method]` or `[static]` matches the `plainname` of a
414+
preceding `resource` import or export, respectively, in the same scope
415+
(component, component type or instance type).
416+
* Validation requires that `[implements=<I>]` annotated `plainname`s only
417+
occur on `instance` imports or exports.
418+
* Validation requires that `interfacename`-named imports or exports are
419+
`instance`-typed.
416420
* Validation of `[constructor]` names requires a `func` type whose result type
417421
is either `(own $R)` or `(result (own $R) E?)` where `$R` is a resource type
418422
labeled `r`.

design/mvp/Explainer.md

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2538,6 +2538,7 @@ plainname ::= <label>
25382538
| '[constructor]' <label>
25392539
| '[method]' <label> '.' <label>
25402540
| '[static]' <label> '.' <label>
2541+
| '[implements=<' <interfacename> '>]' <label>
25412542
label ::= <first-fragment> ( '-' <fragment> )*
25422543
first-fragment ::= [a-z] <word>
25432544
| [A-Z] <acronym>
@@ -2683,6 +2684,31 @@ annotations trigger additional type-validation rules (listed in
26832684
* Similarly, an import or export named `[method]R.foo` must be a function whose
26842685
first parameter must be `(param "self" (borrow $R))`.
26852686

2687+
When an instance import or export is annotated with `[implements=<I>]L`, it
2688+
indicates that the instance implements interface `I` but is given the plain
2689+
name `L`. This enables a component to import or export the same interface
2690+
multiple times with different plain names. For example:
2691+
2692+
```wat
2693+
(component
2694+
(import "[implements=<wasi:keyvalue/store>]primary" (instance ...))
2695+
(import "[implements=<wasi:keyvalue/store>]secondary" (instance ...))
2696+
)
2697+
```
2698+
2699+
Here, both imports implement `wasi:keyvalue/store` but have distinct plain
2700+
names `primary` and `secondary`. Bindings generators can use the
2701+
`[implements=<I>]` annotation to know which interface the instance implements,
2702+
enabling them to share value type bindings across both imports. (Note that
2703+
resource types defined in the interface, such as `bucket`, are treated as
2704+
distinct for each import, since each may have a different implementation.)
2705+
2706+
The `interfacename` also helps hosts and clients of a component. A host that
2707+
sees `[implements=<wasi:keyvalue/store>]primary` knows to supply a
2708+
`wasi:keyvalue/store` implementation for that import, even though the import
2709+
name is just `primary`. Similarly, a client composing components can use the
2710+
annotation to match compatible imports and exports across components.
2711+
26862712
When a function's type is `async`, bindings generators are expected to
26872713
emit whatever asynchronous language construct is appropriate (such as an
26882714
`async` function in JS, Python or Rust). See the [concurrency explainer] for
@@ -2824,11 +2850,11 @@ Values]) are **strongly-unique**:
28242850
* Strip any `[...]` annotation prefix from both names.
28252851
* The names are strongly-unique if the resulting strings are unequal.
28262852

2827-
Thus, the following names are strongly-unique:
2828-
* `foo`, `foo-bar`, `[constructor]foo`, `[method]foo.bar`, `[method]foo.baz`
2853+
Thus, the following set of names are strongly-unique and can thus all be imports (or exports) of the same component (or component type or instance type):
2854+
* `foo`, `foo-bar`, `[constructor]foo`, `[method]foo.bar`, `[method]foo.baz`, `foo:bar/baz`, `[implements=<foo:bar/baz>]bar`, `[implements=<foo:bar/baz>]quux`
28292855

28302856
but attempting to add *any* of the following names would be a validation error:
2831-
* `foo`, `foo-BAR`, `[constructor]foo-BAR`, `[method]foo.foo`, `[method]foo.BAR`
2857+
* `foo`, `foo-BAR`, `[constructor]foo-BAR`, `[method]foo.foo`, `[method]foo.BAR`, `[implements=<a:b/c>]foo`, `foo:bar/baz`, `bar`, `[implements=<x:y/z>]bar`
28322858

28332859
Note that additional validation rules involving types apply to names with
28342860
annotations. For example, the validation rules for `[constructor]foo` require

design/mvp/WIT.md

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,31 @@ world union-my-world-b {
366366
}
367367
```
368368

369+
When a world being included contains plain-named imports or exports that
370+
reference a named interface (using the `id: use-path` syntax), the `with`
371+
keyword renames the plain-name label while preserving the underlying
372+
`[implements=<I>]` annotation in the encoding. For example:
373+
374+
```wit
375+
package local:demo;
376+
377+
interface store {
378+
get: func(key: string) -> option<string>;
379+
}
380+
381+
world base {
382+
import cache: store;
383+
}
384+
385+
world extended {
386+
include base with { cache as my-cache }
387+
}
388+
```
389+
390+
In this case, `extended` has a single import with the plain name `my-cache`
391+
that implements `local:demo/store`, equivalent to writing
392+
`import my-cache: store;` directly.
393+
369394
`with` cannot be used to rename interface names, however, so the following
370395
world would be invalid:
371396
```wit
@@ -1381,9 +1406,30 @@ export-item ::= 'export' id ':' extern-type
13811406
import-item ::= 'import' id ':' extern-type
13821407
| 'import' use-path ';'
13831408
1384-
extern-type ::= func-type ';' | 'interface' '{' interface-items* '}'
1409+
extern-type ::= func-type ';' | 'interface' '{' interface-items* '}' | use-path ';'
1410+
```
1411+
1412+
The third case of `extern-type` allows a named interface to be imported or
1413+
exported with a custom [plain name]. For example:
1414+
1415+
```wit
1416+
world my-world {
1417+
import primary: wasi:keyvalue/store;
1418+
import secondary: wasi:keyvalue/store;
1419+
export my-handler: wasi:http/handler;
1420+
}
13851421
```
13861422

1423+
Here, `primary` and `secondary` are two distinct imports that both have the
1424+
instance type of the `wasi:keyvalue/store` interface. The plain name of the
1425+
import is the `id` before the colon (e.g., `primary`), not the interface name.
1426+
This contrasts with `import wasi:keyvalue/store;` (without the `id :` prefix),
1427+
which would create a single import using the full interface name
1428+
`wasi:keyvalue/store`. Similarly, the export `my-handler` has the instance type
1429+
of `wasi:http/handler` but uses the plain name `my-handler` instead of the full
1430+
interface name, which is useful when a component wants to export the same
1431+
interface multiple times or simply use a more descriptive name.
1432+
13871433
Note that worlds can import types and define their own types to be exported
13881434
from the root of a component and used within functions imported and exported.
13891435
The `interface` item here additionally defines the grammar for IDs used to refer
@@ -2061,6 +2107,52 @@ This duplication is useful in the case of cross-package references or split
20612107
packages, allowing a compiled `world` definition to be fully self-contained and
20622108
able to be used to compile a component without additional type information.
20632109

2110+
When a world imports or exports a named interface with a custom plain name
2111+
(using the `id: use-path` syntax), the encoding uses the `[implements=<I>]`
2112+
annotation defined in [Explainer.md](Explainer.md#import-and-export-definitions) to indicate which
2113+
interface the instance implements. For example, the following WIT:
2114+
2115+
```wit
2116+
package local:demo;
2117+
2118+
interface store {
2119+
get: func(key: string) -> option<string>;
2120+
}
2121+
2122+
world w {
2123+
import one: store;
2124+
import two: store;
2125+
}
2126+
```
2127+
2128+
is encoded as:
2129+
2130+
```wat
2131+
(component
2132+
(type (export "w") (component
2133+
(export "local:demo/w" (component
2134+
(import "[implements=<local:demo/store>]one" (instance
2135+
(export "get" (func (param "key" string) (result (option string))))
2136+
))
2137+
(import "[implements=<local:demo/store>]two" (instance
2138+
(export "get" (func (param "key" string) (result (option string))))
2139+
))
2140+
))
2141+
))
2142+
(type (export "store") (component
2143+
(export "local:demo/store" (instance
2144+
(export "get" (func (param "key" string) (result (option string))))
2145+
))
2146+
))
2147+
)
2148+
```
2149+
2150+
The `[implements=<local:demo/store>]` prefix tells bindings generators and
2151+
toolchains which interface each plain-named instance import implements, while
2152+
the labels `one` and `two` provide distinct plain names. This is a case of
2153+
the general `[implements=<interfacename>]label` pattern described in
2154+
[Explainer.md](Explainer.md#import-and-export-definitions).
2155+
20642156
Putting this all together, the following WIT definitions:
20652157

20662158
```wit

test/wasm-tools/implements.wast

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
;; RUN: wast --assert default --snapshot tests/snapshots %
2+
3+
;; Valid: basic [implements=<...>] on instance import
4+
(component
5+
(import "[implements=<a:b/c>]name" (instance))
6+
)
7+
8+
;; Valid: [implements=<...>] on instance export
9+
(component
10+
(instance $i)
11+
(export "[implements=<a:b/c>]name" (instance $i))
12+
)
13+
14+
;; Valid: two imports with the same interface, different labels
15+
(component
16+
(import "[implements=<a:b/c>]one" (instance))
17+
(import "[implements=<a:b/c>]two" (instance))
18+
)
19+
20+
;; Valid: [implements=<...>] with version in interface name
21+
(component
22+
(import "[implements=<a:b/c@1.2.3>]name" (instance))
23+
)
24+
25+
;; Valid: [implements=<...>] alongside a bare interface import of the same interface
26+
(component
27+
(import "a:b/c" (instance))
28+
(import "[implements=<a:b/c>]alt" (instance))
29+
)
30+
31+
;; Valid: in a component type
32+
(component
33+
(type (component
34+
(import "[implements=<a:b/c>]one" (instance
35+
(export "get" (func (param "key" string) (result (option string))))
36+
))
37+
(import "[implements=<a:b/c>]two" (instance
38+
(export "get" (func (param "key" string) (result (option string))))
39+
))
40+
))
41+
)
42+
43+
;; Invalid: [implements=<...>] on func (must be instance)
44+
(assert_invalid
45+
(component
46+
(import "[implements=<a:b/c>]name" (func))
47+
)
48+
"`[implements=<a:b/c>]` must be on an instance import or export")
49+
50+
;; Invalid: [implements=<...>] on component (must be instance)
51+
(assert_invalid
52+
(component
53+
(import "[implements=<a:b/c>]name" (component))
54+
)
55+
"`[implements=<a:b/c>]` must be on an instance import or export")
56+
57+
;; Invalid: duplicate labels after stripping annotation
58+
(assert_invalid
59+
(component
60+
(import "[implements=<a:b/c>]name" (instance))
61+
(import "[implements=<x:y/z>]name" (instance))
62+
)
63+
"conflicts with previous name")
64+
65+
;; Invalid: duplicate label between annotated and bare plain name
66+
(assert_invalid
67+
(component
68+
(import "name" (func))
69+
(import "[implements=<a:b/c>]name" (instance))
70+
)
71+
"conflicts with previous name")
72+
73+
;; Invalid: malformed interface name inside annotation
74+
(assert_invalid
75+
(component
76+
(import "[implements=<NotValid>]name" (instance))
77+
)
78+
"not a valid extern name")

test/wasm-tools/naming.wast

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,34 @@
125125
(import "[static]a.a" (func))
126126
)
127127
"import name `[static]a.a` conflicts with previous name `a`")
128+
129+
;; [implements=<...>] strong-uniqueness tests
130+
131+
;; Valid: two [implements=<...>] with different labels are strongly-unique
132+
(component definition
133+
(import "[implements=<a:b/c>]one" (instance))
134+
(import "[implements=<a:b/c>]two" (instance))
135+
)
136+
137+
;; Valid: [implements=<...>] and a bare interface name are strongly-unique
138+
;; (different name kinds: plainname vs interfacename)
139+
(component definition
140+
(import "a:b/c" (instance))
141+
(import "[implements=<a:b/c>]alt" (instance))
142+
)
143+
144+
;; Invalid: two [implements=<...>] with the same label conflict
145+
(assert_invalid
146+
(component
147+
(import "[implements=<a:b/c>]name" (instance))
148+
(import "[implements=<x:y/z>]name" (instance))
149+
)
150+
"conflicts with previous name")
151+
152+
;; Invalid: [implements=<...>] label conflicts with a bare plain name
153+
(assert_invalid
154+
(component
155+
(import "name" (func))
156+
(import "[implements=<a:b/c>]name" (instance))
157+
)
158+
"conflicts with previous name")

0 commit comments

Comments
 (0)