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
132 changes: 82 additions & 50 deletions design/mvp/Explainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ more user-focused explanation, take a look at the
* [Import and export definitions](#import-and-export-definitions)
* [Name uniqueness](#name-uniqueness)
* [Canonical interface name](#-canonical-interface-name)
* [External visibility of types](#external-visibility-of-types)
* [Component invariants](#component-invariants)
* [JavaScript embedding](#JavaScript-embedding)
* [JS API](#JS-API)
Expand Down Expand Up @@ -2819,56 +2820,8 @@ import uses an `instance` type, anticipating a subsequent tooling step (likely
the one that performs dependency resolution) to select, instantiate and provide
the instance.

Validation of `export` requires that all transitive uses of resource types in
the types of exported functions or values refer to resources that were either
imported or exported (concretely, via the type index introduced by an `import`
or `export`). The optional `<externtype>?` in `export` can be used to
explicitly ascribe a type to an export which is validated to be a supertype of
the definition's type, thereby allowing a private (non-exported) type
definition to be replaced with a public (exported) type definition.

For example, in the following component:
```wat
(component
(import "R1" (type $R1 (sub resource)))
(type $R2 (resource (rep i32)))
(export $R2' "R2" (type $R2))
(func $f1 (result (own $R1)) (canon lift ...))
(func $f2 (result (own $R2)) (canon lift ...))
(func $f2' (result (own $R2')) (canon lift ...))
(export "f1" (func $f1))
;; (export "f2" (func $f2)) -- invalid
(export "f2" (func $f2) (func (result (own $R2'))))
(export "f2" (func $f2'))
)
```
the commented-out `export` is invalid because its type transitively refers to
`$R2`, which is a private type definition. This requirement is meant to address
the standard [avoidance problem] that appears in module systems with abstract
types. In particular, it ensures that a client of a component is able to
externally define a type compatible with the exports of the component.

Similar to type exports, value exports may also ascribe a type to keep the precise
value from becoming part of the type and public interface.

For example:
```wat
(component
(value $url string "https://example.com")
(export "default-url" (value $url) (value string))
)
```

The inferred type of this component is:
```wat
(component
(export "default-url" (value string))
)
```

Note, that the `url` value definition is absent from the component type

### Name Uniqueness
#### Name Uniqueness

The goal of the `label`, `exportname` and `importname` productions defined and
used above is to allow automated bindings generators to map these names into
Expand Down Expand Up @@ -2904,7 +2857,7 @@ annotations. For example, the validation rules for `[constructor]foo` require
for details.


### 🔗 Canonical Interface Name
#### 🔗 Canonical Interface Name

An `interfacename` (as defined above) is **canonical** iff it either:

Expand Down Expand Up @@ -2951,6 +2904,85 @@ temporarily permitted. These non-canonical names may trigger warnings and will
be rejected at some point in the future.


#### External Visibility of Types

Resource types and certain compound value types (specifically `record`,
`variant`, `enum`, `flags`) have an additional **external visibility**
requirement when used in the function types of imports and exports:

Like other module systems with abstract types, components have to deal with the
[avoidance problem] wherein a resource type that is in-scope when defining a
function becomes out-of-scope when that function is exported and used outside the
component. For example, in the following component, the definitions of `$R` and
`$f` are valid, but when attempting to *export* `$f`, there is a problem because
client components would have no way to refer to `$R` and thus no way to write
`f`'s function type in an import:
```wat
(component
(type $R (resource (rep i32)))
(func $f (result (own $R)) (canon lift ...))
;; ⁉️ (export "f" (func $f))
)
```

To address this problem, when validating `import` and `export` definitions, the
Component Model requires that any resource type transitively used in the type of
an import or export has an *externally-visible name*. Concretely, the `typeidx`
introduced by the `import` or `export` of a `resource` type is considered an
"externally-visible name", since the `typeidx` is uniquely associated with an
`import`/`export`, which has a name. (Note: for `export`s, the `typeidx` that
gets passed *into* the `export` does *not* get retroactively named by the
`export`; only the *new* `typeidx` introduced by the `export`.) Lastly,
according to usual component acyclicity rules, *imported* types cannot
transitively refer to the external names of *exported* types.

Based on these rules, the above example can be expanded with the following valid
imports and exports:
```wat
(component
(type $R (resource (rep i32)))
(func $f (result (own $R)) (canon lift ...))
;; ❌ (export "f" (func $f)) ;; error: f's type uses $R which has no external name
(export $R' "R" (type $R)) ;; $R' is an external name
(export "f" (func $f) (func (result (own $R')))) ;; override $f's default type to use $R'

(func $f2 (result (own $R')) (canon lift ...)) ;; alternatively: define $f2 to use $R' directly
(export "f2" (func $f2)) ;; no ascription necessary

;; ❌ (import "f3" (func (result (own $R')))) ;; imports cannot depend on exports
(import "R2" (type $R2 (sub resource))) ;; $R2 is an external name
(import "f3" (func $f3 (result (own $R2)))) ;; imports can depend on imports

(func $f4 (result (own $R2)) (canon lift ...))
(export "f4" (func $f4)) ;; exports can depend on imports
)
```

The Component Model also imposes the same external-visibility requirement on
`record`, `variant`, `enum` and `flags` value types, even though these types are
purely structural types. The reason for this theoretically-unnecessary
requirement is that, in many source languages, the source-level types that
correspond to these component-level types are not structural and thus require
some fixed type name to use in generated function signatures. For example, in C,
`struct`s, `union`s and `enum`s all require a name, and in the absence of a
component- or WIT-supplied name, a bindings generator would be forced to
mechanically generate a name that will inevitably be less meaningful than what a
component or WIT author would choose.

Thus, the preceding example also applies when `resource` is swapped with
`record`, `variant`, `enum` and `flags`:
```wat
(component
(type $R (record (field "x" u32) (field "y" u32)))
(func $f (result $R) (canon lift ...))
;; ❌ (export "f" (func $f))
(export $R' "R" (type $R))
(export "f" (func $f) (func (result $R')))
... etc
)
```


## Component Invariants

Component validation rules only allow a component to import and export
Expand Down
216 changes: 216 additions & 0 deletions test/validation/external-visibility.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
;; Tests Explainer.md#external-visibility-of-types

;; resources

(component definition
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $R (resource (rep i32)))
(func $f (result (own $R)) (canon lift (core func $i "f")))
(export $R' "r" (type $R))
(export "f" (func $f) (func (result (own $R'))))
(func $f2 (result (own $R')) (canon lift (core func $i "f")))
(export "f2" (func $f2))
(import "r2" (type $R2 (sub resource)))
(import "f3" (func $f3 (result (own $R2))))
(func $f4 (result (own $R2)) (canon lift (core func $i "f")))
(export "f4" (func $f4)))

(assert_invalid
(component
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $R (resource (rep i32)))
(func $f (result (own $R)) (canon lift (core func $i "f")))
(export "f" (func $f)))
"func not valid to be used as export")

(assert_invalid
(component
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $R (resource (rep i32)))
(func $f (result (own $R)) (canon lift (core func $i "f")))
(export $R' "r" (type $R))
(export "f" (func $f)))
"func not valid to be used as export")

(assert_invalid
(component
(type $R (resource (rep i32)))
(export $R' "r" (type $R))
(import "f" (func (result (own $R')))))
"func not valid to be used as import")

(assert_invalid
(component
(type $R (resource (rep i32)))
(import "f" (func (result (own $R)))))
"func not valid to be used as import")

;; records

(component definition
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $Rec (record (field "x" u32)))
(func $f (result $Rec) (canon lift (core func $i "f")))
(export $Rec' "rec" (type $Rec))
(export "f" (func $f) (func (result $Rec'))))

(assert_invalid
(component
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $Rec (record (field "x" u32)))
(func $f (result $Rec) (canon lift (core func $i "f")))
(export "f" (func $f)))
"func not valid to be used as export")

(assert_invalid
(component
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $Rec (record (field "x" u32)))
(func $f (result $Rec) (canon lift (core func $i "f")))
(export $Rec' "rec" (type $Rec))
(export "f" (func $f)))
"func not valid to be used as export")

(component definition
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $R (resource (rep i32)))
(export $R' "r" (type $R))
(type $Rec (record (field "r" (own $R'))))
(export $Rec' "rec" (type $Rec))
(func $f (result $Rec') (canon lift (core func $i "f")))
(export "f" (func $f)))

(assert_invalid
(component
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $R (resource (rep i32)))
(type $Rec (record (field "r" (own $R))))
(export $Rec' "rec" (type $Rec))
(func $f (result $Rec') (canon lift (core func $i "f")))
(export "f" (func $f)))
"type not valid to be used as export")

;; enums

(component definition
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $E (enum "a" "b"))
(func $f (result $E) (canon lift (core func $i "f")))
(export $E' "e" (type $E))
(export "f" (func $f) (func (result $E'))))

(assert_invalid
(component
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $E (enum "a" "b"))
(func $f (result $E) (canon lift (core func $i "f")))
(export "f" (func $f)))
"func not valid to be used as export")

;; flags

(component definition
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $Fl (flags "a" "b"))
(func $f (result $Fl) (canon lift (core func $i "f")))
(export $Fl' "fl" (type $Fl))
(export "f" (func $f) (func (result $Fl'))))

(assert_invalid
(component
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $Fl (flags "a" "b"))
(func $f (result $Fl) (canon lift (core func $i "f")))
(export "f" (func $f)))
"func not valid to be used as export")

;; variants

(component definition
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $V (variant (case "a") (case "b")))
(func $f (result $V) (canon lift (core func $i "f")))
(export $V' "v" (type $V))
(export "f" (func $f) (func (result $V'))))

(assert_invalid
(component
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $V (variant (case "a") (case "b")))
(func $f (result $V) (canon lift (core func $i "f")))
(export "f" (func $f)))
"func not valid to be used as export")

;; tuples/options

(component definition
(core module $m (func (export "f") (param i32 i32 i32 i32)))
(core instance $i (instantiate $m))
(func $f (param "a" (tuple u32 u32)) (param "b" (option u32))
(canon lift (core func $i "f")))
(export "f" (func $f)))

(assert_invalid
(component
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $R (resource (rep i32)))
(func $f (result (tuple (own $R))) (canon lift (core func $i "f")))
(export "f" (func $f)))
"func not valid to be used as export")

(component definition
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $R (resource (rep i32)))
(export $R' "r" (type $R))
(func $f (result (tuple (own $R'))) (canon lift (core func $i "f")))
(export "f" (func $f)))

(assert_invalid
(component
(core module $m
(memory (export "mem") 1)
(func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $R (resource (rep i32)))
(func $f (result (option (own $R))) (canon lift (core func $i "f") (memory $i "mem")))
(export "f" (func $f)))
"func not valid to be used as export")

(assert_invalid
(component
(core module $m
(memory (export "mem") 1)
(func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $Rec (record (field "x" u32)))
(func $f (result (tuple $Rec u32)) (canon lift (core func $i "f") (memory $i "mem")))
(export "f" (func $f)))
"func not valid to be used as export")

;; bag-of-exports:

(assert_invalid
(component
(core module $m (func (export "f") (result i32) unreachable))
(core instance $i (instantiate $m))
(type $R (resource (rep i32)))
(func $f (result (own $R)) (canon lift (core func $i "f")))
(instance $bag (export "f" (func $f)))
(export "bag" (instance $bag)))
"instance not valid to be used as export")
Loading