From ed8ab478d7fcaf33bd6220b06fa2b5b55f096f67 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Mon, 6 Jul 2026 12:46:20 -0500 Subject: [PATCH] Add 'optional' imports/exports --- design/mvp/CanonicalABI.md | 21 +++++++++++++++++++++ design/mvp/Explainer.md | 33 +++++++++++++++++++++++++++++---- design/mvp/WIT.md | 24 ++++++++++++++---------- 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/design/mvp/CanonicalABI.md b/design/mvp/CanonicalABI.md index 621296b5..6ea81d6f 100644 --- a/design/mvp/CanonicalABI.md +++ b/design/mvp/CanonicalABI.md @@ -67,6 +67,7 @@ specified here. * [`canon thread.yield-then-resume`](#-canon-threadyield-then-resume) 🧡 * [`canon thread.suspend-then-promote`](#-canon-threadsuspend-then-promote) 🧡 * [`canon thread.yield-then-promote`](#-canon-threadyield-then-promote) 🧡 + * [`canon optional.present`](#-canon-optionalpresent) ❓ * [`canon error-context.new`](#-canon-error-contextnew) πŸ“ * [`canon error-context.debug-message`](#-canon-error-contextdebug-message) πŸ“ * [`canon error-context.drop`](#-canon-error-contextdrop) πŸ“ @@ -3613,6 +3614,10 @@ validation is performed: * if `len(flatten_types(ft.param_types())) > MAX_FLAT_PARAMS`, `realloc` is required * if `len(flatten_types(ft.result_type())) > max` (where `max = MAX_FLAT_RESULTS` for sync lifts, and `max = MAX_FLAT_PARAMS` for async lifts), `memory` is required +TODO: if `$ft` is optional and one of its referenced resource types is imported and +"none", the `canon lift`ed function becomes "none" +TODO: define some `refers_to_non_existent_resource`, `assert(not refers_to_non_existent_resource(ft))` + Note that an `async`-lifted function whose result type requires a memory to lift (either because it contains lists or strings or because the number of flattened types exceeds `MAX_FLAT_PARAMS`) must include a `memory` option, and that option @@ -3851,10 +3856,13 @@ runtime Core WebAssembly arguments. Based on this, `canon_lower` is defined in chunks as follows. First, like most Canonical ABI functions callable from Core WebAssembly, lowered imports may not be called during `post-return` or `realloc`: +TODO: mention optional ```python def canon_lower(callee, ft, opts, flat_args: list[CoreValType]) -> list[CoreValType]: thread = current_thread() trap_if(not thread.task.inst.may_leave) + assert(ft.optional or callee is not None) + trap_if(callee is None) ``` The component-level function type maps to a Core WebAssembly function type using @@ -5122,6 +5130,19 @@ prepared to propagate cancellation, they can omit `cancellable` so that cancellation is instead delivered at a later `cancellable` call. +### ❓ `canon optional.present` + +For a canonical definition: +```wat +(canon optional.present $sortidx (core global $present)) +``` +validation specifies: +* `$sortidx` must ... and be `optional` +* `$present` is given type `(global i32)` + +TODO: describe + + ### πŸ“ `canon error-context.new` For a canonical definition: diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index 5ae72e9f..6a4ec8bc 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -27,6 +27,7 @@ more user-focused explanation, take a look at the * [Canonical built-ins](#canonical-built-ins) * [Resource built-ins](#resource-built-ins) * [Concurrency built-ins](#-concurrency-built-ins) + * [Optionality built-ins](#-optionality-built-ins) * [Error Context built-ins](#-error-context-built-ins) * [Value definitions](#-value-definitions) * [Start definitions](#-start-definitions) @@ -69,6 +70,7 @@ subsequent WASI Developer Preview minor releases: * 🐘: [memory64] * πŸ—ΊοΈ: the `map` type * 🏷️: `implements` annotations for plain-named interface imports/exports +* ❓: `optional` imports and exports ## Grammar @@ -315,6 +317,7 @@ instanceexpr ::= (instantiate *) | * instantiatearg ::= (with ) | (with (instance *)) + | (with ) ❓ name ::= inlineexport ::= (export ) | (export ) πŸ”— @@ -369,6 +372,8 @@ the `foo` function of its child component `$C` and re-export it directly from ) ``` +TODO: mention `alias export` of `optional` `instance` ~~> optional X (disallow X=component for now) + Additional syntactic sugar is added for allowing export aliases to be defined *inline* as a syntactic generalization of the `{X}sortidx` grammar rules defined [above](#index-spaces) for each core- and component-level sort `X`: @@ -441,6 +446,8 @@ aliased by `$E`: ) ``` +TODO: anything interesting here when reaching out of optional scope? + For `outer` aliases, there is also inline syntactic sugar, which is simply to use the identifier of the outer definition, resolved using normal lexical scoping rules. For example, the following component: @@ -624,9 +631,13 @@ valtype ::= keytype ::= bool | s8 | u8 | s16 | u16 | s32 | u32 | s64 | u64 | char | string πŸ—ΊοΈ resourcetype ::= (resource (rep i32) (dtor core-prefix())?) | (resource (rep i64) (dtor core-prefix())?) 🐘 -functype ::= (func async? (param )* (result )?) +functype ::= (func (param )* (result )?) +optional? ::= Ο΅ + | optional ❓ +async? ::= Ο΅ + | async πŸ”€ componenttype ::= (component *) -instancetype ::= (instance *) +instancetype ::= (instance *) componentdecl ::= | instancedecl ::= core-prefix() @@ -643,9 +654,9 @@ externtype ::= ( (type ) ) | (value ) πŸͺ™ | (type ) typebound ::= (eq ) - | (sub resource) + | (sub resource) valuebound ::= (eq ) πŸͺ™ - | πŸͺ™ + | πŸͺ™ where bind-id(X) parses '(' sort ? Y ')' when X parses '(' sort Y ')' ``` @@ -1315,6 +1326,12 @@ replaced by `$R` when validating the instantiations of `$c1` and `$c2`. These type-checking rules for instantiating type imports mirror the *elimination* rule of [universal types] (βˆ€T). +TODO: if don't provide type, substitute incompatible type; prevent functions +from being supplied... + +TODO: also, maybe have subtyping rule: if all fields of `instance` are +`optional`, is a subtype of `optional` `instance` with non-optional fields + Importantly, this type substitution performed by the parent is not visible to the child at validation- or run-time. In particular, there are no runtime casts that can "see through" to the original type parameter, avoiding @@ -1574,6 +1591,7 @@ canon ::= ... | (canon thread.yield-then-resume cancellable? (core func ?)) 🧡 | (canon thread.suspend-then-promote cancellable? (core func ?)) 🧡 | (canon thread.yield-then-promote cancellable? (core func ?)) 🧡 + | (canon optional.present (core global ?)) ❓ | (canon error-context.new * (core func ?)) πŸ“ | (canon error-context.debug-message * (core func ?)) πŸ“ | (canon error-context.drop (core func ?)) πŸ“ @@ -2343,6 +2361,13 @@ For details, see [`canon_thread_available_parallelism`] in the Canonical ABI explainer. +##### ❓ Optionality built-ins + +###### ❓ `optional.present` + +TODO + + ##### πŸ“ Error Context built-ins ###### πŸ“ `error-context.new` diff --git a/design/mvp/WIT.md b/design/mvp/WIT.md index 5fe1f403..0b52ed2c 100644 --- a/design/mvp/WIT.md +++ b/design/mvp/WIT.md @@ -308,6 +308,9 @@ world union-my-world { } ``` +TODO: add `optional` to some of the `include` examples above (unions *both* imports and exports) +TODO: add a note that `include` does not produce subtype or supertype, but other ways to combine worlds (`meet` and `join`) could be added in the future + ### De-duplication of interfaces If two worlds share an imported or exported [interface name], then the union of @@ -339,6 +342,8 @@ world union-my-world-b { } ``` +TODO: mention merging of `optional` and non-`optional` ~~> non-`optional` + ### Name Conflicts and `with` When two or more included Worlds have the same name for an import or export @@ -436,12 +441,6 @@ world invalid-union-world { ``` -### A Note on Subtyping - -In the future, when `optional` export is supported, the world author may explicitly mark exports as optional to make a component targeting an included World a subtype of the union World. - -For now, we are not following the subtyping rules for the `include` statement. That is, the `include` statement does not imply any subtyping relationship between the included worlds and the union world. - ## WIT Packages and `use` [use]: #wit-packages-and-use @@ -563,6 +562,8 @@ interface my-interface { } ``` +TODO: `use` resolution when only used from `optional`y-imported interfaces --> `optional` + #### Top-level `use` If a package being referred to has a version number, then using the above syntax @@ -1075,6 +1076,7 @@ keyword ::= 'as' | 'list' | 'map' | 'option' + | 'optional' | 'own' | 'package' | 'record' @@ -1441,10 +1443,10 @@ world-definition ::= export-item | typedef-item | include-item -export-item ::= external-id? 'export' id ':' extern-type - | 'export' use-path ';' -import-item ::= external-id? 'import' id ':' extern-type - | 'import' use-path ';' +export-item ::= external-id? 'export' id ':' 'optional'? extern-type + | 'export' 'optional'? use-path ';' +import-item ::= external-id? 'import' id ':' 'optional'? extern-type + | 'import' 'optional'? use-path ';' external-id ::= '@external-id' '(' string-literal ')' 🏷️ @@ -1508,6 +1510,8 @@ world my-world { } ``` +TODO: note `optional` and what it means + [`componenttype`]: Explainer.md#type-definitions ## Item: `include`