mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
- staticCallOn and lowerClassValueProperty fenced every #private static access with a through-a-VALUE receiver, so C.#m()/C.#f on a const-bound class expression, A.#f through a const alias of the class, and Example.#m() under a replacing decorator all died with SC1090 while the direct class-name spelling compiled. - The static-WRITE path already knew the sound rule (exactClassOfReceiver plus the leaf-class arm); classValueIsExactlyOwn now shares it with the call and read paths: a receiver that provably IS the class, or a class with no strict descendants, always carries the declaring class's brand — the fence stays for slots a subclass can inhabit, where Node throws TypeError. - Rebindable decorated names ride the leaf arm for free: replacement decorators fence any class with subclasses before minting the rebindable global, so the decoration result is always the declaring class object. - The read path additionally gains the subclass-spelling fence (reading an inherited private static through a subclass-named binding) that the call and direct-read paths already had. - Fixtures 2520-2523 pin the four call forms (class-name calls including static field initializers and subclassed declarers, const-bound class expressions with reads/writes/calls, decorated classes under both replacing and void decorators, aliased class values local/module/param) byte-identical vs Node on both backends; the private-members diagnostics fixture repins the residual fence on a descendant-capable slot.
40 lines
1.1 KiB
TypeScript
40 lines
1.1 KiB
TypeScript
// #private statics on CLASS EXPRESSIONS bound to a const: the binding IS
|
|
// the class (a const initialized by the class expression can never hold a
|
|
// descendant), so `C.#m()` calls, `C.#f` reads, and `C.#f = v` writes all
|
|
// resolve exactly — including from constructors and instance methods.
|
|
// Static field initializers stay self-contained (referencing the outer
|
|
// const during them is Node's TDZ), so the #private calls run afterwards.
|
|
const C = class {
|
|
static #scale = 3;
|
|
static #mul(n: number): number {
|
|
return n * C.#scale;
|
|
}
|
|
static apply(n: number): number {
|
|
return C.#mul(n);
|
|
}
|
|
static rescale(s: number): number {
|
|
C.#scale = s;
|
|
return C.#scale;
|
|
}
|
|
#v: number;
|
|
constructor(v: number) {
|
|
this.#v = C.#mul(v);
|
|
}
|
|
read(): number {
|
|
return this.#v;
|
|
}
|
|
};
|
|
console.log(C.apply(5));
|
|
console.log(new C(2).read());
|
|
console.log(C.rescale(7));
|
|
console.log(C.apply(5));
|
|
|
|
// The parenthesized (non-whole-initializer) spelling binds the same way.
|
|
const P = (class {
|
|
static #hidden = "quiet";
|
|
static tag(): string {
|
|
return P.#hidden.toUpperCase();
|
|
}
|
|
});
|
|
console.log(P.tag());
|