Files
scriptc/tests/corpus/8232-optional-function-values.ts
T
Malte UblandClaude Opus 5.5 5a3d6d7903 Call function tables without boxing or reference counting
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>
2026-10-25 14:49:00 -07:00

76 lines
2.5 KiB
TypeScript

// Values typed `F | undefined` for a function type F: identity and
// equality (signature adapters compare as the function they adapt), typeof,
// truthiness, storage in arrays, records, class fields and Maps, optional
// parameters and returns.
type Unary = (x: number) => number;
type Binary = (x: number, y: number) => number;
const double: Unary = (x) => x * 2;
const asBinary: Binary = double;
const maybe = (flag: boolean): Unary | undefined => (flag ? double : undefined);
const a = maybe(true);
const b = maybe(true);
const none = maybe(false);
console.log(a === b, a === none, none === undefined, typeof a, typeof none, !!a, !!none);
console.log(a === double, (asBinary as unknown) === (double as unknown));
const slots: (Unary | undefined)[] = [double, undefined, (x) => x + 1];
console.log(slots.map((f) => (f ? f(5) : "empty")).join(" "), slots.length);
console.log(slots.indexOf(double), slots.includes(undefined), slots.filter((f) => f !== undefined).length);
interface Hooks {
before?: Unary;
after?: Unary;
}
const hooks: Hooks = { after: (x) => x - 1 };
console.log(hooks.before?.(1) ?? "no before", hooks.after?.(1) ?? "no after", "before" in hooks);
class Button {
onClick: Unary | undefined;
clicks = 0;
click(): number {
this.clicks++;
return this.onClick?.(this.clicks) ?? -1;
}
}
const button = new Button();
console.log(button.click());
button.onClick = (n) => n * 100;
console.log(button.click(), button.click());
button.onClick = undefined;
console.log(button.click(), button.onClick === undefined);
const registry = new Map<string, Unary | undefined>([
["set", double],
["unset", undefined],
]);
console.log(registry.get("set")?.(4), registry.get("unset")?.(4), registry.has("unset"), registry.get("other") === undefined);
function run(x: number, transform?: Unary, combine?: Binary): number {
const t = transform ? transform(x) : x;
return combine === undefined ? t : combine(t, x);
}
console.log(run(3), run(3, double), run(3, undefined, (p, q) => p * q), run(3, (x) => -x, asBinary));
function pick(name: string): Binary | undefined {
switch (name) {
case "mul":
return (x, y) => x * y;
case "first":
return (x) => x;
default:
return undefined;
}
}
for (const name of ["mul", "first", "none"]) {
const f = pick(name);
console.log(name, f?.(6, 7), f === undefined ? "undefined" : typeof f);
}
let late: Unary | undefined;
const callLate = (): number => late?.(9) ?? 0;
console.log(callLate());
late = (x) => x + 0.5;
console.log(callLate());