Files
ask262/engine262/src/runtime-semantics/ClassDefinitionEvaluation.mts
T

806 lines
40 KiB
TypeScript

import { surroundingAgent } from '../host-defined/engine.mts';
import {
Value, NullValue, ObjectValue, PrivateName,
BooleanValue,
JSStringValue,
type Arguments,
type FunctionCallContext,
UndefinedValue,
type PropertyKeyValue,
ReferenceRecord,
SymbolValue,
} from '../value.mts';
import { Evaluate, type PlainEvaluator, type ValueEvaluator } from '../evaluator.mts';
import {
IsStatic,
ConstructorMethod,
NonConstructorElements,
PrivateBoundIdentifiers,
} from '../static-semantics/all.mts';
import {
Q, X,
AbruptCompletion,
} from '../completion.mts';
import { __ts_cast__, OutOfRange, type Mutable } from '../helpers.mts';
import type { ParseNode } from '../parser/ParseNode.mts';
import {
DefineMethod,
MethodDefinitionEvaluation,
ClassFieldDefinitionEvaluation,
PrivateElementRecord,
ClassFieldDefinitionRecord,
ClassStaticBlockDefinitionEvaluation,
ClassStaticBlockDefinitionRecord,
ClassFieldDefinitionEvaluation_decorator,
} from './all.mts';
import {
Assert,
Call,
Construct,
CreateBuiltinFunction,
Get,
GetValue,
IsConstructor,
MakeConstructor,
MakeClassConstructor,
SetFunctionName,
CreateMethodProperty,
OrdinaryObjectCreate,
OrdinaryCreateFromConstructor,
PrivateMethodOrAccessorAdd,
InitializeInstanceElements,
DefineField,
type ECMAScriptFunctionObject,
type BuiltinFunctionObject,
type FunctionObject,
DefineMethodProperty,
IsCallable,
} from '#self';
import {
DeclarativeEnvironmentRecord,
PrivateEnvironmentRecord,
CreateDataPropertyOrThrow, HasProperty, InitializeFieldOrAccessor, InitializePrivateMethods, IsPropertyKey, markBuiltinFunctionAsConstructor, PrivateElementFind, PrivateGet, PrivateSet, Set, Throw,
} from '#self';
/** https://tc39.es/ecma262/#sec-static-semantics-classelementevaluation */
// -decorator
function ClassElementEvaluation(node: ParseNode.MethodDefinition | ParseNode.GeneratorMethod | ParseNode.AsyncMethod | ParseNode.AsyncGeneratorMethod | ParseNode.FieldDefinition | ParseNode.ClassStaticBlock, object: ObjectValue, enumerable: BooleanValue): PlainEvaluator<PrivateElementRecord | ClassFieldDefinitionRecord | void>
// +decorator
function ClassElementEvaluation(node: ParseNode.MethodDefinition | ParseNode.GeneratorMethod | ParseNode.AsyncMethod | ParseNode.AsyncGeneratorMethod | ParseNode.FieldDefinition | ParseNode.ClassStaticBlock, object: ObjectValue): PlainEvaluator<ClassElementDefinitionRecord | ClassStaticBlockDefinitionRecord | void>
function* ClassElementEvaluation(node: ParseNode.MethodDefinition | ParseNode.GeneratorMethod | ParseNode.AsyncMethod | ParseNode.AsyncGeneratorMethod | ParseNode.FieldDefinition | ParseNode.ClassStaticBlock, object: ObjectValue, enumerable?: BooleanValue): PlainEvaluator<ClassElementDefinitionRecord | ClassFieldDefinitionRecord | ClassStaticBlockDefinitionRecord | PrivateElementRecord | void> {
switch (node.type) {
case 'MethodDefinition':
case 'GeneratorMethod':
case 'AsyncMethod':
case 'AsyncGeneratorMethod': {
if (surroundingAgent.feature('decorators')) {
const decorators = node.Decorators ? Q(yield* DecoratorListEvaluation(node.Decorators)) : [];
const methodDefinition = Q(yield* MethodDefinitionEvaluation(node, object));
methodDefinition.Decorators = decorators;
return methodDefinition;
} else {
return yield* MethodDefinitionEvaluation(node, object, enumerable!);
}
}
case 'FieldDefinition': {
if (surroundingAgent.feature('decorators')) {
const decorators = node.Decorators ? Q(yield* DecoratorListEvaluation(node.Decorators)) : [];
const fieldDefinition = Q(yield* ClassFieldDefinitionEvaluation_decorator(node, object));
fieldDefinition.Decorators = decorators;
return fieldDefinition;
} else {
return yield* ClassFieldDefinitionEvaluation(node, object);
}
}
case 'ClassStaticBlock':
return ClassStaticBlockDefinitionEvaluation(node, object);
default:
throw new OutOfRange('ClassElementEvaluation', node);
}
}
export interface DefaultConstructorBuiltinFunction extends BuiltinFunctionObject {
// -decorator
readonly PrivateMethods: ECMAScriptFunctionObject['PrivateMethods'];
readonly Fields: ECMAScriptFunctionObject['Fields'];
// +decorator (PrivateMethods => Initializers, Fields => Elements)
readonly Initializers: ECMAScriptFunctionObject['Initializers'];
readonly Elements: ECMAScriptFunctionObject['Elements'];
readonly SourceText: ECMAScriptFunctionObject['SourceText'];
readonly ConstructorKind: ECMAScriptFunctionObject['ConstructorKind'];
/**
* Note: this is different than InitialName, which is used and observable in Function.prototype.toString.
* This is only used in the inspector.
*/
readonly HostInitialName: PropertyKeyValue | PrivateName;
}
// ClassTail : ClassHeritage? `{` ClassBody? `}`
/** https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation */
/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-runtime-semantics-classdefinitionevaluation */
export function* ClassDefinitionEvaluation(ClassTail: ParseNode.ClassTail, classBinding: JSStringValue | UndefinedValue, className: PropertyKeyValue | PrivateName, sourceText: string, decorators: readonly DecoratorDefinitionRecord[]): ValueEvaluator<FunctionObject> {
const { ClassHeritage, ClassBody } = ClassTail;
// 1. Let env be the LexicalEnvironment of the running execution context.
const env = surroundingAgent.runningExecutionContext.LexicalEnvironment;
// 2. Let classScope be NewDeclarativeEnvironment(env).
const classScope = new DeclarativeEnvironmentRecord(env);
// 3. If classBinding is not undefined, then
if (!(classBinding instanceof UndefinedValue)) {
// a. Perform classScopeEnv.CreateImmutableBinding(classBinding, true).
classScope.CreateImmutableBinding(classBinding, Value.true);
}
// 4. Let outerPrivateEnvironment be the running execution context's PrivateEnvironment.
const outerPrivateEnvironment = surroundingAgent.runningExecutionContext.PrivateEnvironment;
// 5. Let classPrivateEnvironment be NewPrivateEnvironment(outerPrivateEnvironment).
const classPrivateEnvironment = new PrivateEnvironmentRecord(outerPrivateEnvironment);
// 6. If ClassBody is present, then
if (ClassBody) {
// a. For each String dn of the PrivateBoundIdentifiers of ClassBody, do
for (const dn of PrivateBoundIdentifiers(ClassBody)) {
// i. If classPrivateEnvironment.[[Names]] contains a Private Name whose [[Description]] is dn, then
const existing = classPrivateEnvironment.Names.find((n) => n.Description.stringValue() === dn.stringValue());
if (existing) {
// 1. Assert: This is only possible for getter/setter pairs.
} else { // ii. Else,
// 1. Let name be a new Private Name whose [[Description]] value is dn.
const name = new PrivateName(dn);
// 2. Append name to classPrivateEnvironment.[[Names]].
classPrivateEnvironment.Names.push(name);
}
}
}
let protoParent;
let constructorParent: ObjectValue;
// 7. If ClassHeritage is not present, then
if (!ClassHeritage) {
// a. Let protoParent be %Object.prototype%.
protoParent = surroundingAgent.intrinsic('%Object.prototype%');
// b. Let constructorParent be %Function.prototype%.
constructorParent = surroundingAgent.intrinsic('%Function.prototype%');
} else { // 8. Else,
// a. Set the running execution context's LexicalEnvironment to classScope.
surroundingAgent.runningExecutionContext.LexicalEnvironment = classScope;
// b. Let superclassRef be the result of evaluating ClassHeritage.
const superclassRef = Q(yield* Evaluate(ClassHeritage));
// c. Set the running execution context's LexicalEnvironment to env.
surroundingAgent.runningExecutionContext.LexicalEnvironment = env;
// d. Let superclass be ? GetValue(superclassRef).
const superclass = Q(yield* GetValue(superclassRef));
// e. If superclass is null, then
if (superclass instanceof NullValue) {
// i. Let protoParent be null.
protoParent = Value.null;
// ii. Let constructorParent be %Function.prototype%.
constructorParent = surroundingAgent.intrinsic('%Function.prototype%');
} else if (!IsConstructor(superclass)) {
// f. Else if IsConstructor(superclass) is false, throw a TypeError exception.
return surroundingAgent.Throw('TypeError', 'NotAConstructor', superclass);
} else { // g. Else,
// i. Let protoParent be ? Get(superclass, "prototype").
protoParent = Q(yield* Get(superclass as ObjectValue, Value('prototype')));
// ii. If Type(protoParent) is neither Object nor Null, throw a TypeError exception.
if (!(protoParent instanceof ObjectValue) && !(protoParent instanceof NullValue)) {
return surroundingAgent.Throw('TypeError', 'ObjectPrototypeType');
}
// iii. Let constructorParent be superclass.
constructorParent = superclass as ObjectValue;
}
}
// 9. Let proto be OrdinaryObjectCreate(protoParent).
const proto = OrdinaryObjectCreate(protoParent);
let constructor;
// 10. If ClassBody is not present, let constructor be empty.
if (!ClassBody) {
constructor = undefined;
} else { // 11. Else, let constructor be ConstructorMethod of ClassBody.
constructor = ConstructorMethod(ClassBody);
}
// 12. Set the running execution context's LexicalEnvironment to classScope.
surroundingAgent.runningExecutionContext.LexicalEnvironment = classScope;
// 13. Set the running execution context's PrivateEnvironment to classPrivateEnvironment.
surroundingAgent.runningExecutionContext.PrivateEnvironment = classPrivateEnvironment;
let F;
// 14. If constructor is empty, then
if (constructor === undefined) {
// a. Let defaultConstructor be a new Abstract Closure with no parameters that captures nothing and performs the following steps when called:
const defaultConstructor = function* defaultConstructor(args: Arguments, { NewTarget }: FunctionCallContext) {
// i. Let args be the List of arguments that was passed to this function by [[Call]] or [[Construct]].
// ii. If NewTarget is undefined, throw a TypeError exception.
if (NewTarget instanceof UndefinedValue) {
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', surroundingAgent.activeFunctionObject);
}
// iii. Let F be the active function object.
const F = surroundingAgent.activeFunctionObject as ECMAScriptFunctionObject; // eslint-disable-line no-shadow
let result;
// iv. If F.[[ConstructorKind]] is derived, then
if (F.ConstructorKind === 'derived') {
// 1. NOTE: This branch behaves similarly to `constructor(...args) { super(...args); }`. The most
// notable distinction is that while the aforementioned ECMAScript source text observably calls
// the @@iterator method on `%Array.prototype%`, a Default Constructor Function does not.
// 2. Let func be ! F.[[GetPrototypeOf]]().
const func = X(yield* F.GetPrototypeOf());
// 3. If IsConstructor(func) is false, throw a TypeError exception.
if (!IsConstructor(func)) {
return surroundingAgent.Throw('TypeError', 'NotAConstructor', func);
}
// 4. Let result be ? Construct(func, args, NewTarget).
result = Q(yield* Construct(func, args, NewTarget));
} else { // v. Else,
// 1. NOTE: This branch behaves similarly to `constructor() {}`.
// 2. Let result be ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%").
result = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%Object.prototype%'));
}
Q(yield* InitializeInstanceElements(result, F));
return result;
};
// b. ! CreateBuiltinFunction(defaultConstructor, 0, className, « [[ConstructorKind]], [[SourceText]], [[PrivateMethods]], [[Fields]] », the current Realm Record, constructorParent).
F = X(CreateBuiltinFunction(markBuiltinFunctionAsConstructor(defaultConstructor), 0, className, ['ConstructorKind', 'SourceText', surroundingAgent.feature('decorators') ? 'Initializers' : 'PrivateMethods', surroundingAgent.feature('decorators') ? 'Elements' : 'Fields'], surroundingAgent.currentRealmRecord, constructorParent));
} else { // 15. Else,
// a. Let constructorInfo be ! DefineMethod of constructor with arguments proto and constructorParent.
const constructorInfo = X(yield* DefineMethod(constructor, proto, constructorParent));
// b. Let F be constructorInfo.[[Closure]].
F = constructorInfo.Closure;
// c. Perform SetFunctionName(F, className).
SetFunctionName(F, className);
}
__ts_cast__<Mutable<DefaultConstructorBuiltinFunction>>(F);
F.HostInitialName = className;
F.SourceText = sourceText;
// 16. Perform MakeConstructor(F, false, proto).
MakeConstructor(F, Value.false, proto);
// https://github.com/tc39/ecma262/pull/3212/
// 17. Perform MakeClassConstructor(F).
MakeClassConstructor(F);
// 18. If ClassHeritage is present, set F.[[ConstructorKind]] to derived.
if (ClassHeritage) {
F.ConstructorKind = 'derived';
}
// 19. Perform CreateMethodProperty(proto, "constructor", F).
X(CreateMethodProperty(proto, Value('constructor'), F));
// 20. If ClassBody is not present, let elements be a new empty List.
let elements: ParseNode.ClassElement[];
if (!ClassBody) {
elements = [];
} else { // 20. Else, let elements be NonConstructorElements of ClassBody.
elements = NonConstructorElements(ClassBody);
}
if (surroundingAgent.feature('decorators')) {
const instanceElements: ClassElementDefinitionRecord[] = [];
// 24. Let staticElements be a new empty List.
const staticElements: (ClassElementDefinitionRecord | ClassStaticBlockDefinitionRecord)[] = [];
// 25. For each ClassElement e of elements, do
for (const e of elements) {
let result;
// a. If IsStatic of e is false, then
if (!IsStatic(e)) {
result = yield* ClassElementEvaluation(e, proto);
} else {
result = yield* ClassElementEvaluation(e, F);
}
// c. If field is an abrupt completion, then
if (result instanceof AbruptCompletion) {
// i. Set the running execution context's LexicalEnvironment to env.
surroundingAgent.runningExecutionContext.LexicalEnvironment = env;
// ii. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment;
return result;
}
const element = X(result);
if (element instanceof ClassElementDefinitionRecord) {
if (!IsStatic(e)) {
instanceElements.push(element);
} else {
staticElements.push(element);
}
} else {
Assert(element instanceof ClassStaticBlockDefinitionRecord);
staticElements.push(element);
}
}
// 26. Set the running execution context's LexicalEnvironment to env.
surroundingAgent.runningExecutionContext.LexicalEnvironment = env;
const instanceMethodExtraInitializers: FunctionObject[] = [];
const staticMethodExtraInitializers: FunctionObject[] = [];
for (const e of staticElements) {
if (e instanceof ClassElementDefinitionRecord && e.Kind !== 'field') {
let extraInitializers: FunctionObject[];
if (e.Kind === 'accessor') {
extraInitializers = e.ExtraInitializers;
} else {
extraInitializers = staticMethodExtraInitializers;
}
const result = yield* ApplyDecoratorsAndDefineMethod(F, e, extraInitializers, true);
if (result instanceof AbruptCompletion) {
surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment;
return result;
}
}
}
for (const e of instanceElements) {
let extraInitializers: FunctionObject[];
if (e.Kind !== 'field') {
if (e.Kind === 'accessor') {
extraInitializers = e.ExtraInitializers;
} else {
extraInitializers = instanceMethodExtraInitializers;
}
const result = yield* ApplyDecoratorsAndDefineMethod(proto, e, extraInitializers, false);
if (result instanceof AbruptCompletion) {
surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment;
return result;
}
}
}
for (const e of staticElements) {
if (e instanceof ClassElementDefinitionRecord && e.Kind === 'field') {
const result = yield* ApplyDecoratorsToElementDefinition(F, e, e.ExtraInitializers, true);
if (result instanceof AbruptCompletion) {
surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment;
return result;
}
}
}
for (const e of instanceElements) {
if (e.Kind === 'field') {
const result = yield* ApplyDecoratorsToElementDefinition(proto, e, e.ExtraInitializers, false);
if (result instanceof AbruptCompletion) {
surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment;
return result;
}
}
}
F.Elements = instanceElements;
F.Initializers = instanceMethodExtraInitializers;
// TODO(decorator): spec bug?
// Q(yield* InitializePrivateMethods(F, staticElements));
Q(yield* InitializePrivateMethods(F, staticElements.filter((element): element is ClassElementDefinitionRecord => element instanceof ClassElementDefinitionRecord)));
const classExtraInitializers: FunctionObject[] = [];
const newF = yield* ApplyDecoratorsToClassDefinition(F, decorators, className, classExtraInitializers);
if (newF instanceof AbruptCompletion) {
surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment;
return newF;
}
F = Q(newF);
// 27. If classBinding is not undefined, then
if (!(classBinding instanceof UndefinedValue)) {
// a. Perform classScope.InitializeBinding(classBinding, F).
yield* classScope.InitializeBinding(classBinding, F);
}
for (const initializer of staticMethodExtraInitializers) {
const result = yield* Call(initializer, F);
if (result instanceof AbruptCompletion) {
surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment;
return result;
}
}
// 31. For each element elementRecord of staticElements, do
for (const elementRecord of staticElements) {
let result;
// a. If elementRecord is a ClassFieldDefinition Record, then
if (elementRecord instanceof ClassElementDefinitionRecord && (elementRecord.Kind === 'field' || elementRecord.Kind === 'accessor')) {
// a. Let result be DefineField(F, elementRecord).
result = yield* InitializeFieldOrAccessor(F, elementRecord);
} else if (elementRecord instanceof ClassStaticBlockDefinitionRecord) {
result = yield* Call(elementRecord.BodyFunction, F);
}
// c. If result is an abrupt completion, then
if (result instanceof AbruptCompletion) {
// i. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment;
// ii. Return result.
return result;
}
}
for (const initializer of classExtraInitializers) {
const result = yield* Call(initializer, F);
if (result instanceof AbruptCompletion) {
surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment;
return result;
}
}
// 32. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment;
// 33. Return F.
return F;
} else {
// 21. Let instancePrivateMethods be a new empty List.
const instancePrivateMethods: never[] = [];
// 22. Let staticPrivateMethods be a new empty List.
const staticPrivateMethods: never[] = [];
// 23. Let instanceFields be a new empty List.
const instanceFields: ClassFieldDefinitionRecord[] = [];
// 24. Let staticElements be a new empty List.
const staticElements: (ClassFieldDefinitionRecord | ClassStaticBlockDefinitionRecord)[] = [];
// 25. For each ClassElement e of elements, do
for (const e of elements) {
let field;
// a. If IsStatic of e is false, then
if (IsStatic(e) === false) {
// i. Let field be ClassElementEvaluation of e with arguments proto and false.
field = (yield* ClassElementEvaluation(e, proto, Value.false))!;
} else { // b. Else,
// i. Let field be ClassElementEvaluation of e with arguments F and false.
field = (yield* ClassElementEvaluation(e, F, Value.false))!;
}
// c. If field is an abrupt completion, then
if (field instanceof AbruptCompletion) {
// i. Set the running execution context's LexicalEnvironment to env.
surroundingAgent.runningExecutionContext.LexicalEnvironment = env;
// ii. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment;
// iii. Return Completion(field).
return field;
}
// d. Set field to field.[[Value]].
Q(field);
// e. If field is a PrivateElement, then
if (field instanceof PrivateElementRecord) {
// i. Assert: field.[[Kind]] is either method or accessor.
Assert(field.Kind === 'method' || field.Kind === 'accessor');
// ii. If IsStatic of e is false, let container be instancePrivateMethods.
let container: PrivateElementRecord[];
if (IsStatic(e) === false) {
container = instancePrivateMethods;
} else { // iii. Else, let container be staticPrivateMethods.
container = staticPrivateMethods;
}
// iv. If container contains a PrivateElement whose [[Key]] is field.[[Key]], then
const index = container.findIndex((el) => el.Key === field.Key);
if (index >= 0) {
// 1. Let existing be that PrivateElement.
const existing = container[index];
// 2. Assert: field.[[Kind]] and existing.[[Kind]] are both accessor.
Assert(field.Kind === 'accessor' && existing.Kind === 'accessor');
// 3. If field.[[Get]] is undefined, then
let combined;
if (field.Get === Value.undefined) {
combined = PrivateElementRecord({
Key: field.Key,
Kind: 'accessor',
Get: existing.Get,
Set: field.Set,
});
} else { // 4. Else
combined = PrivateElementRecord({
Key: field.Key,
Kind: 'accessor',
Get: field.Get,
Set: existing.Set,
});
}
// 5. Replace existing in container with combined.
container[index] = combined;
} else { // v. Else,
// 1. Append field to container.
container.push(field);
}
} else if (field instanceof ClassFieldDefinitionRecord) { // f. Else if field is a ClassFieldDefinition Record, then
// i. If IsStatic of e is false, append field to instanceFields.
if (IsStatic(e) === false) {
instanceFields.push(field);
} else { // ii. Else, append field to staticElements.
staticElements.push(field);
}
} else if (field instanceof ClassStaticBlockDefinitionRecord) { // g. Else if element is a ClassStaticBlockDefinition Record, then
// i. Append element to staticElements.
staticElements.push(field);
}
}
// 26. Set the running execution context's LexicalEnvironment to env.
surroundingAgent.runningExecutionContext.LexicalEnvironment = env;
// 27. If classBinding is not undefined, then
if (!(classBinding instanceof UndefinedValue)) {
// a. Perform classScope.InitializeBinding(classBinding, F).
yield* classScope.InitializeBinding(classBinding, F);
}
// 28. Set F.[[PrivateMethods]] to instancePrivateMethods.
F.PrivateMethods = instancePrivateMethods;
// 29. Set F.[[Fields]] to instanceFields.
F.Fields = instanceFields;
// 30. For each PrivateElement method of staticPrivateMethods, do
for (const method of staticPrivateMethods) {
// a. Perform ! PrivateMethodOrAccessorAdd(F, method).
Q(yield* PrivateMethodOrAccessorAdd(F, method));
}
// 31. For each element elementRecord of staticElements, do
for (const elementRecord of staticElements) {
let result;
// a. If elementRecord is a ClassFieldDefinition Record, then
if (elementRecord instanceof ClassFieldDefinitionRecord) {
// a. Let result be DefineField(F, elementRecord).
result = yield* DefineField(F, elementRecord);
} else { // b. Else,
// i. Assert: elementRecord is a ClassStaticBlockDefinition Record.
Assert(elementRecord instanceof ClassStaticBlockDefinitionRecord);
// ii. Let result be Completion(Call(elementRecord.[[BodyFunction]], F)).
result = yield* Call(elementRecord.BodyFunction, F);
}
// c. If result is an abrupt completion, then
if (result instanceof AbruptCompletion) {
// i. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment;
// ii. Return result.
return result;
}
}
// 32. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
surroundingAgent.runningExecutionContext.PrivateEnvironment = outerPrivateEnvironment;
// 33. Return F.
return F;
}
}
/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-decoratorevaluation */
export function* DecoratorEvaluation(decorator: ParseNode.Decorator): PlainEvaluator<DecoratorDefinitionRecord> {
const expr = decorator.MemberExpression || decorator.CallExpression || decorator.ParenthesizedExpression;
const ref = Q(yield* Evaluate(expr));
const value = Q(yield* GetValue(ref));
return { Decorator: value, Receiver: ref };
}
/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-decoratorelistvaluation */
export function* DecoratorListEvaluation(decoratorList: readonly ParseNode.Decorator[]): PlainEvaluator<DecoratorDefinitionRecord[]> {
const decorators: DecoratorDefinitionRecord[] = [];
for (const decoratorNode of decoratorList) {
const decoratorRecord = Q(yield* DecoratorEvaluation(decoratorNode));
decorators.unshift(decoratorRecord);
}
return decorators;
}
/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-createdecoratoraccessobject */
export function CreateDecoratorAccessObject(kind: ClassElementDefinitionRecord['Kind'], name: PropertyKeyValue | PrivateName): ObjectValue {
const accessObj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'));
if (kind === 'field' || kind === 'method' || kind === 'accessor' || kind === 'getter') {
const getterClosure = function* getter([obj = Value.undefined]: Arguments) {
if (!(obj instanceof ObjectValue)) {
return Throw.TypeError('Invalid receiver');
}
if (IsPropertyKey(name)) {
return Q(yield* Get(obj, name));
} else {
return Q(yield* PrivateGet(obj, name));
}
};
const getter = CreateBuiltinFunction(getterClosure, 1, Value(''), []);
X(CreateDataPropertyOrThrow(accessObj, Value('get'), getter));
}
if (kind === 'field' || kind === 'accessor' || kind === 'setter') {
const setterClosure = function* setter([obj = Value.undefined, value = Value.undefined]: Arguments) {
if (!(obj instanceof ObjectValue)) {
return Throw.TypeError('Invalid receiver');
}
if (IsPropertyKey(name)) {
return Q(yield* Set(obj, name, value, Value.true));
} else {
return Q(yield* PrivateSet(obj, name, value));
}
};
const setter = CreateBuiltinFunction(setterClosure, 2, Value(''), []);
X(CreateDataPropertyOrThrow(accessObj, Value('set'), setter));
}
const hasClosure = function* has(this: Value, [obj = Value.undefined]: Arguments) {
if (!(obj instanceof ObjectValue)) {
return Throw.TypeError('Invalid receiver');
}
if (IsPropertyKey(name)) {
return Q(yield* HasProperty(obj, name));
}
if (PrivateElementFind(name, obj)) {
return Value.true;
}
return Value.false;
};
const has = CreateBuiltinFunction(hasClosure, 1, Value('has'), []);
X(CreateDataPropertyOrThrow(accessObj, Value('has'), has));
return accessObj;
}
/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-createaddinitializerfunction */
// TODO(decorator): spec bug, initializers should not require ECMAScriptFunctionObject
export function CreateAddInitializerFunction(initializers: FunctionObject[], decorationState: { Finished: boolean }): FunctionObject {
const addInitializerClosure = function* addInitializer(this: Value, [initializer = Value.undefined]: Arguments) {
if (decorationState.Finished) {
return Throw.TypeError('Cannot call addInitializer after decoration is finished');
}
if (!IsCallable(initializer)) {
return Throw.TypeError('addInitializer must be called with a function, but $1 was passed', initializer);
}
initializers.push(initializer);
return Value.undefined;
};
return CreateBuiltinFunction(addInitializerClosure, 1, Value('addInitializer'), []);
}
/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-createdecoratorcontextobject */
export function CreateDecoratorContextObject(kind: 'class' | ClassElementDefinitionRecord['Kind'], name: PropertyKeyValue | PrivateName, initializers: FunctionObject[], decorationState: { Finished: boolean }, isStatic?: boolean): ObjectValue {
const contextObj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'));
const kindStr = Value(kind);
X(CreateDataPropertyOrThrow(contextObj, Value('kind'), kindStr));
if (kind !== 'class') {
X(CreateDataPropertyOrThrow(contextObj, Value('access'), CreateDecoratorAccessObject(kind, name)));
if (isStatic !== undefined) {
X(CreateDataPropertyOrThrow(contextObj, Value('static'), Value(isStatic)));
}
if (name instanceof PrivateName) {
X(CreateDataPropertyOrThrow(contextObj, Value('private'), Value.true));
X(CreateDataPropertyOrThrow(contextObj, Value('name'), name.Description));
} else {
X(CreateDataPropertyOrThrow(contextObj, Value('private'), Value.false));
X(CreateDataPropertyOrThrow(contextObj, Value('name'), name));
}
} else {
// TODO(decorator): spec bug, no assert to the name
X(CreateDataPropertyOrThrow(contextObj, Value('name'), name as PropertyKeyValue));
}
const addInitializer = CreateAddInitializerFunction(initializers, decorationState);
X(CreateDataPropertyOrThrow(contextObj, Value('addInitializer'), addInitializer));
return contextObj;
}
/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-applydecoratorstoelementdefinition */
// TODO(decorator): unused parameter in the spec
export function* ApplyDecoratorsToElementDefinition(_homeObject: ObjectValue, elementRecord: ClassElementDefinitionRecord, extraInitializers: FunctionObject[], isStatic: boolean): PlainEvaluator<void> {
const decorators = elementRecord.Decorators;
if (!decorators || decorators.length === 0) {
return undefined;
}
const key = elementRecord.Key;
const kind = elementRecord.Kind;
for (const decoratorRecord of decorators) {
const decorator = decoratorRecord.Decorator;
const decoratorReceiver = decoratorRecord.Receiver;
const decorationState = { Finished: false };
const context = CreateDecoratorContextObject(kind, key, extraInitializers, decorationState, isStatic);
let value: Value = Value.undefined;
if (kind === 'method') {
value = elementRecord.Value;
} else if (kind === 'getter') {
value = elementRecord.Get;
} else if (kind === 'setter') {
value = elementRecord.Set;
} else if (kind === 'accessor') {
value = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'));
X(CreateDataPropertyOrThrow(value, Value('get'), elementRecord.Get));
X(CreateDataPropertyOrThrow(value, Value('set'), elementRecord.Set));
}
// TODO(decorator): spec bug, missing GetValue call
// const newValue = Q(yield* Call(decorator, decoratorReceiver), [value, context]));
const newValue = Q(yield* Call(decorator, Q(yield* GetValue(decoratorReceiver)), [value, context]));
decorationState.Finished = true;
if (kind === 'field') {
if (IsCallable(newValue)) {
// 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])
elementRecord.Initializers.unshift(newValue);
} else if (newValue !== Value.undefined) {
return Throw.TypeError('Field decorator must return a function or undefined, but $1 was returned', newValue);
}
} else if (kind === 'accessor') {
if (newValue instanceof ObjectValue) {
const newGetter = Q(yield* Get(newValue, Value('get')));
if (IsCallable(newGetter)) {
elementRecord.Get = newGetter;
} else if (newGetter !== Value.undefined) {
return Throw.TypeError('The get property of the return value of an accessor decorator must be a function or undefined, but $1 was returned', newGetter);
}
const newSetter = Q(yield* Get(newValue, Value('set')));
if (IsCallable(newSetter)) {
elementRecord.Set = newSetter;
} else if (newSetter !== Value.undefined) {
return Throw.TypeError('The set property of the return value of an accessor decorator must be a function or undefined, but $1 was returned', newSetter);
}
const initializer = Q(yield* Get(newValue, Value('init')));
if (IsCallable(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])
elementRecord.Initializers.unshift(initializer);
} else if (initializer !== Value.undefined) {
return Throw.TypeError('The init property of the return value of an accessor decorator must be a function or undefined, but $1 was returned', initializer);
}
} else if (newValue !== Value.undefined) {
return Throw.TypeError('Accessor decorator must return an object or undefined, but $1 was returned', newValue);
}
} else {
if (IsCallable(newValue)) {
if (kind === 'getter') {
elementRecord.Get = newValue;
} else if (kind === 'setter') {
elementRecord.Set = newValue;
} else {
elementRecord.Value = newValue;
}
} else if (newValue !== Value.undefined) {
return Throw.TypeError('Method decorator must return a function or undefined, but $1 was returned', newValue);
}
}
}
elementRecord.Decorators = undefined;
return undefined;
}
/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-applydecoratorstoclassdefinition */
export function* ApplyDecoratorsToClassDefinition(classDef: FunctionObject, decorators: readonly DecoratorDefinitionRecord[], className: PropertyKeyValue | PrivateName, extraInitializers: FunctionObject[]): PlainEvaluator<FunctionObject> {
for (const decoratorRecord of decorators) {
const decorator = decoratorRecord.Decorator;
const decoratorReceiver = decoratorRecord.Receiver;
const decorationState = { Finished: false };
const context = CreateDecoratorContextObject('class', className, extraInitializers, decorationState);
// TODO(decorator): spec bug, missing GetValue call
// const newDef = Q(yield* Call(decorator, decoratorReceiver, [classDef, context]));
const newDef = Q(yield* Call(decorator, Q(yield* GetValue(decoratorReceiver)), [classDef, context]));
decorationState.Finished = true;
if (IsCallable(newDef)) {
classDef = newDef;
} else if (newDef !== Value.undefined) {
return Throw.TypeError('Class decorator must return a function or undefined, but $1 was returned', newDef);
}
}
return classDef;
}
/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-applydecoratorsanddefinemethod */
export function* ApplyDecoratorsAndDefineMethod(homeObject: ObjectValue, methodDefinition: ClassElementDefinitionRecord, extraInitializers: FunctionObject[], isStatic: boolean): PlainEvaluator<void> {
Q(yield* ApplyDecoratorsToElementDefinition(homeObject, methodDefinition, extraInitializers, isStatic));
// TODO(decorator): spec bug, enumerable of class methods, whether decorated or not, should always be false
// Q(yield* DefineMethodProperty(homeObject, methodDefinition, isStatic));
Q(yield* DefineMethodProperty(homeObject, methodDefinition, false));
}
/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-decoratordefinition-record-specification-type */
export interface DecoratorDefinitionRecord {
readonly Decorator: Value;
readonly Receiver: ReferenceRecord | Value;
}
/** https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2417#sec-classfielddefinition-record-specification-type */
export type ClassElementDefinitionRecord = ClassElementDefinitionRecord_Method | ClassElementDefinitionRecord_Field | ClassElementDefinitionRecord_Accessor | ClassElementDefinitionRecord_Getter | ClassElementDefinitionRecord_Setter;
export interface ClassElementDefinitionRecord_Method {
readonly Kind: 'method';
readonly Key: PrivateName | JSStringValue | SymbolValue;
// TODO(decorator): spec bug, spec is ECMAScriptFunctionObject
Value: FunctionObject;
Decorators: DecoratorDefinitionRecord[] | undefined;
}
export interface ClassElementDefinitionRecord_Field {
readonly Kind: 'field';
readonly Key: PrivateName | JSStringValue | SymbolValue;
Decorators: DecoratorDefinitionRecord[] | undefined;
readonly Initializers: FunctionObject[];
readonly ExtraInitializers: FunctionObject[];
}
export interface ClassElementDefinitionRecord_Accessor {
readonly Kind: 'accessor';
readonly Key: PrivateName | JSStringValue | SymbolValue;
// https://github.com/tc39/proposal-decorators/issues/572
Get: FunctionObject;
// https://github.com/tc39/proposal-decorators/issues/572
Set: FunctionObject;
readonly BackingStorageKey: PrivateName;
Decorators: readonly DecoratorDefinitionRecord[] | undefined;
readonly Initializers: FunctionObject[];
readonly ExtraInitializers: FunctionObject[];
}
export interface ClassElementDefinitionRecord_Getter {
readonly Kind: 'getter';
readonly Key: PrivateName | JSStringValue | SymbolValue;
// https://github.com/tc39/proposal-decorators/issues/572
Get: FunctionObject;
Decorators: readonly DecoratorDefinitionRecord[] | undefined;
}
export interface ClassElementDefinitionRecord_Setter {
readonly Kind: 'setter';
readonly Key: PrivateName | JSStringValue | SymbolValue;
// https://github.com/tc39/proposal-decorators/issues/572
Set: FunctionObject;
Decorators: readonly DecoratorDefinitionRecord[] | undefined;
}
// This is a struct defined as a marco.
export const ClassElementDefinitionRecord = (function ClassElementDefinitionRecord(record: ClassElementDefinitionRecord) {
Object.setPrototypeOf(record, ClassElementDefinitionRecord.prototype);
return record;
}) as {
(record: ClassElementDefinitionRecord): ClassElementDefinitionRecord;
[Symbol.hasInstance](instance: unknown): instance is ClassElementDefinitionRecord;
};