mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Keep the generator result of untyped async generator methods
- `%dyn.class.call` dispatch answers a promise for an untyped async method whose signature keeps the promise's payload. An async generator method also has the async flag, but its signature already returns the generator object, so the call now keeps that type. Before, a call from untyped code (`schema.issues(values)` over an `async *issues` method) failed IR validation with "type promise != return generator". - thunkAsyncSlot keeps an async generator method's slot type the same way. - Corpus 6419 adds an async generator method that the package's own untyped helper iterates with `next()`.
This commit is contained in:
@@ -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}`;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<void> {
|
||||
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");
|
||||
|
||||
+2
@@ -10,6 +10,8 @@ export declare class Schema {
|
||||
constructor(check: (value: unknown) => boolean);
|
||||
safeParseAsync(data: unknown): Promise<any>;
|
||||
parseAsync(data: unknown): Promise<any>;
|
||||
issues(values: unknown[]): AsyncGenerator<any>;
|
||||
}
|
||||
export declare function validateAll(schema: Schema, values: unknown[]): Promise<string>;
|
||||
export declare function listIssues(schema: Schema, values: unknown[]): Promise<string>;
|
||||
export declare function prototypeNames(): string;
|
||||
|
||||
+13
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user