import { surroundingAgent } from '../host-defined/engine.mts'; import { X, Q } from '../completion.mts'; import type { ParseNode } from '../parser/ParseNode.mts'; import type { PlainEvaluator, ValueEvaluator } from '../evaluator.mts'; import { Evaluate_PropertyName } from './PropertyName.mts'; import { CreateBuiltinFunction, DefinePropertyOrThrow, MakeMethod, OrdinaryFunctionCreate, PrivateGet, PrivateSet, SymbolDescriptiveString, } from '#self'; import { ClassElementDefinitionRecord, Descriptor, JSStringValue, SymbolValue, Value, type Arguments, type ECMAScriptFunctionObject, type FunctionCallContext, type FunctionObject, type ObjectValue, PrivateName, type PropertyKeyValue, } from '#self'; /** https://tc39.es/ecma262/#sec-classfielddefinition-record-specification-type */ export interface ClassFieldDefinitionRecord { readonly Name: PropertyKeyValue | PrivateName; readonly Initializer: ECMAScriptFunctionObject | undefined; } export const ClassFieldDefinitionRecord = function ClassFieldDefinitionRecord(value: ClassFieldDefinitionRecord) { Object.setPrototypeOf(value, ClassFieldDefinitionRecord.prototype); return value; } as { (value: ClassFieldDefinitionRecord): ClassFieldDefinitionRecord; [Symbol.hasInstance](instance: unknown): instance is ClassFieldDefinitionRecord; }; export function* ClassFieldDefinitionEvaluation(FieldDefinition: ParseNode.FieldDefinition, homeObject: ObjectValue): PlainEvaluator { const { ClassElementName, Initializer } = FieldDefinition; // 1. Let name be the result of evaluating ClassElementName. const name = Q(yield* Evaluate_PropertyName(ClassElementName)); // 3. If Initializer is present, then let initializer; if (Initializer) { // a. Let formalParameterList be an instance of the production FormalParameters : [empty]. const formalParameterList: readonly [] = []; // b. Let scope be the LexicalEnvironment of the running execution context. const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; // c. Let privateScope be the running execution context's PrivateEnvironment. const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; // d. Let sourceText be the empty sequence of Unicode code points. const sourceText = ''; // e. Let initializer be ! OrdinaryFunctionCreate(%Function.prototype%, sourceText, formalParameterList, Initializer, non-lexical-this, scope, privateScope). initializer = X(OrdinaryFunctionCreate( surroundingAgent.intrinsic('%Function.prototype%'), sourceText, formalParameterList, Initializer, 'non-lexical-this', scope, privateScope, )); // f. Perform MakeMethod(initializer, homeObject). MakeMethod(initializer, homeObject); // g. Set initializer.[[ClassFieldInitializerName]] to name. initializer.ClassFieldInitializerName = name; } else { // 4. Else, // a. Let initializer be empty. initializer = undefined; } // 5. Return the ClassFieldDefinition Record { [[Name]]: name, [[Initializer]]: initializer }. return ClassFieldDefinitionRecord({ Name: name, Initializer: initializer, }); } /** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-runtime-semantics-classfielddefinitionevaluation */ export function* ClassFieldDefinitionEvaluation_decorator(FieldDefinition: ParseNode.FieldDefinition, homeObject: ObjectValue): PlainEvaluator { const { ClassElementName, Initializer, accessor } = FieldDefinition; if (!accessor) { const name = Q(yield* Evaluate_PropertyName(ClassElementName)); const initializers: FunctionObject[] = []; const extraInitializers: FunctionObject[] = []; if (Initializer) { const initializer = CreateFieldInitializerFunction(homeObject, name, Initializer); // TODO(decorator): spec bug. ApplyDecoratorsToElementDefinition unshift decorator initializers into this array, but read it in order, so the spec order is wrong (be like [decorator2, decorator1, syntaxInit], but the correct order should be [syntaxInit, decorator2, decorator1]) if (surroundingAgent.feature('decorators.no-bugfix.1')) { initializers.push(initializer); } else { initializers[-1] = initializer; } } return ClassElementDefinitionRecord({ Kind: 'field', Key: name, Initializers: initializers, ExtraInitializers: extraInitializers, Decorators: undefined, }); } else { const name = Q(yield* Evaluate_PropertyName(ClassElementName)); let readableName: JSStringValue; if (name instanceof PrivateName) { readableName = name.Description; } else if (name instanceof SymbolValue) { readableName = SymbolDescriptiveString(name); } else { readableName = name; } const privateStateDesc = `${readableName.stringValue()} accessor storage`; const privateStateName = new PrivateName(Value(privateStateDesc)); const getter = MakeAutoAccessorGetter(homeObject, name, privateStateName); const setter = MakeAutoAccessorSetter(homeObject, name, privateStateName); const initializers = []; const extraInitializers: FunctionObject[] = []; if (Initializer) { const initializer = CreateFieldInitializerFunction(homeObject, name, Initializer); // TODO(decorator): spec bug. ApplyDecoratorsToElementDefinition unshift decorator initializers into this array, but read it in order, so the spec order is wrong (be like [decorator2, decorator1, syntaxInit], but the correct order should be [syntaxInit, decorator2, decorator1]) if (surroundingAgent.feature('decorators.no-bugfix.1')) { initializers.push(initializer); } else { initializers[-1] = initializer; } } if (!(name instanceof PrivateName)) { const desc = new Descriptor({ Get: getter, Set: setter, Enumerable: Value.true, Configurable: Value.true, }); Q(yield* DefinePropertyOrThrow(homeObject, name, desc)); } return ClassElementDefinitionRecord({ Kind: 'accessor', Key: name, Get: getter, Set: setter, BackingStorageKey: privateStateName, Initializers: initializers, ExtraInitializers: extraInitializers, Decorators: undefined, }); } } /** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-createfieldinitializerfunction */ export function CreateFieldInitializerFunction(homeObject: ObjectValue, propName: PropertyKeyValue | PrivateName, Initializer: ParseNode.AssignmentExpressionOrHigher) { const formalParameterList: readonly [] = []; const scope = surroundingAgent.runningExecutionContext.LexicalEnvironment; const privateScope = surroundingAgent.runningExecutionContext.PrivateEnvironment; const sourceText = ''; const initializer = OrdinaryFunctionCreate( surroundingAgent.intrinsic('%Function.prototype%'), sourceText, formalParameterList, Initializer, 'non-lexical-this', scope, privateScope, ); MakeMethod(initializer, homeObject); initializer.ClassFieldInitializerName = propName; return initializer; } /** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-makeautoaccessorgetter */ export function MakeAutoAccessorGetter(_homeObject: ObjectValue, _name: PropertyKeyValue | PrivateName, privateStateName: PrivateName) { const getterClosure = function* getterClosure(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { const o = thisValue as ObjectValue; return Q(yield* PrivateGet(o, privateStateName)); }; const getter = CreateBuiltinFunction(getterClosure, 0, Value('get'), []); // TODO(decorator): spec bug, SetFunctionName only accepts ECMAScriptFunctionObject, but the name is already set when calling CreateBuiltinFunction // SetFunctionName(getter, name, Value('get')); // TODO(decorator): https://github.com/tc39/proposal-decorators/issues/568 // MakeMethod(getter, homeObject); return getter; } export function MakeAutoAccessorSetter(_homeObject: ObjectValue, _name: PropertyKeyValue | PrivateName, privateStateName: PrivateName) { const setterClosure = function* setterClosure([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator { const o = thisValue as ObjectValue; Q(yield* PrivateSet(o, privateStateName, value)); return Value.undefined; }; const setter = CreateBuiltinFunction(setterClosure, 1, Value('set'), []); // TODO(decorator): spec bug // SetFunctionName(setter, name, Value('set')); // TODO(decorator): https://github.com/tc39/proposal-decorators/issues/568 // MakeMethod(setter, homeObject); return setter; }