ClassDefinition.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. using Esprima;
  2. using Esprima.Ast;
  3. using Esprima.Utils;
  4. using Jint.Native.Object;
  5. using Jint.Runtime;
  6. using Jint.Runtime.Descriptors;
  7. using Jint.Runtime.Environments;
  8. using Jint.Runtime.Interpreter;
  9. using Jint.Runtime.Interpreter.Expressions;
  10. namespace Jint.Native.Function;
  11. internal sealed class ClassDefinition
  12. {
  13. private static readonly MethodDefinition _superConstructor;
  14. internal static CallExpression _defaultSuperCall;
  15. internal static readonly MethodDefinition _emptyConstructor;
  16. internal readonly string? _className;
  17. private readonly Expression? _superClass;
  18. private readonly ClassBody _body;
  19. static ClassDefinition()
  20. {
  21. // generate missing constructor AST only once
  22. static MethodDefinition CreateConstructorMethodDefinition(string source)
  23. {
  24. var script = new JavaScriptParser().ParseScript(source);
  25. var classDeclaration = (ClassDeclaration) script.Body[0];
  26. return (MethodDefinition) classDeclaration.Body.Body[0];
  27. }
  28. _superConstructor = CreateConstructorMethodDefinition("class temp { constructor(...args) { super(...args); } }");
  29. _defaultSuperCall = (CallExpression) ((ExpressionStatement) _superConstructor.Value.Body.Body[0]).Expression;
  30. _emptyConstructor = CreateConstructorMethodDefinition("class temp { constructor() {} }");
  31. }
  32. public ClassDefinition(
  33. string? className,
  34. Expression? superClass,
  35. ClassBody body)
  36. {
  37. _className = className;
  38. _superClass = superClass;
  39. _body = body;
  40. }
  41. /// <summary>
  42. /// https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation
  43. /// </summary>
  44. public JsValue BuildConstructor(EvaluationContext context, EnvironmentRecord env)
  45. {
  46. // A class definition is always strict mode code.
  47. using var _ = new StrictModeScope(true, true);
  48. var engine = context.Engine;
  49. var classEnv = JintEnvironment.NewDeclarativeEnvironment(engine, env);
  50. if (_className is not null)
  51. {
  52. classEnv.CreateImmutableBinding(_className, true);
  53. }
  54. var outerPrivateEnvironment = engine.ExecutionContext.PrivateEnvironment;
  55. var classPrivateEnvironment = JintEnvironment.NewPrivateEnvironment(engine, outerPrivateEnvironment);
  56. ObjectInstance? protoParent = null;
  57. ObjectInstance? constructorParent = null;
  58. if (_superClass is null)
  59. {
  60. protoParent = engine.Realm.Intrinsics.Object.PrototypeObject;
  61. constructorParent = engine.Realm.Intrinsics.Function.PrototypeObject;
  62. }
  63. else
  64. {
  65. engine.UpdateLexicalEnvironment(classEnv);
  66. var superclass = JintExpression.Build(_superClass).GetValue(context);
  67. engine.UpdateLexicalEnvironment(env);
  68. if (superclass.IsNull())
  69. {
  70. protoParent = null;
  71. constructorParent = engine.Realm.Intrinsics.Function.PrototypeObject;
  72. }
  73. else if (!superclass.IsConstructor)
  74. {
  75. ExceptionHelper.ThrowTypeError(engine.Realm, "super class is not a constructor");
  76. }
  77. else
  78. {
  79. var temp = superclass.Get("prototype");
  80. if (temp is ObjectInstance protoParentObject)
  81. {
  82. protoParent = protoParentObject;
  83. }
  84. else if (temp.IsNull())
  85. {
  86. // OK
  87. }
  88. else
  89. {
  90. ExceptionHelper.ThrowTypeError(engine.Realm, "cannot resolve super class prototype chain");
  91. return default;
  92. }
  93. constructorParent = (ObjectInstance) superclass;
  94. }
  95. }
  96. ObjectInstance proto = new JsObject(engine) { _prototype = protoParent };
  97. var privateBoundIdentifiers = new HashSet<PrivateIdentifier>(PrivateIdentifierNameComparer._instance);
  98. MethodDefinition? constructor = null;
  99. ref readonly var elements = ref _body.Body;
  100. var classBody = elements;
  101. for (var i = 0; i < classBody.Count; ++i)
  102. {
  103. var element = classBody[i];
  104. if (element is MethodDefinition { Kind: PropertyKind.Constructor } c)
  105. {
  106. constructor = c;
  107. }
  108. privateBoundIdentifiers.Clear();
  109. element.PrivateBoundIdentifiers(privateBoundIdentifiers);
  110. foreach (var name in privateBoundIdentifiers)
  111. {
  112. classPrivateEnvironment.Names.Add(name, new PrivateName(name));
  113. }
  114. }
  115. constructor ??= _superClass != null
  116. ? _superConstructor
  117. : _emptyConstructor;
  118. engine.UpdateLexicalEnvironment(classEnv);
  119. engine.UpdatePrivateEnvironment(classPrivateEnvironment);
  120. ScriptFunctionInstance F;
  121. try
  122. {
  123. var constructorInfo = constructor.DefineMethod(proto, constructorParent);
  124. F = constructorInfo.Closure;
  125. F.SetFunctionName(_className ?? "");
  126. F.MakeConstructor(writableProperty: false, proto);
  127. F._constructorKind = _superClass is null ? ConstructorKind.Base : ConstructorKind.Derived;
  128. F.MakeClassConstructor();
  129. proto.CreateMethodProperty(CommonProperties.Constructor, F);
  130. var instancePrivateMethods = new List<PrivateElement>();
  131. var staticPrivateMethods = new List<PrivateElement>();
  132. var instanceFields = new List<ClassFieldDefinition>();
  133. var staticElements = new List<object>();
  134. foreach (var e in elements)
  135. {
  136. if (e is MethodDefinition { Kind: PropertyKind.Constructor })
  137. {
  138. continue;
  139. }
  140. var isStatic = e is MethodDefinition { Static: true } or AccessorProperty { Static: true } or PropertyDefinition { Static: true } or StaticBlock;
  141. var target = !isStatic ? proto : F;
  142. var element = ClassElementEvaluation(engine, target, e);
  143. if (element is PrivateElement privateElement)
  144. {
  145. var container = !isStatic ? instancePrivateMethods : staticPrivateMethods;
  146. var index = container.FindIndex(x => string.Equals(x.Key.Description, privateElement.Key.Description, StringComparison.Ordinal));
  147. if (index != -1)
  148. {
  149. var pe = container[index];
  150. var combined = privateElement.Get is null
  151. ? new PrivateElement { Key = pe.Key, Kind = PrivateElementKind.Accessor, Get = pe.Get, Set = privateElement.Set }
  152. : new PrivateElement { Key = pe.Key, Kind = PrivateElementKind.Accessor, Get = privateElement.Get, Set = pe.Set };
  153. container[index] = combined;
  154. }
  155. else
  156. {
  157. container.Add(privateElement);
  158. }
  159. }
  160. else if (element is ClassFieldDefinition classFieldDefinition)
  161. {
  162. if (!isStatic)
  163. {
  164. instanceFields.Add(classFieldDefinition);
  165. }
  166. else
  167. {
  168. staticElements.Add(element);
  169. }
  170. }
  171. else if (element is ClassStaticBlockDefinition)
  172. {
  173. staticElements.Add(element);
  174. }
  175. }
  176. if (_className is not null)
  177. {
  178. classEnv.InitializeBinding(_className, F);
  179. }
  180. F._privateMethods = instancePrivateMethods;
  181. F._fields = instanceFields;
  182. for (var i = 0; i < staticPrivateMethods.Count; i++)
  183. {
  184. F.PrivateMethodOrAccessorAdd(staticPrivateMethods[i]);
  185. }
  186. for (var i = 0; i < staticElements.Count; i++)
  187. {
  188. var elementRecord = staticElements[i];
  189. if (elementRecord is ClassFieldDefinition classFieldDefinition)
  190. {
  191. ObjectInstance.DefineField(F, classFieldDefinition);
  192. }
  193. else
  194. {
  195. engine.Call(((ClassStaticBlockDefinition) elementRecord).BodyFunction, F);
  196. }
  197. }
  198. }
  199. finally
  200. {
  201. engine.UpdateLexicalEnvironment(env);
  202. engine.UpdatePrivateEnvironment(outerPrivateEnvironment);
  203. }
  204. return F;
  205. }
  206. /// <summary>
  207. /// https://tc39.es/ecma262/#sec-static-semantics-classelementevaluation
  208. /// </summary>
  209. private static object? ClassElementEvaluation(Engine engine, ObjectInstance target, ClassElement e)
  210. {
  211. return e switch
  212. {
  213. PropertyDefinition p => ClassFieldDefinitionEvaluation(engine, target, p),
  214. MethodDefinition m => MethodDefinitionEvaluation(engine, target, m, enumerable: false),
  215. StaticBlock s => ClassStaticBlockDefinitionEvaluation(engine, target, s),
  216. _ => null
  217. };
  218. }
  219. /// <summary>
  220. /// /https://tc39.es/ecma262/#sec-runtime-semantics-classfielddefinitionevaluation
  221. /// </summary>
  222. private static ClassFieldDefinition ClassFieldDefinitionEvaluation(Engine engine, ObjectInstance homeObject, PropertyDefinition fieldDefinition)
  223. {
  224. var name = fieldDefinition.GetKey(engine);
  225. ScriptFunctionInstance? initializer = null;
  226. if (fieldDefinition.Value is not null)
  227. {
  228. var intrinsics = engine.Realm.Intrinsics;
  229. var env = engine.ExecutionContext.LexicalEnvironment;
  230. var privateEnv = engine.ExecutionContext.PrivateEnvironment;
  231. var definition = new JintFunctionDefinition(new ClassFieldFunction(fieldDefinition.Value));
  232. initializer = intrinsics.Function.OrdinaryFunctionCreate(intrinsics.Function.PrototypeObject, definition, FunctionThisMode.Global, env, privateEnv);
  233. initializer.MakeMethod(homeObject);
  234. initializer._classFieldInitializerName = name;
  235. }
  236. return new ClassFieldDefinition { Name = name, Initializer = initializer };
  237. }
  238. private sealed class ClassFieldFunction : Node, IFunction
  239. {
  240. private readonly NodeList<Node> _nodeList;
  241. private readonly BlockStatement _statement;
  242. public ClassFieldFunction(Expression expression) : base(Nodes.ExpressionStatement)
  243. {
  244. var nodeList = NodeList.Create<Statement>(new [] { new ReturnStatement(expression) });
  245. _statement = new BlockStatement(nodeList);
  246. }
  247. protected override object Accept(AstVisitor visitor) => throw new NotImplementedException();
  248. public Identifier? Id => null;
  249. public ref readonly NodeList<Node> Params => ref _nodeList;
  250. public StatementListItem Body => _statement;
  251. public bool Generator => false;
  252. public bool Expression => false;
  253. public bool Strict => true;
  254. public bool Async => false;
  255. }
  256. /// <summary>
  257. /// https://tc39.es/ecma262/#sec-runtime-semantics-classstaticblockdefinitionevaluation
  258. /// </summary>
  259. private static ClassStaticBlockDefinition ClassStaticBlockDefinitionEvaluation(Engine engine, ObjectInstance homeObject, StaticBlock o)
  260. {
  261. var intrinsics = engine.Realm.Intrinsics;
  262. var definition = new JintFunctionDefinition(new ClassStaticBlockFunction(o));
  263. var lex = engine.ExecutionContext.LexicalEnvironment;
  264. var privateEnv = engine.ExecutionContext.PrivateEnvironment;
  265. var bodyFunction = intrinsics.Function.OrdinaryFunctionCreate(intrinsics.Function.PrototypeObject, definition, FunctionThisMode.Global, lex, privateEnv);
  266. bodyFunction.MakeMethod(homeObject);
  267. return new ClassStaticBlockDefinition { BodyFunction = bodyFunction };
  268. }
  269. private sealed class ClassStaticBlockFunction : Node, IFunction
  270. {
  271. private readonly BlockStatement _statement;
  272. private readonly NodeList<Node> _params;
  273. public ClassStaticBlockFunction(StaticBlock staticBlock) : base(Nodes.StaticBlock)
  274. {
  275. _statement = new BlockStatement(staticBlock.Body);
  276. _params = new NodeList<Node>();
  277. }
  278. protected override object Accept(AstVisitor visitor) => throw new NotImplementedException();
  279. public Identifier? Id => null;
  280. public ref readonly NodeList<Node> Params => ref _params;
  281. public StatementListItem Body => _statement;
  282. public bool Generator => false;
  283. public bool Expression => false;
  284. public bool Strict => false;
  285. public bool Async => false;
  286. }
  287. /// <summary>
  288. /// https://tc39.es/ecma262/#sec-runtime-semantics-methoddefinitionevaluation
  289. /// </summary>
  290. private static PrivateElement? MethodDefinitionEvaluation(
  291. Engine engine,
  292. ObjectInstance obj,
  293. MethodDefinition method,
  294. bool enumerable)
  295. {
  296. if (method.Kind != PropertyKind.Get && method.Kind != PropertyKind.Set)
  297. {
  298. var methodDef = method.DefineMethod(obj);
  299. methodDef.Closure.SetFunctionName(methodDef.Key);
  300. return DefineMethodProperty(obj, methodDef.Key, methodDef.Closure, enumerable);
  301. }
  302. var function = method.Value as IFunction;
  303. if (function is null)
  304. {
  305. ExceptionHelper.ThrowSyntaxError(obj.Engine.Realm);
  306. }
  307. var getter = method.Kind == PropertyKind.Get;
  308. var definition = new JintFunctionDefinition(function);
  309. var intrinsics = engine.Realm.Intrinsics;
  310. var value = method.TryGetKey(engine);
  311. var propKey = TypeConverter.ToPropertyKey(value);
  312. var env = engine.ExecutionContext.LexicalEnvironment;
  313. var privateEnv = engine.ExecutionContext.PrivateEnvironment;
  314. var closure = intrinsics.Function.OrdinaryFunctionCreate(intrinsics.Function.PrototypeObject, definition, definition.ThisMode, env, privateEnv);
  315. closure.MakeMethod(obj);
  316. closure.SetFunctionName(propKey, getter ? "get" : "set");
  317. if (method.Key is PrivateIdentifier privateIdentifier)
  318. {
  319. return new PrivateElement
  320. {
  321. Key = privateEnv!.Names[privateIdentifier],
  322. Kind = PrivateElementKind.Accessor,
  323. Get = getter ? closure : null,
  324. Set = !getter ? closure : null
  325. };
  326. }
  327. var propDesc = new GetSetPropertyDescriptor(
  328. getter ? closure : null,
  329. !getter ? closure : null,
  330. PropertyFlag.Configurable);
  331. obj.DefinePropertyOrThrow(propKey, propDesc);
  332. return null;
  333. }
  334. /// <summary>
  335. /// https://tc39.es/ecma262/#sec-definemethodproperty
  336. /// </summary>
  337. private static PrivateElement? DefineMethodProperty(ObjectInstance homeObject, JsValue key, ScriptFunctionInstance closure, bool enumerable)
  338. {
  339. if (key.IsPrivateName())
  340. {
  341. return new PrivateElement { Key = (PrivateName) key, Kind = PrivateElementKind.Method, Value = closure };
  342. }
  343. var desc = new PropertyDescriptor(closure, enumerable ? PropertyFlag.Enumerable : PropertyFlag.NonEnumerable);
  344. homeObject.DefinePropertyOrThrow(key, desc);
  345. return null;
  346. }
  347. }