mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Values typed `F | undefined` for a function type F are now plain closure pointers (nullable-pointer unions), so a keyed read of a `Record<string, F>` or a Map.get no longer allocates a union box. Keyed record reads, string-keyed Map.get and checked extractions lend the stored closure as a receiver or callee when ReferenceWrites proves the call cannot write the locations it was read from, and optional-chain receivers lend under the same proof. Owned record overflow reads use the inline entry lookup. Indirect closure calls skip the ambient receiver bracket for closures whose bodies never observe it (new ScrClosure.function_kind bit SCR_FN_RECEIVER_FREE; signature adapters that only forward the receiver copy it from their original), and a callValue that forwards `dyn.this` needs no bracket at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
108 lines
3.3 KiB
TypeScript
108 lines
3.3 KiB
TypeScript
// Records of functions used as dispatch tables: keyed reads consumed at once
|
|
// (`t[k]!(…)`, `t[k]?.(…)`, a checked local), missing keys, handlers that
|
|
// declare fewer parameters than the table's type, handlers that replace,
|
|
// delete or re-add table entries (their own included) while they run, and
|
|
// key order after such mutation.
|
|
type Handler = (x: number, y: number) => number;
|
|
|
|
let log: string[] = [];
|
|
|
|
function make(tag: string): Handler {
|
|
let calls = 0;
|
|
return (x, y) => {
|
|
calls++;
|
|
log.push(tag + ":" + calls);
|
|
return x * 10 + y;
|
|
};
|
|
}
|
|
|
|
const table: Record<string, Handler> = {
|
|
add: (x, y) => x + y,
|
|
neg: (x) => -x,
|
|
pow: (x, y) => x ** y,
|
|
made: make("made"),
|
|
};
|
|
|
|
function keys(): string[] {
|
|
return process.argv.length > 99 ? [] : ["add", "neg", "pow", "made", "missing", ""];
|
|
}
|
|
|
|
for (const k of keys()) {
|
|
const h = table[k] as Handler | undefined;
|
|
console.log(JSON.stringify(k), typeof h, h === undefined, h !== undefined && h === table[k]);
|
|
console.log(" optional", table[k]?.(3, 4) ?? "none");
|
|
if (h) console.log(" checked", h(2, 5));
|
|
try {
|
|
console.log(" asserted", table[k]!(5, 2));
|
|
} catch (e) {
|
|
console.log(" asserted threw", (e as Error).name);
|
|
}
|
|
}
|
|
|
|
// A handler that replaces its own entry while it runs: the replaced closure
|
|
// keeps running with its captured state.
|
|
function selfReplacing(): Handler {
|
|
const state = { hits: 0, label: "first" };
|
|
return (x, y) => {
|
|
table["swap"] = make("second");
|
|
state.hits++;
|
|
log.push(state.label + " still running after replace, hits " + state.hits);
|
|
return x + y + state.hits;
|
|
};
|
|
}
|
|
table["swap"] = selfReplacing();
|
|
console.log("swap 1", table["swap"]!(1, 2));
|
|
console.log("swap 2", table["swap"]?.(1, 2));
|
|
console.log("swap 3", table["swap"]!(1, 2));
|
|
|
|
// A handler that deletes its own entry.
|
|
function selfDeleting(name: string): Handler {
|
|
const captured = [name, "captured"];
|
|
return (x) => {
|
|
delete table[name];
|
|
log.push(captured.join(" ") + " after delete");
|
|
return x;
|
|
};
|
|
}
|
|
table["gone"] = selfDeleting("gone");
|
|
const goneKey = keys()[0] === "add" ? "gone" : "add";
|
|
console.log("gone", table[goneKey]?.(7, 0) ?? "none", table[goneKey]?.(7, 0) ?? "none");
|
|
|
|
// A checked local keeps its closure after the entry changes.
|
|
table["kept"] = make("kept-old");
|
|
const kept = table["kept"] as Handler | undefined;
|
|
table["kept"] = make("kept-new");
|
|
delete table["kept"];
|
|
if (kept) console.log("kept", kept(4, 4));
|
|
console.log("kept entry", table["kept"] === undefined);
|
|
|
|
// Iteration while handlers add and delete keys: key order follows inserts.
|
|
const order: Record<string, Handler> = {
|
|
b: (x) => x + 1,
|
|
a: (x) => {
|
|
delete order["c"];
|
|
order["z"] = (v) => v * 2;
|
|
return x + 2;
|
|
},
|
|
c: (x) => x + 3,
|
|
"1": (x) => x + 4,
|
|
};
|
|
for (const k in order) console.log("in", k, order[k]?.(1, 0));
|
|
console.log("keys", Object.keys(order).join(","));
|
|
for (const k of Object.keys(order)) {
|
|
order[k + k] = (x) => x;
|
|
console.log("of", k, order[k]!(10, 0));
|
|
}
|
|
console.log("keys", Object.keys(order).join(","));
|
|
|
|
// Reassigning the table binding itself from inside a handler.
|
|
let current: Record<string, Handler> = {
|
|
go: (x) => {
|
|
current = { go: (y) => y * 100 };
|
|
const local = [x, x];
|
|
return local[0]! + local[1]!;
|
|
},
|
|
};
|
|
console.log("rebind", current["go"]!(3, 0), current["go"]!(3, 0));
|
|
console.log(log.join("\n"));
|