Files
scriptc/tests/corpus/2594-nullish-generic-bindings.ts
Chris Tate c8c8b8634f Ambient roots trap whole chains; dead and nullish generic bindings lower to their runtime truth
- declare-erased roots make every chain rooted there a catchable ReferenceError at first touch, exactly Node's erasure semantics
- Never-read unmappable bindings emit nothing; nullish-cast bindings answer Node's exact TypeError on member access
- Assertion-supplied generic signatures register like annotations and monomorphize per call
- Stdlib generic members take the lib fence naming the member

# Conflicts:
#	packages/compiler/src/frontend/lowering/lower-stmts.ts
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-07-24 08:06:21 -05:00

40 lines
1.1 KiB
TypeScript

// NULLISH generic bindings: `null as any` / `null!` under a type that keeps
// generic signatures. The binding provably holds its unit forever (every
// write is nullish too), so no storage exists and each read knows the
// value — member reads and generic-method calls compile to Node's exact
// TypeError, nullish-to-nullish flows compile to nothing.
type Cond = <T>() => T extends { a?: string } ? 0 : 1;
let a: Cond = null!;
let b: Cond = null!;
a = b; // nullish-to-nullish: nothing happens
interface I<T> {
fn<K extends keyof T>(k: K): void;
}
const i: I<{ x: number }> = null as any;
try {
i.fn("x");
} catch (e) {
console.log("1", e instanceof TypeError, (e as Error).message);
}
// Propagation: a nullish binding initialized FROM a nullish binding.
const j: I<{ y: string }> = i;
try {
j.fn("y");
} catch (e) {
console.log("2", e instanceof TypeError, (e as Error).message);
}
// The undefined unit keeps its own message.
const u: I<{ z: boolean }> = undefined!;
try {
u.fn("z");
} catch (e) {
console.log("3", e instanceof TypeError, (e as Error).message);
}
console.log("done");