Bug 1535166 - Implement computed field names. r=jorendorff
authorAshley Hauck <khyperia@mozilla.com>
Wed, 20 Mar 2019 17:26:01 +0000
changeset 465240 d96f98f974e087224e0a1ef9be67e2fc63397d8b
parent 465239 c786b72a9d8e12f17cf189f69cdd1fcdbf810511
child 465241 f4343e34b8feb81cc31b537a9c1fce846cb2c344
push id35735
push usershindli@mozilla.com
push dateThu, 21 Mar 2019 04:34:45 +0000
treeherdermozilla-central@ac0cd1a710f3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1535166
milestone68.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 1535166 - Implement computed field names. r=jorendorff Differential Revision: https://phabricator.services.mozilla.com/D23408
js/src/builtin/ReflectParse.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/FunctionEmitter.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/jit-test/tests/fields/access.js
js/src/jit-test/tests/fields/basic.js
js/src/jit-test/tests/fields/error.js
js/src/jit-test/tests/fields/field_types.js
js/src/jit-test/tests/fields/literal.js
js/src/jit-test/tests/fields/mixed_methods.js
js/src/jit-test/tests/fields/quirks.js
js/src/tests/jstests.list
js/src/tests/non262/fields/access.js
js/src/tests/non262/fields/basic.js
js/src/tests/non262/fields/error.js
js/src/tests/non262/fields/field_types.js
js/src/tests/non262/fields/literal.js
js/src/tests/non262/fields/mixed_methods.js
js/src/tests/non262/fields/quirks.js
js/src/tests/non262/reflect-parse/PatternBuilders.js
js/src/tests/non262/reflect-parse/class-fields.js
js/src/vm/CommonPropertyNames.h
js/src/vm/Opcodes.h
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -606,17 +606,17 @@ 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,
+  MOZ_MUST_USE bool classField(HandleValue name, HandleValue initializer,
                                TokenPos* pos, MutableHandleValue dst);
 
   /*
    * expressions
    */
 
   MOZ_MUST_USE bool binaryExpression(BinaryOperator op, HandleValue left,
                                      HandleValue right, TokenPos* pos,
@@ -1531,18 +1531,17 @@ bool NodeBuilder::classMethod(HandleValu
 
 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);
+  return newNode(AST_CLASS_FIELD, pos, "name", name, "init", initializer, dst);
 }
 
 bool NodeBuilder::classMembers(NodeVector& members, MutableHandleValue dst) {
   return newArray(members, dst);
 }
 
 bool NodeBuilder::classDefinition(bool expr, HandleValue name,
                                   HandleValue heritage, HandleValue block,
@@ -2536,18 +2535,26 @@ bool ASTSerializer::classField(ClassFiel
                            ->as<LexicalScopeNode>()
                            .scopeBody()
                            ->as<ListNode>()
                            .head()
                            ->as<UnaryNode>()
                            .kid()
                            ->as<AssignmentNode>()
                            .right();
-    if (!expression(value, &val)) {
-      return false;
+    // RawUndefinedExpr is the node we use for "there is no initializer". If one
+    // writes, literally, `x = undefined;`, it will not be a RawUndefinedExpr
+    // node, but rather a variable reference.
+    // Behavior for "there is no initializer" should be { ..., "init": null }
+    if (value->getKind() != ParseNodeKind::RawUndefinedExpr) {
+      if (!expression(value, &val)) {
+        return false;
+      }
+    } else {
+      val.setNull();
     }
   }
   return propertyName(&classField->name(), &key) &&
          builder.classField(key, val, &classField->pn_pos, dst);
 }
 
 bool ASTSerializer::leftAssociate(ListNode* node, MutableHandleValue dst) {
   MOZ_ASSERT(!node->empty());
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -7771,16 +7771,19 @@ bool BytecodeEmitter::emitPropertyList(L
         }
         break;
       default:
         MOZ_CRASH("Invalid op");
     }
   }
 
   if (obj->getKind() == ParseNodeKind::ClassMemberList) {
+    if (!emitCreateFieldKeys(obj)) {
+      return false;
+    }
     if (!emitCreateFieldInitializers(obj)) {
       return false;
     }
   }
 
   return true;
 }
 
@@ -7794,16 +7797,100 @@ FieldInitializers BytecodeEmitter::setup
       if (initializer != nullptr) {
         numFields++;
       }
     }
   }
   return FieldInitializers(numFields);
 }
 
