mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Adopt thenable entries and place tuple slots by position in mixed Promise.all
- A checked-dynamic entry of a tuple Promise.all goes through PromiseResolve (`dyn.promiseResolve`): a native promise is itself, a thenable adopts its settlement through its resolve job, and any other value fulfills. Before, a thenable fulfilled the slot with the object. - Tuple fields read their entry by position. Shape fields are sorted by name, so tuples of more than ten entries paired fields with the wrong entries.
This commit is contained in:
@@ -922,6 +922,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"dyn.promiseAll": "scr_dyn_promise_all",
|
||||
"dyn.promiseAllSettled": "scr_dyn_promise_all_settled",
|
||||
"dyn.promiseAny": "scr_dyn_promise_any",
|
||||
"dyn.promiseResolve": "scr_dyn_promise_resolve",
|
||||
"dyn.typeof": "scr_dyn_typeof",
|
||||
"dyn.objectTag": "scr_dyn_object_tag",
|
||||
"dyn.freeze": "scr_dyn_freeze",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { checkedPromiseAll } from "../checked-promise-all.js";
|
||||
import { checkedPromiseAll, thenReader } from "../checked-promise-all.js";
|
||||
import { InternalCompilerError } from "../../../errors.js";
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { newFnCtx, type Lowerer } from "../lowerer.js";
|
||||
@@ -6,7 +6,6 @@ import { varRef } from "../../../ir/build.js";
|
||||
import { maybePromiseResolve, maybePromiseUnionOf } from "../maybe-promise.js";
|
||||
import { isJsSourceFile, locOf } from "../../program.js";
|
||||
import {
|
||||
BOOL,
|
||||
DYN,
|
||||
type IrExpr,
|
||||
type IrFunction,
|
||||
@@ -31,7 +30,8 @@ import {
|
||||
* checked-dynamic tree. Non-promise entries (numbers, strings, booleans,
|
||||
* unit values — never thenable) pass straight into their slot; a
|
||||
* checked-dynamic entry (a JavaScript function's result) joins through
|
||||
* the promise it holds, or a promise fulfilled with its plain value. The tuple
|
||||
* PromiseResolve: the promise it holds, a promise that adopts a thenable,
|
||||
* or a promise fulfilled with its plain value. The tuple
|
||||
* fulfills one microtask turn after the combinator does. Null when the
|
||||
* result type or an entry has no typed home here. */
|
||||
function lowerTuplePromiseAll(
|
||||
@@ -68,8 +68,9 @@ function lowerTuplePromiseAll(
|
||||
const ref = (i: number): IrExpr => varRef(`entry.${i}`, elems[i]!.type, loc);
|
||||
const dynPromise: IrType = { kind: "promise", inner: DYN };
|
||||
const body: IrStmt[] = [];
|
||||
// Each dyn entry's promise view: the promise it holds, or a promise
|
||||
// fulfilled with the plain value (Promise.resolve's rule).
|
||||
// Each dyn entry's promise view, in entry order: the promise it
|
||||
// holds, a promise that adopts a thenable's settlement, or a promise
|
||||
// fulfilled with the plain value (PromiseResolve's rule).
|
||||
const views = new Map<number, IrExpr>();
|
||||
elems.forEach((e, i) => {
|
||||
if (e.type.kind !== "dyn") return;
|
||||
@@ -78,16 +79,9 @@ function lowerTuplePromiseAll(
|
||||
kind: "varDecl",
|
||||
localId: view.id,
|
||||
init: {
|
||||
kind: "ternary",
|
||||
cond: { kind: "dynTest", test: "promise", value: ref(i), type: BOOL, loc },
|
||||
then: { kind: "dynCheck", value: ref(i), type: dynPromise, loc },
|
||||
else_: {
|
||||
kind: "intrinsic",
|
||||
name: "promise.resolve",
|
||||
args: [ref(i)],
|
||||
type: dynPromise,
|
||||
loc,
|
||||
},
|
||||
kind: "libCall",
|
||||
fn: "dyn.promiseResolve",
|
||||
args: [ref(i), thenReader(lowerer, loc)],
|
||||
type: dynPromise,
|
||||
loc,
|
||||
},
|
||||
@@ -138,7 +132,10 @@ function lowerTuplePromiseAll(
|
||||
type: VOID,
|
||||
loc,
|
||||
};
|
||||
const fields = shape.fields.map((field, i) => {
|
||||
// Shape fields are sorted by name ("10" before "2"); a tuple field's
|
||||
// name is its position.
|
||||
const fields = shape.fields.map((field) => {
|
||||
const i = Number(field.name);
|
||||
const entry = elems[i]!;
|
||||
let value: IrExpr;
|
||||
const view = views.get(i);
|
||||
|
||||
@@ -11,7 +11,7 @@ const COMBINATOR_LIB_CALLS = {
|
||||
|
||||
/** `value.then` through the program's own property dispatch, so a class
|
||||
* instance's `then` method is visible to the runtime's thenable check. */
|
||||
function thenReader(lowerer: Lowerer, loc: SrcLoc): IrExpr {
|
||||
export function thenReader(lowerer: Lowerer, loc: SrcLoc): IrExpr {
|
||||
const name = "%promise.thenOf";
|
||||
const type = funcOf([DYN], DYN);
|
||||
if (!lowerer.liftedFns.some((fn) => fn.name === name)) {
|
||||
|
||||
@@ -239,6 +239,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
"dyn.promiseAll": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } },
|
||||
"dyn.promiseAllSettled": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } },
|
||||
"dyn.promiseAny": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } },
|
||||
/** PromiseResolve over one checked-dynamic value: the promise it holds,
|
||||
* a promise that adopts a thenable's settlement, or a promise fulfilled
|
||||
* with the value. The second argument reads `then` as above. */
|
||||
"dyn.promiseResolve": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } },
|
||||
/** Bare `typeof v` on a dyn value AS A STRING (arg: the dyn value,
|
||||
* borrowed; result: an owned string) — the dyn kind's JS answer:
|
||||
* undefined→"undefined", null/object/array/bytes→"object" (JS's oldest
|
||||
|
||||
@@ -21,6 +21,7 @@ const DYN_ASYNC_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"dyn.promiseAll",
|
||||
"dyn.promiseAllSettled",
|
||||
"dyn.promiseAny",
|
||||
"dyn.promiseResolve",
|
||||
"process.onUncaughtException",
|
||||
"process.offUncaughtException",
|
||||
"process.onUnhandledRejection",
|
||||
|
||||
@@ -1437,24 +1437,29 @@ static void scr_dyn_thenable_schedule(ScrPromise *target, const ScrDyn *thenable
|
||||
* a thenable gets a fresh promise and its resolve job, anything else a
|
||||
* fulfilled promise (a throwing `then` read rejects it). The element
|
||||
* joins the combinator right after, so a thenable's job sits between
|
||||
* the jobs of the entries around it, exactly Node's iteration. */
|
||||
static ScrPromise *scr_dyn_promise_entry(ScrCombine *c, ScrDyn *value, const ScrDyn *reader) {
|
||||
* the jobs of the entries around it, exactly Node's iteration. Without a
|
||||
* combinator (`c` NULL) this is PromiseResolve for one value. */
|
||||
static ScrPromise *scr_dyn_promise_entry(ScrCombine *c, const ScrDyn *value, const ScrDyn *reader) {
|
||||
if (value->kind == SCR_DYN_PROMISE) return scr_promise_retain(value->v.promise);
|
||||
ScrPromise *source = scr_promise_new();
|
||||
ScrDyn *then = scr_dyn_then_of(value, reader);
|
||||
if (scr_exc_pending()) {
|
||||
scr_promise_reject_pending(source);
|
||||
} else if (then) {
|
||||
scr_promise_combine_flush(c);
|
||||
if (c) scr_promise_combine_flush(c);
|
||||
scr_dyn_thenable_schedule(source, value, then, reader);
|
||||
} else {
|
||||
scr_promise_fulfill_ref(source, scr_dyn_retain(value), scr_dyn_retain_v, scr_dyn_release_v,
|
||||
scr_dyn_trace_v);
|
||||
scr_promise_fulfill_ref(source, scr_dyn_retain((ScrDyn *)value), scr_dyn_retain_v,
|
||||
scr_dyn_release_v, scr_dyn_trace_v);
|
||||
}
|
||||
scr_dyn_release(then);
|
||||
return source;
|
||||
}
|
||||
|
||||
ScrPromise *scr_dyn_promise_resolve(const ScrDyn *value, const ScrDyn *reader) {
|
||||
return scr_dyn_promise_entry(NULL, value, reader);
|
||||
}
|
||||
|
||||
static void scr_dyn_settled_store(ScrArr *a, double i, ScrPromise *src) {
|
||||
ScrDyn *record = scr_dyn_new_obj();
|
||||
bool fulfilled = !scr_promise_is_rejected(src);
|
||||
|
||||
@@ -1309,6 +1309,10 @@ extern SCR_TL ScrDyn *(*scr_dyn_promise_member_fn)(const char *key, size_t len);
|
||||
ScrPromise *scr_dyn_promise_all(const ScrDyn *entries, const ScrDyn *reader);
|
||||
ScrPromise *scr_dyn_promise_all_settled(const ScrDyn *entries, const ScrDyn *reader);
|
||||
ScrPromise *scr_dyn_promise_any(const ScrDyn *entries, const ScrDyn *reader);
|
||||
/* PromiseResolve over one dyn value (borrowed): a native promise is
|
||||
* itself, a thenable adopts through its resolve job, a throwing `then`
|
||||
* read rejects, and any other value fulfills. +1. */
|
||||
ScrPromise *scr_dyn_promise_resolve(const ScrDyn *value, const ScrDyn *reader);
|
||||
|
||||
/* Native predicates for typed reference capsules and boxed errors. */
|
||||
bool scr_dyn_ref_typeof_is(const ScrDyn *d, const char *name);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Promise.all over an array literal of DIFFERENT promise types (the
|
||||
// checker's tuple overload): destructuring with holes and nested
|
||||
// patterns, void entries, plain values, `any` entries that may or may not
|
||||
// hold a promise, entry identity, and rejection —
|
||||
// hold a promise or a thenable, tuples of more than ten entries, entry
|
||||
// identity, and rejection —
|
||||
// the first rejection in settlement order wins, later ones count as
|
||||
// handled.
|
||||
interface Version {
|
||||
@@ -48,6 +49,18 @@ function untypedRecord(): any {
|
||||
return Promise.resolve({ label: "from a promise" });
|
||||
}
|
||||
|
||||
// A thenable that is not a native promise: Promise.all adopts its
|
||||
// settlement, calling `then` one microtask after the call.
|
||||
class Deferred {
|
||||
then(resolve: (value: string) => void): void {
|
||||
console.log("then called");
|
||||
resolve("adopted");
|
||||
}
|
||||
}
|
||||
function untypedThenable(): any {
|
||||
return new Deferred();
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const [runtime, { tool: kind, schema: level }] = await Promise.all([version(), scan()]);
|
||||
console.log(runtime.major, kind, level, runtime === shared);
|
||||
@@ -64,6 +77,14 @@ async function main(): Promise<void> {
|
||||
const [count, { label }, plain] = await Promise.all([untypedCount(), untypedRecord(), "p"]);
|
||||
console.log(count + 1, label, plain);
|
||||
|
||||
const pending = Promise.all([untypedThenable(), version()]);
|
||||
console.log("after the call");
|
||||
const [adopted, latest] = await pending;
|
||||
console.log(adopted, latest.major);
|
||||
|
||||
const many = await Promise.all([version(), 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", scan()]);
|
||||
console.log(many[0].major, many[2], many[9], many[10], many[11].tool);
|
||||
|
||||
try {
|
||||
await Promise.all([delay(20, 1, "slow failure"), delay(5, "s", "fast failure"), delay(1, true)]);
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user