Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions design/mvp/Binary.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,13 +391,14 @@ in the explainer.)
```ebnf
import ::= in:<importname'> ed:<externdesc> => (import in ed)
export ::= en:<exportname'> si:<sortidx> ed?:<externdesc>? => (export en si ed?)
importname' ::= 0x00 len:<u32> in:<importname> => in (if len = |in|)
| 0x01 len:<u32> in:<importname> => in (if len = |in|)
| 0x02 len:<u32> in:<importname> vs:<versionsuffix> => in vs (if len = |in|) 🔗
exportname' ::= 0x00 len:<u32> in:<exportname> => in (if len = |in|)
| 0x01 len:<u32> in:<exportname> => in (if len = |in|)
| 0x02 len:<u32> in:<exportname> vs:<versionsuffix> => in vs (if len = |in|) 🔗
versionsuffix ::= len:<u32> vs:<semversuffix> => (versionsuffix vs) (if len = |vs|) 🔗
importname' ::= 0x00 len:<u32> in:<importname> => in (if len = |in|)
| 0x01 len:<u32> in:<importname> => in (if len = |in|)
| 0x02 len:<u32> in:<importname> opts:vec(<nameopt>) => in opts (if len = |in|) 🏷️/🔗
exportname' ::= 0x00 len:<u32> in:<exportname> => in (if len = |in|)
| 0x01 len:<u32> in:<exportname> => in (if len = |in|)
| 0x02 len:<u32> in:<importname> opts:vec(<nameopt>) => in opts (if len = |in|) 🏷️/🔗
nameopt ::= 0x00 len:<u32> n:<interfacename> => (implements i) 🏷️
| 0x01 len:<u32> vs:<semversuffix> => (versionsuffix vs) 🔗
```

Notes:
Expand All @@ -415,11 +416,15 @@ Notes:
release](##binary-format-warts-to-fix-in-a-10-release).
* The `<importname>`s of a component must all be [strongly-unique]. Separately,
the `<exportname>`s of a component must also all be [strongly-unique].
* Validation requires that annotated `plainname`s only occur on `func` imports
or exports and that the first label of a `[constructor]`, `[method]` or
`[static]` matches the `plainname` of a preceding `resource` import or
export, respectively, in the same scope (component, component type or
instance type).
* Validation requires that `[constructor]`, `[method]` and `[static]` annotated
`plainname`s only occur on `func` imports or exports and that the first label
of a `[constructor]`, `[method]` or `[static]` matches the `plainname` of a
preceding `resource` import or export, respectively, in the same scope
(component, component type or instance type).
* 🏷️ Validation requires that `implements`-annotated imports or exports are
`instance`-typed.
* 🏷️ Validation requires that `implements`-annotated imports or exports have a
`<plainname>` name.
* Validation of `[constructor]` names requires a `func` type whose result type
is either `(own $R)` or `(result (own $R) E?)` where `$R` is a resource type
labeled `r`.
Expand Down
32 changes: 29 additions & 3 deletions design/mvp/Explainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ implemented, considered stable and included in a future milestone:
* 🔗: canonical interface names
* 🐘: [memory64]
* 🗺️: the `map` type
* 🏷️: `implements` annotations for plain-named interface imports/exports

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

🏷️ When an instance import or export is named `L` and annotated with
`(implements "I")`, it indicates that the instance implements interface `I` but
is given the plain name `L`. This enables a component to import or export the
same interface multiple times with different plain names. For example:

```wat
(component
(import "primary" (implements "wasi:keyvalue/store") (instance ...))
(import "secondary" (implements "wasi:keyvalue/store") (instance ...))
)
```

Here, both imports implement `wasi:keyvalue/store` but have distinct plain
names `primary` and `secondary`. Bindings generators can use the
`implements` annotation to know which interface the instance implements,
enabling them to share value type bindings across both imports. (Note that
resource types defined in the interface, such as `bucket`, are treated as
distinct for each import, since each may have a different implementation.)

The `interfacename` also helps hosts and clients of a component. A host that
sees `(implements "wasi:keyvalue/store>")` knows to supply a
`wasi:keyvalue/store` implementation for that import, even though the import
name is something else. Similarly, a client composing components can use the
annotation to match compatible imports and exports across components.

When a function's type is `async`, bindings generators are expected to
emit whatever asynchronous language construct is appropriate (such as an
`async` function in JS, Python or Rust). See the [concurrency explainer] for
Expand Down Expand Up @@ -2822,11 +2848,11 @@ Values]) are **strongly-unique**:
* Strip any `[...]` annotation prefix from both names.
* The names are strongly-unique if the resulting strings are unequal.

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

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

