Skip to content

Commit 8b39031

Browse files
authored
Add (implements "I") to plainname imports to allow multiple imports (#613)
1 parent bd01493 commit 8b39031

4 files changed

Lines changed: 364 additions & 17 deletions

File tree

design/mvp/Binary.md

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -391,13 +391,14 @@ in the explainer.)
391391
```ebnf
392392
import ::= in:<importname'> ed:<externdesc> => (import in ed)
393393
export ::= en:<exportname'> si:<sortidx> ed?:<externdesc>? => (export en si ed?)
394-
importname' ::= 0x00 len:<u32> in:<importname> => in (if len = |in|)
395-
| 0x01 len:<u32> in:<importname> => in (if len = |in|)
396-
| 0x02 len:<u32> in:<importname> vs:<versionsuffix> => in vs (if len = |in|) 🔗
397-
exportname' ::= 0x00 len:<u32> in:<exportname> => in (if len = |in|)
398-
| 0x01 len:<u32> in:<exportname> => in (if len = |in|)
399-
| 0x02 len:<u32> in:<exportname> vs:<versionsuffix> => in vs (if len = |in|) 🔗
400-
versionsuffix ::= len:<u32> vs:<semversuffix> => (versionsuffix vs) (if len = |vs|) 🔗
394+
importname' ::= 0x00 len:<u32> in:<importname> => in (if len = |in|)
395+
| 0x01 len:<u32> in:<importname> => in (if len = |in|)
396+
| 0x02 len:<u32> in:<importname> opts:vec(<nameopt>) => in opts (if len = |in|) 🏷️/🔗
397+
exportname' ::= 0x00 len:<u32> in:<exportname> => in (if len = |in|)
398+
| 0x01 len:<u32> in:<exportname> => in (if len = |in|)
399+
| 0x02 len:<u32> in:<importname> opts:vec(<nameopt>) => in opts (if len = |in|) 🏷️/🔗
400+
nameopt ::= 0x00 len:<u32> n:<interfacename> => (implements i) 🏷️
401+
| 0x01 len:<u32> vs:<semversuffix> => (versionsuffix vs) 🔗
401402
```
402403

403404
Notes:
@@ -415,11 +416,15 @@ Notes:
415416
release](##binary-format-warts-to-fix-in-a-10-release).
416417
* The `<importname>`s of a component must all be [strongly-unique]. Separately,
417418
the `<exportname>`s of a component must also all be [strongly-unique].
418-
* Validation requires that annotated `plainname`s only occur on `func` imports
419-
or exports and that the first label of a `[constructor]`, `[method]` or
420-
`[static]` matches the `plainname` of a preceding `resource` import or
421-
export, respectively, in the same scope (component, component type or
422-
instance type).
419+
* Validation requires that `[constructor]`, `[method]` and `[static]` annotated
420+
`plainname`s only occur on `func` imports or exports and that the first label
421+
of a `[constructor]`, `[method]` or `[static]` matches the `plainname` of a
422+
preceding `resource` import or export, respectively, in the same scope
423+
(component, component type or instance type).
424+
* 🏷️ Validation requires that `implements`-annotated imports or exports are
425+
`instance`-typed.
426+
* 🏷️ Validation requires that `implements`-annotated imports or exports have a
427+
`<plainname>` name.
423428
* Validation of `[constructor]` names requires a `func` type whose result type
424429
is either `(own $R)` or `(result (own $R) E?)` where `$R` is a resource type
425430
labeled `r`.

design/mvp/Explainer.md

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ implemented, considered stable and included in a future milestone:
5959
* 🔗: canonical interface names
6060
* 🐘: [memory64]
6161
* 🗺️: the `map` type
62+
* 🏷️: `implements` annotations for plain-named interface imports/exports
6263

6364
(Based on the previous [scoping and layering] proposal to the WebAssembly CG,
6465
this repo merges and supersedes the [module-linking] and [interface-types]
@@ -2697,6 +2698,31 @@ annotations trigger additional type-validation rules (listed in
26972698
* Similarly, an import or export named `[method]R.foo` must be a function whose
26982699
first parameter must be `(param "self" (borrow $R))`.
26992700

2701+
🏷️ When an instance import or export is named `L` and annotated with
2702+
`(implements "I")`, it indicates that the instance implements interface `I` but
2703+
is given the plain name `L`. This enables a component to import or export the
2704+
same interface multiple times with different plain names. For example:
2705+
2706+
```wat
2707+
(component
2708+
(import "primary" (implements "wasi:keyvalue/store") (instance ...))
2709+
(import "secondary" (implements "wasi:keyvalue/store") (instance ...))
2710+
)
2711+
```
2712+
2713+
Here, both imports implement `wasi:keyvalue/store` but have distinct plain
2714+
names `primary` and `secondary`. Bindings generators can use the
2715+
`implements` annotation to know which interface the instance implements,
2716+
enabling them to share value type bindings across both imports. (Note that
2717+
resource types defined in the interface, such as `bucket`, are treated as
2718+
distinct for each import, since each may have a different implementation.)
2719+
2720+
The `interfacename` also helps hosts and clients of a component. A host that
2721+
sees `(implements "wasi:keyvalue/store>")` knows to supply a
2722+
`wasi:keyvalue/store` implementation for that import, even though the import
2723+
name is something else. Similarly, a client composing components can use the
2724+
annotation to match compatible imports and exports across components.
2725+
27002726
When a function's type is `async`, bindings generators are expected to
27012727
emit whatever asynchronous language construct is appropriate (such as an
27022728
`async` function in JS, Python or Rust). See the [concurrency explainer] for
@@ -2838,11 +2864,11 @@ Values]) are **strongly-unique**:
28382864
* Strip any `[...]` annotation prefix from both names.
28392865
* The names are strongly-unique if the resulting strings are unequal.
28402866

2841-
Thus, the following names are strongly-unique:
2842-
* `foo`, `foo-bar`, `[constructor]foo`, `[method]foo.bar`, `[method]foo.baz`
2867+
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):
2868+
* `foo`, `foo-bar`, `[constructor]foo`, `[method]foo.bar`, `[method]foo.baz`, `foo:bar/baz`
28432869

