From e0e2cf90d8f08d31634cede7d0a3993a675194b9 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Mon, 1 Apr 2024 17:00:54 -0500 Subject: [PATCH 01/10] Add @feature and @since gates to WIT --- design/mvp/WIT.md | 96 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/design/mvp/WIT.md b/design/mvp/WIT.md index e0d09db2..0b249b81 100644 --- a/design/mvp/WIT.md +++ b/design/mvp/WIT.md @@ -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. + +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 +`@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: @@ -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' '=' '"' '"' ')' +feature-gate ::= '@feature' '(' 'name' '=' '"' id '"' ')' + +interface-definition ::= typedef-item + | use-item + | func-item typedef-item ::= resource-item | variant-items @@ -970,6 +1015,7 @@ named-type-list ::= ϵ named-type ::= id ':' ty ``` + ## Item: `use` A `use` statement enables importing type or resource definitions from other @@ -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:p@1.1.0; + +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`: +```wat +(component + (type (export "i") (component + (export "ns:p/i@1.0.0" (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/i@1.1.0" (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. From d7f49173733b09a5b8a264fc9b1b21d88ce9e8b2 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Wed, 10 Apr 2024 14:39:32 -0500 Subject: [PATCH 02/10] Switch @feature to @unstable, add an optional 'feature' field to @since instead of allowing both --- design/mvp/WIT.md | 55 ++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/design/mvp/WIT.md b/design/mvp/WIT.md index 0b249b81..13085868 100644 --- a/design/mvp/WIT.md +++ b/design/mvp/WIT.md @@ -945,35 +945,37 @@ interface foo { @since(version = "0.2.1") b: func(); - @since(version = "0.2.2") - @feature(name = "fancy-foo") + @since(version = "0.2.2", feature = "fancy-foo") c: func(); - @feature(name = "fancier-foo") + @unstable(feature = "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 +`@since` gate is that, once applied to an item, the item is not 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 +In contrast, the `@unstable` 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 -`@feature` gate is that toolchains will not expose `@feature`-only-gated items -by default unless explicitly opted-into by the developer. +`@unstable` gate is that toolchains will not expose `@unstable` features 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. +with an `@unstable` gate while the details are still being hashed out. Then, +once the feature is stable (and, in a WASI context, voted upon), the +`@unstable` gate is switched to a `@since` gate. To enable a smooth transition +(during which producer toolchains are targeting a version earlier than the +`@since`-specified `version`), the `@since` gate contains an optional `feature` +field that, when present, says to enable the feature when *either* the target +version is greator-or-equal *or* the feature name is explicitly enabled by the +developer. Thus, `c` is enabled if the version is `0.2.2` or newer or the +`fancy-foo` feature is explicitly enabled by the developer. The `feature` field +can be removed once producer toolchains have updated their default version to +enable the feature by default. Specifically interfaces have the structure: @@ -984,9 +986,11 @@ interface-item ::= 'interface' id '{' interface-items* '}' interface-items ::= gate interface-definition -gate ::= since-gate? feature-gate? -since-gate ::= '@since' '(' 'version' '=' '"' '"' ')' -feature-gate ::= '@feature' '(' 'name' '=' '"' id '"' ')' +gate ::= unstable-gate + | since-gate +unstable-gate ::= '@unstable' '(' feature-field ')' +feature-field ::= 'feature' '=' '"' id '"' +since-gate ::= '@since' '(' 'version' '=' '"' '"' ( ',' feature-field )? ')' interface-definition ::= typedef-item | use-item @@ -1672,12 +1676,13 @@ 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. +Unlike most other WIT constructs, the `@since` and `@unstable` gates are not +represented in the component binary. Instead, they 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, the target version and set of explicitly-enabled feature names +determine whether individual gated features are included in the encoded type or +not. For example, the following WIT document: ```wit @@ -1712,5 +1717,5 @@ encoded as: )) ) ``` -Thus, `@since` and `@feature` gates are not part of the runtime semantics of +Thus, `@since` and `@unstable` gates are not part of the runtime semantics of components, just part of the source-level tooling for producing components. From 8c066b49f1662ee5f622e93d51750ed4aa3a0b0f Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Fri, 19 Apr 2024 15:19:43 -0500 Subject: [PATCH 03/10] Extend example to cover transitive gating --- design/mvp/WIT.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/design/mvp/WIT.md b/design/mvp/WIT.md index 13085868..447eb655 100644 --- a/design/mvp/WIT.md +++ b/design/mvp/WIT.md @@ -1689,10 +1689,15 @@ For example, the following WIT document: package ns:p@1.1.0; interface i { - f: func() + f: func(); @since(version = "1.1.0") - g: func() + g: func(); + + @since(version = "1.1.0") + type t1 = u32; + + type t2 = t1 } ``` is encoded as the following component when the target version is `1.0.0`: @@ -1705,6 +1710,7 @@ is encoded as the following component when the target version is `1.0.0`: )) ) ``` +Note that `t2` is transitively disabled since it relied on the gated `t1`. If the target version was instead `1.1.0`, the same WIT document would be encoded as: ```wat @@ -1713,6 +1719,8 @@ encoded as: (export "ns:p/i@1.1.0" (instance (export "f" (func)) (export "g" (func)) + (export $t1 "t1" (type (eq u32))) + (export "t2" (type (eq $t1))) )) )) ) From 8d34a706c056c2643bcefea44c1319831c5ca982 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Fri, 19 Apr 2024 15:33:32 -0500 Subject: [PATCH 04/10] Add gates to 'interface' and 'world' definitions --- design/mvp/WIT.md | 105 +++++++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/design/mvp/WIT.md b/design/mvp/WIT.md index 447eb655..aaa05ae4 100644 --- a/design/mvp/WIT.md +++ b/design/mvp/WIT.md @@ -851,6 +851,61 @@ Concretely, the structure of a `wit` file is: wit-file ::= package-decl? (toplevel-use-item | interface-item | world-item)* ``` +### Feature Gates + +Various WIT items can be "gated", to reflect the fact that the item is part of +an unstable feature or that the item was added as part of a minor version +update and shouldn't be used when targeting an earlier minor version. + +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 = "fancy-foo") + c: func(); + + @unstable(feature = "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 not modified +incompatibly going forward (according to general semantic versioning rules). + +In contrast, the `@unstable` 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 +`@unstable` gate is that toolchains will not expose `@unstable` features by +default unless explicitly opted-into by the developer. + +Together, these gates support a development flow in which new features start +with an `@unstable` gate while the details are still being hashed out. Then, +once the feature is stable (and, in a WASI context, voted upon), the +`@unstable` gate is switched to a `@since` gate. To enable a smooth transition +(during which producer toolchains are targeting a version earlier than the +`@since`-specified `version`), the `@since` gate contains an optional `feature` +field that, when present, says to enable the feature when *either* the target +version is greator-or-equal *or* the feature name is explicitly enabled by the +developer. Thus, `c` is enabled if the version is `0.2.2` or newer or the +`fancy-foo` feature is explicitly enabled by the developer. The `feature` field +can be removed once producer toolchains have updated their default version to +enable the feature by default. + +Specifically, the syntax for feature gates is: +```wit +gate ::= unstable-gate + | since-gate +unstable-gate ::= '@unstable' '(' feature-field ')' +feature-field ::= 'feature' '=' '"' id '"' +since-gate ::= '@since' '(' 'version' '=' '"' '"' ( ',' feature-field )? ')' +``` + ## Package declaration WIT files optionally start with a package declaration which defines the ID of @@ -895,7 +950,7 @@ Worlds define a [componenttype](https://github.com/WebAssembly/component-model/b Concretely, the structure of a world is: ```ebnf -world-item ::= 'world' id '{' world-items* '}' +world-item ::= gate 'world' id '{' world-items* '}' world-items ::= export-item | import-item | use-item | typedef-item | include-item @@ -937,61 +992,15 @@ Interfaces can be defined in a `wit` file. Interfaces have a name and a sequence of items and functions, all of which can be gated on an `id` or a semantic version. -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 = "fancy-foo") - c: func(); - - @unstable(feature = "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 not modified -incompatibly going forward (according to general semantic versioning rules). - -In contrast, the `@unstable` 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 -`@unstable` gate is that toolchains will not expose `@unstable` features by -default unless explicitly opted-into by the developer. - -Together, these gates support a development flow in which new features start -with an `@unstable` gate while the details are still being hashed out. Then, -once the feature is stable (and, in a WASI context, voted upon), the -`@unstable` gate is switched to a `@since` gate. To enable a smooth transition -(during which producer toolchains are targeting a version earlier than the -`@since`-specified `version`), the `@since` gate contains an optional `feature` -field that, when present, says to enable the feature when *either* the target -version is greator-or-equal *or* the feature name is explicitly enabled by the -developer. Thus, `c` is enabled if the version is `0.2.2` or newer or the -`fancy-foo` feature is explicitly enabled by the developer. The `feature` field -can be removed once producer toolchains have updated their default version to -enable the feature by default. - Specifically interfaces have the structure: > **Note**: The symbol `ε`, also known as Epsilon, denotes an empty string. ```ebnf -interface-item ::= 'interface' id '{' interface-items* '}' +interface-item ::= gate 'interface' id '{' interface-items* '}' interface-items ::= gate interface-definition -gate ::= unstable-gate - | since-gate -unstable-gate ::= '@unstable' '(' feature-field ')' -feature-field ::= 'feature' '=' '"' id '"' -since-gate ::= '@since' '(' 'version' '=' '"' '"' ( ',' feature-field )? ')' - interface-definition ::= typedef-item | use-item | func-item From a6c9c98cc44500c8e9da1cb2bd91db3087d2d8d3 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Fri, 19 Apr 2024 15:41:44 -0500 Subject: [PATCH 05/10] Remove now-out-of-context verbiage --- design/mvp/WIT.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/design/mvp/WIT.md b/design/mvp/WIT.md index aaa05ae4..93081345 100644 --- a/design/mvp/WIT.md +++ b/design/mvp/WIT.md @@ -989,8 +989,7 @@ 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, all of which can be gated on an `id` or a -semantic version. +sequence of items and functions, all of which can be gated. Specifically interfaces have the structure: From ca34dccc1a8b45980f0765d6b035003ef70d0ab7 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Fri, 19 Apr 2024 15:42:33 -0500 Subject: [PATCH 06/10] Also make world items gate-able --- design/mvp/WIT.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/design/mvp/WIT.md b/design/mvp/WIT.md index 93081345..778bfaa1 100644 --- a/design/mvp/WIT.md +++ b/design/mvp/WIT.md @@ -945,14 +945,21 @@ nesting both namespaces and packages, which would then generalize the syntax of ## Item: `world` -Worlds define a [componenttype](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#type-definitions) as a collection of imports and exports. +Worlds define a [`componenttype`] as a collection of imports and exports, all +of which can be gated. Concretely, the structure of a world is: ```ebnf world-item ::= gate 'world' id '{' world-items* '}' -world-items ::= export-item | import-item | use-item | typedef-item | include-item +world-items ::= gate world-definition + +world-definition ::= export-item + | import-item + | use-item + | typedef-item + | include-item export-item ::= 'export' id ':' extern-type | 'export' use-path ';' @@ -967,6 +974,8 @@ 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 to `interface` items. +[`componenttype`]: Explainer.md#type-definitions + ## Item: `include` A `include` statement enables the union of the current world with another world. The structure of an `include` statement is: From efbde41222717ec7a6ba5c34f928d870c64e3c1d Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Fri, 19 Apr 2024 15:57:19 -0500 Subject: [PATCH 07/10] Remove quotes around the since it's not a full string literal --- design/mvp/WIT.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/design/mvp/WIT.md b/design/mvp/WIT.md index 778bfaa1..c0051c51 100644 --- a/design/mvp/WIT.md +++ b/design/mvp/WIT.md @@ -865,10 +865,10 @@ interface foo { @since(version = "0.2.1") b: func(); - @since(version = "0.2.2", feature = "fancy-foo") + @since(version = "0.2.2", feature = fancy-foo) c: func(); - @unstable(feature = "fancier-foo") + @unstable(feature = fancier-foo) d: func(); } ``` @@ -902,7 +902,7 @@ Specifically, the syntax for feature gates is: gate ::= unstable-gate | since-gate unstable-gate ::= '@unstable' '(' feature-field ')' -feature-field ::= 'feature' '=' '"' id '"' +feature-field ::= 'feature' '=' id since-gate ::= '@since' '(' 'version' '=' '"' '"' ( ',' feature-field )? ')' ``` From 86453cf57cfc4d4a7be5e17eed1354a5f2429618 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Fri, 19 Apr 2024 16:19:27 -0500 Subject: [PATCH 08/10] Actually, make it a validation error --- design/mvp/WIT.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/design/mvp/WIT.md b/design/mvp/WIT.md index c0051c51..dfe504f6 100644 --- a/design/mvp/WIT.md +++ b/design/mvp/WIT.md @@ -905,6 +905,8 @@ unstable-gate ::= '@unstable' '(' feature-field ')' feature-field ::= 'feature' '=' id since-gate ::= '@since' '(' 'version' '=' '"' '"' ( ',' feature-field )? ')' ``` +As part of WIT validation, any item that refers to another gated item must also +be gated. ## Package declaration @@ -1710,11 +1712,6 @@ interface i { @since(version = "1.1.0") g: func(); - - @since(version = "1.1.0") - type t1 = u32; - - type t2 = t1 } ``` is encoded as the following component when the target version is `1.0.0`: @@ -1727,7 +1724,6 @@ is encoded as the following component when the target version is `1.0.0`: )) ) ``` -Note that `t2` is transitively disabled since it relied on the gated `t1`. If the target version was instead `1.1.0`, the same WIT document would be encoded as: ```wat @@ -1736,8 +1732,6 @@ encoded as: (export "ns:p/i@1.1.0" (instance (export "f" (func)) (export "g" (func)) - (export $t1 "t1" (type (eq u32))) - (export "t2" (type (eq $t1))) )) )) ) From 3ebfc0432c0c6dfd2e00d96287ae015d1859adc5 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Fri, 19 Apr 2024 16:24:52 -0500 Subject: [PATCH 09/10] Require transitive gating, and add examples --- design/mvp/WIT.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/design/mvp/WIT.md b/design/mvp/WIT.md index dfe504f6..83f70650 100644 --- a/design/mvp/WIT.md +++ b/design/mvp/WIT.md @@ -905,8 +905,28 @@ unstable-gate ::= '@unstable' '(' feature-field ')' feature-field ::= 'feature' '=' id since-gate ::= '@since' '(' 'version' '=' '"' '"' ( ',' feature-field )? ')' ``` + As part of WIT validation, any item that refers to another gated item must also -be gated. +be compatibly gated. For example, this is an error: +```wit +interface i { + @since("1.0.1") + type t1 = u32; + + type t2 = t1; // error +} +``` +Additionally, if an item is *contained* by a gated item, it must also be +compatibly gated. For example, this is an error: +```wit +@since("1.0.2") +interface i { + foo: func(); // error: no gate + + @since("1.0.1") + bar: func(); // also error: weaker gate +} +``` ## Package declaration From 8d75db5fb4cc8281e41bb84f2e90f98b3de6dd5b Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Fri, 19 Apr 2024 16:33:38 -0500 Subject: [PATCH 10/10] Also remove quotes from versions, and fix previous syntax error in example --- design/mvp/WIT.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/design/mvp/WIT.md b/design/mvp/WIT.md index 83f70650..b97f316a 100644 --- a/design/mvp/WIT.md +++ b/design/mvp/WIT.md @@ -862,10 +862,10 @@ For example, the following interface has 4 items, 3 of which are gated: interface foo { a: func(); - @since(version = "0.2.1") + @since(version = 0.2.1) b: func(); - @since(version = "0.2.2", feature = fancy-foo) + @since(version = 0.2.2, feature = fancy-foo) c: func(); @unstable(feature = fancier-foo) @@ -903,14 +903,14 @@ gate ::= unstable-gate | since-gate unstable-gate ::= '@unstable' '(' feature-field ')' feature-field ::= 'feature' '=' id -since-gate ::= '@since' '(' 'version' '=' '"' '"' ( ',' feature-field )? ')' +since-gate ::= '@since' '(' 'version' '=' ( ',' feature-field )? ')' ``` As part of WIT validation, any item that refers to another gated item must also be compatibly gated. For example, this is an error: ```wit interface i { - @since("1.0.1") + @since(version = 1.0.1) type t1 = u32; type t2 = t1; // error @@ -919,11 +919,11 @@ interface i { Additionally, if an item is *contained* by a gated item, it must also be compatibly gated. For example, this is an error: ```wit -@since("1.0.2") +@since(version = 1.0.2) interface i { foo: func(); // error: no gate - @since("1.0.1") + @since(version = 1.0.1) bar: func(); // also error: weaker gate } ``` @@ -1730,7 +1730,7 @@ package ns:p@1.1.0; interface i { f: func(); - @since(version = "1.1.0") + @since(version = 1.1.0) g: func(); } ```