Note that additional validation rules involving types apply to names with
annotations. For example, the validation rules for `[constructor]foo` require
Expand Down
212 changes: 210 additions & 2 deletions design/mvp/WIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ document, a pseudo-formal [grammar specification][lexical-structure], and
additionally a specification of the [package format][package-format] of a WIT
package suitable for distribution.

See [Gated Features] for an explanation of 🔧.
See [Gated Features] for an explanation of 🔧 and 🏷️.

[IDL]: https://en.wikipedia.org/wiki/Interface_description_language
[components]: https://github.com/webassembly/component-model
Expand Down Expand Up @@ -366,6 +366,57 @@ world union-my-world-b {
}
```

🏷️ When a world being included contains plain-named imports or exports that
reference a named interface (using the `id: use-path` syntax), the `with`
keyword renames the plain-name label while preserving the underlying
`(implements "I")` annotation in the encoding. For example:

```wit
package local:demo;

interface store {
get: func(key: string) -> option<string>;
}

world base {
import cache: store;
}

world extended {
import cache: func();
include base with { cache as my-cache }
Comment thread
alexcrichton marked this conversation as resolved.
}
```

In this case, `extended` requires a `with` during its `include` because
the plain-name `cache` is already taken in the world. The import here is renamed
to `my-cache` that implements `local:demo/store`, equivalent to writing
`import my-cache: store;` directly.

Unlike interface names (which are automatically de-duplicated when two
`include`s import the same interface), plain names cannot be de-duplicated
and will conflict. For example:

```wit
world base-a {
import cache: store;
}

world base-b {
import cache: store;
}

world conflict {
include base-a;
include base-b; // error: plain name 'cache' conflicts
}

world resolved {
include base-a;
include base-b with { cache as other-cache } // ok: renamed to avoid conflict
}
```

`with` cannot be used to rename interface names, however, so the following
world would be invalid:
```wit
Expand Down Expand Up @@ -1382,7 +1433,41 @@ export-item ::= 'export' id ':' extern-type
import-item ::= 'import' id ':' extern-type
| 'import' use-path ';'

extern-type ::= func-type ';' | 'interface' '{' interface-items* '}'
extern-type ::= func-type ';'
| 'interface' '{' interface-items* '}'
| use-path ';' 🏷️
```

🏷️ The third case of `extern-type` allows a named interface to be imported or
exported with a custom [plain name]. For example:

```wit
world my-world {
import primary: wasi:keyvalue/store;
import secondary: wasi:keyvalue/store;
export my-handler: wasi:http/handler;
}
```

Here, `primary` and `secondary` are two distinct imports that both have the
instance type of the `wasi:keyvalue/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 wasi:keyvalue/store;` (without the `id :` prefix),
which would create a single import using the full interface name
`wasi:keyvalue/store`. Similarly, the export `my-handler` has the instance type
of `wasi:http/handler` but uses the plain name `my-handler` instead of the full
interface name, which is useful when a component wants to export the same
interface multiple times or simply use a more descriptive name.
Comment thread
alexcrichton marked this conversation as resolved.

Note that the `use-path` form can have an ambiguity with the nested packages
feature (🪺) where `a:b` could mean two things. To resolve this `a:b` is lexed
as a single token instead of separate tokens, meaning:

```wit
world w {
import a:b; // error: can't import a package
import a: b; // ok, assuming `b` names an interface in scope
}
```

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

🏷️ When a world imports or exports a named interface with a custom plain name
(using the `id: use-path` syntax), the encoding uses the `(implements "I")`
annotation defined in [Explainer.md](Explainer.md#import-and-export-definitions) to indicate which
interface the instance implements. Note though that each copy implements a
unique version of the interface in question. For example, the following WIT:

```wit
package local:demo;

