diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index d845bfc54..6c69a80cb 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -5246,9 +5246,12 @@ export class ClassDynamicDispatch { owner = methodInfo.declarer; params = methodInfo.sig.params; // An untyped async method's `dyn` result is its promise's payload; the - // call answers the promise. + // call answers the promise. An async generator method answers its + // generator object, which its signature already returns. result = - methodInfo.sig.async === true && methodInfo.sig.ret.kind !== "promise" + methodInfo.sig.async === true && + !methodInfo.sig.gen && + methodInfo.sig.ret.kind !== "promise" ? { kind: "promise", inner: methodInfo.sig.ret } : methodInfo.sig.ret; callee = `%${owner.def.name}.${method}`; diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index a6dd6e3dd..10927e49b 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -6724,9 +6724,10 @@ function thunkAsyncSlot( if (overridden.sig.async !== true) return; const declarer = overridden.declarer; // The slot answers the body's promise: an untyped async method's `dyn` - // result is the promise's payload, so the slot's ABI is that promise. + // result is the promise's payload, so the slot's ABI is that promise. An + // async generator method's slot keeps its generator object. const ret: IrType = - overridden.sig.ret.kind === "promise" + overridden.sig.gen || overridden.sig.ret.kind === "promise" ? overridden.sig.ret : { kind: "promise", inner: overridden.sig.ret }; declarer.returnAdapters ??= new Map(); diff --git a/tests/corpus/6419-untyped-async-package/main.ts b/tests/corpus/6419-untyped-async-package/main.ts index e118adba4..9ec10f7c5 100644 --- a/tests/corpus/6419-untyped-async-package/main.ts +++ b/tests/corpus/6419-untyped-async-package/main.ts @@ -1,7 +1,8 @@ // Untyped async methods of a package, reached through the package's own // untyped callers and prototype reflection, and overridden by a typed -// subclass. -import { Pool, Schema, limiter, prototypeNames, validateAll } from "token-pool"; +// subclass. An async generator method called from the package answers its +// generator object. +import { Pool, Schema, limiter, listIssues, prototypeNames, validateAll } from "token-pool"; class CountingPool extends Pool { acquired = 0; @@ -25,6 +26,7 @@ async function main(): Promise { console.log(await Promise.all([run(async () => 1), run(async () => 2)])); const schema = new Schema((value) => typeof value === "number"); console.log(await validateAll(schema, [1, "x", 3])); + console.log(await listIssues(schema, ["p", 5, "q"])); console.log(await schema.parseAsync(4)); try { await schema.parseAsync("y"); diff --git a/tests/corpus/6419-untyped-async-package/node_modules/token-pool/index.d.ts b/tests/corpus/6419-untyped-async-package/node_modules/token-pool/index.d.ts index a7a8459ac..ed0c82579 100644 --- a/tests/corpus/6419-untyped-async-package/node_modules/token-pool/index.d.ts +++ b/tests/corpus/6419-untyped-async-package/node_modules/token-pool/index.d.ts @@ -10,6 +10,8 @@ export declare class Schema { constructor(check: (value: unknown) => boolean); safeParseAsync(data: unknown): Promise; parseAsync(data: unknown): Promise; + issues(values: unknown[]): AsyncGenerator; } export declare function validateAll(schema: Schema, values: unknown[]): Promise; +export declare function listIssues(schema: Schema, values: unknown[]): Promise; export declare function prototypeNames(): string; diff --git a/tests/corpus/6419-untyped-async-package/node_modules/token-pool/index.js b/tests/corpus/6419-untyped-async-package/node_modules/token-pool/index.js index 9140e50af..04935ff29 100644 --- a/tests/corpus/6419-untyped-async-package/node_modules/token-pool/index.js +++ b/tests/corpus/6419-untyped-async-package/node_modules/token-pool/index.js @@ -88,6 +88,12 @@ class Schema { this.results.push(this.check(data) ? { success: true, data } : { success: false, issue: "invalid " + data }); return this.results.pop(); } + async *issues(values) { + for (const value of values) { + const result = await this.safeParseAsync(value); + if (!result.success) yield result.issue; + } + } async parseAsync(data) { const result = await this.safeParseAsync(data); if (result.success) return result.data; @@ -104,6 +110,13 @@ async function validateAll(schema, values) { return out.join(", "); } exports.validateAll = validateAll; +async function listIssues(schema, values) { + const out = []; + const it = schema.issues(values); + for (let step = await it.next(); !step.done; step = await it.next()) out.push(step.value); + return out.join(", "); +} +exports.listIssues = listIssues; function prototypeNames() { return typeof Schema.prototype.parseAsync + " " + typeof Schema.prototype.safeParseAsync; }