Files
scriptc/tests/corpus/function-local-private-evaluations.js
T
2026-10-20 17:56:44 -05:00

173 lines
5.0 KiB
JavaScript

// Each evaluation of a function-local class creates its own private names.
// A method taken from one evaluation and called with an instance of another
// (through call, a prototype read, or a checked receiver) finds no brand:
// field reads and writes throw Node's TypeErrors and `#x in this` answers
// false. Calls within one evaluation share the instance's fields.
'use strict';
function make(label) {
class Counter {
#count = 0;
#label = label;
#format = (n) => `${label}#${n}`;
bump() {
this.#count = this.#count + 1;
return `${this.#label}:${this.#count}`;
}
add(n) {
this.#count += n;
return this.#count;
}
increment() {
this.#count++;
return this.#count;
}
reset() {
[this.#count] = [0];
return this.#count;
}
assign() {
return (this.#count = 7);
}
order(note) {
this.#count = note();
return this.#count;
}
has() {
return #count in this;
}
later() {
return () => this.#label;
}
describe() {
return this.#format(this.#count);
}
get count() {
return this.#count;
}
set count(value) {
this.#count = value;
}
}
class Tagged extends Counter {
tag() {
return `tagged ${this.bump()}`;
}
}
return { counter: () => new Counter(), tagged: () => new Tagged() };
}
function attempt(label, fn) {
try {
console.log(label, fn());
} catch (e) {
console.log(label, e instanceof TypeError, e.message);
}
}
const one = make('one');
const two = make('two');
const a = one.counter();
const b = two.counter();
const a2 = one.counter();
console.log(a.bump(), a.bump(), b.bump(), a2.bump());
console.log(a.add(5), a.increment(), a.has(), a.later()());
// Borrowed within one evaluation: the fields are the instance's own.
const bump = a.bump;
console.log(bump.call(a2), a2.bump(), bump.call(a));
console.log(Object.getPrototypeOf(a).bump.call(a2));
// Borrowed across evaluations.
attempt('call read', () => a.bump.call(b));
attempt('compound', () => a.add.call(b, 1));
attempt('update', () => a.increment.call(b));
attempt('destructure', () => a.reset.call(b));
attempt('assign value', () => a.assign.call(b));
attempt('value first', () =>
a.order.call(b, () => {
console.log('value evaluated');
return 1;
}),
);
attempt('proto read', () => Object.getPrototypeOf(a).bump.call(b));
attempt('has other', () => a.has.call(b));
attempt('has own', () => a.has.call(a2));
attempt('arrow', () => a.later.call(b)());
attempt('field call', () => a.describe.call(b));
attempt('field call own', () => a.describe.call(a2));
attempt('apply', () => a.bump.apply(b));
attempt('bind', () => a.bump.bind(b)());
attempt('Reflect.apply', () => Reflect.apply(a.bump, b, []));
const proto = Object.getPrototypeOf(a);
const bumpDescriptor = /** @type {any} */ (Object.getOwnPropertyDescriptor(proto, 'bump'));
const countDescriptor = /** @type {any} */ (Object.getOwnPropertyDescriptor(proto, 'count'));
attempt('descriptor value', () => bumpDescriptor.value.call(b));
attempt('getter', () => countDescriptor.get.call(b));
attempt('setter', () => countDescriptor.set.call(b, 5));
attempt('Reflect.get', () => Reflect.get(a, 'count', b));
attempt('Reflect.set', () => Reflect.set(a, 'count', 3, b));
attempt('own getter', () => [countDescriptor.get.call(a2), Reflect.get(a, 'count', a2)].join(' '));
console.log(b.bump(), b.count, (b.count = 10), b.bump());
// Receivers that are not instances.
for (const recv of [{}, [], undefined, null, 1, 'x']) {
attempt(`read ${String(recv)}`, () => a.bump.call(recv));
attempt(`write ${String(recv)}`, () => a.reset.call(recv));
attempt(`has ${String(recv)}`, () => a.has.call(recv));
}
// A subclass declared after the class in the same function carries the
// brand of its own evaluation.
const t1 = one.tagged();
const t2 = two.tagged();
console.log(t1.tag(), t2.tag(), t1.has(), a.has.call(t1), a.has.call(t2));
attempt('subclass across', () => a.bump.call(t2));
attempt('subclass own', () => b.bump.call(t2));
// undici's iterator guard: the error comes from the method itself.
function createIterator(name) {
class FastIterableIterator {
#target;
#index = 0;
constructor(target) {
this.#target = target;
}
next() {
if (typeof this !== 'object' || this === null || !(#target in this)) {
throw new TypeError(`'next' called on an object that does not implement interface ${name} Iterator.`);
}
const index = this.#index;
if (index >= this.#target.length) return { value: undefined, done: true };
this.#index = index + 1;
return { value: this.#target[index], done: false };
}
}
return (target) => new FastIterableIterator(target);
}
const headers = createIterator('Headers');
const params = createIterator('Params');
const it = headers(['a', 'b']);
const next = it.next;
console.log(JSON.stringify(next.call(it)), JSON.stringify(it.next()), JSON.stringify(next.call(it)));
for (const recv of [headers(['c']), params(['d']), {}, null]) {
attempt('next', () => JSON.stringify(next.call(recv)));
}