interface store {
resource bucket {
constructor(name: string);
get: func(key: string) -> option<string>;
}
}

world w {
import one: store;
import two: store;
}
```

is encoded as:

```wat
(component
(type (export "store") (component
(export "local:demo/store" (instance
(export "bucket" (type $b (sub resource)))
(export "[constructor]bucket" (func (param "name" string) (result (own $b))))
(export "[method]bucket.get" (func (param "self" (borrow $b)) (param "key" string) (result (option string))))
))
))
(type (export "w") (component
(export "local:demo/w" (component
(import "one" (implements "local:demo/store") (instance
(export "bucket" (type $b (sub resource)))
(export "[constructor]bucket" (func (param "name" string) (result (own $b))))
(export "[method]bucket.get" (func (param "self" (borrow $b)) (param "key" string) (result (option string))))
))
(import "two" (implements "local:demo/store") (instance
(export "bucket" (type $b (sub resource)))
(export "[constructor]bucket" (func (param "name" string) (result (own $b))))
(export "[method]bucket.get" (func (param "self" (borrow $b)) (param "key" string) (result (option string))))
))
))
))
)
```

The `(implements "local:demo/store")` prefix tells bindings generators and
toolchains which interface each plain-named instance import implements, while
the labels `one` and `two` provide distinct plain names. This is a case of
the general `(implements ..)` pattern described in
[Explainer.md](Explainer.md#import-and-export-definitions). Also note here that
two copies of the `"bucket"` resource are imported for the `local:demo/w` world.
This is because the interfaces `one` and `two` duplicate the `store` interface.
Note that this can import just a single `bucket` resource by extracting out the
resource definition into a separate interface. For example:

```wit
package local:demo;

interface types {
resource bucket {
get: func(key: string) -> option<string>;
}
}

interface store {
use types.{bucket};
open: func(name: string) -> bucket;
}

world w {
import one: store;
import two: store;
}
```

is encoded as:

```wat
(component
(type (export "types") (component
(export "local:demo/types" (instance
(export "bucket" (type $b (sub resource)))
(export "[method]bucket.get" (func (param "self" (borrow $b)) (param "key" string) (result (option string))))
))
))
(type (export "store") (component
(import "local:demo/types" (instance $types
(export "bucket" (type $b (sub resource)))
))
(alias export $types "bucket" (type $b))
(export "local:demo/store" (instance
(export "bucket" (type $b' (eq $b)))
(export "open" (func (param "name" string) (result (own $b'))))
))
))
(type (export "w") (component
(export "local:demo/w" (component
(import "local:demo/types" (instance $types
(export "bucket" (type $b (sub resource)))
))
(alias export $types "bucket" (type $b))
(import "one" (implements "local:demo/store") (instance
(export "bucket" (type $b' (eq $b)))
(export "open" (func (param "name" string) (result (own $b'))))
))
(import "two" (implements "local:demo/store") (instance
(export "bucket" (type $b' (eq $b)))
(export "open" (func (param "name" string) (result (own $b'))))
))
))
))
)
```

Where in this example the `local:demo/w` world imports only a single `bucket`
resource under the `local:demo/types` interface. This resource is then used
by the `one` and `two` export.

Putting this all together, the following WIT definitions:

```wit
Expand Down
Loading
Loading