Files
scriptc/tests/corpus/function-local-detached-methods.js
T
Chris Tate a1b077e16f Check private fields of function-local classes per class evaluation
Each evaluation of a function-local class creates its own private names,
while one compiled class serves every evaluation. A JavaScript class's
prototype methods are closures per evaluation whose `this` is the
call-site value, so a method taken from one evaluation could be called
with another evaluation's instance (`a.next.call(b)`, a prototype read,
Reflect.get with a receiver). Those calls read and wrote the other
instance's fields, and `#x in this` answered true. Reads and writes of
`this.#x` inside the closures also went through the property bag under
the key "#x" instead of the native field.

Private fields on a checked `this` now go through a brand check: the
receiver must be an instance of the class or a subclass whose class
environment is the closure's own evaluation, which the class's own
binding names. A matching receiver reads and writes the native field;
any other receiver throws Node's TypeError for the read or the write
(after the assigned value evaluates), and `#x in this` answers false.
Method values and accessors taken from typed instances of these classes
resolve to the instance's own evaluation's prototype functions instead of
a native thunk shared by every evaluation.

The classes this applies to are narrowed to the shapes the check covers:
JavaScript class declarations whose private members are instance fields
used only on `this`. Private methods and accessors, class expressions,
and TypeScript classes keep the "function-local classes with private
brands" refusal.
2026-10-19 22:45:03 -05:00

23 lines
500 B
JavaScript

// A method of a function-local class that never reads `this` keeps working
// when it is taken off an instance and called detached or with another
// receiver.
'use strict';
function make(label) {
class Box {
one() {
return 1;
}
named() {
return `box ${label}`;
}
}
return new Box();
}
const box = make('a');
const one = box.one;
console.log(one(), box.one.call(null), box.one.call({}), box.named());
console.log([box, make('b')].map((b) => b.one()).join(','));