mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
- `const r: Repo = new MemRepo()` over an interface whose every member is a generic-callable method keeps the initializer's CLASS representation (genericIfaceBindingKeepsClass): the record shape maps empty — generic members are excluded — so the width copy would drop the class the calls dispatch against, and in JS the binding IS the instance. - Calls like `r.get(...)` resolve through exactInstanceClassOf to a class generic-method call on the exact class: `this` works, state mutates on the one real instance, and record-typed USES width-coerce at the use site instead of the declaration. - Receivers with no provable class (parameters, `let`, produced values) keep the fence, now naming the actual blocker: static dispatch needs a const bound to its `new` expression. - Fixture 2552 (stateful implementations, two classes, block scope, casts back to the class, record-typed uses), byte-identical vs Node on both backends.
72 lines
1.9 KiB
TypeScript
72 lines
1.9 KiB
TypeScript
// Generic methods called through INTERFACE-typed receivers: when every
|
|
// member of the interface is a generic-callable method, a const binding
|
|
// initialized with `new C(...)` keeps the class representation (the record
|
|
// shape maps empty — generic members are excluded — and in JS the binding
|
|
// IS the instance), so calls monomorphize against the implementing class's
|
|
// bodies exactly like class-typed receivers. `this` works: state mutates
|
|
// on the one real instance.
|
|
interface Repo {
|
|
get<T>(id: string, mk: () => T): T;
|
|
put<T>(id: string, v: T): T;
|
|
}
|
|
|
|
class MemRepo implements Repo {
|
|
private hits = 0;
|
|
get<T>(id: string, mk: () => T): T {
|
|
this.hits++;
|
|
return mk();
|
|
}
|
|
put<T>(id: string, v: T): T {
|
|
this.hits += 2;
|
|
return v;
|
|
}
|
|
count(): number {
|
|
return this.hits;
|
|
}
|
|
}
|
|
|
|
const r: Repo = new MemRepo();
|
|
console.log(r.get("a", () => 42), r.get("b", () => "hi"), r.put("c", [1, 2, 3]).length);
|
|
|
|
// The binding holds the real instance: a cast reads the mutated state.
|
|
console.log((r as MemRepo).count());
|
|
|
|
// Identity and inspection behave like the instance they are.
|
|
console.log(typeof r, r === r);
|
|
|
|
// Block-scoped receivers take the same discipline.
|
|
function inBlock(): string {
|
|
const r2: Repo = new MemRepo();
|
|
return r2.get("k", () => "block") + r2.put("k2", "!");
|
|
}
|
|
console.log(inBlock());
|
|
|
|
// A second implementing class: each binding monomorphizes against its own
|
|
// class's bodies.
|
|
class DoubleRepo implements Repo {
|
|
get<T>(id: string, mk: () => T): T {
|
|
mk();
|
|
return mk();
|
|
}
|
|
put<T>(id: string, v: T): T {
|
|
return v;
|
|
}
|
|
}
|
|
const d: Repo = new DoubleRepo();
|
|
let calls = 0;
|
|
console.log(
|
|
d.get("z", () => {
|
|
calls++;
|
|
return calls * 10;
|
|
}),
|
|
);
|
|
|
|
// Record-typed USES still work: the empty-shape width copy happens at the
|
|
// use site instead of the declaration.
|
|
function hold(x: Repo): string {
|
|
return typeof x;
|
|
}
|
|
console.log(hold(r), hold(d));
|
|
const all: Repo[] = [r, d];
|
|
console.log(all.length);
|