1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 07:57:46 +00:00

LibJS/Bytecode: Implement async generators

This commit is contained in:
Luke Wilde 2023-07-14 21:57:49 +01:00 committed by Linus Groh
parent d4e30710e7
commit d1cb78c411
9 changed files with 1290 additions and 71 deletions

View file

@ -0,0 +1,145 @@
describe("correct behaviour", () => {
async function* generatorFunction() {
yield 1;
await Promise.resolve(2);
const b = yield 3;
await Promise.resolve(b);
yield b + 1;
yield Promise.resolve(b + 2);
yield* [Promise.resolve(b + 3), Promise.resolve(b + 4), Promise.resolve(b + 5)];
return b + 6;
}
test("length is 1", () => {
expect(generatorFunction.prototype.next).toHaveLength(1);
});
const generator = generatorFunction();
function runGenerator(valueToPass, unwrapIteratorResult = true) {
let result = null;
test(`generator runs valueToPass=${valueToPass}`, () => {
const promise = generator.next(valueToPass);
promise
.then(value => {
result = value;
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
expect(result).toBeInstanceOf(Object);
expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
expect(Object.keys(result)).toEqual(["value", "done"]);
});
return unwrapIteratorResult ? result.value : result;
}
test("can yield", () => {
const firstRunResult = runGenerator("bad1");
expect(firstRunResult).toBe(1);
});
test("await does not yield", () => {
const secondRunResult = runGenerator("bad2");
expect(secondRunResult).toBe(3);
});
test("can pass values via yield", () => {
const thirdRunResult = runGenerator(4);
expect(thirdRunResult).toBe(5);
});
test("yield implicitly awaits", () => {
const fourthRunResult = runGenerator("bad3");
expect(fourthRunResult).toBe(6);
const fifthRunResult = runGenerator("bad4");
expect(fifthRunResult).toBe(7);
const sixthRunResult = runGenerator("bad5");
expect(sixthRunResult).toBe(8);
const seventhRunResult = runGenerator("bad6");
expect(seventhRunResult).toBe(9);
});
test("can return a value", () => {
const eighthRunResult = runGenerator("bad7", false);
expect(eighthRunResult.value).toBe(10);
expect(eighthRunResult.done).toBeTrue();
});
test("gets undefined in completed state", () => {
const ninethRunResult = runGenerator("bad8", false);
expect(ninethRunResult.value).toBeUndefined();
expect(ninethRunResult.done).toBeTrue();
});
async function* implicitReturnFunction() {
0xbbadbeef;
}
const implicitReturnGenerator = implicitReturnFunction();
test("gets undefined from implicit return", () => {
implicitReturnGenerator
.next("bad9")
.then(iteratorResult => {
expect(iteratorResult.value).toBeUndefined();
expect(iteratorResult.done).toBeTrue();
})
.catch(e => {
expect().fail(`Implicit await generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
});
async function* unhandledExceptionFunction() {
throw 1337;
}
const unhandledExceptionGenerator = unhandledExceptionFunction();
test("promise is rejected on unhandled exceptions", () => {
unhandledExceptionGenerator
.next("bad10")
.then(() => {
expect().fail(
"Unhandled exception generator did NOT throw an unhandled exception."
);
})
.catch(e => {
expect(e).toBe(1337);
});
runQueuedPromiseJobs();
});
test("generator is complete after unhandled exception", () => {
unhandledExceptionGenerator
.next("bad11")
.then(iteratorResult => {
expect(iteratorResult.value).toBeUndefined();
expect(iteratorResult.done).toBeTrue();
})
.catch(e => {
expect().fail(
"Unhandled exception generator threw an unhandled exception in Completed state."
);
});
runQueuedPromiseJobs();
});
});
describe("errors", () => {
test("this value must be an AsyncGenerator object", () => {
async function* generator() {}
let rejection = null;
generator.prototype.next.call("foo").catch(error => {
rejection = error;
});
runQueuedPromiseJobs();
expect(rejection).toBeInstanceOf(TypeError);
expect(rejection.message).toBe("Not an object of type AsyncGenerator");
});
});

View file

@ -0,0 +1,146 @@
describe("correct behavior", () => {
async function* emptyGeneratorFunction() {}
test("length is 1", () => {
expect(emptyGeneratorFunction.prototype.return).toHaveLength(1);
});
const emptyGenerator = emptyGeneratorFunction();
test("return from SuspendedStart", () => {
emptyGenerator
.return(1337)
.then(result => {
expect(result.value).toBe(1337);
expect(result.done).toBeTrue();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
});
test("return from Completed", () => {
emptyGenerator
.return(123)
.then(result => {
expect(result.value).toBe(123);
expect(result.done).toBeTrue();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
});
async function* generatorTwo() {
yield 1337;
yield 123;
}
const generatorTwoIterator = generatorTwo();
test("return from SuspendedYield", () => {
generatorTwoIterator
.next("bad1")
.then(result => {
expect(result.value).toBe(1337);
expect(result.done).toBeFalse();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
generatorTwoIterator
.return(999)
.then(result => {
expect(result.value).toBe(999);
expect(result.done).toBeTrue();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
generatorTwoIterator
.next("bad2")
.then(result => {
expect(result.value).toBeUndefined();
expect(result.done).toBeTrue();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
});
async function* injectedCompletionGenerator() {
try {
yield 1;
} finally {
yield 2;
}
}
const injectedCompletionGeneratorObject = injectedCompletionGenerator();
test("return completion is injected into generator", () => {
injectedCompletionGeneratorObject
.next("bad1")
.then(result => {
expect(result.value).toBe(1);
expect(result.done).toBeFalse();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
injectedCompletionGeneratorObject
.return(3)
.then(result => {
expect(result.value).toBe(2);
expect(result.done).toBeFalse();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
injectedCompletionGeneratorObject
.next("bad3")
.then(result => {
expect(result.value).toBe(3);
expect(result.done).toBeTrue();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
injectedCompletionGeneratorObject
.next("bad4")
.then(result => {
expect(result.value).toBeUndefined();
expect(result.done).toBeTrue();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
});
});
describe("errors", () => {
test("this value must be an AsyncGenerator object", () => {
async function* generator() {}
let rejection = null;
generator.prototype.return.call("foo").catch(error => {
rejection = error;
});
runQueuedPromiseJobs();
expect(rejection).toBeInstanceOf(TypeError);
expect(rejection.message).toBe("Not an object of type AsyncGenerator");
});
});

View file

@ -0,0 +1,132 @@
describe("correct behavior", () => {
async function* emptyGeneratorFunction() {}
test("length is 1", () => {
expect(emptyGeneratorFunction.prototype.throw).toHaveLength(1);
});
const emptyGenerator = emptyGeneratorFunction();
test("throw from SuspendedStart", () => {
emptyGenerator
.throw(1337)
.then(() => {
expect().fail("Generator did NOT throw an unhandled exception.");
})
.catch(e => {
expect(e).toBe(1337);
});
runQueuedPromiseJobs();
});
test("throw from Completed", () => {
emptyGenerator
.throw(123)
.then(() => {
expect().fail("Generator did NOT throw an unhandled exception.");
})
.catch(e => {
expect(e).toBe(123);
});
runQueuedPromiseJobs();
});
async function* generatorTwo() {
yield 1337;
yield 123;
}
const generatorTwoIterator = generatorTwo();
test("throw from SuspendedYield", () => {
generatorTwoIterator
.next("bad1")
.then(result => {
expect(result.value).toBe(1337);
expect(result.done).toBeFalse();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
generatorTwoIterator
.throw(999)
.then(() => {
expect().fail("Generator did NOT throw an unhandled exception.");
})
.catch(e => {
expect(e).toBe(999);
});
runQueuedPromiseJobs();
generatorTwoIterator
.next("bad2")
.then(result => {
expect(result.value).toBeUndefined();
expect(result.done).toBeTrue();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
});
async function* injectedCompletionGenerator() {
try {
yield 1;
} catch (e) {
yield e;
}
}
const injectedCompletionGeneratorObject = injectedCompletionGenerator();
test("throw completion is injected into generator", () => {
injectedCompletionGeneratorObject
.next("bad1")
.then(result => {
expect(result.value).toBe(1);
expect(result.done).toBeFalse();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
injectedCompletionGeneratorObject
.throw(9999)
.then(result => {
expect(result.value).toBe(9999);
expect(result.done).toBeFalse();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
injectedCompletionGeneratorObject
.next("bad2")
.then(result => {
expect(result.value).toBeUndefined();
expect(result.done).toBeTrue();
})
.catch(e => {
expect().fail(`Generator threw an unhandled exception: ${e}`);
});
runQueuedPromiseJobs();
});
});
describe("errors", () => {
test("this value must be an AsyncGenerator object", () => {
async function* generator() {}
let rejection = null;
generator.prototype.throw.call("foo").catch(error => {
rejection = error;
});
runQueuedPromiseJobs();
expect(rejection).toBeInstanceOf(TypeError);
expect(rejection.message).toBe("Not an object of type AsyncGenerator");
});
});