Files
scriptc/tests/corpus/2450-private-instance-methods.ts
Chris Tate f94067ec7e The #private-members chain: fields, methods, accessors, generators
- Privates ride the ordinary member maps under their unspellable spelled names; a subclass redeclaring an inherited private name is fenced, so base-chain walks ARE lexical resolution and privates never enter vtables — JS's no-dynamic-dispatch semantics by construction.
- Private GENERATOR methods compose the generator machinery with this as param 0 through the gen-spawn wrapper (the vtable problem that keeps public generator methods fenced does not exist for privates); async and generic privates ride their existing static-dispatch machinery.
- Private statics lower as module globals/functions through the declaring class's own name; subclass-named and class-value receivers fence (JS brands the declaring constructor alone).
- '#x in obj' lowers as instanceof-the-declarer: static folds at/below and disjoint, the vtable interval test when the declarer sits below the receiver's static class, and unions of decided class arms collapse to tag tests; unknown/generic/static brands fence with pointed hints.
- util.inspect omits private fields exactly like Node (an all-private class prints 'C {}'); corpus 2450-2457 pin every form differentially on both backends, diagnostics private-members pins the fences.
2026-07-23 02:42:10 -05:00

103 lines
2.9 KiB
TypeScript

// #private instance methods: lexically bound, per-class, no dynamic dispatch — `this.#m()` and cross-instance `other.#m()` lower as DIRECT calls of the declaring class's module function (privates never enter vtables; a subclass redeclaring an inherited private name is fenced, so overrideBelow can never flip). Recursion, privates calling privates, constructor calls, and subclass instances flowing through inherited public methods all match Node byte-for-byte.
class Chain {
#links: string[] = [];
label: string;
constructor(label: string, seed: string) {
this.label = label;
// JS installs private methods at construction start, so the ctor may
// call them before any field initializer it declares later runs.
this.#push(seed);
}
#push(link: string): number {
this.#links.push(link);
return this.#links.length;
}
#render(sep: string): string {
return this.label + ":" + this.#links.join(sep);
}
#merge(other: Chain): string {
// Cross-instance private access: the receiver is another instance of
// the declaring class — legal exactly here, inside the class body.
for (const l of other.#links) this.#push(l);
return this.#render("+");
}
add(link: string): number {
return this.#push(link);
}
show(sep: string): string {
return this.#render(sep);
}
absorb(other: Chain): string {
return this.#merge(other);
}
}
const a = new Chain("a", "one");
a.add("two");
const b = new Chain("b", "three");
console.log(a.show("-"));
console.log(a.absorb(b));
console.log(a.show("|"));
// Recursion through a private method.
class Fib {
#memoHits = 0;
#calc(n: number): number {
if (n < 2) return n;
this.#memoHits++;
return this.#calc(n - 1) + this.#calc(n - 2);
}
run(n: number): string {
return `${this.#calc(n)}/${this.#memoHits}`;
}
}
console.log(new Fib().run(10));
// A subclass instance through the base's public method still reaches the
// base's private method (the instance carries the brand — JS-exact).
class Base {
#secret(): string {
return "base-secret";
}
reveal(): string {
return this.#secret();
}
peek(other: Base): string {
return other.#secret().toUpperCase();
}
}
class Derived extends Base {
extra = 1;
}
const d = new Derived();
console.log(d.reveal());
console.log(new Base().peek(d), d.extra);
// Class EXPRESSIONS declare privates like declarations do.
const Boxed = class {
#v: number;
constructor(v: number) {
this.#v = v;
}
#dbl(): number {
return this.#v * 2;
}
out(): number {
return this.#dbl() + 1;
}
};
console.log(new Boxed(20).out());
// Generic private methods monomorphize per call site, dispatched
// statically like every private.
class Wrap {
#lift<T>(v: T): T[] {
return [v, v];
}
use(): number {
const nums = this.#lift(3);
const strs = this.#lift("ab");
return nums[0]! + nums[1]! + strs[0]!.length + strs[1]!.length;
}
}
console.log(new Wrap().use());