+// Purpose of .fieldKeys:
+// Computed field names (`["x"] = 2;`) must be ran at class-evaluation time, not
+// object construction time. The transformation to do so is roughly as follows:
+//
+// class C {
+//   [keyExpr] = valueExpr;
+// }
+// -->
+// let .fieldKeys = [keyExpr];
+// let .initializers = [
+//   () => {
+//     this[.fieldKeys[0]] = valueExpr;
+//   }
+// ];
+// class C {
+//   constructor() {
+//     .initializers[0]();
+//   }
+// }
+//
+// BytecodeEmitter::emitCreateFieldKeys does `let .fieldKeys = [keyExpr, ...];`
+// See GeneralParser::fieldInitializer for the `this[.fieldKeys[0]]` part.
+bool BytecodeEmitter::emitCreateFieldKeys(ListNode* obj) {
+  size_t numFieldKeys = 0;
+  for (ParseNode* propdef : obj->contents()) {
+    if (propdef->is<ClassField>()) {
+      ClassField* field = &propdef->as<ClassField>();
+      if (field->name().getKind() == ParseNodeKind::ComputedName) {
+        numFieldKeys++;
+      }
+    }
+  }
+
+  if (numFieldKeys == 0) {
+    return true;
+  }
+
+  NameOpEmitter noe(this, cx->names().dotFieldKeys,
+                    NameOpEmitter::Kind::Initialize);
+  if (!noe.prepareForRhs()) {
+    return false;
+  }
+
+  if (!emitUint32Operand(JSOP_NEWARRAY, numFieldKeys)) {
+    //            [stack] ARRAY
+    return false;
+  }
+
+  size_t curFieldKeyIndex = 0;
+  for (ParseNode* propdef : obj->contents()) {
+    if (propdef->is<ClassField>()) {
+      ClassField* field = &propdef->as<ClassField>();
+      if (field->name().getKind() == ParseNodeKind::ComputedName) {
+        ParseNode* nameExpr = field->name().as<UnaryNode>().kid();
+
+        if (!emitTree(nameExpr)) {
+          //        [stack] ARRAY KEY
+          return false;
+        }
+
+        if (!emitUint32Operand(JSOP_INITELEM_ARRAY, curFieldKeyIndex)) {
+          //        [stack] ARRAY
+          return false;
+        }
+
+        curFieldKeyIndex++;
+      }
+    }
+  }
+  MOZ_ASSERT(curFieldKeyIndex == numFieldKeys);
+
+  if (!noe.emitAssignment()) {
+    //            [stack] ARRAY
+    return false;
+  }
+
+  if (!emit1(JSOP_POP)) {
+    //            [stack]
+    return false;
+  }
+
+  return true;
+}
+
 bool BytecodeEmitter::emitCreateFieldInitializers(ListNode* obj) {
   const FieldInitializers& fieldInitializers = fieldInitializers_;
   MOZ_ASSERT(fieldInitializers.valid);
   size_t numFields = fieldInitializers.numFieldInitializers;
 
   if (numFields == 0) {
     return true;
   }
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -602,16 +602,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
                                                 ptrdiff_t offset);
 
   MOZ_MUST_USE bool emitHoistedFunctionsInList(ListNode* stmtList);
 
   MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe,
                                      PropListType type);
 
   FieldInitializers setupFieldInitializers(ListNode* classMembers);
+  MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj);
   MOZ_MUST_USE bool emitCreateFieldInitializers(ListNode* obj);
 
   // To catch accidental misuse, emitUint16Operand/emit3 assert that they are
   // not used to unconditionally emit JSOP_GETLOCAL. Variable access should
   // instead be emitted using EmitVarOp. In special cases, when the caller
   // definitely knows that a given local slot is unaliased, this function may be
   // used as a non-asserting version of emitUint16Operand.
   MOZ_MUST_USE bool emitLocalOp(JSOp op, uint32_t slot);
--- a/js/src/frontend/FunctionEmitter.cpp
+++ b/js/src/frontend/FunctionEmitter.cpp
@@ -622,17 +622,20 @@ bool FunctionScriptEmitter::emitInitiali
       }
     }
 
     if (!bce_->emitNumberOp(fieldIndex)) {
       //            [stack] ARRAY? ARRAY INDEX
       return false;
     }
 