28442870
but attempting to add *any* of the following names would be a validation error:
2845-
* `foo`, `foo-BAR`, `[constructor]foo-BAR`, `[method]foo.foo`, `[method]foo.BAR`
2871+
* `foo`, `foo-BAR`, `[constructor]foo-BAR`, `[method]foo.foo`, `[method]foo.BAR`, `foo:bar/baz`, `bar`
28462872

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

design/mvp/WIT.md

Lines changed: 210 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ document, a pseudo-formal [grammar specification][lexical-structure], and
2525
additionally a specification of the [package format][package-format] of a WIT
2626
package suitable for distribution.
2727

28-
See [Gated Features] for an explanation of 🔧.
28+
See [Gated Features] for an explanation of 🔧 and 🏷️.
2929

3030
[IDL]: https://en.wikipedia.org/wiki/Interface_description_language
3131
[components]: https://github.com/webassembly/component-model
@@ -366,6 +366,57 @@ 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+
import cache: func();
387+
include base with { cache as my-cache }
388+
}
389+
```
390+
391+
In this case, `extended` requires a `with` during its `include` because
392+
the plain-name `cache` is already taken in the world. The import here is renamed
393+
to `my-cache` that implements `local:demo/store`, equivalent to writing
394+
`import my-cache: store;` directly.
395+
396+
Unlike interface names (which are automatically de-duplicated when two
397+
`include`s import the same interface), plain names cannot be de-duplicated
398+
and will conflict. For example:
399+
400+
```wit
401+
world base-a {
402+
import cache: store;
403+
}
404+
405+
world base-b {
406+
import cache: store;
407+
}
408+
409+
world conflict {
410+
include base-a;
411+
include base-b; // error: plain name 'cache' conflicts
412+
}
413+
414+
world resolved {
415+
include base-a;
416+
include base-b with { cache as other-cache } // ok: renamed to avoid conflict
417+
}
418+
```
419+
369420
`with` cannot be used to rename interface names, however, so the following
370421
world would be invalid:
371422
```wit
@@ -1382,7 +1433,41 @@ export-item ::= 'export' id ':' extern-type
13821433
import-item ::= 'import' id ':' extern-type
13831434
| 'import' use-path ';'
13841435
1385-
extern-type ::= func-type ';' | 'interface' '{' interface-items* '}'
1436+
extern-type ::= func-type ';'
1437+
| 'interface' '{' interface-items* '}'
1438+
| use-path ';' 🏷️
1439+
```
1440+
1441+
🏷️ The third case of `extern-type` allows a named interface to be imported or
1442+
exported with a custom [plain name]. For example:
1443+
1444+
```wit
1445+
world my-world {
1446+
import primary: wasi:keyvalue/store;
1447+
import secondary: wasi:keyvalue/store;
1448+
export my-handler: wasi:http/handler;
1449+
}
1450+
```
1451+
1452+
Here, `primary` and `secondary` are two distinct imports that both have the
1453+
instance type of the `wasi:keyvalue/store` interface. The plain name of the
1454+
import is the `id` before the colon (e.g., `primary`), not the interface name.
1455+
This contrasts with `import wasi:keyvalue/store;` (without the `id :` prefix),
1456+
which would create a single import using the full interface name
1457+
`wasi:keyvalue/store`. Similarly, the export `my-handler` has the instance type
1458+
of `wasi:http/handler` but uses the plain name `my-handler` instead of the full
1459+
interface name, which is useful when a component wants to export the same
1460+
interface multiple times or simply use a more descriptive name.
1461+
1462+
Note that the `use-path` form can have an ambiguity with the nested packages
1463+
feature (🪺) where `a:b` could mean two things. To resolve this `a:b` is lexed
1464+
as a single token instead of separate tokens, meaning:
1465+
1466+
```wit
1467+
world w {
1468+
import a:b; // error: can't import a package
1469+
import a: b; // ok, assuming `b` names an interface in scope
1470+
}
13861471
```
13871472

13881473
Note that worlds can import types and define their own types to be exported
@@ -2073,6 +2158,129 @@ This duplication is useful in the case of cross-package references or split
20732158
packages, allowing a compiled `world` definition to be fully self-contained and
20742159
able to be used to compile a component without additional type information.
20752160

2161+
🏷️ When a world imports or exports a named interface with a custom plain name
2162+
(using the `id: use-path` syntax), the encoding uses the `(implements "I")`
2163+
annotation defined in [Explainer.md](Explainer.md#import-and-export-definitions) to indicate which
2164+
interface the instance implements. Note though that each copy implements a
2165+
unique version of the interface in question. For example, the following WIT:
2166+
2167+
```wit
2168+
package local:demo;
2169+
2170+
interface store {
2171+
resource bucket {
2172+
constructor(name: string);
2173+
get: func(key: string) -> option<string>;
2174+
}
2175+
}
2176+
2177+
world w {
2178+
import one: store;
2179+
import two: store;
2180+
}
2181+
```
2182+
2183+
is encoded as:
2184+
2185+
```wat
2186+
(component
2187+
(type (export "store") (component
2188+
(export "local:demo/store" (instance
2189+
(export "bucket" (type $b (sub resource)))
2190+
(export "[constructor]bucket" (func (param "name" string) (result (own $b))))
2191+
(export "[method]bucket.get" (func (param "self" (borrow $b)) (param "key" string) (result (option string))))
2192+
))
2193+
))
2194+
(type (export "w") (component
2195+
(export "local:demo/w" (component
2196+
(import "one" (implements "local:demo/store") (instance
2197+
(export "bucket" (type $b (sub resource)))
2198+
(export "[constructor]bucket" (func (param "name" string) (result (own $b))))
2199+
(export "[method]bucket.get" (func (param "self" (borrow $b)) (param "key" string) (result (option string))))
2200+
))
2201+
(import "two" (implements "local:demo/store") (instance
2202+
(export "bucket" (type $b (sub resource)))
2203+
(export "[constructor]bucket" (func (param "name" string) (result (own $b))))
2204+
(export "[method]bucket.get" (func (param "self" (borrow $b)) (param "key" string) (result (option string))))
2205+
))
2206+
))
2207+
))
2208+
)
2209+
```
2210+
2211+
The `(implements "local:demo/store")` prefix tells bindings generators and
2212+
toolchains which interface each plain-named instance import implements, while
2213+
the labels `one` and `two` provide distinct plain names. This is a case of
2214+
the general `(implements ..)` pattern described in
2215+
[Explainer.md](Explainer.md#import-and-export-definitions). Also note here that
2216+
two copies of the `"bucket"` resource are imported for the `local:demo/w` world.
2217+
This is because the interfaces `one` and `two` duplicate the `store` interface.
2218+
Note that this can import just a single `bucket` resource by extracting out the
2219+
resource definition into a separate interface. For example:
2220+
2221+
```wit
2222+
package local:demo;
2223+
2224+
interface types {
2225+
resource bucket {
2226+
get: func(key: string) -> option<string>;
2227+
}
2228+
}
2229+
2230+
interface store {
2231+
use types.{bucket};
2232+
open: func(name: string) -> bucket;
2233+
}
2234+
2235+
world w {
2236+
import one: store;
2237+
import two: store;
2238+
}
2239+
```
2240+
2241+
is encoded as:
2242+
2243+
```wat
2244+
(component
2245+
(type (export "types") (component
2246+
(export "local:demo/types" (instance
2247+
(export "bucket" (type $b (sub resource)))
2248+
(export "[method]bucket.get" (func (param "self" (borrow $b)) (param "key" string) (result (option string))))
2249+
))
2250+
))
2251+
(type (export "store") (component
2252+
(import "local:demo/types" (instance $types
2253+
(export "bucket" (type $b (sub resource)))
2254+
))
2255+
(alias export $types "bucket" (type $b))
2256+
(export "local:demo/store" (instance
2257+
(export "bucket" (type $b' (eq $b)))
2258+
(export "open" (func (param "name" string) (result (own $b'))))
2259+
))
2260+
))
2261+
(type (export "w") (component
2262+
(export "local:demo/w" (component
2263+
(import "local:demo/types" (instance $types
2264+
(export "bucket" (type $b (sub resource)))
2265+
))
2266+
(alias export $types "bucket" (type $b))
2267+
(import "one" (implements "local:demo/store") (instance
2268+
(export "bucket" (type $b' (eq $b)))
2269+
(export "open" (func (param "name" string) (result (own $b'))))
2270+
))
2271+
(import "two" (implements "local:demo/store") (instance
2272+
(export "bucket" (type $b' (eq $b)))
2273+
(export "open" (func (param "name" string) (result (own $b'))))
2274+
))
2275+
))
2276+
))
2277+
)
2278+
```
2279+
2280+
Where in this example the `local:demo/w` world imports only a single `bucket`
2281+
resource under the `local:demo/types` interface. This resource is then used
2282+
by the `one` and `two` export.
2283+
20762284
Putting this all together, the following WIT definitions:
20772285

20782286
```wit

0 commit comments

Comments
 (0)