ClassDefinition.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  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. var parser = new Parser(Engine.BaseParserOptions);
  20. // generate missing constructor AST only once
  21. static MethodDefinition CreateConstructorMethodDefinition(Parser parser, string source)
  22. {
  23. var script = parser.ParseScriptGuarded(new Engine().Realm, source);
  24. var classDeclaration = (ClassDeclaration) script.Body[0];
  25. return (MethodDefinition) classDeclaration.Body.Body[0];
  26. }
  27. _superConstructor = CreateConstructorMethodDefinition(parser, "class temp extends X { constructor(...args) { super(...args); } }");
  28. _defaultSuperCall = (CallExpression) ((NonSpecialExpressionStatement) _superConstructor.Value.Body.Body[0]).Expression;
  29. _emptyConstructor = CreateConstructorMethodDefinition(parser, "class temp { constructor() {} }");
  30. }
  31. public ClassDefinition(
  32. string? className,
  33. Expression? superClass,
  34. ClassBody body)
  35. {
  36. _className = className;
  37. _superClass = superClass;
  38. _body = body;
  39. }
  40. /// <summary>
  41. /// https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation
  42. /// </summary>
  43. public JsValue BuildConstructor(EvaluationContext context, Environment env)
  44. {
  45. // A class definition is always strict mode code.
  46. using var _ = new StrictModeScope(true, true);
  47. var engine = context.Engine;
  48. var classEnv = JintEnvironment.NewDeclarativeEnvironment(engine, env);
  49. if (_className is not null)
  50. {
  51. classEnv.CreateImmutableBinding(_className, true);
  52. }
  53. var outerPrivateEnvironment = engine.ExecutionContext.PrivateEnvironment;
  54. var classPrivateEnvironment = JintEnvironment.NewPrivateEnvironment(engine, outerPrivateEnvironment);
  55. ObjectInstance? protoParent = null;
  56. ObjectInstance? constructorParent = null;
  57. if (_superClass is null)
  58. {
  59. protoParent = engine.Realm.Intrinsics.Object.PrototypeObject;
  60. constructorParent = engine.Realm.Intrinsics.Function.PrototypeObject;
  61. }
  62. else
  63. {
  64. engine.UpdateLexicalEnvironment(classEnv);
  65. var superclass = JintExpression.Build(_superClass).GetValue(context);
  66. engine.UpdateLexicalEnvironment(env);
  67. if (superclass.IsNull())
  68. {
  69. protoParent = null;
  70. constructorParent = engine.Realm.Intrinsics.Function.PrototypeObject;
  71. }
  72. else if (!superclass.IsConstructor)
  73. {
  74. Throw.TypeError(engine.Realm, "super class is not a constructor");
  75. }
  76. else
  77. {
  78. var temp = superclass.Get(CommonProperties.Prototype);
  79. if (temp is ObjectInstance protoParentObject)
  80. {
  81. protoParent = protoParentObject;
  82. }
  83. else if (temp.IsNull())
  84. {
  85. // OK
  86. }
  87. else
  88. {
  89. Throw.TypeError(engine.Realm, "cannot resolve super class prototype chain");
  90. return default;
  91. }
  92. constructorParent = (ObjectInstance) superclass;
  93. }
  94. }
  95. ObjectInstance proto = new JsObject(engine) { _prototype = protoParent };
  96. var privateBoundIdentifiers = new HashSet<PrivateIdentifier>(PrivateIdentifierNameComparer._instance);
  97. MethodDefinition? constructor = null;
  98. ref readonly var elements = ref _body.Body;
  99. var classBody = elements;
  100. for (var i = 0; i < classBody.Count; ++i)
  101. {
  102. var element = classBody[i];
  103. if (element is MethodDefinition { Kind: PropertyKind.Constructor } c)
  104. {
  105. constructor = c;
  106. }
  107. privateBoundIdentifiers.Clear();
  108. element.PrivateBoundIdentifiers(privateBoundIdentifiers);
  109. foreach (var name in privateBoundIdentifiers)
  110. {
  111. classPrivateEnvironment.Names.Add(name, new PrivateName(name));
  112. }
  113. }
  114. constructor ??= _superClass != null
  115. ? _superConstructor
  116. : _emptyConstructor;
  117. engine.UpdateLexicalEnvironment(classEnv);
  118. engine.UpdatePrivateEnvironment(classPrivateEnvironment);
  119. ScriptFunction F;
  120. try
  121. {
  122. var constructorInfo = constructor.DefineMethod(proto, constructorParent);
  123. F = constructorInfo.Closure;
  124. F.SetFunctionName(_className ?? "");
  125. F.MakeConstructor(writableProperty: false, proto);
  126. F._constructorKind = _superClass is null ? ConstructorKind.Base : ConstructorKind.Derived;
  127. F.MakeClassConstructor();
  128. proto.CreateMethodProperty(CommonProperties.Constructor, F);
  129. var instancePrivateMethods = new List<PrivateElement>();
  130. var staticPrivateMethods = new List<PrivateElement>();
  131. var instanceFields = new List<ClassFieldDefinition>();
  132. var staticElements = new List<object>();
  133. foreach (IClassElement e in elements)
  134. {
  135. if (e is MethodDefinition { Kind: PropertyKind.Constructor })
  136. {
  137. continue;
  138. }
  139. var isStatic = e.Static;
  140. var target = !isStatic ? proto : F;
  141. var element = ClassElementEvaluation(engine, target, e);
  142. if (element is PrivateElement privateElement)
  143. {
  144. var container = !isStatic ? instancePrivateMethods : staticPrivateMethods;
  145. var index = container.FindIndex(x => string.Equals(x.Key.Description, privateElement.Key.Description, StringComparison.Ordinal));
  146. if (index != -1)
  147. {
  148. var pe = container[index];
  149. var combined = privateElement.Get is null
  150. ? new PrivateElement { Key = pe.Key, Kind = PrivateElementKind.Accessor, Get = pe.Get, Set = privateElement.Set }
  151. : new PrivateElement { Key = pe.Key, Kind = PrivateElementKind.Accessor, Get = privateElement.Get, Set = pe.Set };
  152. container[index] = combined;
  153. }
  154. else
  155. {
  156. container.Add(privateElement);
  157. }
  158. }
  159. else if (element is ClassFieldDefinition classFieldDefinition)
  160. {
  161. if (!isStatic)
  162. {
  163. instanceFields.Add(classFieldDefinition);
  164. }
  165. else
  166. {
  167. staticElements.Add(element);
  168. }
  169. }
  170. else if (element is ClassStaticBlockDefinition)
  171. {
  172. staticElements.Add(element);
  173. }
  174. }
  175. if (_className is not null)
  176. {
  177. classEnv.InitializeBinding(_className, F, DisposeHint.Normal);
  178. }
  179. F._privateMethods = instancePrivateMethods;
  180. F._fields = instanceFields;
  181. for (var i = 0; i < staticPrivateMethods.Count; i++)
  182. {
  183. F.PrivateMethodOrAccessorAdd(staticPrivateMethods[i]);
  184. }
  185. for (var i = 0; i < staticElements.Count; i++)
  186. {
  187. var elementRecord = staticElements[i];
  188. if (elementRecord is ClassFieldDefinition classFieldDefinition)
  189. {
  190. ObjectInstance.DefineField(F, classFieldDefinition);
  191. }
  192. else
  193. {
  194. engine.Call(((ClassStaticBlockDefinition) elementRecord).BodyFunction, F);
  195. }
  196. }
  197. }
  198. finally
  199. {
  200. engine.UpdateLexicalEnvironment(env);
  201. engine.UpdatePrivateEnvironment(outerPrivateEnvironment);
  202. }
  203. return F;
  204. }
  205. /// <summary>
  206. /// https://tc39.es/ecma262/#sec-static-semantics-classelementevaluation
  207. /// </summary>
  208. private static object? ClassElementEvaluation(Engine engine, ObjectInstance target, IClassElement e)
  209. {
  210. return e switch
  211. {
  212. PropertyDefinition p => ClassFieldDefinitionEvaluation(engine, target, p),
  213. MethodDefinition m => MethodDefinitionEvaluation(engine, target, m, enumerable: false),
  214. StaticBlock s => ClassStaticBlockDefinitionEvaluation(engine, target, s),
  215. // AccessorProperty ap => throw new NotImplementedException(), // not implemented yet
  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. ScriptFunction? 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 FunctionBody _statement;
  242. public ClassFieldFunction(Expression expression) : base(NodeType.ExpressionStatement)
  243. {
  244. var nodeList = NodeList.From<Statement>(new ReturnStatement(expression));
  245. _statement = new FunctionBody(nodeList, strict: true);
  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 StatementOrExpression Body => _statement;
  251. public bool Generator => false;
  252. public bool Expression => false;
  253. public bool Async => false;
  254. }
  255. /// <summary>
  256. /// https://tc39.es/ecma262/#sec-runtime-semantics-classstaticblockdefinitionevaluation
  257. /// </summary>
  258. private static ClassStaticBlockDefinition ClassStaticBlockDefinitionEvaluation(Engine engine, ObjectInstance homeObject, StaticBlock o)
  259. {
  260. var intrinsics = engine.Realm.Intrinsics;
  261. var definition = new JintFunctionDefinition(new ClassStaticBlockFunction(o));
  262. var lex = engine.ExecutionContext.LexicalEnvironment;
  263. var privateEnv = engine.ExecutionContext.PrivateEnvironment;
  264. var bodyFunction = intrinsics.Function.OrdinaryFunctionCreate(intrinsics.Function.PrototypeObject, definition, FunctionThisMode.Global, lex, privateEnv);
  265. bodyFunction.MakeMethod(homeObject);
  266. return new ClassStaticBlockDefinition { BodyFunction = bodyFunction };
  267. }
  268. private sealed class ClassStaticBlockFunction : Node, IFunction
  269. {
  270. private readonly FunctionBody _statement;
  271. private readonly NodeList<Node> _params;
  272. public ClassStaticBlockFunction(StaticBlock staticBlock) : base(NodeType.StaticBlock)
  273. {
  274. _statement = new FunctionBody(staticBlock.Body, strict: true);
  275. _params = new NodeList<Node>();
  276. }
  277. protected override object Accept(AstVisitor visitor) => throw new NotImplementedException();
  278. public Identifier? Id => null;
  279. public ref readonly NodeList<Node> Params => ref _params;
  280. public StatementOrExpression Body => _statement;
  281. public bool Generator => false;
  282. public bool Expression => 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. Throw.SyntaxError(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. }