import { ObjectValue, PrivateName, Value } from '../value.mts'; import { Q, X } from '../completion.mts'; import { ClassElementDefinitionRecord, PrivateElementRecord } from '../runtime-semantics/all.mts'; import { Assert, Call, IsExtensible, } from './all.mts'; import { Throw, type PlainEvaluator } from '#self'; /** https://tc39.es/ecma262/#sec-privateelementfind */ export function PrivateElementFind(P: PrivateName, O: ObjectValue) { const entry = O.PrivateElements.find((e) => e.Key === P); // 1. If O.[[PrivateElements]] contains a PrivateElement whose [[Key]] is P, then if (entry) { // a. Let entry be that PrivateElement. // b. Return entry. return entry; } // 2. Return empty. return undefined; } /** https://tc39.es/ecma262/#sec-privateget */ export function* PrivateGet(O: ObjectValue, P: PrivateName) { // 1. Let entry be ! PrivateElementFind(P, O). const entry = X(PrivateElementFind(P, O)); // 2. If entry is empty, throw a TypeError exception. if (entry === undefined) { return Throw.TypeError('$1 does not exist on $2', P, O); } // 3. If entry.[[Kind]] is field or method, then if (entry.Kind === 'field' || entry.Kind === 'method') { // a. Return entry.[[Value]]. return entry.Value!; } // 4. Assert: entry.[[Kind]] is accessor. Assert(entry.Kind === 'accessor'); // 5. If entry.[[Get]] is undefined, throw a TypeError exception. if (entry.Get === Value.undefined) { return Throw.TypeError('Private field $1 is not a getter', P); } // 6. Let getter be entry.[[Get]]. const getter = entry.Get!; // 7. Return ? Call(getter, O). return Q(yield* Call(getter, O)); } export function* PrivateSet(O: ObjectValue, P: PrivateName, value: Value) { // 1. Let entry be ! PrivateElementFind(P, O). const entry = X(PrivateElementFind(P, O)); // 2. If entry is empty, throw a TypeError exception. if (entry === undefined) { return Throw.TypeError('$1 does not exist on $2', P, O); } // 3. If entry.[[Kind]] is field, then if (entry.Kind === 'field') { // a. Set entry.[[Value]] to value. entry.Value = value; } else if (entry.Kind === 'method') { // 4. Else if entry.[[Kind]] is method, then // a. Throw a TypeError exception. return Throw.TypeError('Private method $1 cannot be set', P); } else { // 5. Else, // a. Assert: entry.[[Kind]] is accessor. Assert(entry.Kind === 'accessor'); // b. If entry.[[Set]] is undefined, throw a TypeError exception. if (entry.Set === Value.undefined) { return Throw.TypeError('Private field $1 is not a setter', P); } // c. Let setter be entry.[[Set]]. const setter = entry.Set!; // d. Perform ? Call(setter, O, « value »). Q(yield* Call(setter, O, [value])); } return undefined; } /** https://tc39.es/ecma262/#sec-privatemethodoraccessoradd */ export function* PrivateMethodOrAccessorAdd(O: ObjectValue, method: PrivateElementRecord) { // 1. Assert: method.[[Kind]] is either method or accessor. Assert(method.Kind === 'method' || method.Kind === 'accessor'); if (Q(yield* IsExtensible(O)) === Value.false) { return Throw.TypeError('Cannot define private element to a non-extensible object'); } // 2. Let entry be ! PrivateElementFind(method.[[Key]], O). const entry = X(PrivateElementFind(method.Key, O)); // 3. If entry is not empty, throw a TypeError exception. if (entry !== undefined) { return Throw.TypeError('Private element $1 is already defined on $2', method.Key, O); } // 4. Append method to O.[[PrivateElements]]. O.PrivateElements.push(method); // 5. NOTE: The values for private methods and accessors are shared across instances. // This step does not create a new copy of the method or accessor. return undefined; } /** https://tc39.es/ecma262/#sec-privatefieldadd */ export function* PrivateFieldAdd(O: ObjectValue, P: PrivateName, value: Value) { // 1. Let entry be ! PrivateElementFind(P, O). const entry = X(PrivateElementFind(P, O)); if (Q(yield* IsExtensible(O)) === Value.false) { return Throw.TypeError('Cannot define private element to a non-extensible object'); } // 2. If entry is not empty, throw a TypeError exception. if (entry !== undefined) { return Throw.TypeError('Private element $1 is already defined on $2', P, O); } // 3. Append PrivateElement { [[Key]]: P, [[Kind]]: field, [[Value]]: value } to O.[[PrivateElements]]. O.PrivateElements.push(PrivateElementRecord({ Key: P, Kind: 'field', Value: value, })); return undefined; } /** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-initializeprivatemethods */ export function* InitializePrivateMethods(O: ObjectValue, elementDefinitions: readonly ClassElementDefinitionRecord[]): PlainEvaluator { const privateMethods: PrivateElementRecord[] = []; for (const element of elementDefinitions) { if (element.Key instanceof PrivateName && (element.Kind === 'method' || element.Kind === 'getter' || element.Kind === 'setter' || element.Kind === 'accessor')) { if (element.Kind === 'method') { const privateElement = PrivateElementRecord({ Key: element.Key, Kind: 'method', Value: element.Value, }); privateMethods.push(privateElement); } else if (element.Kind === 'accessor') { const privateElement = PrivateElementRecord({ Key: element.Key, Kind: 'accessor', Get: element.Get, Set: element.Set, }); privateMethods.push(privateElement); } else { Assert(element.Kind === 'getter' || element.Kind === 'setter'); let getter = element.Kind === 'getter' ? element.Get : Value.undefined; let setter = element.Kind === 'setter' ? element.Set : Value.undefined; let existing: PrivateElementRecord | undefined; const e = privateMethods.find(((e) => e.Key === element.Key)); if (e) { Assert(e.Kind === 'accessor'); existing = e; if (e.Get !== undefined && e.Get !== Value.undefined) { getter = e.Get; } if (e.Set !== undefined && e.Set !== Value.undefined) { setter = e.Set; } } const privateElement = PrivateElementRecord({ Key: element.Key, Kind: 'accessor', Get: getter, Set: setter, }); if (existing) { const index = privateMethods.indexOf(existing); privateMethods[index] = privateElement; } else { privateMethods.push(privateElement); } } } } for (const method of privateMethods) { Q(yield* PrivateMethodOrAccessorAdd(O, method)); } }