mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 13:21:55 +00:00
806 lines
40 KiB
TypeScript
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;
|
|
};
|