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
13 changes: 8 additions & 5 deletions design/mvp/Binary.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,11 +408,14 @@ Notes:
[text format](Explainer.md#import-and-export-definitions).
* 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=<I>]` annotated `plainname`s only
occur on `instance` imports or exports and that `I` is a valid
`interfacename`.
* 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
24 changes: 24 additions & 0 deletions design/mvp/Explainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -2538,6 +2538,7 @@ plainname ::= <label>
| '[constructor]' <label>
| '[method]' <label> '.' <label>
| '[static]' <label> '.' <label>
| '[implements=<' <interfacename> '>]' <label>
label ::= <first-fragment> ( '-' <fragment> )*
first-fragment ::= [a-z] <word>
| [A-Z] <acronym>
Expand Down Expand Up @@ -2683,6 +2684,23 @@ 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 annotated with `[implements=<I>]L`, 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 "[implements=<wasi:keyvalue/store>]primary" (instance ...))
(import "[implements=<wasi:keyvalue/store>]secondary" (instance ...))
)
```

Here, both imports implement `wasi:keyvalue/store` but have distinct plain
names `primary` and `secondary`. Bindings generators can use the
`[implements=<I>]` annotation to know which interface the instance implements,
enabling them to generate the same typed bindings for both imports.

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 @@ -2819,13 +2837,19 @@ Values]) are **strongly-unique**:
* If one name is `l` and the other name is `[*]l.l` (for the same
`label` `l` and any annotation `*` with a dotted `l.l` name), they *are
not* strongly-unique.
* An `[implements=<I>]L` name and a bare `I` `interfacename` are always
strongly-unique (they are different name kinds: `plainname` vs
`interfacename`).
* Otherwise:
* Lowercase all the `acronym`s (uppercase letters) in both names.
* 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`
* `[implements=<a:b/c>]one`, `[implements=<a:b/c>]two` (different labels
after stripping annotation)
* `[implements=<a:b/c>]foo` and `a:b/c` (different name kinds)

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`
Expand Down
97 changes: 96 additions & 1 deletion design/mvp/WIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,31 @@ 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 {
include base with { cache as my-cache }
}
```

In this case, `extended` has a single import with the plain name `my-cache`
that implements `local:demo/store`, equivalent to writing
`import my-cache: store;` directly.

`with` cannot be used to rename interface names, however, so the following
world would be invalid:
```wit
Expand Down Expand Up @@ -1381,9 +1406,32 @@ 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
package local:demo;

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

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

Here, `primary` and `secondary` are two distinct imports that both have the
instance type of the `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 store;` (without the `id :` prefix), which would create
a single import using the full interface name `local:demo/store`.

Note that worlds can import types and define their own types to be exported
from the root of a component and used within functions imported and exported.
The `interface` item here additionally defines the grammar for IDs used to refer
Expand Down Expand Up @@ -2061,6 +2109,53 @@ 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 [Binary.md](Binary.md) and
[Explainer.md](Explainer.md#import-and-export-definitions) to indicate which
interface the instance implements. For example, the following WIT:

```wit
package local:demo;

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

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

is encoded as:

```wat
(component
(type (export "w") (component
(export "local:demo/w" (component
(import "[implements=<local:demo/store>]one" (instance
(export "get" (func (param "key" string) (result (option string))))
))
(import "[implements=<local:demo/store>]two" (instance
(export "get" (func (param "key" string) (result (option string))))
))
))
))
(type (export "store") (component
(export "local:demo/store" (instance
(export "get" (func (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=<interfacename>]label` pattern described in
[Explainer.md](Explainer.md#import-and-export-definitions).

Putting this all together, the following WIT definitions:

```wit
Expand Down
78 changes: 78 additions & 0 deletions test/wasm-tools/implements.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
;; RUN: wast --assert default --snapshot tests/snapshots %

;; Valid: basic [implements=<...>] on instance import
(component
(import "[implements=<a:b/c>]name" (instance))
)

;; Valid: [implements=<...>] on instance export
(component
(instance $i)
(export "[implements=<a:b/c>]name" (instance $i))
)

;; Valid: two imports with the same interface, different labels
(component
(import "[implements=<a:b/c>]one" (instance))
(import "[implements=<a:b/c>]two" (instance))
)

;; Valid: [implements=<...>] with version in interface name
(component
(import "[implements=<a:b/c@1.2.3>]name" (instance))
)

;; Valid: [implements=<...>] alongside a bare interface import of the same interface
(component
(import "a:b/c" (instance))
(import "[implements=<a:b/c>]alt" (instance))
)

;; Valid: in a component type
(component
(type (component
(import "[implements=<a:b/c>]one" (instance
(export "get" (func (param "key" string) (result (option string))))
))
(import "[implements=<a:b/c>]two" (instance
(export "get" (func (param "key" string) (result (option string))))
))
))
)

;; Invalid: [implements=<...>] on func (must be instance)
(assert_invalid
(component
(import "[implements=<a:b/c>]name" (func))
)
"`[implements=<a:b/c>]` must be on an instance import or export")

;; Invalid: [implements=<...>] on component (must be instance)
(assert_invalid
(component
(import "[implements=<a:b/c>]name" (component))
)
"`[implements=<a:b/c>]` must be on an instance import or export")

;; Invalid: duplicate labels after stripping annotation
(assert_invalid
(component
(import "[implements=<a:b/c>]name" (instance))
(import "[implements=<x:y/z>]name" (instance))
)
"conflicts with previous name")

;; Invalid: duplicate label between annotated and bare plain name
(assert_invalid
(component
(import "name" (func))
(import "[implements=<a:b/c>]name" (instance))
)
"conflicts with previous name")

;; Invalid: malformed interface name inside annotation
(assert_invalid
(component
(import "[implements=<NotValid>]name" (instance))
)
"not a valid extern name")
31 changes: 31 additions & 0 deletions test/wasm-tools/naming.wast
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,34 @@
(import "[static]a.a" (func))
)
"import name `[static]a.a` conflicts with previous name `a`")

;; [implements=<...>] strong-uniqueness tests

;; Valid: two [implements=<...>] with different labels are strongly-unique
(component definition
(import "[implements=<a:b/c>]one" (instance))
(import "[implements=<a:b/c>]two" (instance))
)

;; Valid: [implements=<...>] and a bare interface name are strongly-unique
;; (different name kinds: plainname vs interfacename)
(component definition
(import "a:b/c" (instance))
(import "[implements=<a:b/c>]alt" (instance))
)

;; Invalid: two [implements=<...>] with the same label conflict
(assert_invalid
(component
(import "[implements=<a:b/c>]name" (instance))
(import "[implements=<x:y/z>]name" (instance))
)
"conflicts with previous name")

;; Invalid: [implements=<...>] label conflicts with a bare plain name
(assert_invalid
(component
(import "name" (func))
(import "[implements=<a:b/c>]name" (instance))
)
"conflicts with previous name")