123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- using Jint.Native.Object;
- using Jint.Runtime;
- using Jint.Runtime.Descriptors;
- using Jint.Runtime.Environments;
- using Jint.Runtime.Interpreter;
- using Jint.Runtime.Interpreter.Expressions;
- using Environment = Jint.Runtime.Environments.Environment;
- namespace Jint.Native.Function;
- internal sealed class ClassDefinition
- {
- private static readonly MethodDefinition _superConstructor;
- internal static CallExpression _defaultSuperCall;
- internal static readonly MethodDefinition _emptyConstructor;
- internal readonly string? _className;
- private readonly Expression? _superClass;
- private readonly ClassBody _body;
- static ClassDefinition()
- {
- var parser = new Parser(Engine.BaseParserOptions);
- // generate missing constructor AST only once
- static MethodDefinition CreateConstructorMethodDefinition(Parser parser, string source)
- {
- var script = parser.ParseScriptGuarded(new Engine().Realm, source);
- var classDeclaration = (ClassDeclaration) script.Body[0];
- return (MethodDefinition) classDeclaration.Body.Body[0];
- }
- _superConstructor = CreateConstructorMethodDefinition(parser, "class temp extends X { constructor(...args) { super(...args); } }");
- _defaultSuperCall = (CallExpression) ((NonSpecialExpressionStatement) _superConstructor.Value.Body.Body[0]).Expression;
- _emptyConstructor = CreateConstructorMethodDefinition(parser, "class temp { constructor() {} }");
- }
- public ClassDefinition(
- string? className,
- Expression? superClass,
- ClassBody body)
- {
- _className = className;
- _superClass = superClass;
- _body = body;
- }
- /// <summary>
- /// https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation
- /// </summary>
- public JsValue BuildConstructor(EvaluationContext context, Environment env)
- {
- // A class definition is always strict mode code.
- using var _ = new StrictModeScope(true, true);
- var engine = context.Engine;
- var classEnv = JintEnvironment.NewDeclarativeEnvironment(engine, env);
- if (_className is not null)
- {
- classEnv.CreateImmutableBinding(_className, true);
- }
- var outerPrivateEnvironment = engine.ExecutionContext.PrivateEnvironment;
- var classPrivateEnvironment = JintEnvironment.NewPrivateEnvironment(engine, outerPrivateEnvironment);
- ObjectInstance? protoParent = null;
- ObjectInstance? constructorParent = null;
- if (_superClass is null)
- {
- protoParent = engine.Realm.Intrinsics.Object.PrototypeObject;
- constructorParent = engine.Realm.Intrinsics.Function.PrototypeObject;
- }
- else
- {
- engine.UpdateLexicalEnvironment(classEnv);
- var superclass = JintExpression.Build(_superClass).GetValue(context);
- engine.UpdateLexicalEnvironment(env);
- if (superclass.IsNull())
- {
- protoParent = null;
- constructorParent = engine.Realm.Intrinsics.Function.PrototypeObject;
- }
- else if (!superclass.IsConstructor)
- {
- Throw.TypeError(engine.Realm, "super class is not a constructor");
- }
- else
- {
- var temp = superclass.Get(CommonProperties.Prototype);
- if (temp is ObjectInstance protoParentObject)
- {
- protoParent = protoParentObject;
- }
- else if (temp.IsNull())
- {
- // OK
- }
- else
- {
- Throw.TypeError(engine.Realm, "cannot resolve super class prototype chain");
- return default;
- }
- constructorParent = (ObjectInstance) superclass;
- }
- }
- ObjectInstance proto = new JsObject(engine) { _prototype = protoParent };
- var privateBoundIdentifiers = new HashSet<PrivateIdentifier>(PrivateIdentifierNameComparer._instance);
- MethodDefinition? constructor = null;
- ref readonly var elements = ref _body.Body;
- var classBody = elements;
- for (var i = 0; i < classBody.Count; ++i)
- {
- var element = classBody[i];
- if (element is MethodDefinition { Kind: PropertyKind.Constructor } c)
- {
- constructor = c;
- }
- privateBoundIdentifiers.Clear();
- element.PrivateBoundIdentifiers(privateBoundIdentifiers);
- foreach (var name in privateBoundIdentifiers)
- {
- classPrivateEnvironment.Names.Add(name, new PrivateName(name));
- }
- }
- constructor ??= _superClass != null
- ? _superConstructor
- : _emptyConstructor;
- engine.UpdateLexicalEnvironment(classEnv);
- engine.UpdatePrivateEnvironment(classPrivateEnvironment);
- ScriptFunction F;
- try
- {
- var constructorInfo = constructor.DefineMethod(proto, constructorParent);
- F = constructorInfo.Closure;
- F.SetFunctionName(_className ?? "");
- F.MakeConstructor(writableProperty: false, proto);
- F._constructorKind = _superClass is null ? ConstructorKind.Base : ConstructorKind.Derived;
- F.MakeClassConstructor();
- proto.CreateMethodProperty(CommonProperties.Constructor, F);
- var instancePrivateMethods = new List<PrivateElement>();
- var staticPrivateMethods = new List<PrivateElement>();
- var instanceFields = new List<ClassFieldDefinition>();
- var staticElements = new List<object>();
- foreach (IClassElement e in elements)
- {
- if (e is MethodDefinition { Kind: PropertyKind.Constructor })
- {
- continue;
- }
- var isStatic = e.Static;
- var target = !isStatic ? proto : F;
- var element = ClassElementEvaluation(engine, target, e);
- if (element is PrivateElement privateElement)
- {
- var container = !isStatic ? instancePrivateMethods : staticPrivateMethods;
- var index = container.FindIndex(x => string.Equals(x.Key.Description, privateElement.Key.Description, StringComparison.Ordinal));
- if (index != -1)
- {
- var pe = container[index];
- var combined = privateElement.Get is null
- ? new PrivateElement { Key = pe.Key, Kind = PrivateElementKind.Accessor, Get = pe.Get, Set = privateElement.Set }
- : new PrivateElement { Key = pe.Key, Kind = PrivateElementKind.Accessor, Get = privateElement.Get, Set = pe.Set };
- container[index] = combined;
- }
- else
- {
- container.Add(privateElement);
- }
- }
- else if (element is ClassFieldDefinition classFieldDefinition)
- {
- if (!isStatic)
- {
- instanceFields.Add(classFieldDefinition);
- }
- else
- {
- staticElements.Add(element);
- }
- }
- else if (element is ClassStaticBlockDefinition)
- {
- staticElements.Add(element);
- }
- }
- if (_className is not null)
- {
- classEnv.InitializeBinding(_className, F, DisposeHint.Normal);
- }
- F._privateMethods = instancePrivateMethods;
- F._fields = instanceFields;
- for (var i = 0; i < staticPrivateMethods.Count; i++)
- {
- F.PrivateMethodOrAccessorAdd(staticPrivateMethods[i]);
- }
- for (var i = 0; i < staticElements.Count; i++)
- {
- var elementRecord = staticElements[i];
- if (elementRecord is ClassFieldDefinition classFieldDefinition)
- {
- ObjectInstance.DefineField(F, classFieldDefinition);
- }
- else
- {
- engine.Call(((ClassStaticBlockDefinition) elementRecord).BodyFunction, F);
- }
- }
- }
- finally
- {
- engine.UpdateLexicalEnvironment(env);
- engine.UpdatePrivateEnvironment(outerPrivateEnvironment);
- }
- return F;
- }
- /// <summary>
- /// https://tc39.es/ecma262/#sec-static-semantics-classelementevaluation
- /// </summary>
- private static object? ClassElementEvaluation(Engine engine, ObjectInstance target, IClassElement e)
- {
- return e switch
- {
- PropertyDefinition p => ClassFieldDefinitionEvaluation(engine, target, p),
- MethodDefinition m => MethodDefinitionEvaluation(engine, target, m, enumerable: false),
- StaticBlock s => ClassStaticBlockDefinitionEvaluation(engine, target, s),
- // AccessorProperty ap => throw new NotImplementedException(), // not implemented yet
- _ => null
- };
- }
- /// <summary>
- /// /https://tc39.es/ecma262/#sec-runtime-semantics-classfielddefinitionevaluation
- /// </summary>
- private static ClassFieldDefinition ClassFieldDefinitionEvaluation(Engine engine, ObjectInstance homeObject, PropertyDefinition fieldDefinition)
- {
- var name = fieldDefinition.GetKey(engine);
- ScriptFunction? initializer = null;
- if (fieldDefinition.Value is not null)
- {
- var intrinsics = engine.Realm.Intrinsics;
- var env = engine.ExecutionContext.LexicalEnvironment;
- var privateEnv = engine.ExecutionContext.PrivateEnvironment;
- var definition = new JintFunctionDefinition(new ClassFieldFunction(fieldDefinition.Value));
- initializer = intrinsics.Function.OrdinaryFunctionCreate(intrinsics.Function.PrototypeObject, definition, FunctionThisMode.Global, env, privateEnv);
- initializer.MakeMethod(homeObject);
- initializer._classFieldInitializerName = name;
- }
- return new ClassFieldDefinition { Name = name, Initializer = initializer };
- }
- private sealed class ClassFieldFunction : Node, IFunction
- {
- private readonly NodeList<Node> _nodeList;
- private readonly FunctionBody _statement;
- public ClassFieldFunction(Expression expression) : base(NodeType.ExpressionStatement)
- {
- var nodeList = NodeList.From<Statement>(new ReturnStatement(expression));
- _statement = new FunctionBody(nodeList, strict: true);
- }
- protected override object Accept(AstVisitor visitor) => throw new NotImplementedException();
- public Identifier? Id => null;
- public ref readonly NodeList<Node> Params => ref _nodeList;
- public StatementOrExpression Body => _statement;
- public bool Generator => false;
- public bool Expression => false;
- public bool Async => false;
- }
- /// <summary>
- /// https://tc39.es/ecma262/#sec-runtime-semantics-classstaticblockdefinitionevaluation
- /// </summary>
- private static ClassStaticBlockDefinition ClassStaticBlockDefinitionEvaluation(Engine engine, ObjectInstance homeObject, StaticBlock o)
- {
- var intrinsics = engine.Realm.Intrinsics;
- var definition = new JintFunctionDefinition(new ClassStaticBlockFunction(o));
- var lex = engine.ExecutionContext.LexicalEnvironment;
- var privateEnv = engine.ExecutionContext.PrivateEnvironment;
- var bodyFunction = intrinsics.Function.OrdinaryFunctionCreate(intrinsics.Function.PrototypeObject, definition, FunctionThisMode.Global, lex, privateEnv);
- bodyFunction.MakeMethod(homeObject);
- return new ClassStaticBlockDefinition { BodyFunction = bodyFunction };
- }
- private sealed class ClassStaticBlockFunction : Node, IFunction
- {
- private readonly FunctionBody _statement;
- private readonly NodeList<Node> _params;
- public ClassStaticBlockFunction(StaticBlock staticBlock) : base(NodeType.StaticBlock)
- {
- _statement = new FunctionBody(staticBlock.Body, strict: true);
- _params = new NodeList<Node>();
- }
- protected override object Accept(AstVisitor visitor) => throw new NotImplementedException();
- public Identifier? Id => null;
- public ref readonly NodeList<Node> Params => ref _params;
- public StatementOrExpression Body => _statement;
- public bool Generator => false;
- public bool Expression => false;
- public bool Async => false;
- }
- /// <summary>
- /// https://tc39.es/ecma262/#sec-runtime-semantics-methoddefinitionevaluation
- /// </summary>
- internal static PrivateElement? MethodDefinitionEvaluation<T>(
- Engine engine,
- ObjectInstance obj,
- T method,
- bool enumerable) where T : IProperty
- {
- var function = method.Value as IFunction;
- if (function is null)
- {
- Throw.SyntaxError(obj.Engine.Realm);
- }
- if (method.Kind != PropertyKind.Get && method.Kind != PropertyKind.Set && !function.Generator)
- {
- var methodDef = method.DefineMethod(obj);
- methodDef.Closure.SetFunctionName(methodDef.Key);
- return DefineMethodProperty(obj, methodDef.Key, methodDef.Closure, enumerable);
- }
- var getter = method.Kind == PropertyKind.Get;
- var definition = new JintFunctionDefinition(function);
- var intrinsics = engine.Realm.Intrinsics;
- var value = method.TryGetKey(engine);
- var propKey = TypeConverter.ToPropertyKey(value);
- var env = engine.ExecutionContext.LexicalEnvironment;
- var privateEnv = engine.ExecutionContext.PrivateEnvironment;
- if (function.Generator)
- {
- var closure = intrinsics.Function.OrdinaryFunctionCreate(intrinsics.GeneratorFunction.PrototypeObject, definition, definition.ThisMode, env, privateEnv);
- closure.MakeMethod(obj);
- closure.SetFunctionName(propKey);
- var prototype = ObjectInstance.OrdinaryObjectCreate(engine, intrinsics.GeneratorFunction.PrototypeObject.PrototypeObject);
- closure.DefinePropertyOrThrow(CommonProperties.Prototype, new PropertyDescriptor(prototype, PropertyFlag.Writable));
- return DefineMethodProperty(obj, propKey, closure, enumerable);
- }
- else
- {
- var closure = intrinsics.Function.OrdinaryFunctionCreate(intrinsics.Function.PrototypeObject, definition, definition.ThisMode, env, privateEnv);
- closure.MakeMethod(obj);
- closure.SetFunctionName(propKey, getter ? "get" : "set");
- if (method.Key is PrivateIdentifier privateIdentifier)
- {
- return new PrivateElement
- {
- Key = privateEnv!.Names[privateIdentifier],
- Kind = PrivateElementKind.Accessor,
- Get = getter ? closure : null,
- Set = !getter ? closure : null
- };
- }
- var propDesc = new GetSetPropertyDescriptor(
- getter ? closure : null,
- !getter ? closure : null,
- PropertyFlag.Configurable);
- obj.DefinePropertyOrThrow(propKey, propDesc);
- }
- return null;
- }
- /// <summary>
- /// https://tc39.es/ecma262/#sec-definemethodproperty
- /// </summary>
- private static PrivateElement? DefineMethodProperty(ObjectInstance homeObject, JsValue key, ScriptFunction closure, bool enumerable)
- {
- if (key.IsPrivateName())
- {
- return new PrivateElement { Key = (PrivateName) key, Kind = PrivateElementKind.Method, Value = closure };
- }
- var desc = new PropertyDescriptor(closure, enumerable ? PropertyFlag.ConfigurableEnumerableWritable : PropertyFlag.NonEnumerable);
- homeObject.DefinePropertyOrThrow(key, desc);
- return null;
- }
- }
|