Skip to content

feat: implement new async_iterable syntax #819

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

Merged
merged 2 commits into from
Jul 23, 2025
Merged
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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ For `"Infinity"`:

* `negative`: Boolean indicating whether this is negative Infinity or not.

### `iterable<>`, `async iterable<>`, `maplike<>`, and `setlike<>` declarations
### `iterable<>`, `async_iterable<>`, `maplike<>`, and `setlike<>` declarations

These appear as members of interfaces that look like this:

Expand All @@ -755,7 +755,7 @@ These appear as members of interfaces that look like this:
"idlType": /* One or two types */ ,
"readonly": false, // only for maplike and setlike
"async": false, // iterable can be async
"arguments": [], // only for async iterable
"arguments": [], // only for async_iterable
"extAttrs": [],
"parent": { ... }
}
Expand All @@ -766,8 +766,8 @@ The fields are as follows:
* `type`: Always one of "iterable", "maplike" or "setlike".
* `idlType`: An array with one or more [IDL Types](#idl-type) representing the declared type arguments.
* `readonly`: `true` if the maplike or setlike is declared as read only.
* `async`: `true` if the type is async iterable.
* `arguments`: An array of arguments if exists, empty otherwise. Currently only `async iterable` supports the syntax.
* `async`: `true` if the type is `async iterable`. Note that it's false for the new `async_iterable`.
* `arguments`: An array of arguments if exists, empty otherwise. Currently only `async_iterable` supports the syntax.
* `extAttrs`: An array of [extended attributes](#extended-attributes).
* `parent`: The container of this type as an Object.

Expand Down
36 changes: 33 additions & 3 deletions lib/productions/iterable.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { validationError } from "../error.js";
import { Base } from "./base.js";
import {
type_with_extended_attributes,
Expand All @@ -23,16 +24,18 @@ export class IterableLike extends Base {
? tokeniser.consume("maplike", "setlike")
: tokens.async
? tokeniser.consume("iterable")
: tokeniser.consume("iterable", "maplike", "setlike");
: tokeniser.consume("iterable", "async_iterable", "maplike", "setlike");
if (!tokens.base) {
tokeniser.unconsume(start_position);
return;
}

const { type } = ret;
const secondTypeRequired = type === "maplike";
const secondTypeAllowed = secondTypeRequired || type === "iterable";
const argumentAllowed = ret.async && type === "iterable";
const secondTypeAllowed =
secondTypeRequired || type === "iterable" || type === "async_iterable";
const argumentAllowed =
type === "async_iterable" || (ret.async && type === "iterable");

tokens.open =
tokeniser.consume("<") ||
Expand Down Expand Up @@ -86,6 +89,18 @@ export class IterableLike extends Base {
}

*validate(defs) {
if (this.async && this.type === "iterable") {
const message = "`async iterable` is now changed to `async_iterable`.";
yield validationError(
this.tokens.async,
this,
"obsolete-async-iterable-syntax",
message,
{
autofix: autofixAsyncIterableSyntax(this),
},
);
}
for (const type of this.idlType) {
yield* type.validate(defs);
}
Expand Down Expand Up @@ -114,3 +129,18 @@ export class IterableLike extends Base {
);
}
}

/**
* @param {IterableLike} iterableLike
*/
function autofixAsyncIterableSyntax(iterableLike) {
return () => {
const async = iterableLike.tokens.async;
iterableLike.tokens.base = {
...async,
type: "async_iterable",
value: "async_iterable",
};
delete iterableLike.tokens.async;
};
}
1 change: 1 addition & 0 deletions lib/tokeniser.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const nonRegexTerminals = [
"NaN",
"ObservableArray",
"Promise",
"async_iterable",
"bigint",
"boolean",
"byte",
Expand Down
16 changes: 16 additions & 0 deletions test/autofix.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,20 @@ describe("Writer template functions", () => {
`;
expect(autofix(input)).toBe(output);
});

it("should replace `async iterable` to `async_iterable`", () => {
const input = `
[Exposed=Window]
interface AsyncIterable {
async iterable<long, short>;
};
`;
const output = `
[Exposed=Window]
interface AsyncIterable {
async_iterable<long, short>;
};
`;
expect(autofix(input)).toBe(output);
});
});
2 changes: 1 addition & 1 deletion test/invalid/baseline/argument-dict-default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
(dict-arg-default) Validation error at line 18 in argument-dict-default.webidl, inside `interface X -> operation z -> argument union`:
undefined z(optional Union union);
^ Optional dictionary arguments must have a default value of `{}`.
(dict-arg-default) Validation error at line 22 in argument-dict-default.webidl, inside `interface X -> iterable -> argument union`:
(dict-arg-default) Validation error at line 22 in argument-dict-default.webidl, inside `interface X -> async_iterable -> argument union`:
DOMString>(optional Union union);
^ Optional dictionary arguments must have a default value of `{}`.
2 changes: 1 addition & 1 deletion test/invalid/baseline/argument-dict-nullable.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ boolean or Dict)? union = {})
(no-nullable-dict-arg) Validation error at line 17 in argument-dict-nullable.webidl, inside `interface X -> operation r -> argument req`:
undefined r(Required? req);
^ Dictionary arguments cannot be nullable.
(no-nullable-dict-arg) Validation error at line 19 in argument-dict-nullable.webidl, inside `interface X -> iterable -> argument dict`:
(no-nullable-dict-arg) Validation error at line 19 in argument-dict-nullable.webidl, inside `interface X -> async_iterable -> argument dict`:
>(optional Dict? dict);
^ Dictionary arguments cannot be nullable.
2 changes: 1 addition & 1 deletion test/invalid/baseline/argument-dict-optional.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
(dict-arg-optional) Validation error at line 38 in argument-dict-optional.webidl, inside `interface mixin Container -> operation op8 -> argument lastRequired`:
undefined op8(Optional lastRequired, optional DOMString yay
^ Dictionary argument must be optional if it has no required fields
(dict-arg-optional) Validation error at line 44 in argument-dict-optional.webidl, inside `interface ContainerInterface -> iterable -> argument shouldBeOptional`:
(dict-arg-optional) Validation error at line 44 in argument-dict-optional.webidl, inside `interface ContainerInterface -> async_iterable -> argument shouldBeOptional`:
<DOMString>(Optional shouldBeOptional);
^ Dictionary argument must be optional if it has no required fields
2 changes: 1 addition & 1 deletion test/invalid/baseline/async-iterable-readonly.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Syntax error at line 3 in async-iterable-readonly.webidl, since `interface AsyncIterable`:
readonly async iterable<long
readonly async_iterable<long,
^ Missing return type
3 changes: 3 additions & 0 deletions test/invalid/baseline/async-space-iterable.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(obsolete-async-iterable-syntax) Validation error at line 3 in async-space-iterable.webidl:
async iterable<long,
^ `async iterable` is now changed to `async_iterable`.
2 changes: 1 addition & 1 deletion test/invalid/idl/argument-dict-default.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ interface X {
undefined z2(optional Union union = {});
undefined r(Required req);

async iterable<DOMString>(optional Union union);
async_iterable<DOMString>(optional Union union);
};
2 changes: 1 addition & 1 deletion test/invalid/idl/argument-dict-nullable.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ interface X {
undefined z2(optional Union? union = {});
undefined r(Required? req);

async iterable<DOMString>(optional Dict? dict);
async_iterable<DOMString>(optional Dict? dict);
};
2 changes: 1 addition & 1 deletion test/invalid/idl/argument-dict-optional.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,5 @@ interface mixin Container {

[Exposed=Window]
interface ContainerInterface {
async iterable<DOMString>(Optional shouldBeOptional);
async_iterable<DOMString>(Optional shouldBeOptional);
};
2 changes: 1 addition & 1 deletion test/invalid/idl/async-iterable-readonly.webidl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[Exposed=Window]
interface AsyncIterable {
readonly async iterable<long, short>;
readonly async_iterable<long, short>;
};
2 changes: 1 addition & 1 deletion test/invalid/idl/async-iterable-unterminated-args.webidl
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
interface X {
async iterable<DOMString>(
async_iterable<DOMString>(
};
4 changes: 4 additions & 0 deletions test/invalid/idl/async-space-iterable.webidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[Exposed=Window]
interface AsyncIterable {
async iterable<long, short>;
};
28 changes: 14 additions & 14 deletions test/syntax/baseline/async-iterable.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"inheritance": null,
"members": [
{
"type": "iterable",
"type": "async_iterable",
"idlType": [
{
"type": null,
Expand All @@ -27,7 +27,7 @@
"arguments": [],
"extAttrs": [],
"readonly": false,
"async": true
"async": false
}
],
"extAttrs": [],
Expand All @@ -39,7 +39,7 @@
"inheritance": null,
"members": [
{
"type": "iterable",
"type": "async_iterable",
"idlType": [
{
"type": null,
Expand Down Expand Up @@ -75,7 +75,7 @@
"arguments": [],
"extAttrs": [],
"readonly": false,
"async": true
"async": false
}
],
"extAttrs": [],
Expand All @@ -87,7 +87,7 @@
"inheritance": null,
"members": [
{
"type": "iterable",
"type": "async_iterable",
"idlType": [
{
"type": null,
Expand All @@ -109,7 +109,7 @@
"arguments": [],
"extAttrs": [],
"readonly": false,
"async": true
"async": false
}
],
"extAttrs": [],
Expand All @@ -121,7 +121,7 @@
"inheritance": null,
"members": [
{
"type": "iterable",
"type": "async_iterable",
"idlType": [
{
"type": null,
Expand Down Expand Up @@ -160,7 +160,7 @@
],
"extAttrs": [],
"readonly": false,
"async": true
"async": false
}
],
"extAttrs": [],
Expand All @@ -172,7 +172,7 @@
"inheritance": null,
"members": [
{
"type": "iterable",
"type": "async_iterable",
"idlType": [
{
"type": null,
Expand All @@ -186,7 +186,7 @@
"arguments": [],
"extAttrs": [],
"readonly": false,
"async": true
"async": false
}
],
"extAttrs": [],
Expand All @@ -198,7 +198,7 @@
"inheritance": null,
"members": [
{
"type": "iterable",
"type": "async_iterable",
"idlType": [
{
"type": null,
Expand All @@ -212,7 +212,7 @@
"arguments": [],
"extAttrs": [],
"readonly": false,
"async": true
"async": false
}
],
"extAttrs": [],
Expand All @@ -224,7 +224,7 @@
"inheritance": null,
"members": [
{
"type": "iterable",
"type": "async_iterable",
"idlType": [
{
"type": null,
Expand Down Expand Up @@ -271,7 +271,7 @@
],
"extAttrs": [],
"readonly": false,
"async": true
"async": false
}
],
"extAttrs": [],
Expand Down
14 changes: 7 additions & 7 deletions test/syntax/idl/async-iterable.webidl
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
interface AsyncIterable {
async iterable<long, float>;
async_iterable<long, float>;
};

interface AsyncIterableWithExtAttr {
async iterable<[XAttr2] DOMString, [XAttr3] long>;
async_iterable<[XAttr2] DOMString, [XAttr3] long>;
};

interface AsyncIterableWithNoParam {
async iterable<float, ByteString>();
async_iterable<float, ByteString>();
};

interface AsyncIterableWithParam {
async iterable<float, ByteString>(USVString str);
async_iterable<float, ByteString>(USVString str);
};

interface AsyncValueIterable {
async iterable<float>;
async_iterable<float>;
};

interface AsyncValueIterableWithNoParam {
async iterable<float>();
async_iterable<float>();
};

interface AsyncValueIterableWithParams {
async iterable<float>(DOMString str, short s);
async_iterable<float>(DOMString str, short s);
};