ClassDefinition.cs 16 KB

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