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); +};