Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add @feature and @since gates to WIT #332

Merged
merged 10 commits into from
May 28, 2024
96 changes: 92 additions & 4 deletions design/mvp/WIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,46 @@ include-names-item ::= id 'as' id
## Item: `interface`

Interfaces can be defined in a `wit` file. Interfaces have a name and a
sequence of items and functions.
sequence of items and functions, all of which can be gated on an `id` or a
semantic version.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be putting the cart before the horse a bit since we're a ways away from 1.0, but maybe it's worth discussing now anyway: what would an example of a traditional semver patch version be in the WIT context? I've spent a bit trying to think of an example, and I can't seem to think of one, except for something trivial like a comment typo fix.

I generally take a patch bump to mean "the interface is exactly the same as before, but the implementation has changed in some non-semantically-affecting way". But a WIT file is all interface, no implementation. So it seems impossible to make a patch-only change? Ex: if I have local:[email protected], except for (maybe?) comments, I believe the *.wit for local:[email protected] must be identical to be valid semver.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right that patch versions don't seem to immediately have a real use case for an interface-only language like WIT. Comments are indeed to main, and I think important, use case for them in this context though: just as implementation code, comments can contain bugs, and the use of patch versions allow us to fix those.


For example, the following interface has 4 items, 3 of which are gated:
```wit
interface foo {
a: func();

@since(version = "0.2.1")
b: func();

@since(version = "0.2.2")
@feature(name = "fancy-foo")
c: func();

@feature(name = "fancier-foo")
d: func();
}
```
The `@since` gate indicates that `b` and `c` were added as part of the `0.2.1`
and `0.2.2` releases, resp. Thus, when building a component targeting, e.g.,
`0.2.1`, `b` can be used, but `c` cannot. An important expectation set by the
`@since` gate is that, once applied to an item, the item is never modified
incompatibly going forward (according to general semantic versioning rules).

In contrast, the lone `@feature` gate on `d` indicates that `d` is part of the
`fancier-foo` feature that is still under active development and thus `d` may
change type or be removed at any time. An important expectation set by the
lukewagner marked this conversation as resolved.
Show resolved Hide resolved
`@feature` gate is that toolchains will not expose `@feature`-only-gated items
by default unless explicitly opted-into by the developer.

Together, these gates support a development flow in which new features start
with a `@feature` gate while the details are still being hashed out. Then, once
the feature is stable (and, in a WASI context, voted upon), the `@since` gate
can be added. To enable a smooth transition, both gates can be present at the
same time, exposing the feature by when the target version is greater-or-equal.
After a suitable transition period (during which producer toolchains bump their
default target versions to enable the feature by default), the `@feature` gate
can then be removed. Thus, in the above example, the `fancy-foo` feature gate
could be removed from `c` once `0.2.2` is the default target version.

Specifically interfaces have the structure:

Expand All @@ -943,9 +982,15 @@ Specifically interfaces have the structure:
```ebnf
interface-item ::= 'interface' id '{' interface-items* '}'

interface-items ::= typedef-item
| use-item
| func-item
interface-items ::= gate interface-definition

gate ::= since-gate? feature-gate?
since-gate ::= '@since' '(' 'version' '=' '"' <valid semver> '"' ')'
feature-gate ::= '@feature' '(' 'name' '=' '"' id '"' ')'

interface-definition ::= typedef-item
| use-item
| func-item

typedef-item ::= resource-item
| variant-items
Expand All @@ -970,6 +1015,7 @@ named-type-list ::= ϵ
named-type ::= id ':' ty
```


## Item: `use`

A `use` statement enables importing type or resource definitions from other
Expand Down Expand Up @@ -1626,3 +1672,45 @@ standalone interface definitions (such `wasi:http/handler`) are no longer in a
`use`s are replaced by direct aliases to preceding type imports as determined
by the WIT resolution process.

Unlike most other WIT constructs, the `@feature` and `@version` gates are not
represented in the component binary. Instead, these are considered "macro"
constructs that take the place of maintaining two copies of a single WIT document.
In particular, when encoding a collection of WIT documents into a binary, a target
version and set of feature names is supplied and determines whether individual
gated items are included or not.

For example, the following WIT document:
```wit
package ns:[email protected];

interface i {
f: func()

@since(version = "1.1.0")
g: func()
}
```
is encoded as the following component when the target version is `1.0.0`:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My impression is that the main intention for @since and `@unstable is less-so targetting old version of WASI but rather enabling a story for in-progress feature-development. Along those lines I do not yet plan on implementing (not that it can't be done, I just don't plan on doing it initially) support for "view this WIT from version 0.2.0". Instead what I plan on implementing is "view this WIT with these features active".

Given that would it perhaps make more sense to change this example to showcase the gating in that regard?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to talk through the workflow you're planning for how 0.2.1 is released: I imagined that, once 0.2.1 becomes official via the WASI SG, the interfaces/functions added by 0.2.1 would receive the @since(version = "0.2.1") gate so that producer toolchains can immediately pull in the new WITs but keep their default version at 0.2.0 for the transition period where most runtimes don't yet have 0.2.1 deployed (so that the default output continues to run in most places). If that was the case, then I would imagine you would need @since (in addition to @unstable) in the short-term. But are you planning the roll-out of 0.2.1 with a different sequencing or use of gates?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh my assumption has been that when 0.2.1 is released everyone updates on their own schedule. If guest languages update before runtimes that's ok because a runtime would see an 0.2.1 import but realize it has an 0.2.0 version and would work ok. The only bad case would be when you use something only available in 0.2.1 and run it on an 0.2.0 runtime.

Given that there's no need for languages to pull in 0.2.1 WITs but pretend they're 0.2.0

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there perhaps value in an intermediate state in which:

  • The newer (0.2.1) WIT is present in the toolchain, and so it's possible to use it if you want it.
  • But it's not yet made available by default because, once it's available, various things might pull it in unnecessarily leading to real failures (because the feature is actually being used) unnecessarily. Hypothetical examples I can think of include: (1) standard libraries that use the feature if it's present, even though they had a fallback path that didn't need the feature, (2) developers who use it because not because they absolutely needed it, but because it was there and they didn't know it was going to be an issue.

```wat
(component
(type (export "i") (component
(export "ns:p/[email protected]" (instance
(export "f" (func))
))
))
)
```
If the target version was instead `1.1.0`, the same WIT document would be
encoded as:
```wat
(component
(type (export "i") (component
(export "ns:p/[email protected]" (instance
(export "f" (func))
(export "g" (func))
))
))
)
```
Thus, `@since` and `@feature` gates are not part of the runtime semantics of
components, just part of the source-level tooling for producing components.
Loading