Skip to content
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
99 changes: 99 additions & 0 deletions lib/output/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,103 @@ function isAccessorDescriptor(desc) {
return Object.hasOwn(desc, "get") || Object.hasOwn(desc, "set");
}

function getMethod(value, property, errPrefix = "The provided value") {
const func = value[property];
if (func === undefined || func === null) {
return undefined;
}
if (typeof func !== "function") {
throw new TypeError(`${errPrefix}'s ${property} property is not a function.`);
}
return func;
}

function createAsyncFromSyncIterator(syncIterator) {
// Instead of re-implementing CreateAsyncFromSyncIterator and %AsyncFromSyncIteratorPrototype%,
// we use yield* inside an async generator function to achieve the same result.

// Wrap the sync iterator inside a sync iterable, so we can use it with yield*.
const syncIterable = {
[Symbol.iterator]: () => syncIterator
};
// Create an async generator function and immediately invoke it.
const asyncIterator = (async function* () {
return yield* syncIterable;
})();
// Return as an async iterator record.
return asyncIterator;
}

function convertAsyncSequence(object, itemConverter, errPrefix = "The provided value") {
if (!isObject(object)) {
throw new TypeError(`${errPrefix} is not an object.`);
}
let method = getMethod(object, Symbol.asyncIterator, errPrefix);
let type = "async";
if (method === undefined) {
method = getMethod(object, Symbol.iterator, errPrefix);
if (method === undefined) {
throw new TypeError(`${errPrefix} is not an async iterable object.`);
}
type = "sync";
}

return {
object,
method,
type,
// The wrapperSymbol ensures that if the async sequence is used as a return value,
// that it exposes the original JavaScript value.
// https://webidl.spec.whatwg.org/#js-async-iterable
[wrapperSymbol]: object,
// Implement the async iterator protocol, so users can iterate
// the async sequence directly (e.g. with for await...of)
// instead of needing to call a separate helper function to open the async sequence.
// https://webidl.spec.whatwg.org/#async-sequence-open
[Symbol.asyncIterator]() {
return openAsyncSequence(object, method, type, itemConverter, `${errPrefix}'s iterator`);
}
};
}

function openAsyncSequence(object, method, type, itemConverter, errPrefix = "The provided value") {
let iterator = call(method, object);
if (!isObject(iterator)) {
throw new TypeError(`${errPrefix}'s method must return an object`);
}
if (type === "sync") {
iterator = createAsyncFromSyncIterator(iterator);
}
const nextMethod = iterator.next;
return {
async next() {
const nextResult = await call(nextMethod, iterator);
if (!isObject(nextResult)) {
throw new TypeError(`${errPrefix}'s next method must return an object`);
}
const { done, value } = nextResult;
if (done) {
return { done: true, value: undefined };
}
return { done: false, value: itemConverter(value) };
},
async return(reason) {
const returnMethod = getMethod(iterator, "return", errPrefix);
if (returnMethod === undefined) {
return { done: true, value: undefined };
}
const returnResult = await call(returnMethod, iterator, reason);
if (!isObject(returnResult)) {
throw new TypeError(`${errPrefix}'s return method must return an object`);
}
return { done: true, value: undefined };
},
[Symbol.asyncIterator]() {
return this;
}
};
}

const supportsPropertyIndex = Symbol("supports property index");
const supportedPropertyIndices = Symbol("supported property indices");
const supportsPropertyName = Symbol("supports property name");
Expand Down Expand Up @@ -232,6 +329,8 @@ module.exports = exports = {
isArrayBuffer,
isSharedArrayBuffer,
isArrayIndexPropName,
getMethod,
convertAsyncSequence,
supportsPropertyIndex,
supportedPropertyIndices,
supportsPropertyName,
Expand Down
49 changes: 44 additions & 5 deletions lib/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ function generateTypeConversion(
if (idlType.union) {
// union type
generateUnion();
} else if (idlType.generic === "async_sequence") {
// async_sequence type
generateAsyncSequence();
} else if (idlType.generic === "sequence") {
// sequence type
generateSequence();
Expand Down Expand Up @@ -278,14 +281,21 @@ function generateTypeConversion(
let code = `if (utils.isObject(${name})) {`;

if (union.sequenceLike) {
code += `if (${name}[Symbol.iterator] !== undefined) {`;
if (union.sequenceLike.generic === "async_sequence") {
code += `if (
utils.getMethod(${name}, Symbol.asyncIterator, ${errPrefix}) !== undefined ||
utils.getMethod(${name}, Symbol.iterator, ${errPrefix}) !== undefined
) {`;
} else {
code += `if (utils.getMethod(${name}, Symbol.iterator, ${errPrefix}) !== undefined) {`;
}
const conv = generateTypeConversion(
ctx,
name,
union.sequenceLike,
[],
parentName,
`${errPrefix} + " sequence"`
`${errPrefix} + " ${union.sequenceLike.generic}"`
);
requires.merge(conv.requires);
code += conv.body;
Expand Down Expand Up @@ -362,6 +372,29 @@ function generateTypeConversion(
str += output.join(" else ");
}

function generateAsyncSequence() {
const conv = generateTypeConversion(
ctx,
"item",
idlType.idlType[0],
[],
parentName,
`${errPrefix} + "'s element"`
);
requires.merge(conv.requires);

str += `
${name} = utils.convertAsyncSequence(
${name},
function (item) {
${conv.body};
return item;
},
${errPrefix}
);
`;
}

function generateSequence() {
const conv = generateTypeConversion(
ctx,
Expand Down Expand Up @@ -520,7 +553,7 @@ function extractUnionInfo(ctx, idlType, errPrefix) {
unknown: false
};
for (const item of idlType.idlType) {
if (item.generic === "sequence" || item.generic === "FrozenArray") {
if (item.generic === "sequence" || item.generic === "async_sequence" || item.generic === "FrozenArray") {
if (seen.sequenceLike) {
error("There can only be one sequence-like type in a union type");
}
Expand Down Expand Up @@ -721,6 +754,12 @@ function sameArray(array1, array2, comparator = (x, y) => x === y) {
return array1.length === array2.length && array1.every((element1, index) => comparator(element1, array2[index]));
}

function isSequenceLike(type) {
return type.generic === "sequence" ||
type.generic === "async_sequence" ||
type.generic === "FrozenArray";
}

function areDistinguishable(ctx, type1, type2) {
const resolved1 = resolveType(ctx, type1);
const resolved2 = resolveType(ctx, type2);
Expand Down Expand Up @@ -775,8 +814,8 @@ function areDistinguishable(ctx, type1, type2) {
const isDictionaryLike2 = ctx.dictionaries.has(inner2.idlType) ||
ctx.callbackInterfaces.has(inner2.idlType) ||
inner2.generic === "record";
const isSequenceLike1 = inner1.generic === "sequence" || inner1.generic === "FrozenArray";
const isSequenceLike2 = inner2.generic === "sequence" || inner2.generic === "FrozenArray";
const isSequenceLike1 = isSequenceLike(inner1);
const isSequenceLike2 = isSequenceLike(inner2);

if (inner1.idlType === "object") {
return inner2.idlType !== "object" &&
Expand Down
Loading