Skip to content

Commit 0c29ac6

Browse files
committed
Add [implements=<I>]L plainname for multiple imports
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
1 parent c7176a5 commit 0c29ac6

File tree

5 files changed

+237
-6
lines changed

5 files changed

+237
-6
lines changed

design/mvp/Binary.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -408,11 +408,14 @@ 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 and that `I` is a valid
418+
`interfacename`.
416419
* Validation of `[constructor]` names requires a `func` type whose result type
417420
is either `(own $R)` or `(result (own $R) E?)` where `$R` is a resource type
418421
labeled `r`.

design/mvp/Explainer.md

Lines changed: 24 additions & 0 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,23 @@ 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 generate the same typed bindings for both imports.
2703+
26862704
When a function's type is `async`, bindings generators are expected to
26872705
emit whatever asynchronous language construct is appropriate (such as an
26882706
`async` function in JS, Python or Rust). See the [concurrency explainer] for
@@ -2819,13 +2837,19 @@ Values]) are **strongly-unique**:
28192837
* If one name is `l` and the other name is `[*]l.l` (for the same
28202838
`label` `l` and any annotation `*` with a dotted `l.l` name), they *are
28212839
not* strongly-unique.
2840+
* An `[implements=<I>]L` name and a bare `I` `interfacename` are always
2841+
strongly-unique (they are different name kinds: `plainname` vs
2842+
`interfacename`).
28222843
* Otherwise:
28232844
* Lowercase all the `acronym`s (uppercase letters) in both names.
28242845
* Strip any `[...]` annotation prefix from both names.
28252846
* The names are strongly-unique if the resulting strings are unequal.
28262847

28272848
Thus, the following names are strongly-unique:
28282849
* `foo`, `foo-bar`, `[constructor]foo`, `[method]foo.bar`, `[method]foo.baz`
2850+
* `[implements=<a:b/c>]one`, `[implements=<a:b/c>]two` (different labels
2851+
after stripping annotation)
2852+
* `[implements=<a:b/c>]foo` and `a:b/c` (different name kinds)
28292853

28302854
but attempting to add *any* of the following names would be a validation error:
28312855
* `foo`, `foo-BAR`, `[constructor]foo-BAR`, `[method]foo.foo`, `[method]foo.BAR`

design/mvp/WIT.md

Lines changed: 96 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,32 @@ 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 ';'
13851410
```
13861411

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+
package local:demo;
1417+
1418+
interface store {
1419+
get: func(key: string) -> option<string>;
1420+
}
1421+
1422+
world my-world {
1423+
import primary: store;
1424+
import secondary: store;
1425+
export my-handler: wasi:http/incoming-handler;
1426+
}
1427+
```
1428+
1429+
Here, `primary` and `secondary` are two distinct imports that both have the
1430+
instance type of the `store` interface. The plain name of the import is the
1431+
`id` before the colon (e.g., `primary`), not the interface name. This
1432+
contrasts with `import store;` (without the `id :` prefix), which would create
1433+
a single import using the full interface name `local:demo/store`.
1434+
13871435
Note that worlds can import types and define their own types to be exported
13881436
from the root of a component and used within functions imported and exported.
13891437
The `interface` item here additionally defines the grammar for IDs used to refer
@@ -2061,6 +2109,53 @@ This duplication is useful in the case of cross-package references or split
20612109
packages, allowing a compiled `world` definition to be fully self-contained and
20622110
able to be used to compile a component without additional type information.
20632111

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

20662161
```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)