Bug 1528039 - Make Reflect.Parse work for fields. r=jorendorff
authorAshley Hauck <khyperia@mozilla.com>
Thu, 07 Mar 2019 03:22:11 +0000
changeset 520718 6645f91d0adcf8266d52971822d8e90644dd6f35
parent 520717 44d8a4dbe146fb84464b66634e94c6c00a8d066d
child 520719 093def45364a9f4915bfb9c78a4500cd9c145b77
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1528039
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1528039 - Make Reflect.Parse work for fields. r=jorendorff Differential Revision: https://phabricator.services.mozilla.com/D21537
js/src/builtin/ReflectParse.cpp
js/src/frontend/Parser.cpp
js/src/tests/jstests.list
js/src/tests/non262/reflect-parse/PatternBuilders.js
js/src/tests/non262/reflect-parse/class-fields.js
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -606,16 +606,18 @@ class NodeBuilder {
 
   MOZ_MUST_USE bool classDefinition(bool expr, HandleValue name,
                                     HandleValue heritage, HandleValue block,
                                     TokenPos* pos, MutableHandleValue dst);
   MOZ_MUST_USE bool classMembers(NodeVector& members, MutableHandleValue dst);
   MOZ_MUST_USE bool classMethod(HandleValue name, HandleValue body,
                                 PropKind kind, bool isStatic, TokenPos* pos,
                                 MutableHandleValue dst);
+  MOZ_MUST_USE bool classField(HandleValue name, HandleValue body,
+                               TokenPos* pos, MutableHandleValue dst);
 
   /*
    * expressions
    */
 
   MOZ_MUST_USE bool binaryExpression(BinaryOperator op, HandleValue left,
                                      HandleValue right, TokenPos* pos,
                                      MutableHandleValue dst);
@@ -1523,16 +1525,27 @@ bool NodeBuilder::classMethod(HandleValu
   if (!cb.isNull()) {
     return callback(cb, kindName, name, body, isStaticVal, pos, dst);
   }
 
   return newNode(AST_CLASS_METHOD, pos, "name", name, "body", body, "kind",
                  kindName, "static", isStaticVal, dst);
 }
 
+bool NodeBuilder::classField(HandleValue name, HandleValue initializer,
+                             TokenPos* pos, MutableHandleValue dst) {
+  RootedValue cb(cx, callbacks[AST_CLASS_FIELD]);
+  if (!cb.isNull()) {
+    return callback(cb, name, initializer, pos, dst);
+  }
+
+  return newNode(AST_CLASS_FIELD, pos, "name", name, "initializer", initializer,
+                 dst);
+}
+
 bool NodeBuilder::classMembers(NodeVector& members, MutableHandleValue dst) {
   return newArray(members, dst);
 }
 
 bool NodeBuilder::classDefinition(bool expr, HandleValue name,
                                   HandleValue heritage, HandleValue block,
                                   TokenPos* pos, MutableHandleValue dst) {
   ASTType type = expr ? AST_CLASS_EXPR : AST_CLASS_STMT;
@@ -1644,16 +1657,17 @@ class ASTSerializer {
   }
 
   bool expression(ParseNode* pn, MutableHandleValue dst);
 
   bool propertyName(ParseNode* key, MutableHandleValue dst);
   bool property(ParseNode* pn, MutableHandleValue dst);
 
   bool classMethod(ClassMethod* classMethod, MutableHandleValue dst);
+  bool classField(ClassField* classField, MutableHandleValue dst);
 
   bool optIdentifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst) {
     if (!atom) {
       dst.setMagic(JS_SERIALIZE_NO_NODE);
       return true;
     }
     return identifier(atom, pos, dst);
   }
@@ -2452,28 +2466,34 @@ bool ASTSerializer::statement(ParseNode*
       ListNode* memberList = &pn->as<ListNode>();
       NodeVector members(cx);
       if (!members.reserve(memberList->count())) {
         return false;
       }
 
       for (ParseNode* item : memberList->contents()) {
         if (item->is<ClassField>()) {
-          // TODO(khyperia): Implement private field access.
-          return false;
+          ClassField* field = &item->as<ClassField>();
+          MOZ_ASSERT(memberList->pn_pos.encloses(field->pn_pos));
+
+          RootedValue prop(cx);
+          if (!classField(field, &prop)) {
+            return false;
+          }
+          members.infallibleAppend(prop);
+        } else {
+          ClassMethod* method = &item->as<ClassMethod>();
+          MOZ_ASSERT(memberList->pn_pos.encloses(method->pn_pos));
+
+          RootedValue prop(cx);
+          if (!classMethod(method, &prop)) {
+            return false;
+          }
+          members.infallibleAppend(prop);
         }
-
-        ClassMethod* method = &item->as<ClassMethod>();
-        MOZ_ASSERT(memberList->pn_pos.encloses(method->pn_pos));
-
-        RootedValue prop(cx);
-        if (!classMethod(method, &prop)) {
-          return false;
-        }
-        members.infallibleAppend(prop);
       }
 
       return builder.classMembers(members, dst);
     }
 
     default:
       LOCAL_NOT_REACHED("unexpected statement type");
   }
