mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-23 06:10:02 +08:00
- 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.
99 lines
2.7 KiB
TypeScript
99 lines
2.7 KiB
TypeScript
// #private fields are ordinary slots under an unspellable name: initializer order interleaves with public fields exactly like Node, reads/writes/compound ops/increments ride the normal field machinery, optional (`#x?: T`) and readonly privates behave like their public twins — and NO enumeration surface sees them: util.inspect prints `C {}` for an all-private class and omits the private slots of a mixed one (they are not properties in any observable way).
|
|
import { inspect } from "node:util";
|
|
|
|
class Ledger {
|
|
opened = 100;
|
|
#balance = this.opened + 5;
|
|
fees: number = this.#tally();
|
|
#log: string[] = [];
|
|
#tally(): number {
|
|
return 2;
|
|
}
|
|
deposit(n: number): number {
|
|
this.#balance += n;
|
|
this.#log.push(`+${n}`);
|
|
return this.#balance;
|
|
}
|
|
withdraw(n: number): number {
|
|
this.#balance -= n;
|
|
this.#balance--;
|
|
this.#log.push(`-${n}`);
|
|
return this.#balance;
|
|
}
|
|
history(): string {
|
|
return this.#log.join(",");
|
|
}
|
|
}
|
|
const l = new Ledger();
|
|
console.log(l.deposit(20));
|
|
console.log(l.withdraw(10));
|
|
console.log(l.history());
|
|
// inspect shows only the public fields, in declaration order.
|
|
console.log(inspect(l));
|
|
|
|
// All fields private: Node prints `Ghost {}`.
|
|
class Ghost {
|
|
#a = 1;
|
|
#b = "hidden";
|
|
blend(): string {
|
|
return `${this.#a}${this.#b.length}`;
|
|
}
|
|
}
|
|
console.log(inspect(new Ghost()), new Ghost().blend());
|
|
|
|
// Optional and readonly privates.
|
|
class Cfg {
|
|
#retries?: number;
|
|
readonly #tag: string;
|
|
constructor(tag: string) {
|
|
this.#tag = tag;
|
|
}
|
|
arm(n: number): void {
|
|
this.#retries = n;
|
|
}
|
|
describe(): string {
|
|
const r = this.#retries;
|
|
return `${this.#tag}:${r !== undefined ? r : -1}`;
|
|
}
|
|
}
|
|
const c = new Cfg("cfg");
|
|
console.log(c.describe());
|
|
c.arm(3);
|
|
console.log(c.describe());
|
|
|
|
// A private field on an Error subclass: the runtime error surface (name,
|
|
// message, instanceof) is untouched, and the private slot reads normally.
|
|
class QuietError extends Error {
|
|
#detail: string;
|
|
constructor(detail: string) {
|
|
super("quiet");
|
|
this.#detail = detail;
|
|
}
|
|
detail(): string {
|
|
return this.#detail;
|
|
}
|
|
}
|
|
const qe = new QuietError("hidden");
|
|
console.log(qe.name, qe.message, qe.detail());
|
|
console.log(qe instanceof Error, qe instanceof QuietError);
|
|
try {
|
|
throw new QuietError("thrown");
|
|
} catch (e) {
|
|
if (e instanceof QuietError) console.log("caught", e.detail());
|
|
}
|
|
|
|
// Privates holding closures: the field value calls like any func field.
|
|
class Hook {
|
|
#cb: (x: number) => number = (x) => x + 1;
|
|
set(f: (x: number) => number): void {
|
|
this.#cb = f;
|
|
}
|
|
fire(x: number): number {
|
|
return this.#cb(x);
|
|
}
|
|
}
|
|
const h = new Hook();
|
|
console.log(h.fire(1));
|
|
h.set((x) => x * 10);
|
|
console.log(h.fire(2));
|