Files
scriptc/tests/diagnostics/generic-value-bindings.ts
T
Malte UblandClaude Opus 5.5 3e2b4ab25b Share reassigned bindings with generic arrow instances through closures
A generic arrow declared inside a function compiles to shared module
instances that took the enclosing values they read as trailing
parameters: a copy, so any binding written in the file was refused,
including a counter the arrow itself increments. When one of its
captures is reassigned, the instance is now a lifted function that
receives every capture through a closure environment, and each call site
calls a closure over its own boxes, the representation ordinary closures
use. Writes from the instance, the frame and other closures meet in the
same box, as in Node. Arrows whose captures never change keep the
trailing parameters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:49:30 -07:00

86 lines
3.3 KiB
TypeScript

// Generic function VALUE fences, named: every shape here keeps the generic
// signature alive where static monomorphization cannot follow.
// A reassigned binding cannot resolve statically — a later write could hold
// a structurally identical function with a different body.
let flip = <T>(x: T): T => x;
flip = <T>(x: T): T => x;
console.log(flip(1));
// An ALIAS binding registers its target (calls through it monomorphize),
// but a USE with no pinning context still keeps the generic signature —
// the value fences at the reference, not the binding.
const id = <T>(x: T): T => x;
const stored = id;
console.log(typeof stored);
// An alias of a REASSIGNED binding never registers: the target's own
// fence reports, and the alias falls back to the ordinary binding story.
const flipAlias = flip;
console.log(flipAlias(5));
// Declared inside a function and reading a binding declared AFTER it: a
// call could run before the binding's initializer (a temporal dead zone).
// (Bindings declared before it are supported — copied as trailing
// parameters, or shared through a closure environment when reassigned.)
function outer(base: number): number {
const inner = <T>(x: T): [T, number] => [x, scale];
const scale = base + 1;
return inner(3)[1];
}
console.log(outer(4));
// Declared inside a GENERIC function: the enclosing frame re-lowers per
// instantiation, so the shared instance cannot carry its type argument.
function outerGeneric<U>(seed: U): string {
const inner = <T>(x: T): string => `${String(x)}${String(seed)}`;
return inner(3);
}
console.log(outerGeneric<number>(1));
// Used as a value inside a function: each call of the enclosing function
// makes a new function object (`make() !== make()`), and the compiled
// instances are shared module functions.
function make(): (x: number) => number {
const same = <T>(x: T): T => x;
return same<number>;
}
console.log(make() === make());
// An ambient binding of generic function type now compiles to Node's exact
// ReferenceError at the access (the declare-erasure stance — no diagnostic
// here; the optional chain cannot guard the root's own throw).
declare const ambient: undefined | (<T>(f: (a: T) => T) => T);
console.log(ambient?.((n: number) => n) === undefined);
// A call whose RESULT is itself a generic function: the returned signature
// keeps its type parameters and would need the producing call's frame.
const curried = <T>(x: T) => <U>(y: U): string => `${x}|${y}`;
console.log(curried(1)("a"));
// Ambient `declare class` generic methods are signature-only. Through an
// AMBIENT-ROOTED receiver the call now compiles to Node's ReferenceError
// at the root (the declare-const chain stance — no diagnostic here); a
// receiver that exists at runtime keeps the fence.
declare class Amb {
m<T>(x: T): T;
}
declare const amb: Amb;
console.log(amb.m(4));
function throughParam(a: Amb): void {
console.log(a.m(4));
}
throughParam(amb);
// A `var` binding inside a function, declared in a branch and called from
// a closure: when the branch does not run, Node finds the binding undefined.
// The compiled instances have no per-call flag for that, so the call fences.
function branchVar(flag: boolean): number {
if (flag) {
var picked = <T>(x: T): T => x;
}
const invoke = (): number => picked(1);
return invoke();
}
console.log(branchVar(true));