LibJS: Do not generate implicit await for async functions

Despite the comment in our source, as far as I can tell from my
testing, other engines do not appear to do this.

For example, the result of the following test now matches node and
LibJS by logging `0` calls.

```
async function myFunction() {
    let calls = 0;
    function makeConstructorObservable(promise) {
        Object.defineProperty(promise, "constructor", {
            get() {
                calls++;
                return Promise;
            },
        });
        return promise;
    }

    async function test() {
        try {
            return makeConstructorObservable(Promise.reject(1));
        } catch {
            return 2;
        }
    }
    test().catch(() => { });

    console.log(calls);
};

myFunction()
```

We regress in one test262 Array.fromAsync test, but as far as I can
tell this is due to some unrelated issue in the engine.

Fixes #3251
This commit is contained in:
Shannon Booth 2025-04-06 23:36:40 +12:00
parent cba9c099c8
commit b34a8a38c6
2 changed files with 11 additions and 16 deletions

View file

@ -1852,12 +1852,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ReturnStatement::genera
return_value = TRY(m_argument->generate_bytecode(generator)).value();
// 3. If GetGeneratorKind() is async, set exprValue to ? Await(exprValue).
// Spec Issue?: The spec doesn't seem to do implicit await on explicit return for async functions, but does for
// async generators. However, the major engines do so, and this is observable via constructor lookups
// on Promise objects and custom thenables.
// See: https://tc39.es/ecma262/#sec-asyncblockstart
// c. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done.
if (generator.is_in_async_function()) {
if (generator.is_in_async_generator_function()) {
auto received_completion = generator.allocate_register();
auto received_completion_type = generator.allocate_register();
auto received_completion_value = generator.allocate_register();

View file

@ -202,7 +202,7 @@ describe("await cannot be used in class static init blocks", () => {
});
describe("await thenables", () => {
test("async returning a thanable variable without fulfilling", () => {
test("async returning a thenable variable without fulfilling", () => {
let isCalled = false;
const obj = {
then() {
@ -216,7 +216,7 @@ describe("await thenables", () => {
expect(isCalled).toBe(true);
});
test("async returning a thanable variable that fulfills", () => {
test("async returning a thenable variable that fulfills", () => {
let isCalled = false;
const obj = {
then(fulfill) {
@ -240,7 +240,7 @@ describe("await thenables", () => {
});
f();
runQueuedPromiseJobs();
expect(isCalled).toBe(true);
expect(isCalled).toBe(false);
});
test("async returning a thenable directly that fulfills", () => {
@ -253,7 +253,7 @@ describe("await thenables", () => {
});
f();
runQueuedPromiseJobs();
expect(isCalled).toBe(true);
expect(isCalled).toBe(false);
});
});
@ -279,15 +279,10 @@ describe("await observably looks up constructor of Promise objects increasing ca
try {
await makeConstructorObservable(Promise.reject(3));
} catch {}
try {
return makeConstructorObservable(Promise.reject(1));
} catch {
return 2;
}
}
test();
runQueuedPromiseJobs();
expect(calls).toBe(4);
expect(calls).toBe(3);
});
describe("await observably looks up constructor of Promise objects not increasing call count", () => {
@ -306,6 +301,11 @@ describe("await observably looks up constructor of Promise objects not increasin
await makeConstructorObservable(new Boolean(true));
await makeConstructorObservable({});
await makeConstructorObservable(new Number(2));
try {
return makeConstructorObservable(Promise.reject(1));
} catch {
return 2;
}
}
test();
runQueuedPromiseJobs();