mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
173 lines
5.0 KiB
JavaScript
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)));
|
|
}
|