Support JavaScript async class methods in static mode (#515)

This commit is contained in:
Chris Tate
2026-09-28 00:46:44 -05:00
committed by GitHub
parent 82d7d9a237
commit ec2ebb6ef4
6 changed files with 48 additions and 16 deletions
@@ -1714,6 +1714,9 @@ export class CEmitter {
* every type that cannot hold undefined (tsc's SPI guards those) and for
* record shapes' construction paths, which write every field. */
undefFieldInitLineC(name: string, t: IrType): string[] {
// Error.cause uses NULL for absence; only the options constructor may
// install a present value (including undefined) in this runtime slot.
if (name === "%cause") return [];
if (t.kind === "dyn") {
return [` o->${mangleField(name)} = scr_dyn_undefined(); /* ${cCommentText(name)} starts undefined */`];
}
@@ -205,6 +205,9 @@ export interface ClassHost extends ShapeHost {
function undefFieldInits(host: ClassHost, meta: LlClassMeta): string[] {
const out: string[] = [];
meta.def.fields.forEach((f, i) => {
// Error.cause uses NULL for absence; an options constructor installs
// a value only when the cause property is present.
if (f.name === "%cause") return;
const { index } = classFieldIndex(meta, f.name);
if (f.type.kind === "jsval" || f.type.kind === "dyn") {
const undefinedFn = f.type.kind === "dyn" ? "scr_dyn_undefined" : "scr_jsval_undefined";
@@ -1797,22 +1797,9 @@ export function collectClassShapeInner(lowerer: Lowerer, decl: ts.ClassLikeDecla
collectGenericMember(member, false);
continue;
}
// Async METHODS in JS classes simply do not COLLECT — each call
// fences at its own site (the JS deferral stance, the
// async-static precedent above), so a class whose driven
// surface is synchronous still compiles (commander: parse()
// works, parseAsync() traps where called). TS async methods
// collect below like any method: the body is an async
// IrFunction (fiber spawn wrapper, `this` as param 0), calls
// dispatch STATICALLY — override chains fence (the vtable slot
// machinery has no fiber-spawn story), so every call site is a
// direct call the emitter routes through the spawn wrapper.
if (
member.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) &&
isJsSourceFile(decl.getSourceFile())
) {
continue;
}
// Async methods collect with the same direct-call spawn wrapper
// for JavaScript and TypeScript classes. Override chains remain
// fenced below because vtable slots cannot enter that wrapper.
// IMPLICIT-ANY monomorphization (npm-static JS): a method whose
// signature carries bindable untyped params collects like a
// GENERIC method — into genericMethods, no vtable slot, one
@@ -7619,6 +7619,12 @@
],
"diags": []
},
"<repo>/tests/corpus/3122-js-async-class-methods.js": {
"order": [
"<repo>/tests/corpus/3122-js-async-class-methods.js"
],
"diags": []
},
"<repo>/tests/corpus/400-fib.ts": {
"order": [
"<repo>/tests/corpus/400-fib.ts"
@@ -16,6 +16,7 @@ report("uri", new URIError("uri"));
report("child reference", new ChildReferenceError("child"));
report("child eval", new ChildEvalError("child"));
report("child uri", new ChildURIError("child"));
report("child cause", new ChildReferenceError("present", { cause: undefined }));
try {
throw new ReferenceError("caught");
@@ -0,0 +1,32 @@
class Source {
value = 4;
async doubled() {
await Promise.resolve();
return this.value * 2;
}
async *steps() {
yield this.value;
await Promise.resolve();
yield this.value + 1;
}
}
var Pair = class {
async *values([first, second]) {
yield first;
yield second;
}
};
async function main() {
const source = new Source();
console.log(await source.doubled());
const steps = source.steps();
console.log((await steps.next()).value, (await steps.next()).value, (await steps.next()).done);
const pair = new Pair().values([7, 8]);
console.log((await pair.next()).value, (await pair.next()).value, (await pair.next()).done);
}
void main();