@@ -2502,16 +2522,39 @@ bool ASTSerializer::classMethod(ClassMet
   RootedValue key(cx), val(cx);
   bool isStatic = classMethod->isStatic();
   return propertyName(&classMethod->name(), &key) &&
          expression(&classMethod->method(), &val) &&
          builder.classMethod(key, val, kind, isStatic, &classMethod->pn_pos,
                              dst);
 }
 
+bool ASTSerializer::classField(ClassField* classField, MutableHandleValue dst) {
+  RootedValue key(cx), val(cx);
+  // Dig through the lambda and get to the actual expression
+  if (classField->hasInitializer()) {
+    ParseNode* value = classField->initializer()
+                           .body()
+                           ->head()
+                           ->as<LexicalScopeNode>()
+                           .scopeBody()
+                           ->as<ListNode>()
+                           .head()
+                           ->as<UnaryNode>()
+                           .kid()
+                           ->as<AssignmentNode>()
+                           .right();
+    if (!expression(value, &val)) {
+      return false;
+    }
+  }
+  return propertyName(&classField->name(), &key) &&
+         builder.classField(key, val, &classField->pn_pos, dst);
+}
+
 bool ASTSerializer::leftAssociate(ListNode* node, MutableHandleValue dst) {
   MOZ_ASSERT(!node->empty());
 
   ParseNodeKind kind = node->getKind();
   bool lor = kind == ParseNodeKind::OrExpr;
   bool logop = lor || (kind == ParseNodeKind::AndExpr);
 
   ParseNode* head = node->head();
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -7098,25 +7098,25 @@ GeneralParser<ParseHandler, Unit>::class
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::FunctionNodeType
 GeneralParser<ParseHandler, Unit>::synthesizeConstructor(
     HandleAtom className, uint32_t classNameOffset) {
   FunctionSyntaxKind functionSyntaxKind = FunctionSyntaxKind::ClassConstructor;
 
-  // Create the function object
+  // Create the function object.
   RootedFunction fun(cx_, newFunction(className, functionSyntaxKind,
                                       GeneratorKind::NotGenerator,
                                       FunctionAsyncKind::SyncFunction));
   if (!fun) {
     return null();
   }
 
-  // Create the top-level field initializer node
+  // Create the top-level field initializer node.
   FunctionNodeType funNode = handler_.newFunction(functionSyntaxKind, pos());
   if (!funNode) {
     return null();
   }
 
   // Create the FunctionBox and link it to the function object.
   Directives directives(true);
   FunctionBox* funbox = newFunctionBox(funNode, fun, classNameOffset,
@@ -7124,34 +7124,34 @@ GeneralParser<ParseHandler, Unit>::synth
                                        FunctionAsyncKind::SyncFunction);
   if (!funbox) {
     return null();
   }
   funbox->initWithEnclosingParseContext(pc_, functionSyntaxKind);
   handler_.setFunctionBox(funNode, funbox);
   funbox->setEnd(anyChars);
 
-  // push a SourceParseContext on to the stack.
+  // Push a SourceParseContext on to the stack.
   SourceParseContext funpc(this, funbox, /* newDirectives = */ nullptr);
   if (!funpc.init()) {
     return null();
   }
 
   TokenPos synthesizedBodyPos = TokenPos(classNameOffset, classNameOffset + 1);
-  // Create a ListNode for the parameters + body (there are no parameters)
+  // Create a ListNode for the parameters + body (there are no parameters).
   ListNodeType argsbody =
       handler_.newList(ParseNodeKind::ParamsBody, synthesizedBodyPos);
   if (!argsbody) {
     return null();
   }
   handler_.setFunctionFormalParametersAndBody(funNode, argsbody);
   funbox->function()->setArgCount(0);
   tokenStream.setFunctionStart(funbox);
 
-  // push a LexicalScope on to the stack
+  // Push a LexicalScope on to the stack.
   ParseContext::Scope lexicalScope(this);
   if (!lexicalScope.init(pc_)) {
     return null();
   }
 
   auto stmtList = handler_.newStatementList(synthesizedBodyPos);
   if (!stmtList) {
     return null();
@@ -7161,17 +7161,16 @@ GeneralParser<ParseHandler, Unit>::synth
     return null();
   }
 
   bool canSkipLazyClosedOverBindings = handler_.canSkipLazyClosedOverBindings();
   if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) {
     return null();
   }
 
-  // Set the function's body to the field assignment.
   auto initializerBody = finishLexicalScope(lexicalScope, stmtList);
   if (!initializerBody) {
     return null();
   }
   handler_.setBeginPosition(initializerBody, stmtList);
   handler_.setEndPosition(initializerBody, stmtList);
 
   handler_.setFunctionBody(funNode, initializerBody);
@@ -7186,129 +7185,136 @@ GeneralParser<ParseHandler, Unit>::synth
 
   return funNode;
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::FunctionNodeType
 GeneralParser<ParseHandler, Unit>::fieldInitializer(YieldHandling yieldHandling,
                                                     HandleAtom propAtom) {
-  TokenPos fieldPos = pos();
-
-  // Create the function object
+  TokenPos firstTokenPos = pos();
+
+  // Create the function object.
   RootedFunction fun(cx_, newFunction(propAtom, FunctionSyntaxKind::Expression,
                                       GeneratorKind::NotGenerator,
                                       FunctionAsyncKind::SyncFunction));
   if (!fun) {
     return null();
   }
 
-  // Create the top-level field initializer node
+  // Create the top-level field initializer node.
   FunctionNodeType funNode =
-      handler_.newFunction(FunctionSyntaxKind::Expression, fieldPos);
+      handler_.newFunction(FunctionSyntaxKind::Expression, firstTokenPos);
   if (!funNode) {
     return null();
   }
 
   // Create the FunctionBox and link it to the function object.
   Directives directives(true);
-  FunctionBox* funbox = newFunctionBox(funNode, fun, fieldPos.begin, directives,
-                                       GeneratorKind::NotGenerator,
+  FunctionBox* funbox = newFunctionBox(funNode, fun, firstTokenPos.begin,
+                                       directives, GeneratorKind::NotGenerator,
                                        FunctionAsyncKind::SyncFunction);
   if (!funbox) {
     return null();
   }
   funbox->initWithEnclosingParseContext(pc_, FunctionSyntaxKind::Expression);
   handler_.setFunctionBox(funNode, funbox);
-
-  // push a SourceParseContext on to the stack.
+  tokenStream.setFunctionStart(funbox);
+
+  // Push a SourceParseContext on to the stack.
   SourceParseContext funpc(this, funbox, /* newDirectives = */ nullptr);
   if (!funpc.init()) {
     return null();
   }
 
-  // push a VarScope on to the stack
+  // Push a VarScope on to the stack.
   ParseContext::VarScope varScope(this);
   if (!varScope.init(pc_)) {
     return null();
   }
 
-  // push a LexicalScope on to the stack
+  // Push a LexicalScope on to the stack.
   ParseContext::Scope lexicalScope(this);
   if (!lexicalScope.init(pc_)) {
     return null();
   }
 
-  // Create a ListNode for the parameters + body (there are no parameters)
-  ListNodeType argsbody = handler_.newList(ParseNodeKind::ParamsBody, fieldPos);
+  // Parse the expression for the field initializer.
+  Node initializerExpr =
+      assignExpr(InAllowed, yieldHandling, TripledotProhibited);
+  if (!initializerExpr) {
+    return null();
+  }
+
+  // Create a ListNode for the parameters + body (there are no parameters).
+  ListNodeType argsbody =
+      handler_.newList(ParseNodeKind::ParamsBody, firstTokenPos);
   if (!argsbody) {
     return null();
   }
   handler_.setFunctionFormalParametersAndBody(funNode, argsbody);
   funbox->function()->setArgCount(0);
 
-  tokenStream.setFunctionStart(funbox);
-
-  // Parse the expression for the field initializer.
-  Node initializerExpr =
-      assignExpr(InAllowed, yieldHandling, TripledotProhibited);
-  if (!initializerExpr) {
-    return null();
-  }
-
   funbox->setEnd(anyChars);
 
   funbox->usesThis = true;
   NameNodeType thisName = newThisName();
   if (!thisName) {
     return null();
   }
 
-  // build `this.field` expression
-  ThisLiteralType propAssignThis = handler_.newThisLiteral(fieldPos, thisName);
+  // Build `this.field` expression.
+  ThisLiteralType propAssignThis =
+      handler_.newThisLiteral(firstTokenPos, thisName);
   if (!propAssignThis) {
     return null();
   }
 
   NameNodeType propAssignName =
-      handler_.newPropertyName(propAtom->asPropertyName(), fieldPos);
+      handler_.newPropertyName(propAtom->asPropertyName(), firstTokenPos);
   if (!propAssignName) {
     return null();
   }
 
   PropertyAccessType propAssignFieldAccess =
       handler_.newPropertyAccess(propAssignThis, propAssignName);
   if (!propAssignFieldAccess) {
     return null();
   }
-  handler_.setBeginPosition(propAssignFieldAccess, propAssignName);
-  handler_.setEndPosition(propAssignFieldAccess, propAssignName);
 
   // Synthesize an assignment expression for the property.
   AssignmentNodeType initializerAssignment = handler_.newAssignment(
       ParseNodeKind::AssignExpr, propAssignFieldAccess, initializerExpr);
   if (!initializerAssignment) {
     return null();
   }
-  handler_.setBeginPosition(initializerAssignment, initializerExpr);
-  handler_.setEndPosition(initializerAssignment, initializerExpr);
 
   bool canSkipLazyClosedOverBindings = handler_.canSkipLazyClosedOverBindings();
   if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) {
     return null();
   }
 
+  UnaryNodeType exprStatement =
+      handler_.newExprStatement(initializerAssignment, pos().end);
+  if (!exprStatement) {
+    return null();
+  }
+
+  ListNodeType statementList = handler_.newStatementList(firstTokenPos);
+  if (!argsbody) {
+    return null();
+  }
+  handler_.addStatementToList(statementList, exprStatement);
+
   // Set the function's body to the field assignment.
   LexicalScopeNodeType initializerBody =
-      finishLexicalScope(lexicalScope, initializerAssignment);
+      finishLexicalScope(lexicalScope, statementList);
   if (!initializerBody) {
     return null();
   }
-  handler_.setBeginPosition(initializerBody, initializerAssignment);
-  handler_.setEndPosition(initializerBody, initializerAssignment);
 
   handler_.setFunctionBody(funNode, initializerBody);
 
   if (!finishFunction()) {
     return null();
   }
 
   return funNode;
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -14,16 +14,17 @@ slow script test262/built-ins/decodeURIC
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1499448
 skip script non262/fields/access.js
 skip script non262/fields/basic.js
 skip script non262/fields/error.js
 skip script non262/fields/field_types.js
 skip script non262/fields/literal.js
 skip script non262/fields/mixed_methods.js
 skip script non262/fields/quirks.js
+skip script non262/reflect-parse/class-fields.js
 
 
 #################################################################
 # Tests disabled due to intentional alternative implementations #
 #################################################################
 
 # Legacy "caller" and "arguments" implemented as accessor properties on Function.prototype.
 skip script test262/built-ins/Function/prototype/restricted-property-arguments.js
--- a/js/src/tests/non262/reflect-parse/PatternBuilders.js
+++ b/js/src/tests/non262/reflect-parse/PatternBuilders.js
@@ -148,16 +148,21 @@ function classExpr(id, heritage, body) {
 }
 function classMethod(id, body, kind, static) {
     return Pattern({ type: "ClassMethod",
                      name: id,
                      body: body,
                      kind: kind,
                      static: static });
 }
+function classField(id, initializer) {
+    return Pattern({ type: "ClassField",
+                     name: id,
+                     initializer: initializer });
+}
 
 function funExpr(id, args, body, gen) {
     return Pattern({ type: "FunctionExpression",
                      id: id,
                      params: args,
                      body: body,
                      generator: false });
 }
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/reflect-parse/class-fields.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!xulRuntime.shell)
+// Classes
+function testClassFields() {
+    function constructor_(name) {
+        let body = blockStmt([]);
+        let method = funExpr(ident(name), [], body);
+        return classMethod(ident("constructor"), method, "method", false);
+    }
+
+    let genConstructor = constructor_("C");
+
+    assertExpr("(class C { x = 2; })", classExpr(ident("C"), null, [classField(ident("x"), lit(2)), genConstructor]));
+    assertExpr("(class C { x = x; })", classExpr(ident("C"), null, [classField(ident("x"), ident("x")), genConstructor]))
+    assertExpr("(class C { x; })", classExpr(ident("C"), null, [classField(ident("x"), undefined)]))
+    assertExpr("(class C { x; y = 2; })", classExpr(ident("C"), null, [classField(ident("x"), undefined), classField(ident("y"), lit(2)), genConstructor]))
+    assertExpr("(class C { x = 2; constructor(){} })", classExpr(ident("C"), null, [classField(ident("x"), lit(2)), genConstructor]))
+}
+
+runtest(testClassFields);