-    if (!bce_->emit1(JSOP_CALLELEM)) {
+    // Don't use CALLELEM here, because the receiver of the call != the receiver
+    // of this getelem. (Specifically, the call receiver is `this`, and the
+    // receiver of this getelem is `.initializers`)
+    if (!bce_->emit1(JSOP_GETELEM)) {
       //            [stack] ARRAY? FUNC
       return false;
     }
 
     // This is guaranteed to run after super(), so we don't need TDZ checks.
     if (!bce_->emitGetName(bce_->cx->names().dotThis)) {
       //            [stack] ARRAY? FUNC THIS
       return false;
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -1775,19 +1775,19 @@ class NullLiteral : public NullaryNode {
 
   static bool test(const ParseNode& node) {
     bool match = node.isKind(ParseNodeKind::NullExpr);
     MOZ_ASSERT_IF(match, node.is<NullaryNode>());
     return match;
   }
 };
 
-// This is only used internally, currently just for tagged templates.
-// It represents the value 'undefined' (aka `void 0`), like NullLiteral
-// represents the value 'null'.
+// This is only used internally, currently just for tagged templates and the
+// initial value of fields without initializers. It represents the value
+// 'undefined' (aka `void 0`), like NullLiteral represents the value 'null'.
 class RawUndefinedLiteral : public NullaryNode {
  public:
   explicit RawUndefinedLiteral(const TokenPos& pos)
       : NullaryNode(ParseNodeKind::RawUndefinedExpr, JSOP_UNDEFINED, pos) {}
 
   static bool test(const ParseNode& node) {
     bool match = node.isKind(ParseNodeKind::RawUndefinedExpr);
     MOZ_ASSERT_IF(match, node.is<NullaryNode>());
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -6728,18 +6728,18 @@ static AccessorType ToAccessorType(Prope
       MOZ_CRASH("unexpected property type");
   }
 }
 
 template <class ParseHandler, typename Unit>
 bool GeneralParser<ParseHandler, Unit>::classMember(
     YieldHandling yieldHandling, DefaultHandling defaultHandling,
     const ParseContext::ClassStatement& classStmt, HandlePropertyName className,
-    uint32_t classStartOffset, bool hasHeritage,
-    size_t& numFieldsWithInitializers, ListNodeType& classMembers, bool* done) {
+    uint32_t classStartOffset, bool hasHeritage, size_t& numFields,
+    size_t& numFieldKeys, ListNodeType& classMembers, bool* done) {
   *done = false;
 
   TokenKind tt;
   if (!tokenStream.getToken(&tt)) {
     return false;
   }
   if (tt == TokenKind::RightCurly) {
     *done = true;
@@ -6789,33 +6789,33 @@ bool GeneralParser<ParseHandler, Unit>::
       errorAt(propNameOffset, JSMSG_FIELDS_NOT_SUPPORTED);
       return false;
     }
 
     if (isStatic) {
       errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
       return false;
     }
+
+    if (!abortIfSyntaxParser()) {
+      return false;
+    }
+
+    numFields++;
+
+    FunctionNodeType initializer =
+        fieldInitializerOpt(yieldHandling, propName, propAtom, numFieldKeys);
+    if (!initializer) {
+      return false;
+    }
+
     if (!tokenStream.getToken(&tt)) {
       return false;
     }
 
-    FunctionNodeType initializer = null();
-    if (tt == TokenKind::Assign) {
-      initializer = fieldInitializer(yieldHandling, propAtom);
-      if (!initializer) {
-        return false;
-      }
-
-      numFieldsWithInitializers++;
-      if (!tokenStream.getToken(&tt)) {
-        return false;
-      }
-    }
-
     // TODO(khyperia): Implement ASI
     if (tt != TokenKind::Semi) {
       error(JSMSG_MISSING_SEMI_FIELD);
       return false;
     }
 
     return handler_.addClassFieldDefinition(classMembers, propName,
                                             initializer);
@@ -6880,22 +6880,22 @@ bool GeneralParser<ParseHandler, Unit>::
   AccessorType atype = ToAccessorType(propType);
   return handler_.addClassMethodDefinition(classMembers, propName, funNode,
                                            atype, isStatic);
 }
 
 template <class ParseHandler, typename Unit>
 bool GeneralParser<ParseHandler, Unit>::finishClassConstructor(
     const ParseContext::ClassStatement& classStmt, HandlePropertyName className,
-    uint32_t classStartOffset, uint32_t classEndOffset,
-    size_t numFieldsWithInitializers, ListNodeType& classMembers) {
+    uint32_t classStartOffset, uint32_t classEndOffset, size_t numFields,
+    ListNodeType& classMembers) {
   // Fields cannot re-use the constructor obtained via JSOP_CLASSCONSTRUCTOR or
   // JSOP_DERIVEDCONSTRUCTOR due to needing to emit calls to the field
   // initializers in the constructor. So, synthesize a new one.
-  if (classStmt.constructorBox == nullptr && numFieldsWithInitializers > 0) {
+  if (classStmt.constructorBox == nullptr && numFields > 0) {
     // synthesizeConstructor assigns to classStmt.constructorBox
     FunctionNodeType synthesizedCtor =
         synthesizeConstructor(className, classStartOffset);
     if (!synthesizedCtor) {
       return false;
     }
 
     MOZ_ASSERT(classStmt.constructorBox != nullptr);
@@ -6915,33 +6915,33 @@ bool GeneralParser<ParseHandler, Unit>::
     }
   }
 
   if (FunctionBox* ctorbox = classStmt.constructorBox) {
     // Amend the toStringEnd offset for the constructor now that we've
     // finished parsing the class.
     ctorbox->toStringEnd = classEndOffset;
 
-    if (numFieldsWithInitializers > 0) {
+    if (numFields > 0) {
       // Field initialization need access to `this`.
       ctorbox->setHasThisBinding();
     }
 
     // Set the same information, but on the lazyScript.
     if (ctorbox->function()->isInterpretedLazy()) {
       ctorbox->function()->lazyScript()->setToStringEnd(classEndOffset);
 
-      if (numFieldsWithInitializers > 0) {
+      if (numFields > 0) {
         ctorbox->function()->lazyScript()->setHasThisBinding();
       }
 
       // Field initializers can be retrieved if the class and constructor are
       // being compiled at the same time, but we need to stash the field
       // information if the constructor is being compiled lazily.
-      FieldInitializers fieldInfo(numFieldsWithInitializers);
+      FieldInitializers fieldInfo(numFields);
       ctorbox->function()->lazyScript()->setFieldInitializers(fieldInfo);
     }
   }
 
   return true;
 }
 
 template <class ParseHandler, typename Unit>
@@ -7020,30 +7020,31 @@ GeneralParser<ParseHandler, Unit>::class
       return null();
     }
 
     ListNodeType classMembers = handler_.newClassMemberList(pos().begin);
     if (!classMembers) {
       return null();
     }
 
-    size_t numFieldsWithInitializers = 0;
+    size_t numFields = 0;
+    size_t numFieldKeys = 0;
     for (;;) {
       bool done;
       if (!classMember(yieldHandling, defaultHandling, classStmt, className,
-                       classStartOffset, hasHeritage, numFieldsWithInitializers,
+                       classStartOffset, hasHeritage, numFields, numFieldKeys,
                        classMembers, &done)) {
         return null();
       }
       if (done) {
         break;
       }
     }
 
-    if (numFieldsWithInitializers > 0) {
+    if (numFields > 0) {
       // .initializers is always closed over by the constructor when there are
       // fields with initializers. However, there's some strange circumstances
       // which prevents us from using the normal noteUsedName() system. We
       // cannot call noteUsedName(".initializers") when parsing the constructor,
       // because .initializers should be marked as used *only if* there are
       // fields with initializers. Even if we haven't seen any fields yet,
       // there may be fields after the constructor.
       // Consider the following class:
@@ -7058,26 +7059,32 @@ GeneralParser<ParseHandler, Unit>::class
       // So, instead, at the end of class parsing (where we are now), we do some
       // tricks to pretend that noteUsedName(".initializers") was called in the
       // constructor.
       if (!usedNames_.markAsAlwaysClosedOver(cx_, cx_->names().dotInitializers,
                                              pc_->scriptId(),
                                              pc_->innermostScope()->id())) {
         return null();
       }
-      if (!noteDeclaredName(cx_->names().dotInitializers,
-                            DeclarationKind::Const, namePos)) {
+      if (!noteDeclaredName(cx_->names().dotInitializers, DeclarationKind::Var,
+                            namePos)) {
+        return null();
+      }
+    }
+
+    if (numFieldKeys > 0) {
+      if (!noteDeclaredName(cx_->names().dotFieldKeys, DeclarationKind::Var,
+                            namePos)) {
         return null();
       }
     }
 
     classEndOffset = pos().end;
     if (!finishClassConstructor(classStmt, className, classStartOffset,
-                                classEndOffset, numFieldsWithInitializers,
-                                classMembers)) {
+                                classEndOffset, numFields, classMembers)) {
       return null();
     }
 
     if (className) {
       // The inner name is immutable.
       if (!noteDeclaredName(className, DeclarationKind::Const, namePos)) {
         return null();
       }
@@ -7211,19 +7218,36 @@ GeneralParser<ParseHandler, Unit>::synth
   // not directly set in this function, but rather in
   // initWithEnclosingParseContext.
 
   return funNode;
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::FunctionNodeType
-GeneralParser<ParseHandler, Unit>::fieldInitializer(YieldHandling yieldHandling,
-                                                    HandleAtom propAtom) {
-  TokenPos firstTokenPos = pos();
+GeneralParser<ParseHandler, Unit>::fieldInitializerOpt(
+    YieldHandling yieldHandling, Node propName, HandleAtom propAtom,
+    size_t& numFieldKeys) {
+  bool hasInitializer = false;
+  if (!tokenStream.matchToken(&hasInitializer, TokenKind::Assign,
+                              TokenStream::None)) {
+    return null();
+  }
+
+  TokenPos firstTokenPos;
+  if (hasInitializer) {
+    firstTokenPos = pos();
+  } else {
+    // the location of the "initializer" should be a zero-width span:
+    // class C {
+    //   x /* here */ ;
+    // }
+    uint32_t endPos = pos().end;
+    firstTokenPos = TokenPos(endPos, endPos);
+  }
 
   // Create the function object.
   RootedFunction fun(cx_, newFunction(propAtom, FunctionSyntaxKind::Expression,
                                       GeneratorKind::NotGenerator,
                                       FunctionAsyncKind::SyncFunction));
   if (!fun) {
     return null();
   }
@@ -7240,17 +7264,25 @@ GeneralParser<ParseHandler, Unit>::field
   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);
-  tokenStream.setFunctionStart(funbox);
+
+  // We can't use tokenStream.setFunctionStart, because that uses pos().begin,
+  // which is incorrect for fields without initializers (pos() points to the
+  // field identifier)
+  uint32_t firstTokenLine, firstTokenColumn;
+  tokenStream.computeLineAndColumn(firstTokenPos.begin, &firstTokenLine,
+                                   &firstTokenColumn);
+
+  funbox->setStart(firstTokenPos.begin, firstTokenLine, firstTokenColumn);
 
   // 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.
@@ -7260,25 +7292,33 @@ GeneralParser<ParseHandler, Unit>::field
   }
 
   // Push a LexicalScope on to the stack.
   ParseContext::Scope lexicalScope(this);
   if (!lexicalScope.init(pc_)) {
     return null();
   }
 
-  // Parse the expression for the field initializer.
-  Node initializerExpr =
-      assignExpr(InAllowed, yieldHandling, TripledotProhibited);
-  if (!initializerExpr) {
-    return null();
-  }
-
-  TokenPos wholeInitializerPos = pos();
-  wholeInitializerPos.begin = firstTokenPos.begin;
+  Node initializerExpr;
+  TokenPos wholeInitializerPos;
+  if (hasInitializer) {
+    // Parse the expression for the field initializer.
+    initializerExpr = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
+    if (!initializerExpr) {
+      return null();
+    }
+    wholeInitializerPos = pos();
+    wholeInitializerPos.begin = firstTokenPos.begin;
+  } else {
+    initializerExpr = handler_.newRawUndefinedLiteral(firstTokenPos);
+    if (!initializerExpr) {
+      return null();
+    }
+    wholeInitializerPos = firstTokenPos;
+  }
 
   // Update the end position of the parse node.
   handler_.setEndPosition(funNode, wholeInitializerPos.end);
   funbox->setEnd(anyChars);
 
   // Create a ListNode for the parameters + body (there are no parameters).
   ListNodeType argsbody =
       handler_.newList(ParseNodeKind::ParamsBody, wholeInitializerPos);
@@ -7296,42 +7336,79 @@ GeneralParser<ParseHandler, Unit>::field
 
   // Build `this.field` expression.
   ThisLiteralType propAssignThis =
       handler_.newThisLiteral(wholeInitializerPos, thisName);
   if (!propAssignThis) {
     return null();
   }
 
-  NameNodeType propAssignName =
-      handler_.newPropertyName(propAtom->asPropertyName(), wholeInitializerPos);
-  if (!propAssignName) {
-    return null();
-  }
-
-  PropertyAccessType propAssignFieldAccess =
-      handler_.newPropertyAccess(propAssignThis, propAssignName);
-  if (!propAssignFieldAccess) {
-    return null();
+  Node propAssignFieldAccess;
+  uint32_t indexValue;
+  if (!propAtom) {
+    // See BytecodeEmitter::emitCreateFieldKeys for an explanation of what
+    // .fieldKeys means and its purpose.
+    Node dotFieldKeys = newInternalDotName(cx_->names().dotFieldKeys);
+    if (!dotFieldKeys) {
+      return null();
+    }
+
+    double fieldKeyIndex = numFieldKeys;
+    numFieldKeys++;
+    Node fieldKeyIndexNode = handler_.newNumber(
+        fieldKeyIndex, DecimalPoint::NoDecimal, wholeInitializerPos);
+    if (!fieldKeyIndexNode) {
+      return null();
+    }
+
+    Node fieldKeyValue = handler_.newPropertyByValue(
+        dotFieldKeys, fieldKeyIndexNode, wholeInitializerPos.end);
+    if (!fieldKeyValue) {
+      return null();
+    }
+
+    propAssignFieldAccess = handler_.newPropertyByValue(
+        propAssignThis, fieldKeyValue, wholeInitializerPos.end);
+    if (!propAssignFieldAccess) {
+      return null();
+    }
+  } else if (propAtom->isIndex(&indexValue)) {
+    propAssignFieldAccess = handler_.newPropertyByValue(
+        propAssignThis, propName, wholeInitializerPos.end);
+    if (!propAssignFieldAccess) {
+      return null();
+    }
+  } else {
+    NameNodeType propAssignName = handler_.newPropertyName(
+        propAtom->asPropertyName(), wholeInitializerPos);
+    if (!propAssignName) {
+      return null();
+    }
+
+    propAssignFieldAccess =
+        handler_.newPropertyAccess(propAssignThis, propAssignName);
+    if (!propAssignFieldAccess) {
+      return null();
+    }
   }
 
   // Synthesize an assignment expression for the property.
   AssignmentNodeType initializerAssignment = handler_.newAssignment(
       ParseNodeKind::AssignExpr, propAssignFieldAccess, initializerExpr);
   if (!initializerAssignment) {
     return null();
   }
 
   bool canSkipLazyClosedOverBindings = handler_.canSkipLazyClosedOverBindings();
   if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) {
     return null();
   }
 
   UnaryNodeType exprStatement =
-      handler_.newExprStatement(initializerAssignment, pos().end);
+      handler_.newExprStatement(initializerAssignment, wholeInitializerPos.end);
   if (!exprStatement) {
     return null();
   }
 
   ListNodeType statementList = handler_.newStatementList(wholeInitializerPos);
   if (!argsbody) {
     return null();
   }
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -1306,25 +1306,26 @@ class MOZ_STACK_CLASS GeneralParser : pu
                                 ClassContext classContext,
                                 DefaultHandling defaultHandling);
   MOZ_MUST_USE bool classMember(YieldHandling yieldHandling,
                                 DefaultHandling defaultHandling,
                                 const ParseContext::ClassStatement& classStmt,
                                 HandlePropertyName className,
                                 uint32_t classStartOffset, bool hasHeritage,
                                 size_t& numFieldsWithInitializers,
+                                size_t& numFieldKeys,
                                 ListNodeType& classMembers, bool* done);
   MOZ_MUST_USE bool finishClassConstructor(
       const ParseContext::ClassStatement& classStmt,
       HandlePropertyName className, uint32_t classStartOffset,
       uint32_t classEndOffset, size_t numFieldsWithInitializers,
       ListNodeType& classMembers);
 
-  FunctionNodeType fieldInitializer(YieldHandling yieldHandling,
-                                    HandleAtom atom);
+  FunctionNodeType fieldInitializerOpt(YieldHandling yieldHandling, Node name,
+                                       HandleAtom atom, size_t& numFieldKeys);
   FunctionNodeType synthesizeConstructor(HandleAtom className,
                                          uint32_t classNameOffset);
 
   bool checkBindingIdentifier(PropertyName* ident, uint32_t offset,
                               YieldHandling yieldHandling,
                               TokenKind hint = TokenKind::Limit);
 
   PropertyName* labelOrIdentifierReference(YieldHandling yieldHandling);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/fields/access.js
@@ -0,0 +1,18 @@
+// |jit-test| --enable-experimental-fields
+
+class C {
+    x = 5;
+}
+
+c = new C();
+assertEq(5, c.x);
+
+class D {
+    #y = 5;
+}
+
+d = new D();
+assertEq(5, d.#y);
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
rename from js/src/tests/non262/fields/basic.js
rename to js/src/jit-test/tests/fields/basic.js
--- a/js/src/tests/non262/fields/basic.js
+++ b/js/src/jit-test/tests/fields/basic.js
@@ -1,9 +1,9 @@
-// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet
+// |jit-test| --enable-experimental-fields
 
 class C {
     x;
     y = 2;
 }
 
 class D {
     #x;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/fields/error.js
@@ -0,0 +1,58 @@
+// |jit-test| --enable-experimental-fields
+
+load(libdir + "asserts.js");
+
+let source = `class C {
+    x
+}`;
+assertErrorMessage(() => Function(source), SyntaxError, /./);
+
+source = `class C {
+    -2;
+    -2 = 2;
+}`;
+assertErrorMessage(() => Function(source), SyntaxError, /./);
+
+source = `class C {
+    x += 2;
+}`;
+assertErrorMessage(() => Function(source), SyntaxError, /./);
+
+source = `class C {
+    #2;
+}`;
+assertErrorMessage(() => Function(source), SyntaxError, /./);
+
+source = `class C {
+    #["h" + "i"];
+}`;
+assertErrorMessage(() => Function(source), SyntaxError, /./);
+
+source = `class C {
+    #"hi";
+}`;
+assertErrorMessage(() => Function(source), SyntaxError, /./);
+
+source = `function f() {
+class C {
+    #"should still throw error during lazy parse";
+}
+}`;
+assertErrorMessage(() => Function(source), SyntaxError, /./);
+
+// TODO
+//source = `#outside;`;
+//assertErrorMessage(() => eval(source), SyntaxError);
+
+source = `class C {
+    x = super();
+}`;
+assertErrorMessage(() => Function(source), SyntaxError, /./);
+
+source = `class C {
+    x = sper();
+}`;
+eval(source);
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/fields/field_types.js
@@ -0,0 +1,45 @@
+// |jit-test| --enable-experimental-fields
+
+class C {
+    [Math.sqrt(16)];
+    [Math.sqrt(8)] = 5 + 2;
+    "hi";
+    "bye" = {};
+    2 = 2;
+    0x101 = 2;
+    0o101 = 2;
+    0b101 = 2;
+    NaN = 0; // actually the field called "NaN", not the number
+    Infinity = 50; // actually the field called "Infinity", not the number
+    // all the keywords below are proper fields (?!?)
+    with = 0;
+    //static = 0; // doesn't work yet
+    async = 0;
+    get = 0;
+    set = 0;
+    export = 0;
+    function = 0;
+}
+
+let obj = new C();
+assertEq(Math.sqrt(16) in obj, true);
+assertEq(obj[Math.sqrt(16)], undefined);
+assertEq(obj[Math.sqrt(8)], 7);
+assertEq("hi" in obj, true);
+assertEq(obj["hi"], undefined);
+assertEq(typeof obj["bye"], "object");
+assertEq(obj[2], 2);
+assertEq(obj[0x101], 2);
+assertEq(obj[0o101], 2);
+assertEq(obj[0b101], 2);
+assertEq(obj.NaN, 0);
+assertEq(obj.Infinity, 50);
+assertEq(obj.with, 0);
+assertEq(obj.async, 0);
+assertEq(obj.get, 0);
+assertEq(obj.set, 0);
+assertEq(obj.export, 0);
+assertEq(obj.function, 0);
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
rename from js/src/tests/non262/fields/literal.js
rename to js/src/jit-test/tests/fields/literal.js
--- a/js/src/tests/non262/fields/literal.js
+++ b/js/src/jit-test/tests/fields/literal.js
@@ -1,46 +1,48 @@
-// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet
+// |jit-test| --enable-experimental-fields
+
+load(libdir + "asserts.js");
 
 source = `var y = {
     x;
 }`;
-assertThrowsInstanceOf(() => eval(source), SyntaxError);
+assertErrorMessage(() => eval(source), SyntaxError, /./);
 
 // This is legal, and is equivalent to `var y = { x: x };`
 // source = `var y = {
 //     x
 // }`;
 // assertThrowsInstanceOf(() => eval(source), SyntaxError);
 
 source = `var y = {
     #x;
 }`;
-assertThrowsInstanceOf(() => eval(source), SyntaxError);
+assertErrorMessage(() => eval(source), SyntaxError, /./);
 
 // Temporarily disabled due to the same reason above.
 // source = `var y = {
 //     #x
 // }`;
 // assertThrowsInstanceOf(() => eval(source), SyntaxError);
 
 source = `var y = {
     x = 2;
 }`;
-assertThrowsInstanceOf(() => eval(source), SyntaxError);
+assertErrorMessage(() => eval(source), SyntaxError, /./);
 
 source = `var y = {
     x = 2
 }`;
-assertThrowsInstanceOf(() => eval(source), SyntaxError);
+assertErrorMessage(() => eval(source), SyntaxError, /./);
 
 source = `var y = {
     #x = 2;
 }`;
-assertThrowsInstanceOf(() => eval(source), SyntaxError);
+assertErrorMessage(() => eval(source), SyntaxError, /./);
 
 source = `var y = {
     #x = 2
 }`;
-assertThrowsInstanceOf(() => eval(source), SyntaxError);
+assertErrorMessage(() => eval(source), SyntaxError, /./);
 
 if (typeof reportCompare === "function")
   reportCompare(true, true);
rename from js/src/tests/non262/fields/mixed_methods.js
rename to js/src/jit-test/tests/fields/mixed_methods.js
--- a/js/src/tests/non262/fields/mixed_methods.js
+++ b/js/src/jit-test/tests/fields/mixed_methods.js
@@ -1,9 +1,9 @@
-// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet
+// |jit-test| --enable-experimental-fields
 
 class C {
     x;
     y(){}
     z = 2;
     w(){};
 }
 
rename from js/src/tests/non262/fields/quirks.js
rename to js/src/jit-test/tests/fields/quirks.js
--- a/js/src/tests/non262/fields/quirks.js
+++ b/js/src/jit-test/tests/fields/quirks.js
@@ -1,16 +1,19 @@
-// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet
+// |jit-test| --enable-experimental-fields
 
 class C {
     x;;;;
     y
     ;
 }
 
 class D {
     x = 5;
-    y = (x += 1);
-    // TODO: Assert values of x and y
+    y = (this.x += 1) + 2;
 }
 
+let val = new D();
+assertEq(6, val.x);
+assertEq(8, val.y);
+
 if (typeof reportCompare === "function")
   reportCompare(true, true);
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -7,23 +7,16 @@ skip include test/jstests.list
 skip script non262/String/normalize-generateddata-input.js # input data for other test
 
 # Timeouts on arm and cgc builds.
 slow script test262/built-ins/decodeURI/S15.1.3.1_A2.5_T1.js
 slow script test262/built-ins/decodeURIComponent/S15.1.3.2_A2.5_T1.js
 
 # Fields are not fully implemented yet
 # 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
 
 
 ###########################################################################
 # Generated jstests.list for test262 when inline |reftest| isn't possible #
 ###########################################################################
 
 include test262/jstests.list
deleted file mode 100644
--- a/js/src/tests/non262/fields/access.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet
-
-class C {
-    x = 5;
-}
-
-c = new C();
-
-reportCompare(c.x, undefined); // TODO
-//reportCompare(c.x, 5);
-
-class D {
-    #y = 5;
-}
-
-d = new D();
-
-reportCompare(d.#y, undefined); // TODO
-//reportCompare(d.#y, 5);
deleted file mode 100644
--- a/js/src/tests/non262/fields/error.js
+++ /dev/null
@@ -1,55 +0,0 @@
-// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet
-
-let source = `class C {
-    x
-}`;
-assertThrowsInstanceOf(() => Function(source), SyntaxError);
-
-source = `class C {
-    -2;
-    -2 = 2;
-}`;
-assertThrowsInstanceOf(() => Function(source), SyntaxError);
-
-source = `class C {
-    x += 2;
-}`;
-assertThrowsInstanceOf(() => Function(source), SyntaxError);
-
-source = `class C {
-    #2;
-}`;
-assertThrowsInstanceOf(() => Function(source), SyntaxError);
-
-source = `class C {
-    #["h" + "i"];
-}`;
-assertThrowsInstanceOf(() => Function(source), SyntaxError);
-
-source = `class C {
-    #"hi";
-}`;
-assertThrowsInstanceOf(() => Function(source), SyntaxError);
-
-source = `function f() {
-class C {
-    #"should still throw error during lazy parse";
-}
-}`;
-assertThrowsInstanceOf(() => Function(source), SyntaxError);
-
-source = `#outside;`;
-assertThrowsInstanceOf(() => eval(source), SyntaxError);
-
-source = `class C {
-    x = super();
-}`;
-assertThrowsInstanceOf(() => Function(source), SyntaxError);
-
-source = `class C {
-    x = sper();
-}`;
-assertThrowsInstanceOf(() => Function(source), SyntaxError);
-
-if (typeof reportCompare === "function")
-  reportCompare(true, true);
deleted file mode 100644
--- a/js/src/tests/non262/fields/field_types.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet
-
-class C {
-    [Math.sqrt(4)];
-    [Math.sqrt(8)] = 5 + 2;
-    "hi";
-    "bye" = {};
-    2 = 2;
-    0x101 = 2;
-    0o101 = 2;
-    0b101 = 2;
-    NaN = 0; // actually the field called "NaN", not the number
-    Infinity = 50; // actually the field called "Infinity", not the number
-    // all the keywords below are proper fields (?!?)
-    with = 0;
-    //static = 0; // doesn't work yet
-    async = 0;
-    get = 0;
-    set = 0;
-    export = 0;
-    function = 0;
-}
-
-if (typeof reportCompare === "function")
-  reportCompare(true, true);
--- a/js/src/tests/non262/reflect-parse/PatternBuilders.js
+++ b/js/src/tests/non262/reflect-parse/PatternBuilders.js
@@ -148,20 +148,20 @@ 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) {
+function classField(id, init) {
     return Pattern({ type: "ClassField",
                      name: id,
-                     initializer: initializer });
+                     init: init });
 }
 
 function funExpr(id, args, body, gen) {
     return Pattern({ type: "FunctionExpression",
                      id: id,
                      params: args,
                      body: body,
                      generator: false });
--- a/js/src/tests/non262/reflect-parse/class-fields.js
+++ b/js/src/tests/non262/reflect-parse/class-fields.js
@@ -4,16 +4,17 @@ 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");
 
+    // TODO: Should genConstructor be present?
     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; })", classExpr(ident("C"), null, [classField(ident("x"), null), genConstructor]))
+    assertExpr("(class C { x; y = 2; })", classExpr(ident("C"), null, [classField(ident("x"), null), classField(ident("y"), lit(2)), genConstructor]))
     assertExpr("(class C { x = 2; constructor(){} })", classExpr(ident("C"), null, [classField(ident("x"), lit(2)), genConstructor]))
 }
 
 runtest(testClassFields);
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -114,16 +114,17 @@
   MACRO(direction, direction, "direction")                                     \
   MACRO(displayURL, displayURL, "displayURL")                                  \
   MACRO(do, do_, "do")                                                         \
   MACRO(domNode, domNode, "domNode")                                           \
   MACRO(done, done, "done")                                                    \
   MACRO(dotGenerator, dotGenerator, ".generator")                              \
   MACRO(dotThis, dotThis, ".this")                                             \
   MACRO(dotInitializers, dotInitializers, ".initializers")                     \
+  MACRO(dotFieldKeys, dotFieldKeys, ".fieldKeys")                              \
   MACRO(each, each, "each")                                                    \
   MACRO(elementType, elementType, "elementType")                               \
   MACRO(else, else_, "else")                                                   \
   MACRO(empty, empty, "")                                                      \
   MACRO(emptyRegExp, emptyRegExp, "(?:)")                                      \
   MACRO(encodeURI, encodeURI, "encodeURI")                                     \
   MACRO(encodeURIComponent, encodeURIComponent, "encodeURIComponent")          \
   MACRO(endTimestamp, endTimestamp, "endTimestamp")                            \
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1984,16 +1984,17 @@
      *   Category: Operators
      *   Type: Stack Operations
      *   Operands: uint8_t n
      *   Stack: v[n], v[n-1], ..., v[1], v[0] => v[0], v[n], v[n-1], ..., v[1]
      */ \
     MACRO(JSOP_UNPICK, 183, "unpick", NULL, 2, 0, 0, JOF_UINT8) \
     /*
      * Pops the top of stack value, pushes property of it onto the stack.
+     * Requires the value under 'obj' to be the receiver of the following call.
      *
      * Like JSOP_GETPROP but for call context.
      *
      *   Category: Literals
      *   Type: Object
      *   Operands: uint32_t nameIndex
      *   Stack: obj => obj[name]
      */ \
@@ -2078,17 +2079,18 @@
      *   Category: Statements
      *   Type: Generator
      *   Operands: uint8_t fulfillOrReject
      *   Stack: valueOrReason, gen => promise
      */ \
     MACRO(JSOP_ASYNCRESOLVE, 192, "async-resolve", NULL, 2, 2, 1, JOF_UINT8) \
     /*
      * Pops the top two values on the stack as 'propval' and 'obj', pushes
-     * 'propval' property of 'obj' onto the stack.
+     * 'propval' property of 'obj' onto the stack. Requires the value under
+     * 'obj' to be the receiver of the following call.
      *
      * Like JSOP_GETELEM but for call context.
      *
      *   Category: Literals
      *   Type: Object
      *   Operands:
      *   Stack: obj, propval => obj[propval]
      */ \