From 88cb13f775344539f39f73b0eba1b94bae80d466 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Tue, 26 Mar 2024 15:11:24 +0100 Subject: [PATCH 1/8] feat: add support for parsing `async iterable` type --- README.md | 2 +- lib/productions/attribute.js | 4 +- lib/productions/callback.js | 13 ++ lib/productions/iterable.js | 14 +- lib/productions/operation.js | 9 ++ lib/productions/type.js | 14 ++ test/invalid/baseline/invalid-attribute.txt | 15 +- .../baseline/invalid-callback-argument.txt | 3 + .../baseline/invalid-operation-return.txt | 3 + test/invalid/idl/invalid-attribute.webidl | 6 + .../idl/invalid-callback-argument.webidl | 1 + .../idl/invalid-operation-return.webidl | 4 + ...iterable.json => async-iterable-decl.json} | 0 test/syntax/baseline/async-iterable-type.json | 131 ++++++++++++++++++ ...able.webidl => async-iterable-decl.webidl} | 0 test/syntax/idl/async-iterable-type.webidl | 7 + 16 files changed, 216 insertions(+), 10 deletions(-) create mode 100644 test/invalid/baseline/invalid-callback-argument.txt create mode 100644 test/invalid/baseline/invalid-operation-return.txt create mode 100644 test/invalid/idl/invalid-callback-argument.webidl create mode 100644 test/invalid/idl/invalid-operation-return.webidl rename test/syntax/baseline/{async-iterable.json => async-iterable-decl.json} (100%) create mode 100644 test/syntax/baseline/async-iterable-type.json rename test/syntax/idl/{async-iterable.webidl => async-iterable-decl.webidl} (100%) create mode 100644 test/syntax/idl/async-iterable-type.webidl diff --git a/README.md b/README.md index f45ee123..1a9c5bbf 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,7 @@ attached to a field called `idlType`: Where the fields are as follows: * `type`: String indicating where this type is used. Can be `null` if not applicable. -* `generic`: String indicating the generic type (e.g. "Promise", "sequence"). +* `generic`: String indicating the generic type (e.g. "Promise", "sequence", "async iterable"). * `idlType`: String indicating the type name, or array of subtypes if the type is generic or a union. * `nullable`: `true` if the type is nullable. diff --git a/lib/productions/attribute.js b/lib/productions/attribute.js index 0dab2516..0c09a486 100644 --- a/lib/productions/attribute.js +++ b/lib/productions/attribute.js @@ -75,7 +75,9 @@ export class Attribute extends Base { yield* this.extAttrs.validate(defs); yield* this.idlType.validate(defs); - if (["sequence", "record"].includes(this.idlType.generic)) { + if ( + ["async iterable", "sequence", "record"].includes(this.idlType.generic) + ) { const message = `Attributes cannot accept ${this.idlType.generic} types.`; yield validationError( this.tokens.name, diff --git a/lib/productions/callback.js b/lib/productions/callback.js index 56dba106..d8e15d50 100644 --- a/lib/productions/callback.js +++ b/lib/productions/callback.js @@ -5,6 +5,7 @@ import { unescape, autoParenter, } from "./helpers.js"; +import { validationError } from "../error.js"; export class CallbackFunction extends Base { /** @@ -44,6 +45,18 @@ export class CallbackFunction extends Base { *validate(defs) { yield* this.extAttrs.validate(defs); + for (const arg of this.arguments) { + yield* arg.validate(defs); + if (arg.idlType.generic === "async iterable") { + const message = `Callback function arguments can not be ${arg.idlType.generic} types.`; + yield validationError( + arg.tokens.name, + arg, + "operation-return-invalid-type", + message, + ); + } + } yield* this.idlType.validate(defs); } diff --git a/lib/productions/iterable.js b/lib/productions/iterable.js index cfa93c54..baff2415 100644 --- a/lib/productions/iterable.js +++ b/lib/productions/iterable.js @@ -68,9 +68,19 @@ export class IterableLike extends Base { } } - tokens.termination = - tokeniser.consume(";") || + tokens.termination = tokeniser.consume(";"); + if ( + !tokens.termination && + tokens.async && + tokens.base.value === "iterable" + ) { + // Instead of a missing semicolon, this could be an invalid async iterable + // return type for a regular operation. Let's bail out early in that case. + tokeniser.unconsume(start_position); + return; + } else if (!tokens.termination) { tokeniser.error(`Missing semicolon after ${type} declaration`); + } return ret.this; } diff --git a/lib/productions/operation.js b/lib/productions/operation.js index f4c561be..40b84b25 100644 --- a/lib/productions/operation.js +++ b/lib/productions/operation.js @@ -69,6 +69,15 @@ export class Operation extends Base { } if (this.idlType) { yield* this.idlType.validate(defs); + if (this.idlType.generic === "async iterable") { + const message = `Operations can not return ${this.idlType.generic} types.`; + yield validationError( + this.tokens.name, + this, + "operation-return-invalid-type", + message, + ); + } } for (const argument of this.arguments) { yield* argument.validate(defs); diff --git a/lib/productions/type.js b/lib/productions/type.js index d4d64165..7429dfab 100644 --- a/lib/productions/type.js +++ b/lib/productions/type.js @@ -16,7 +16,9 @@ import { ExtendedAttributes } from "./extended-attributes.js"; * @param {string} typeName */ function generic_type(tokeniser, typeName) { + const prevPosition = tokeniser.position; const base = tokeniser.consume( + "async", "FrozenArray", "ObservableArray", "Promise", @@ -29,6 +31,14 @@ function generic_type(tokeniser, typeName) { const ret = autoParenter( new Type({ source: tokeniser.source, tokens: { base } }), ); + if (base.value === "async") { + ret.tokens.async = base; + ret.tokens.base = tokeniser.consume("iterable"); + if (!ret.tokens.base) { + tokeniser.unconsume(prevPosition); // unconsume the "async" token + return; + } + } ret.tokens.open = tokeniser.consume("<") || tokeniser.error(`No opening bracket after ${base.value}`); @@ -42,6 +52,7 @@ function generic_type(tokeniser, typeName) { ret.subtype.push(subtype); break; } + case "async": case "sequence": case "FrozenArray": case "ObservableArray": { @@ -169,6 +180,8 @@ export class Type extends Base { get generic() { if (this.subtype.length && this.tokens.base) { + if (this.tokens.base.value === "iterable" && this.tokens.async) + return "async iterable"; return this.tokens.base.value; } return ""; @@ -259,6 +272,7 @@ for more information.`; const type_body = () => { if (this.union || this.generic) { return w.ts.wrap([ + w.token(this.tokens.async, w.ts.generic), w.token(this.tokens.base, w.ts.generic), w.token(this.tokens.open), ...this.subtype.map((t) => t.write(w)), diff --git a/test/invalid/baseline/invalid-attribute.txt b/test/invalid/baseline/invalid-attribute.txt index 68b15c5e..eff26aa1 100644 --- a/test/invalid/baseline/invalid-attribute.txt +++ b/test/invalid/baseline/invalid-attribute.txt @@ -1,21 +1,24 @@ (attr-invalid-type) Validation error at line 3 in invalid-attribute.webidl, inside `interface sequenceAsAttribute -> attribute invalid`: attribute sequence invalid; ^ Attributes cannot accept sequence types. -(attr-invalid-type) Validation error at line 9 in invalid-attribute.webidl, inside `interface recordAsAttribute -> attribute invalid`: +(attr-invalid-type) Validation error at line 9 in invalid-attribute.webidl, inside `interface asyncIterableAsAttribute -> attribute invalid`: + async iterable invalid; + ^ Attributes cannot accept async iterable types. +(attr-invalid-type) Validation error at line 15 in invalid-attribute.webidl, inside `interface recordAsAttribute -> attribute invalid`: invalid; ^ Attributes cannot accept record types. -(attr-invalid-type) Validation error at line 17 in invalid-attribute.webidl, inside `interface dictionaryAsAttribute -> attribute dict`: +(attr-invalid-type) Validation error at line 23 in invalid-attribute.webidl, inside `interface dictionaryAsAttribute -> attribute dict`: attribute Dict dict; ^ Attributes cannot accept dictionary types. -(attr-invalid-type) Validation error at line 18 in invalid-attribute.webidl, inside `interface dictionaryAsAttribute -> attribute dictUnion`: +(attr-invalid-type) Validation error at line 24 in invalid-attribute.webidl, inside `interface dictionaryAsAttribute -> attribute dictUnion`: attribute (Dict or boolean) dictUnion ^ Attributes cannot accept dictionary types. -(attr-invalid-type) Validation error at line 28 in invalid-attribute.webidl, inside `interface EnforceRangeInReadonlyAttribute -> attribute readOnlyAttr1`: +(attr-invalid-type) Validation error at line 34 in invalid-attribute.webidl, inside `interface EnforceRangeInReadonlyAttribute -> attribute readOnlyAttr1`: readonly attribute [EnforceRange] long readOnlyAttr1; ^ Readonly attributes cannot accept [EnforceRange] extended attribute. -(attr-invalid-type) Validation error at line 29 in invalid-attribute.webidl, inside `interface EnforceRangeInReadonlyAttribute -> attribute readOnlyAttr2`: +(attr-invalid-type) Validation error at line 35 in invalid-attribute.webidl, inside `interface EnforceRangeInReadonlyAttribute -> attribute readOnlyAttr2`: readonly attribute [EnforceRange] GPUInt32In readOnlyAttr2; ^ Readonly attributes cannot accept [EnforceRange] extended attribute. -(attr-invalid-type) Validation error at line 30 in invalid-attribute.webidl, inside `interface EnforceRangeInReadonlyAttribute -> attribute readOnlyAttr2`: +(attr-invalid-type) Validation error at line 36 in invalid-attribute.webidl, inside `interface EnforceRangeInReadonlyAttribute -> attribute readOnlyAttr2`: readonly attribute GPUInt32 readOnlyAttr2; ^ Readonly attributes cannot accept [EnforceRange] extended attribute. diff --git a/test/invalid/baseline/invalid-callback-argument.txt b/test/invalid/baseline/invalid-callback-argument.txt new file mode 100644 index 00000000..ad7c2399 --- /dev/null +++ b/test/invalid/baseline/invalid-callback-argument.txt @@ -0,0 +1,3 @@ +(operation-return-invalid-type) Validation error at line 1 in invalid-callback-argument.webidl, inside `callback DoSomething -> argument bool`: +async iterable bool); + ^ Callback function arguments can not be async iterable types. diff --git a/test/invalid/baseline/invalid-operation-return.txt b/test/invalid/baseline/invalid-operation-return.txt new file mode 100644 index 00000000..2bb314d7 --- /dev/null +++ b/test/invalid/baseline/invalid-operation-return.txt @@ -0,0 +1,3 @@ +(operation-return-invalid-type) Validation error at line 3 in invalid-operation-return.webidl, inside `interface asyncIterableReturn -> operation stream`: + async iterable stream(async iterable< + ^ Operations can not return async iterable types. diff --git a/test/invalid/idl/invalid-attribute.webidl b/test/invalid/idl/invalid-attribute.webidl index 8292e1fd..5f33c517 100644 --- a/test/invalid/idl/invalid-attribute.webidl +++ b/test/invalid/idl/invalid-attribute.webidl @@ -4,6 +4,12 @@ interface sequenceAsAttribute { attribute (sequence or boolean) invalidUnion; // TODO }; +[Exposed=Window] +interface asyncIterableAsAttribute { + attribute async iterable invalid; + attribute (async iterable or boolean) invalidUnion; // TODO +}; + [Exposed=Window] interface recordAsAttribute { attribute record invalid; diff --git a/test/invalid/idl/invalid-callback-argument.webidl b/test/invalid/idl/invalid-callback-argument.webidl new file mode 100644 index 00000000..c7ef1df6 --- /dev/null +++ b/test/invalid/idl/invalid-callback-argument.webidl @@ -0,0 +1 @@ +callback DoSomething = Promise (async iterable bool); \ No newline at end of file diff --git a/test/invalid/idl/invalid-operation-return.webidl b/test/invalid/idl/invalid-operation-return.webidl new file mode 100644 index 00000000..a697a845 --- /dev/null +++ b/test/invalid/idl/invalid-operation-return.webidl @@ -0,0 +1,4 @@ +[Exposed=Window] +interface asyncIterableReturn { + async iterable stream(async iterable foo); +}; diff --git a/test/syntax/baseline/async-iterable.json b/test/syntax/baseline/async-iterable-decl.json similarity index 100% rename from test/syntax/baseline/async-iterable.json rename to test/syntax/baseline/async-iterable-decl.json diff --git a/test/syntax/baseline/async-iterable-type.json b/test/syntax/baseline/async-iterable-type.json new file mode 100644 index 00000000..fb4ca4aa --- /dev/null +++ b/test/syntax/baseline/async-iterable-type.json @@ -0,0 +1,131 @@ +[ + { + "type": "interface", + "name": "Canvas", + "inheritance": null, + "members": [ + { + "type": "operation", + "name": "drawPolygonAsync", + "idlType": { + "type": "return-type", + "extAttrs": [], + "generic": "Promise", + "nullable": false, + "union": false, + "idlType": [ + { + "type": "return-type", + "extAttrs": [], + "generic": "", + "nullable": false, + "union": false, + "idlType": "undefined" + } + ] + }, + "arguments": [ + { + "type": "argument", + "name": "coordinates", + "extAttrs": [], + "idlType": { + "type": "argument-type", + "extAttrs": [], + "generic": "async iterable", + "nullable": false, + "union": false, + "idlType": [ + { + "type": "argument-type", + "extAttrs": [], + "generic": "", + "nullable": false, + "union": false, + "idlType": "float" + } + ] + }, + "default": null, + "optional": false, + "variadic": false + } + ], + "extAttrs": [], + "special": "" + } + ], + "extAttrs": [], + "partial": false + }, + { + "type": "interface", + "name": "I", + "inheritance": null, + "members": [ + { + "type": "operation", + "name": "f1", + "idlType": { + "type": "return-type", + "extAttrs": [], + "generic": "Promise", + "nullable": false, + "union": false, + "idlType": [ + { + "type": "return-type", + "extAttrs": [], + "generic": "", + "nullable": false, + "union": false, + "idlType": "undefined" + } + ] + }, + "arguments": [ + { + "type": "argument", + "name": "arg", + "extAttrs": [], + "idlType": { + "type": "argument-type", + "extAttrs": [], + "generic": "async iterable", + "nullable": false, + "union": false, + "idlType": [ + { + "type": "argument-type", + "extAttrs": [ + { + "type": "extended-attribute", + "name": "XAttr", + "rhs": null, + "arguments": [] + } + ], + "generic": "", + "nullable": false, + "union": false, + "idlType": "float" + } + ] + }, + "default": null, + "optional": false, + "variadic": false + } + ], + "extAttrs": [], + "special": "" + } + ], + "extAttrs": [], + "partial": false + }, + { + "type": "eof", + "value": "" + } +] diff --git a/test/syntax/idl/async-iterable.webidl b/test/syntax/idl/async-iterable-decl.webidl similarity index 100% rename from test/syntax/idl/async-iterable.webidl rename to test/syntax/idl/async-iterable-decl.webidl diff --git a/test/syntax/idl/async-iterable-type.webidl b/test/syntax/idl/async-iterable-type.webidl new file mode 100644 index 00000000..1f9d0bd6 --- /dev/null +++ b/test/syntax/idl/async-iterable-type.webidl @@ -0,0 +1,7 @@ +interface Canvas { + Promise drawPolygonAsync(async iterable coordinates); +}; + +interface I { + Promise f1(async iterable<[XAttr] float> arg); +}; From 7d78f2c14bb1f4b93e265e619ebc99bf6ecd8975 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Tue, 20 May 2025 00:20:24 +0200 Subject: [PATCH 2/8] it can be return type of operations --- lib/productions/iterable.js | 4 +- lib/productions/operation.js | 9 --- .../baseline/invalid-operation-return.txt | 3 - .../idl/invalid-callback-argument.webidl | 2 +- .../idl/invalid-operation-return.webidl | 4 -- test/syntax/baseline/async-iterable-type.json | 69 +++++++++++++++++++ test/syntax/idl/async-iterable-type.webidl | 5 ++ 7 files changed, 77 insertions(+), 19 deletions(-) delete mode 100644 test/invalid/baseline/invalid-operation-return.txt delete mode 100644 test/invalid/idl/invalid-operation-return.webidl diff --git a/lib/productions/iterable.js b/lib/productions/iterable.js index baff2415..d95f1e9a 100644 --- a/lib/productions/iterable.js +++ b/lib/productions/iterable.js @@ -74,8 +74,8 @@ export class IterableLike extends Base { tokens.async && tokens.base.value === "iterable" ) { - // Instead of a missing semicolon, this could be an invalid async iterable - // return type for a regular operation. Let's bail out early in that case. + // Instead of a missing semicolon, this could be an async iterable return + // type for a regular operation. Let's bail out early in that case. tokeniser.unconsume(start_position); return; } else if (!tokens.termination) { diff --git a/lib/productions/operation.js b/lib/productions/operation.js index 40b84b25..f4c561be 100644 --- a/lib/productions/operation.js +++ b/lib/productions/operation.js @@ -69,15 +69,6 @@ export class Operation extends Base { } if (this.idlType) { yield* this.idlType.validate(defs); - if (this.idlType.generic === "async iterable") { - const message = `Operations can not return ${this.idlType.generic} types.`; - yield validationError( - this.tokens.name, - this, - "operation-return-invalid-type", - message, - ); - } } for (const argument of this.arguments) { yield* argument.validate(defs); diff --git a/test/invalid/baseline/invalid-operation-return.txt b/test/invalid/baseline/invalid-operation-return.txt deleted file mode 100644 index 2bb314d7..00000000 --- a/test/invalid/baseline/invalid-operation-return.txt +++ /dev/null @@ -1,3 +0,0 @@ -(operation-return-invalid-type) Validation error at line 3 in invalid-operation-return.webidl, inside `interface asyncIterableReturn -> operation stream`: - async iterable stream(async iterable< - ^ Operations can not return async iterable types. diff --git a/test/invalid/idl/invalid-callback-argument.webidl b/test/invalid/idl/invalid-callback-argument.webidl index c7ef1df6..e609bad3 100644 --- a/test/invalid/idl/invalid-callback-argument.webidl +++ b/test/invalid/idl/invalid-callback-argument.webidl @@ -1 +1 @@ -callback DoSomething = Promise (async iterable bool); \ No newline at end of file +callback DoSomething = Promise (async iterable bool); diff --git a/test/invalid/idl/invalid-operation-return.webidl b/test/invalid/idl/invalid-operation-return.webidl deleted file mode 100644 index a697a845..00000000 --- a/test/invalid/idl/invalid-operation-return.webidl +++ /dev/null @@ -1,4 +0,0 @@ -[Exposed=Window] -interface asyncIterableReturn { - async iterable stream(async iterable foo); -}; diff --git a/test/syntax/baseline/async-iterable-type.json b/test/syntax/baseline/async-iterable-type.json index fb4ca4aa..41340316 100644 --- a/test/syntax/baseline/async-iterable-type.json +++ b/test/syntax/baseline/async-iterable-type.json @@ -124,6 +124,75 @@ "extAttrs": [], "partial": false }, + { + "type": "interface", + "name": "asyncIterableReturn", + "inheritance": null, + "members": [ + { + "type": "operation", + "name": "stream", + "idlType": { + "type": "return-type", + "extAttrs": [], + "generic": "async iterable", + "nullable": false, + "union": false, + "idlType": [ + { + "type": "return-type", + "extAttrs": [], + "generic": "", + "nullable": false, + "union": false, + "idlType": "short" + } + ] + }, + "arguments": [ + { + "type": "argument", + "name": "foo", + "extAttrs": [], + "idlType": { + "type": "argument-type", + "extAttrs": [], + "generic": "async iterable", + "nullable": false, + "union": false, + "idlType": [ + { + "type": "argument-type", + "extAttrs": [], + "generic": "", + "nullable": false, + "union": false, + "idlType": "short" + } + ] + }, + "default": null, + "optional": false, + "variadic": false + } + ], + "extAttrs": [], + "special": "" + } + ], + "extAttrs": [ + { + "type": "extended-attribute", + "name": "Exposed", + "rhs": { + "type": "identifier", + "value": "Window" + }, + "arguments": [] + } + ], + "partial": false + }, { "type": "eof", "value": "" diff --git a/test/syntax/idl/async-iterable-type.webidl b/test/syntax/idl/async-iterable-type.webidl index 1f9d0bd6..f0e0f8d3 100644 --- a/test/syntax/idl/async-iterable-type.webidl +++ b/test/syntax/idl/async-iterable-type.webidl @@ -5,3 +5,8 @@ interface Canvas { interface I { Promise f1(async iterable<[XAttr] float> arg); }; + +[Exposed=Window] +interface asyncIterableReturn { + async iterable stream(async iterable foo); +}; From 21c5f8b8715a25ffb2f9d1f96fdebce0c45bc695 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Wed, 23 Jul 2025 11:43:44 +0200 Subject: [PATCH 3/8] Update to async_sequence --- lib/productions/attribute.js | 2 +- lib/productions/callback.js | 6 +++--- lib/productions/operation.js | 9 +++++++++ lib/productions/type.js | 13 ++----------- lib/tokeniser.js | 1 + test/invalid/baseline/async-sequence-const.txt | 3 +++ .../invalid/baseline/async-sequence-idl-to-js.txt | 9 +++++++++ .../baseline/async-sequence-two-params.txt | 3 +++ test/invalid/baseline/invalid-attribute.txt | 15 ++++++--------- .../baseline/invalid-callback-argument.txt | 3 --- test/invalid/idl/async-sequence-const.webidl | 4 ++++ test/invalid/idl/async-sequence-idl-to-js.webidl | 9 +++++++++ test/invalid/idl/async-sequence-two-params.webidl | 4 ++++ test/invalid/idl/invalid-attribute.webidl | 6 ------ test/invalid/idl/invalid-callback-argument.webidl | 1 - ...ync-iterable-decl.json => async-iterable.json} | 0 ...ync-iterable-type.json => async-sequence.json} | 8 ++++---- test/syntax/idl/async-iterable-type.webidl | 12 ------------ ...iterable-decl.webidl => async-iterable.webidl} | 0 test/syntax/idl/async-sequence.webidl | 12 ++++++++++++ 20 files changed, 70 insertions(+), 50 deletions(-) create mode 100644 test/invalid/baseline/async-sequence-const.txt create mode 100644 test/invalid/baseline/async-sequence-idl-to-js.txt create mode 100644 test/invalid/baseline/async-sequence-two-params.txt delete mode 100644 test/invalid/baseline/invalid-callback-argument.txt create mode 100644 test/invalid/idl/async-sequence-const.webidl create mode 100644 test/invalid/idl/async-sequence-idl-to-js.webidl create mode 100644 test/invalid/idl/async-sequence-two-params.webidl delete mode 100644 test/invalid/idl/invalid-callback-argument.webidl rename test/syntax/baseline/{async-iterable-decl.json => async-iterable.json} (100%) rename test/syntax/baseline/{async-iterable-type.json => async-sequence.json} (96%) delete mode 100644 test/syntax/idl/async-iterable-type.webidl rename test/syntax/idl/{async-iterable-decl.webidl => async-iterable.webidl} (100%) create mode 100644 test/syntax/idl/async-sequence.webidl diff --git a/lib/productions/attribute.js b/lib/productions/attribute.js index 0c09a486..0c203a0b 100644 --- a/lib/productions/attribute.js +++ b/lib/productions/attribute.js @@ -76,7 +76,7 @@ export class Attribute extends Base { yield* this.idlType.validate(defs); if ( - ["async iterable", "sequence", "record"].includes(this.idlType.generic) + ["async_sequence", "sequence", "record"].includes(this.idlType.generic) ) { const message = `Attributes cannot accept ${this.idlType.generic} types.`; yield validationError( diff --git a/lib/productions/callback.js b/lib/productions/callback.js index d8e15d50..815fe693 100644 --- a/lib/productions/callback.js +++ b/lib/productions/callback.js @@ -47,12 +47,12 @@ export class CallbackFunction extends Base { yield* this.extAttrs.validate(defs); for (const arg of this.arguments) { yield* arg.validate(defs); - if (arg.idlType.generic === "async iterable") { - const message = `Callback function arguments can not be ${arg.idlType.generic} types.`; + if (arg.idlType.generic === "async_sequence") { + const message = `async_sequence types can not returned as a callback argument.`; yield validationError( arg.tokens.name, arg, - "operation-return-invalid-type", + "async-sequence-idl-to-js", message, ); } diff --git a/lib/productions/operation.js b/lib/productions/operation.js index f4c561be..ae888b81 100644 --- a/lib/productions/operation.js +++ b/lib/productions/operation.js @@ -68,6 +68,15 @@ export class Operation extends Base { yield validationError(this.tokens.open, this, "incomplete-op", message); } if (this.idlType) { + if (this.idlType.generic === "async_sequence") { + const message = `async_sequence types can not returned from an operation.`; + yield validationError( + this.idlType.tokens.base, + this, + "async-sequence-idl-to-js", + message, + ); + } yield* this.idlType.validate(defs); } for (const argument of this.arguments) { diff --git a/lib/productions/type.js b/lib/productions/type.js index 7429dfab..7a51e5e1 100644 --- a/lib/productions/type.js +++ b/lib/productions/type.js @@ -16,12 +16,11 @@ import { ExtendedAttributes } from "./extended-attributes.js"; * @param {string} typeName */ function generic_type(tokeniser, typeName) { - const prevPosition = tokeniser.position; const base = tokeniser.consume( - "async", "FrozenArray", "ObservableArray", "Promise", + "async_sequence", "sequence", "record", ); @@ -31,14 +30,6 @@ function generic_type(tokeniser, typeName) { const ret = autoParenter( new Type({ source: tokeniser.source, tokens: { base } }), ); - if (base.value === "async") { - ret.tokens.async = base; - ret.tokens.base = tokeniser.consume("iterable"); - if (!ret.tokens.base) { - tokeniser.unconsume(prevPosition); // unconsume the "async" token - return; - } - } ret.tokens.open = tokeniser.consume("<") || tokeniser.error(`No opening bracket after ${base.value}`); @@ -52,7 +43,7 @@ function generic_type(tokeniser, typeName) { ret.subtype.push(subtype); break; } - case "async": + case "async_sequence": case "sequence": case "FrozenArray": case "ObservableArray": { diff --git a/lib/tokeniser.js b/lib/tokeniser.js index 7c787a1a..a934c52a 100644 --- a/lib/tokeniser.js +++ b/lib/tokeniser.js @@ -73,6 +73,7 @@ const nonRegexTerminals = [ "ObservableArray", "Promise", "async_iterable", + "async_sequence", "bigint", "boolean", "byte", diff --git a/test/invalid/baseline/async-sequence-const.txt b/test/invalid/baseline/async-sequence-const.txt new file mode 100644 index 00000000..9cb28ef3 --- /dev/null +++ b/test/invalid/baseline/async-sequence-const.txt @@ -0,0 +1,3 @@ +Syntax error at line 3 in async-sequence-const.webidl, since `interface AsyncSequence`: + const async_sequence attribute invalid`: + attribute async_sequence invalid; + ^ Attributes cannot accept async_sequence types. +(async-sequence-idl-to-js) Validation error at line 5 in async-sequence-idl-to-js.webidl, inside `interface asyncIterableAsAttribute -> operation invalidOp`: + async_sequence invalidOp + ^ async_sequence types can not returned from an operation. +(async-sequence-idl-to-js) Validation error at line 8 in async-sequence-idl-to-js.webidl, inside `callback DoSomething -> argument bool`: + (async_sequence bool); + ^ async_sequence types can not returned as a callback argument. diff --git a/test/invalid/baseline/async-sequence-two-params.txt b/test/invalid/baseline/async-sequence-two-params.txt new file mode 100644 index 00000000..b14f1242 --- /dev/null +++ b/test/invalid/baseline/async-sequence-two-params.txt @@ -0,0 +1,3 @@ +Syntax error at line 3 in async-sequence-two-params.webidl, since `interface AsyncSequence`: + async_sequence foo( + ^ Missing closing bracket after async_sequence diff --git a/test/invalid/baseline/invalid-attribute.txt b/test/invalid/baseline/invalid-attribute.txt index eff26aa1..68b15c5e 100644 --- a/test/invalid/baseline/invalid-attribute.txt +++ b/test/invalid/baseline/invalid-attribute.txt @@ -1,24 +1,21 @@ (attr-invalid-type) Validation error at line 3 in invalid-attribute.webidl, inside `interface sequenceAsAttribute -> attribute invalid`: attribute sequence invalid; ^ Attributes cannot accept sequence types. -(attr-invalid-type) Validation error at line 9 in invalid-attribute.webidl, inside `interface asyncIterableAsAttribute -> attribute invalid`: - async iterable invalid; - ^ Attributes cannot accept async iterable types. -(attr-invalid-type) Validation error at line 15 in invalid-attribute.webidl, inside `interface recordAsAttribute -> attribute invalid`: +(attr-invalid-type) Validation error at line 9 in invalid-attribute.webidl, inside `interface recordAsAttribute -> attribute invalid`: invalid; ^ Attributes cannot accept record types. -(attr-invalid-type) Validation error at line 23 in invalid-attribute.webidl, inside `interface dictionaryAsAttribute -> attribute dict`: +(attr-invalid-type) Validation error at line 17 in invalid-attribute.webidl, inside `interface dictionaryAsAttribute -> attribute dict`: attribute Dict dict; ^ Attributes cannot accept dictionary types. -(attr-invalid-type) Validation error at line 24 in invalid-attribute.webidl, inside `interface dictionaryAsAttribute -> attribute dictUnion`: +(attr-invalid-type) Validation error at line 18 in invalid-attribute.webidl, inside `interface dictionaryAsAttribute -> attribute dictUnion`: attribute (Dict or boolean) dictUnion ^ Attributes cannot accept dictionary types. -(attr-invalid-type) Validation error at line 34 in invalid-attribute.webidl, inside `interface EnforceRangeInReadonlyAttribute -> attribute readOnlyAttr1`: +(attr-invalid-type) Validation error at line 28 in invalid-attribute.webidl, inside `interface EnforceRangeInReadonlyAttribute -> attribute readOnlyAttr1`: readonly attribute [EnforceRange] long readOnlyAttr1; ^ Readonly attributes cannot accept [EnforceRange] extended attribute. -(attr-invalid-type) Validation error at line 35 in invalid-attribute.webidl, inside `interface EnforceRangeInReadonlyAttribute -> attribute readOnlyAttr2`: +(attr-invalid-type) Validation error at line 29 in invalid-attribute.webidl, inside `interface EnforceRangeInReadonlyAttribute -> attribute readOnlyAttr2`: readonly attribute [EnforceRange] GPUInt32In readOnlyAttr2; ^ Readonly attributes cannot accept [EnforceRange] extended attribute. -(attr-invalid-type) Validation error at line 36 in invalid-attribute.webidl, inside `interface EnforceRangeInReadonlyAttribute -> attribute readOnlyAttr2`: +(attr-invalid-type) Validation error at line 30 in invalid-attribute.webidl, inside `interface EnforceRangeInReadonlyAttribute -> attribute readOnlyAttr2`: readonly attribute GPUInt32 readOnlyAttr2; ^ Readonly attributes cannot accept [EnforceRange] extended attribute. diff --git a/test/invalid/baseline/invalid-callback-argument.txt b/test/invalid/baseline/invalid-callback-argument.txt deleted file mode 100644 index ad7c2399..00000000 --- a/test/invalid/baseline/invalid-callback-argument.txt +++ /dev/null @@ -1,3 +0,0 @@ -(operation-return-invalid-type) Validation error at line 1 in invalid-callback-argument.webidl, inside `callback DoSomething -> argument bool`: -async iterable bool); - ^ Callback function arguments can not be async iterable types. diff --git a/test/invalid/idl/async-sequence-const.webidl b/test/invalid/idl/async-sequence-const.webidl new file mode 100644 index 00000000..8d48f57d --- /dev/null +++ b/test/invalid/idl/async-sequence-const.webidl @@ -0,0 +1,4 @@ +[Exposed=Window] +interface AsyncSequence { + const async_sequence ASYNC_SEQUENCE = 0; +}; diff --git a/test/invalid/idl/async-sequence-idl-to-js.webidl b/test/invalid/idl/async-sequence-idl-to-js.webidl new file mode 100644 index 00000000..4e558581 --- /dev/null +++ b/test/invalid/idl/async-sequence-idl-to-js.webidl @@ -0,0 +1,9 @@ +[Exposed=Window] +interface asyncIterableAsAttribute { + attribute async_sequence invalid; + attribute (async_sequence or boolean) invalidUnion; // TODO + async_sequence invalidOp(); +}; + +callback DoSomething = Promise (async_sequence bool); + diff --git a/test/invalid/idl/async-sequence-two-params.webidl b/test/invalid/idl/async-sequence-two-params.webidl new file mode 100644 index 00000000..472b556d --- /dev/null +++ b/test/invalid/idl/async-sequence-two-params.webidl @@ -0,0 +1,4 @@ +[Exposed=Window] +interface AsyncSequence { + async_sequence foo(); +}; diff --git a/test/invalid/idl/invalid-attribute.webidl b/test/invalid/idl/invalid-attribute.webidl index 5f33c517..8292e1fd 100644 --- a/test/invalid/idl/invalid-attribute.webidl +++ b/test/invalid/idl/invalid-attribute.webidl @@ -4,12 +4,6 @@ interface sequenceAsAttribute { attribute (sequence or boolean) invalidUnion; // TODO }; -[Exposed=Window] -interface asyncIterableAsAttribute { - attribute async iterable invalid; - attribute (async iterable or boolean) invalidUnion; // TODO -}; - [Exposed=Window] interface recordAsAttribute { attribute record invalid; diff --git a/test/invalid/idl/invalid-callback-argument.webidl b/test/invalid/idl/invalid-callback-argument.webidl deleted file mode 100644 index e609bad3..00000000 --- a/test/invalid/idl/invalid-callback-argument.webidl +++ /dev/null @@ -1 +0,0 @@ -callback DoSomething = Promise (async iterable bool); diff --git a/test/syntax/baseline/async-iterable-decl.json b/test/syntax/baseline/async-iterable.json similarity index 100% rename from test/syntax/baseline/async-iterable-decl.json rename to test/syntax/baseline/async-iterable.json diff --git a/test/syntax/baseline/async-iterable-type.json b/test/syntax/baseline/async-sequence.json similarity index 96% rename from test/syntax/baseline/async-iterable-type.json rename to test/syntax/baseline/async-sequence.json index 41340316..837523a1 100644 --- a/test/syntax/baseline/async-iterable-type.json +++ b/test/syntax/baseline/async-sequence.json @@ -32,7 +32,7 @@ "idlType": { "type": "argument-type", "extAttrs": [], - "generic": "async iterable", + "generic": "async_sequence", "nullable": false, "union": false, "idlType": [ @@ -91,7 +91,7 @@ "idlType": { "type": "argument-type", "extAttrs": [], - "generic": "async iterable", + "generic": "async_sequence", "nullable": false, "union": false, "idlType": [ @@ -135,7 +135,7 @@ "idlType": { "type": "return-type", "extAttrs": [], - "generic": "async iterable", + "generic": "async_sequence", "nullable": false, "union": false, "idlType": [ @@ -157,7 +157,7 @@ "idlType": { "type": "argument-type", "extAttrs": [], - "generic": "async iterable", + "generic": "async_sequence", "nullable": false, "union": false, "idlType": [ diff --git a/test/syntax/idl/async-iterable-type.webidl b/test/syntax/idl/async-iterable-type.webidl deleted file mode 100644 index f0e0f8d3..00000000 --- a/test/syntax/idl/async-iterable-type.webidl +++ /dev/null @@ -1,12 +0,0 @@ -interface Canvas { - Promise drawPolygonAsync(async iterable coordinates); -}; - -interface I { - Promise f1(async iterable<[XAttr] float> arg); -}; - -[Exposed=Window] -interface asyncIterableReturn { - async iterable stream(async iterable foo); -}; diff --git a/test/syntax/idl/async-iterable-decl.webidl b/test/syntax/idl/async-iterable.webidl similarity index 100% rename from test/syntax/idl/async-iterable-decl.webidl rename to test/syntax/idl/async-iterable.webidl diff --git a/test/syntax/idl/async-sequence.webidl b/test/syntax/idl/async-sequence.webidl new file mode 100644 index 00000000..199352bd --- /dev/null +++ b/test/syntax/idl/async-sequence.webidl @@ -0,0 +1,12 @@ +interface Canvas { + Promise drawPolygonAsync(async_sequence coordinates); +}; + +interface I { + Promise f1(async_sequence<[XAttr] float> arg); +}; + +[Exposed=Window] +interface asyncIterableReturn { + async_sequence stream(async_sequence foo); +}; From 7e1b0bfce4069b11faad856eebe787834a002216 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Wed, 23 Jul 2025 11:48:27 +0200 Subject: [PATCH 4/8] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1558995b..7f488b06 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ properties: * `sourceName`: the source name you passed to `parse()`. * `level`: `"error"` by default, can be `"warning"` for some validations for e.g. potential future deprecations. * `ruleName`: Only for validations. Currently the followings are supported: + * `async-iterable-idl-to-js`: `async_iterable` types cannot be returned from the IDL to JS. * `attr-invalid-type`: Attributes cannot have sequences, records, nor dictionaries. * `dict-arg-default`: Optional dictionary type arguments must have a default value of `{}`. * `dict-arg-optional`: Dictionary type arguments must be optional if the type does not include a required field. @@ -236,7 +237,7 @@ attached to a field called `idlType`: Where the fields are as follows: * `type`: String indicating where this type is used. Can be `null` if not applicable. -* `generic`: String indicating the generic type (e.g. "Promise", "sequence", "async iterable"). +* `generic`: String indicating the generic type (e.g. "Promise", "sequence", "async_sequence"). * `idlType`: String indicating the type name, or array of subtypes if the type is generic or a union. * `nullable`: `true` if the type is nullable. From 3e1098a705d3ad63dec8f5b00416b556817561c3 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Wed, 23 Jul 2025 11:53:11 +0200 Subject: [PATCH 5/8] revert the unconsume caused by LL(1) violation --- lib/productions/iterable.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/productions/iterable.js b/lib/productions/iterable.js index 79a6103a..22041800 100644 --- a/lib/productions/iterable.js +++ b/lib/productions/iterable.js @@ -71,19 +71,9 @@ export class IterableLike extends Base { } } - tokens.termination = tokeniser.consume(";"); - if ( - !tokens.termination && - tokens.async && - tokens.base.value === "iterable" - ) { - // Instead of a missing semicolon, this could be an async iterable return - // type for a regular operation. Let's bail out early in that case. - tokeniser.unconsume(start_position); - return; - } else if (!tokens.termination) { + tokens.termination = + tokeniser.consume(";") || tokeniser.error(`Missing semicolon after ${type} declaration`); - } return ret.this; } From 31fa8f9223cc178a7e6cad7dd08629bdd417819c Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Wed, 23 Jul 2025 12:06:52 +0200 Subject: [PATCH 6/8] revert usages of `async` --- lib/productions/type.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/productions/type.js b/lib/productions/type.js index 7a51e5e1..393743d0 100644 --- a/lib/productions/type.js +++ b/lib/productions/type.js @@ -171,8 +171,6 @@ export class Type extends Base { get generic() { if (this.subtype.length && this.tokens.base) { - if (this.tokens.base.value === "iterable" && this.tokens.async) - return "async iterable"; return this.tokens.base.value; } return ""; @@ -263,7 +261,6 @@ for more information.`; const type_body = () => { if (this.union || this.generic) { return w.ts.wrap([ - w.token(this.tokens.async, w.ts.generic), w.token(this.tokens.base, w.ts.generic), w.token(this.tokens.open), ...this.subtype.map((t) => t.write(w)), From c29c1d581b72c2d1e07e7d900e1a5018ceedbaee Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Wed, 23 Jul 2025 14:13:55 +0200 Subject: [PATCH 7/8] remove async_sequence return The grammar isn't wrong but it's not valid. It's covered in invalid/idl instead. --- test/syntax/baseline/async-sequence.json | 69 ------------------------ test/syntax/idl/async-sequence.webidl | 5 -- 2 files changed, 74 deletions(-) diff --git a/test/syntax/baseline/async-sequence.json b/test/syntax/baseline/async-sequence.json index 837523a1..6978e1fd 100644 --- a/test/syntax/baseline/async-sequence.json +++ b/test/syntax/baseline/async-sequence.json @@ -124,75 +124,6 @@ "extAttrs": [], "partial": false }, - { - "type": "interface", - "name": "asyncIterableReturn", - "inheritance": null, - "members": [ - { - "type": "operation", - "name": "stream", - "idlType": { - "type": "return-type", - "extAttrs": [], - "generic": "async_sequence", - "nullable": false, - "union": false, - "idlType": [ - { - "type": "return-type", - "extAttrs": [], - "generic": "", - "nullable": false, - "union": false, - "idlType": "short" - } - ] - }, - "arguments": [ - { - "type": "argument", - "name": "foo", - "extAttrs": [], - "idlType": { - "type": "argument-type", - "extAttrs": [], - "generic": "async_sequence", - "nullable": false, - "union": false, - "idlType": [ - { - "type": "argument-type", - "extAttrs": [], - "generic": "", - "nullable": false, - "union": false, - "idlType": "short" - } - ] - }, - "default": null, - "optional": false, - "variadic": false - } - ], - "extAttrs": [], - "special": "" - } - ], - "extAttrs": [ - { - "type": "extended-attribute", - "name": "Exposed", - "rhs": { - "type": "identifier", - "value": "Window" - }, - "arguments": [] - } - ], - "partial": false - }, { "type": "eof", "value": "" diff --git a/test/syntax/idl/async-sequence.webidl b/test/syntax/idl/async-sequence.webidl index 199352bd..6c5405a8 100644 --- a/test/syntax/idl/async-sequence.webidl +++ b/test/syntax/idl/async-sequence.webidl @@ -5,8 +5,3 @@ interface Canvas { interface I { Promise f1(async_sequence<[XAttr] float> arg); }; - -[Exposed=Window] -interface asyncIterableReturn { - async_sequence stream(async_sequence foo); -}; From 9ec23603af5a0dc58a6f98adf341e39b760be7d9 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Wed, 23 Jul 2025 21:54:35 +0200 Subject: [PATCH 8/8] update validation message --- lib/productions/callback.js | 2 +- lib/productions/operation.js | 2 +- test/invalid/baseline/async-sequence-idl-to-js.txt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/productions/callback.js b/lib/productions/callback.js index 815fe693..316a19f1 100644 --- a/lib/productions/callback.js +++ b/lib/productions/callback.js @@ -48,7 +48,7 @@ export class CallbackFunction extends Base { for (const arg of this.arguments) { yield* arg.validate(defs); if (arg.idlType.generic === "async_sequence") { - const message = `async_sequence types can not returned as a callback argument.`; + const message = `async_sequence types cannot be returned as a callback argument.`; yield validationError( arg.tokens.name, arg, diff --git a/lib/productions/operation.js b/lib/productions/operation.js index ae888b81..bec24a00 100644 --- a/lib/productions/operation.js +++ b/lib/productions/operation.js @@ -69,7 +69,7 @@ export class Operation extends Base { } if (this.idlType) { if (this.idlType.generic === "async_sequence") { - const message = `async_sequence types can not returned from an operation.`; + const message = `async_sequence types cannot be returned by an operation.`; yield validationError( this.idlType.tokens.base, this, diff --git a/test/invalid/baseline/async-sequence-idl-to-js.txt b/test/invalid/baseline/async-sequence-idl-to-js.txt index b4bb0e82..cfcd7b5b 100644 --- a/test/invalid/baseline/async-sequence-idl-to-js.txt +++ b/test/invalid/baseline/async-sequence-idl-to-js.txt @@ -3,7 +3,7 @@ ^ Attributes cannot accept async_sequence types. (async-sequence-idl-to-js) Validation error at line 5 in async-sequence-idl-to-js.webidl, inside `interface asyncIterableAsAttribute -> operation invalidOp`: async_sequence invalidOp - ^ async_sequence types can not returned from an operation. + ^ async_sequence types cannot be returned by an operation. (async-sequence-idl-to-js) Validation error at line 8 in async-sequence-idl-to-js.webidl, inside `callback DoSomething -> argument bool`: (async_sequence bool); - ^ async_sequence types can not returned as a callback argument. + ^ async_sequence types cannot be returned as a callback argument.