Bug 1499448 - Implement syntax for public and private fields. r=jorendorff
authorAshley Hauck <khyperia@mozilla.com>
Thu, 25 Oct 2018 18:25:34 +0000
changeset 443085 d0b577458d53
parent 443084 fba3ebc7b200
child 443086 f3d23a7bcbb6
child 443101 4d10fab34466
push id71785
push useraciure@mozilla.com
push dateFri, 26 Oct 2018 00:08:02 +0000
treeherderautoland@d0b577458d53 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1499448
milestone65.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 1499448 - Implement syntax for public and private fields. r=jorendorff Differential Revision: https://phabricator.services.mozilla.com/D8887
js/src/builtin/ReflectParse.cpp
js/src/frontend/BytecodeCompiler.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/FoldConstants.cpp
js/src/frontend/FullParseHandler.h
js/src/frontend/NameFunctions.cpp
js/src/frontend/ParseNode.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/SyntaxParseHandler.h
js/src/frontend/TokenKind.h
js/src/frontend/TokenStream.cpp
js/src/frontend/TokenStream.h
js/src/js.msg
js/src/jsast.tbl
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/vm/CompilationAndEvaluation.cpp
js/src/vm/StringType.cpp
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -544,17 +544,17 @@ class NodeBuilder
                                         HandleValue isDefault, TokenPos* pos, MutableHandleValue dst);
 
     MOZ_MUST_USE bool exportSpecifier(HandleValue bindingName, HandleValue exportName, TokenPos* pos, MutableHandleValue dst);
 
     MOZ_MUST_USE bool exportBatchSpecifier(TokenPos* pos, MutableHandleValue dst);
 
     MOZ_MUST_USE bool classDefinition(bool expr, HandleValue name, HandleValue heritage,
                                       HandleValue block, TokenPos* pos, MutableHandleValue dst);
-    MOZ_MUST_USE bool classMethods(NodeVector& methods, 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);
 
     /*
      * expressions
      */
 
     MOZ_MUST_USE bool binaryExpression(BinaryOperator op, HandleValue left, HandleValue right,
@@ -1647,19 +1647,19 @@ NodeBuilder::classMethod(HandleValue nam
                    "name", name,
                    "body", body,
                    "kind", kindName,
                    "static", isStaticVal,
                    dst);
 }
 
 bool
-NodeBuilder::classMethods(NodeVector& methods, MutableHandleValue dst)
+NodeBuilder::classMembers(NodeVector& members, MutableHandleValue dst)
 {
-    return newArray(methods, 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;
     RootedValue cb(cx, callbacks[type]);
@@ -2387,17 +2387,17 @@ ASTSerializer::classDefinition(ClassNode
 
     if (ClassNames* names = pn->names()) {
         if (!identifier(names->innerBinding(), &className)) {
             return false;
         }
     }
 
     return optExpression(pn->heritage(), &heritage) &&
-           statement(pn->methodList(), &classBody) &&
+           statement(pn->memberList(), &classBody) &&
            builder.classDefinition(expr, className, heritage, classBody, &pn->pn_pos, dst);
 }
 
 bool
 ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst)
 {
     if (!CheckRecursionLimit(cx)) {
         return false;
@@ -2615,36 +2615,41 @@ ASTSerializer::statement(ParseNode* pn, 
       }
 
       case ParseNodeKind::Debugger:
         return builder.debuggerStatement(&pn->pn_pos, dst);
 
       case ParseNodeKind::Class:
         return classDefinition(&pn->as<ClassNode>(), false, dst);
 
-      case ParseNodeKind::ClassMethodList:
+      case ParseNodeKind::ClassMemberList:
       {
-        ListNode* methodList = &pn->as<ListNode>();
-        NodeVector methods(cx);
-        if (!methods.reserve(methodList->count())) {
+        ListNode* memberList = &pn->as<ListNode>();
+        NodeVector members(cx);
+        if (!members.reserve(memberList->count())) {
             return false;
         }
 
-        for (ParseNode* item : methodList->contents()) {
+        for (ParseNode* item : memberList->contents()) {
+            if (item->is<ClassField>()) {
+                // TODO(khyperia): Implement private field access.
+                return false;
+            }
+
             ClassMethod* method = &item->as<ClassMethod>();
-            MOZ_ASSERT(methodList->pn_pos.encloses(method->pn_pos));
+            MOZ_ASSERT(memberList->pn_pos.encloses(method->pn_pos));
 
             RootedValue prop(cx);
             if (!classMethod(method, &prop)) {
                 return false;
             }
-            methods.infallibleAppend(prop);
+            members.infallibleAppend(prop);
         }
 
-        return builder.classMethods(methods, dst);
+        return builder.classMembers(members, dst);
       }
 
       default:
         LOCAL_NOT_REACHED("unexpected statement type");
     }
 }
 
 bool
@@ -2955,16 +2960,17 @@ ASTSerializer::expression(ParseNode* pn,
         return node->isKind(ParseNodeKind::New)
                ? builder.newExpression(callee, args, &node->pn_pos, dst)
                : builder.callExpression(callee, args, &node->pn_pos, dst);
       }
 
       case ParseNodeKind::Dot:
       {
         PropertyAccess* prop = &pn->as<PropertyAccess>();
+        // TODO(khyperia): Implement private field access.
         MOZ_ASSERT(prop->pn_pos.encloses(prop->expression().pn_pos));
 
         RootedValue expr(cx);
         RootedValue propname(cx);
         RootedAtom pnAtom(cx, prop->key().atom());
 
         if (prop->isSuper()) {
             if (!builder.super(&prop->expression().pn_pos, &expr)) {
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -116,24 +116,32 @@ CreateScriptSourceObject(JSContext* cx, 
  *
  * This returns true even if str is a keyword like "if".
  *
  * Defined in TokenStream.cpp.
  */
 bool
 IsIdentifier(JSLinearString* str);
 
+bool
+IsIdentifierNameOrPrivateName(JSLinearString* str);
+
 /*
  * As above, but taking chars + length.
  */
 bool
-IsIdentifier(const char* chars, size_t length);
+IsIdentifier(const Latin1Char* chars, size_t length);
 bool
 IsIdentifier(const char16_t* chars, size_t length);
 
+bool
+IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length);
+bool
+IsIdentifierNameOrPrivateName(const char16_t* chars, size_t length);
+
 /* True if str is a keyword. Defined in TokenStream.cpp. */
 bool
 IsKeyword(JSLinearString* str);
 
 /* Trace all GC things reachable from parser. Defined in Parser.cpp. */
 void
 TraceParser(JSTracer* trc, JS::AutoGCRooter* parser);
 
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -1038,16 +1038,17 @@ BytecodeEmitter::checkSideEffects(ParseN
       case ParseNodeKind::RawUndefined:
       case ParseNodeKind::Elision:
       case ParseNodeKind::Generator:
         MOZ_ASSERT(pn->is<NullaryNode>());
         *answer = false;
         return true;
 
       case ParseNodeKind::ObjectPropertyName:
+      case ParseNodeKind::PrivateName: // no side effects, unlike ParseNodeKind::Name
       case ParseNodeKind::String:
       case ParseNodeKind::TemplateString:
         MOZ_ASSERT(pn->is<NameNode>());
         *answer = false;
         return true;
 
       case ParseNodeKind::RegExp:
         MOZ_ASSERT(pn->is<RegExpLiteral>());
@@ -1481,27 +1482,28 @@ BytecodeEmitter::checkSideEffects(ParseN
       case ParseNodeKind::ParamsBody:
         *answer = true;
         return true;
 
       case ParseNodeKind::ForIn:           // by ParseNodeKind::For
       case ParseNodeKind::ForOf:           // by ParseNodeKind::For
       case ParseNodeKind::ForHead:         // by ParseNodeKind::For
       case ParseNodeKind::ClassMethod:     // by ParseNodeKind::Class
+      case ParseNodeKind::ClassField:      // by ParseNodeKind::Class
       case ParseNodeKind::ClassNames:      // by ParseNodeKind::Class
-      case ParseNodeKind::ClassMethodList: // by ParseNodeKind::Class
-      case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import
+      case ParseNodeKind::ClassMemberList: // by ParseNodeKind::Class
+      case ParseNodeKind::ImportSpecList:  // by ParseNodeKind::Import
       case ParseNodeKind::ImportSpec:      // by ParseNodeKind::Import
-      case ParseNodeKind::ExportBatchSpec:// by ParseNodeKind::Export
-      case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export
+      case ParseNodeKind::ExportBatchSpec: // by ParseNodeKind::Export
+      case ParseNodeKind::ExportSpecList:  // by ParseNodeKind::Export
       case ParseNodeKind::ExportSpec:      // by ParseNodeKind::Export
-      case ParseNodeKind::CallSiteObj:      // by ParseNodeKind::TaggedTemplate
-      case ParseNodeKind::PosHolder:        // by ParseNodeKind::NewTarget
-      case ParseNodeKind::SuperBase:        // by ParseNodeKind::Elem and others
-      case ParseNodeKind::PropertyName:     // by ParseNodeKind::Dot
+      case ParseNodeKind::CallSiteObj:     // by ParseNodeKind::TaggedTemplate
+      case ParseNodeKind::PosHolder:       // by ParseNodeKind::NewTarget
+      case ParseNodeKind::SuperBase:       // by ParseNodeKind::Elem and others
+      case ParseNodeKind::PropertyName:    // by ParseNodeKind::Dot
         MOZ_CRASH("handled by parent nodes");
 
       case ParseNodeKind::Limit: // invalid sentinel value
         MOZ_CRASH("invalid node kind");
     }
 
     MOZ_CRASH("invalid, unenumerated ParseNodeKind value encountered in "
               "BytecodeEmitter::checkSideEffects");
@@ -1784,16 +1786,18 @@ BytecodeEmitter::emitPropLHS(PropertyAcc
     }
 
     // pndown is a primary expression, not a dotted property reference.
     if (!emitTree(pndown)) {
         return false;
     }
 
     while (true) {
+        // TODO(khyperia): Implement private field access.
+
         // Walk back up the list, emitting annotated name ops.
         if (!emitAtomOp(pndot->key().atom(), JSOP_GETPROP)) {
             return false;
         }
 
         // Reverse the pndot->expression() link again.
         pnup = pndot->maybeExpression();
         pndot->setExpression(pndown);
@@ -1805,16 +1809,17 @@ BytecodeEmitter::emitPropLHS(PropertyAcc
     }
     return true;
 }
 
 bool
 BytecodeEmitter::emitPropIncDec(UnaryNode* incDec)
 {
     PropertyAccess* prop = &incDec->kid()->as<PropertyAccess>();
+    // TODO(khyperia): Implement private field access.
     bool isSuper = prop->isSuper();
     ParseNodeKind kind = incDec->getKind();
     PropOpEmitter poe(this,
                       kind == ParseNodeKind::PostIncrement ? PropOpEmitter::Kind::PostIncrement
                       : kind == ParseNodeKind::PreIncrement ? PropOpEmitter::Kind::PreIncrement
                       : kind == ParseNodeKind::PostDecrement ? PropOpEmitter::Kind::PostDecrement
                       : PropOpEmitter::Kind::PreDecrement,
                       isSuper
@@ -2619,16 +2624,17 @@ BytecodeEmitter::emitSetOrInitializeDest
 
           case ParseNodeKind::Dot: {
             // The reference is already pushed by emitDestructuringLHSRef.
             //                                            // [Super]
             //                                            // THIS SUPERBASE VAL
             //                                            // [Other]
             //                                            // OBJ VAL
             PropertyAccess* prop = &target->as<PropertyAccess>();
+            // TODO(khyperia): Implement private field access.
             bool isSuper = prop->isSuper();
             PropOpEmitter poe(this,
                               PropOpEmitter::Kind::SimpleAssignment,
                               isSuper
                               ? PropOpEmitter::ObjKind::Super
                               : PropOpEmitter::ObjKind::Other);
             if (!poe.skipObjAndRhs()) {
                 return false;
@@ -3988,16 +3994,17 @@ BytecodeEmitter::emitAssignment(ParseNod
         MOZ_ASSERT(0);
     }
 
     if (isCompound) {
         MOZ_ASSERT(rhs);
         switch (lhs->getKind()) {
           case ParseNodeKind::Dot: {
             PropertyAccess* prop = &lhs->as<PropertyAccess>();
+            // TODO(khyperia): Implement private field access.
             if (!poe->emitGet(prop->key().atom())) {      // [Super]
                 //                                        // THIS SUPERBASE PROP
                 //                                        // [Other]
                 //                                        // OBJ PROP
                 return false;
             }
             break;
           }
@@ -4061,16 +4068,17 @@ BytecodeEmitter::emitAssignment(ParseNod
             return false;
         }
     }
 
     /* Finally, emit the specialized assignment bytecode. */
     switch (lhs->getKind()) {
       case ParseNodeKind::Dot: {
         PropertyAccess* prop = &lhs->as<PropertyAccess>();
+        // TODO(khyperia): Implement private field access.
         if (!poe->emitAssignment(prop->key().atom())) {   // VAL
             return false;
         }
 
         poe.reset();
         break;
       }
       case ParseNodeKind::Call:
@@ -6387,16 +6395,17 @@ BytecodeEmitter::emitDeleteName(UnaryNod
 }
 
 bool
 BytecodeEmitter::emitDeleteProperty(UnaryNode* deleteNode)
 {
     MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteProp));
 
     PropertyAccess* propExpr = &deleteNode->kid()->as<PropertyAccess>();
+    // TODO(khyperia): Implement private field access.
     PropOpEmitter poe(this,
                       PropOpEmitter::Kind::Delete,
                       propExpr->as<PropertyAccess>().isSuper()
                       ? PropOpEmitter::ObjKind::Super
                       : PropOpEmitter::ObjKind::Other);
     if (propExpr->isSuper()) {
         // The expression |delete super.foo;| has to evaluate |super.foo|,
         // which could throw if |this| hasn't yet been set by a |super(...)|
@@ -6788,16 +6797,17 @@ BytecodeEmitter::emitCalleeAndThis(Parse
       case ParseNodeKind::Name:
         if (!cone.emitNameCallee(callee->as<NameNode>().name())) {
             return false;                                 // CALLEE THIS
         }
         break;
       case ParseNodeKind::Dot: {
         MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
         PropertyAccess* prop = &callee->as<PropertyAccess>();
+        // TODO(khyperia): Implement private field access.
         bool isSuper = prop->isSuper();
 
         PropOpEmitter& poe = cone.prepareForPropCallee(isSuper);
         if (!poe.prepareForObj()) {
             return false;
         }
         if (isSuper) {
             UnaryNode* base = &prop->expression().as<UnaryNode>();
@@ -7013,17 +7023,19 @@ BytecodeEmitter::emitCallOrNew(BinaryNod
 
             // Check if this member is a simple chain of simple chain of
             // property accesses, e.g. x.y.z, this.x.y, super.x.y
             bool simpleDotChain = false;
             for (ParseNode* cur = calleeNode;
                  cur->isKind(ParseNodeKind::Dot);
                  cur = &cur->as<PropertyAccess>().expression())
             {
-                ParseNode* left = &cur->as<PropertyAccess>().expression();
+                PropertyAccess* prop = &cur->as<PropertyAccess>();
+                ParseNode* left = &prop->expression();
+                // TODO(khyperia): Implement private field access.
                 if (left->isKind(ParseNodeKind::Name) || left->isKind(ParseNodeKind::This) ||
                     left->isKind(ParseNodeKind::SuperBase))
                 {
                     simpleDotChain = true;
                 }
             }
 
             if (!simpleDotChain) {
@@ -7297,16 +7309,20 @@ BytecodeEmitter::emitConditionalExpressi
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, PropListType type)
 {
     for (ParseNode* propdef : obj->contents()) {
+        if (propdef->is<ClassField>()) {
+            // TODO(khyperia): Implement private field access.
+            return false;
+        }
         if (!updateSourceCoordNotes(propdef->pn_pos.begin)) {
             return false;
         }
 
         // Handle __proto__: v specially because *only* this form, and no other
         // involving "__proto__", performs [[Prototype]] mutation.
         if (propdef->isKind(ParseNodeKind::MutateProto)) {
             MOZ_ASSERT(type == ObjectLiteral);
@@ -8127,19 +8143,23 @@ BytecodeEmitter::emitLexicalInitializati
 
 // This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
 // (BindingClassDeclarationEvaluation).
 bool
 BytecodeEmitter::emitClass(ClassNode* classNode)
 {
     ClassNames* names = classNode->names();
     ParseNode* heritageExpression = classNode->heritage();
-    ListNode* classMethods = classNode->methodList();
+    ListNode* classMembers = classNode->memberList();
     CodeNode* constructor = nullptr;
-    for (ParseNode* mn : classMethods->contents()) {
+    for (ParseNode* mn : classMembers->contents()) {
+        if (mn->is<ClassField>()) {
+            // TODO(khyperia): Implement private field access.
+            return false;
+        }
         ClassMethod& method = mn->as<ClassMethod>();
         ParseNode& methodName = method.name();
         if (!method.isStatic() &&
             (methodName.isKind(ParseNodeKind::ObjectPropertyName) ||
              methodName.isKind(ParseNodeKind::String)) &&
             methodName.as<NameNode>().atom() == cx->names().constructor)
         {
             constructor = &method.method();
@@ -8316,17 +8336,17 @@ BytecodeEmitter::emitClass(ClassNode* cl
     if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) {  // ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR
         return false;
     }
     if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) {  // ... CONSTRUCTOR HOMEOBJ
         return false;
     }
 
     RootedPlainObject obj(cx);
-    if (!emitPropertyList(classMethods, &obj, ClassBody)) {     // ... CONSTRUCTOR HOMEOBJ
+    if (!emitPropertyList(classMembers, &obj, ClassBody)) {     // ... CONSTRUCTOR HOMEOBJ
         return false;
     }
 
     if (!emit1(JSOP_POP)) {                                     // ... CONSTRUCTOR
         return false;
     }
 
     if (names) {
@@ -8688,16 +8708,17 @@ BytecodeEmitter::emitTree(ParseNode* pn,
       case ParseNodeKind::DeleteExpr:
         if (!emitDeleteExpression(&pn->as<UnaryNode>())) {
             return false;
         }
         break;
 
       case ParseNodeKind::Dot: {
         PropertyAccess* prop = &pn->as<PropertyAccess>();
+        // TODO(khyperia): Implement private field access.
         bool isSuper = prop->isSuper();
         PropOpEmitter poe(this,
                           PropOpEmitter::Kind::Get,
                           isSuper
                           ? PropOpEmitter::ObjKind::Super
                           : PropOpEmitter::ObjKind::Other);
         if (!poe.prepareForObj()) {
             return false;
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -360,16 +360,17 @@ ContainsHoistedDeclaration(JSContext* cx
       case ParseNodeKind::Array:
       case ParseNodeKind::Object:
       case ParseNodeKind::PropertyName:
       case ParseNodeKind::Dot:
       case ParseNodeKind::Elem:
       case ParseNodeKind::Arguments:
       case ParseNodeKind::Call:
       case ParseNodeKind::Name:
+      case ParseNodeKind::PrivateName:
       case ParseNodeKind::TemplateString:
       case ParseNodeKind::TemplateStringList:
       case ParseNodeKind::TaggedTemplate:
       case ParseNodeKind::CallSiteObj:
       case ParseNodeKind::String:
       case ParseNodeKind::RegExp:
       case ParseNodeKind::True:
       case ParseNodeKind::False:
@@ -381,17 +382,18 @@ ContainsHoistedDeclaration(JSContext* cx
       case ParseNodeKind::New:
       case ParseNodeKind::Generator:
       case ParseNodeKind::ParamsBody:
       case ParseNodeKind::Catch:
       case ParseNodeKind::ForIn:
       case ParseNodeKind::ForOf:
       case ParseNodeKind::ForHead:
       case ParseNodeKind::ClassMethod:
-      case ParseNodeKind::ClassMethodList:
+      case ParseNodeKind::ClassField:
+      case ParseNodeKind::ClassMemberList:
       case ParseNodeKind::ClassNames:
       case ParseNodeKind::NewTarget:
       case ParseNodeKind::ImportMeta:
       case ParseNodeKind::PosHolder:
       case ParseNodeKind::SuperCall:
       case ParseNodeKind::SuperBase:
       case ParseNodeKind::SetThis:
         MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on "
@@ -1626,16 +1628,17 @@ Fold(JSContext* cx, ParseNode** pnp, Per
         MOZ_ASSERT(pn->is<BreakStatement>());
         return true;
 
       case ParseNodeKind::Continue:
         MOZ_ASSERT(pn->is<ContinueStatement>());
         return true;
 
       case ParseNodeKind::ObjectPropertyName:
+      case ParseNodeKind::PrivateName:
       case ParseNodeKind::String:
       case ParseNodeKind::TemplateString:
         MOZ_ASSERT(pn->is<NameNode>());
         return true;
 
       case ParseNodeKind::RegExp:
         MOZ_ASSERT(pn->is<RegExpLiteral>());
         return true;
@@ -1754,17 +1757,17 @@ Fold(JSContext* cx, ParseNode** pnp, Per
       case ParseNodeKind::Gt:
       case ParseNodeKind::Ge:
       case ParseNodeKind::InstanceOf:
       case ParseNodeKind::In:
       case ParseNodeKind::Comma:
       case ParseNodeKind::Array:
       case ParseNodeKind::Object:
       case ParseNodeKind::StatementList:
-      case ParseNodeKind::ClassMethodList:
+      case ParseNodeKind::ClassMemberList:
       case ParseNodeKind::TemplateStringList:
       case ParseNodeKind::Var:
       case ParseNodeKind::Const:
       case ParseNodeKind::Let:
       case ParseNodeKind::ParamsBody:
       case ParseNodeKind::CallSiteObj:
       case ParseNodeKind::ExportSpecList:
       case ParseNodeKind::ImportSpecList:
@@ -1844,16 +1847,27 @@ Fold(JSContext* cx, ParseNode** pnp, Per
       case ParseNodeKind::ImportSpec:
       case ParseNodeKind::ExportSpec:
       case ParseNodeKind::SetThis: {
         BinaryNode* node = &pn->as<BinaryNode>();
         return Fold(cx, node->unsafeLeftReference(), parser) &&
                Fold(cx, node->unsafeRightReference(), parser);
       }
 
+      case ParseNodeKind::ClassField: {
+        ClassField* node = &pn->as<ClassField>();
+        if (node->hasInitializer()) {
+            if (!Fold(cx, node->unsafeInitializerReference(), parser)) {
+                return false;
+            }
+        }
+
+        return true;
+      }
+
       case ParseNodeKind::NewTarget:
       case ParseNodeKind::ImportMeta: {
 #ifdef DEBUG
         BinaryNode* node = &pn->as<BinaryNode>();
         MOZ_ASSERT(node->left()->isKind(ParseNodeKind::PosHolder));
         MOZ_ASSERT(node->right()->isKind(ParseNodeKind::PosHolder));
 #endif
         return true;
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -259,40 +259,40 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
 
     MOZ_MUST_USE bool addElision(ListNodeType literal, const TokenPos& pos) {
         MOZ_ASSERT(literal->isKind(ParseNodeKind::Array));
 
         NullaryNode* elision = new_<NullaryNode>(ParseNodeKind::Elision, pos);
         if (!elision) {
             return false;
         }
-        addList(/* list = */ literal, /* child = */ elision);
+        addList(/* list = */ literal, /* kid = */ elision);
         literal->setHasArrayHoleOrSpread();
         literal->setHasNonConstInitializer();
         return true;
     }
 
     MOZ_MUST_USE bool addSpreadElement(ListNodeType literal, uint32_t begin, Node inner) {
         MOZ_ASSERT(literal->isKind(ParseNodeKind::Array));
 
         ParseNode* spread = newSpread(begin, inner);
         if (!spread) {
             return false;
         }
-        addList(/* list = */ literal, /* child = */ spread);
+        addList(/* list = */ literal, /* kid = */ spread);
         literal->setHasArrayHoleOrSpread();
         literal->setHasNonConstInitializer();
         return true;
     }
 
     void addArrayElement(ListNodeType literal, Node element) {
         if (!element->isConstant()) {
             literal->setHasNonConstInitializer();
         }
-        addList(/* list = */ literal, /* child = */ element);
+        addList(/* list = */ literal, /* kid = */ element);
     }
 
     BinaryNodeType newCall(Node callee, Node args) {
         return new_<BinaryNode>(ParseNodeKind::Call, JSOP_CALL, callee, args);
     }
 
     ListNodeType newArguments(const TokenPos& pos) {
         return new_<ListNode>(ParseNodeKind::Arguments, JSOP_NOP, pos);
@@ -305,21 +305,21 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
     BinaryNodeType newTaggedTemplate(Node tag, Node args) {
         return new_<BinaryNode>(ParseNodeKind::TaggedTemplate, JSOP_CALL, tag, args);
     }
 
     ListNodeType newObjectLiteral(uint32_t begin) {
         return new_<ListNode>(ParseNodeKind::Object, TokenPos(begin, begin + 1));
     }
 
-    ClassNodeType newClass(Node name, Node heritage, Node methodBlock, const TokenPos& pos) {
-        return new_<ClassNode>(name, heritage, methodBlock, pos);
+    ClassNodeType newClass(Node name, Node heritage, Node memberBlock, const TokenPos& pos) {
+        return new_<ClassNode>(name, heritage, memberBlock, pos);
     }
-    ListNodeType newClassMethodList(uint32_t begin) {
-        return new_<ListNode>(ParseNodeKind::ClassMethodList, TokenPos(begin, begin + 1));
+    ListNodeType newClassMemberList(uint32_t begin) {
+        return new_<ListNode>(ParseNodeKind::ClassMemberList, TokenPos(begin, begin + 1));
     }
     ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) {
         return new_<ClassNames>(outer, inner, pos);
     }
     BinaryNodeType newNewTarget(NullaryNodeType newHolder, NullaryNodeType targetHolder) {
         return new_<BinaryNode>(ParseNodeKind::NewTarget, JSOP_NOP, newHolder, targetHolder);
     }
     NullaryNodeType newPosHolder(const TokenPos& pos) {
@@ -334,17 +334,17 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
         // Object literals with mutated [[Prototype]] are non-constant so that
         // singleton objects will have Object.prototype as their [[Prototype]].
         literal->setHasNonConstInitializer();
 
         UnaryNode* mutation = newUnary(ParseNodeKind::MutateProto, begin, expr);
         if (!mutation) {
             return false;
         }
-        addList(/* list = */ literal, /* child = */ mutation);
+        addList(/* list = */ literal, /* kid = */ mutation);
         return true;
     }
 
     BinaryNodeType newPropertyDefinition(Node key, Node val) {
         MOZ_ASSERT(isUsableAsObjectPropertyName(key));
         checkAndSetIsDirectRHSAnonFunction(val);
         return newBinary(ParseNodeKind::Colon, key, val, JSOP_INITPROP);
     }
@@ -352,17 +352,17 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
     void addPropertyDefinition(ListNodeType literal, BinaryNodeType propdef) {
         MOZ_ASSERT(literal->isKind(ParseNodeKind::Object));
         MOZ_ASSERT(propdef->isKind(ParseNodeKind::Colon));
 
         if (!propdef->right()->isConstant()) {
             literal->setHasNonConstInitializer();
         }
 
-        addList(/* list = */ literal, /* child = */ propdef);
+        addList(/* list = */ literal, /* kid = */ propdef);
     }
 
     MOZ_MUST_USE bool addPropertyDefinition(ListNodeType literal, Node key, Node val) {
         BinaryNode* propdef = newPropertyDefinition(key, val);
         if (!propdef) {
             return false;
         }
         addPropertyDefinition(literal, propdef);
@@ -375,63 +375,78 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
         MOZ_ASSERT(expr->isKind(ParseNodeKind::Name));
         MOZ_ASSERT(name->atom() == expr->atom());
 
         literal->setHasNonConstInitializer();
         BinaryNode* propdef = newBinary(ParseNodeKind::Shorthand, name, expr, JSOP_INITPROP);
         if (!propdef) {
             return false;
         }
-        addList(/* list = */ literal, /* child = */ propdef);
+        addList(/* list = */ literal, /* kid = */ propdef);
         return true;
     }
 
     MOZ_MUST_USE bool addSpreadProperty(ListNodeType literal, uint32_t begin, Node inner) {
         MOZ_ASSERT(literal->isKind(ParseNodeKind::Object));
 
         literal->setHasNonConstInitializer();
         ParseNode* spread = newSpread(begin, inner);
         if (!spread) {
             return false;
         }
-        addList(/* list = */ literal, /* child = */ spread);
+        addList(/* list = */ literal, /* kid = */ spread);
         return true;
     }
 
     MOZ_MUST_USE bool addObjectMethodDefinition(ListNodeType literal, Node key,
                                                 CodeNodeType funNode, AccessorType atype)
     {
         literal->setHasNonConstInitializer();
 
         checkAndSetIsDirectRHSAnonFunction(funNode);
 
         ParseNode* propdef = newObjectMethodOrPropertyDefinition(key, funNode, atype);
         if (!propdef) {
             return false;
         }
 
-        addList(/* list = */ literal, /* child = */ propdef);
+        addList(/* list = */ literal, /* kid = */ propdef);
         return true;
     }
 
-    MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType methodList, Node key,
+    MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType memberList, Node key,
                                                CodeNodeType funNode, AccessorType atype,
                                                bool isStatic)
     {
-        MOZ_ASSERT(methodList->isKind(ParseNodeKind::ClassMethodList));
+        MOZ_ASSERT(memberList->isKind(ParseNodeKind::ClassMemberList));
         MOZ_ASSERT(isUsableAsObjectPropertyName(key));
 
         checkAndSetIsDirectRHSAnonFunction(funNode);
 
         ClassMethod* classMethod = new_<ClassMethod>(key, funNode, AccessorTypeToJSOp(atype),
                                                      isStatic);
         if (!classMethod) {
             return false;
         }
-        addList(/* list = */ methodList, /* child = */ classMethod);
+        addList(/* list = */ memberList, /* kid = */ classMethod);
+        return true;
+    }
+
+    MOZ_MUST_USE bool addClassFieldDefinition(ListNodeType memberList,
+                                              Node name, Node initializer)
+    {
+        MOZ_ASSERT(memberList->isKind(ParseNodeKind::ClassMemberList));
+        MOZ_ASSERT(isUsableAsObjectPropertyName(name));
+
+        ClassField* classField = new_<ClassField>(name, initializer);
+
+        if (!classField) {
+            return false;
+        }
+        addList(/* list = */ memberList, /* kid = */ classField);
         return true;
     }
 
     UnaryNodeType newInitialYieldExpression(uint32_t begin, Node gen) {
         TokenPos pos(begin, begin + 1);
         return new_<UnaryNode>(ParseNodeKind::InitialYield, pos, gen);
     }
 
@@ -461,34 +476,34 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
             stmt = stmt->as<LabeledStatement>().statement();
         }
         return stmt->isKind(ParseNodeKind::Function);
     }
 
     void addStatementToList(ListNodeType list, Node stmt) {
         MOZ_ASSERT(list->isKind(ParseNodeKind::StatementList));
 
-        addList(/* list = */ list, /* child = */ stmt);
+        addList(/* list = */ list, /* kid = */ stmt);
 
         if (isFunctionStmt(stmt)) {
             // Notify the emitter that the block contains body-level function
             // definitions that should be processed before the rest of nodes.
             list->setHasTopLevelFunctionDeclarations();
         }
     }
 
     void setListEndPosition(ListNodeType list, const TokenPos& pos) {
         MOZ_ASSERT(list->isKind(ParseNodeKind::StatementList));
         list->pn_pos.end = pos.end;
     }
 
     void addCaseStatementToList(ListNodeType list, CaseClauseType caseClause) {
         MOZ_ASSERT(list->isKind(ParseNodeKind::StatementList));
 
-        addList(/* list = */ list, /* child = */ caseClause);
+        addList(/* list = */ list, /* kid = */ caseClause);
 
         if (caseClause->statementList()->hasTopLevelFunctionDeclarations()) {
             list->setHasTopLevelFunctionDeclarations();
         }
     }
 
     MOZ_MUST_USE bool prependInitialYield(ListNodeType stmtList, Node genName) {
         MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList));
@@ -730,21 +745,21 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
         funNode->setBody(paramsBody);
     }
     void setFunctionBox(CodeNodeType funNode, FunctionBox* funbox) {
         MOZ_ASSERT(funNode->isKind(ParseNodeKind::Function));
         funNode->setFunbox(funbox);
         funbox->functionNode = funNode;
     }
     void addFunctionFormalParameter(CodeNodeType funNode, Node argpn) {
-        addList(/* list = */ funNode->body(), /* child = */ argpn);
+        addList(/* list = */ funNode->body(), /* kid = */ argpn);
     }
     void setFunctionBody(CodeNodeType funNode, LexicalScopeNodeType body) {
         MOZ_ASSERT(funNode->body()->isKind(ParseNodeKind::ParamsBody));
-        addList(/* list = */ funNode->body(), /* child = */ body);
+        addList(/* list = */ funNode->body(), /* kid = */ body);
     }
 
     CodeNodeType newModule(const TokenPos& pos) {
         return new_<CodeNode>(ParseNodeKind::Module, JSOP_NOP, pos);
     }
 
     LexicalScopeNodeType newLexicalScope(LexicalScope::Data* bindings, Node body) {
         return new_<LexicalScopeNode>(bindings, body);
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -85,16 +85,17 @@ class NameResolver
             }
             if (!*foundName) {
                 return true;
             }
             return appendPropertyReference(prop->right()->as<NameNode>().atom());
           }
 
           case ParseNodeKind::Name:
+          case ParseNodeKind::PrivateName:
             *foundName = true;
             return buf->append(n->as<NameNode>().atom());
 
           case ParseNodeKind::This:
             *foundName = true;
             return buf->append("this");
 
           case ParseNodeKind::Elem: {
@@ -143,16 +144,17 @@ class NameResolver
 
         for (int pos = nparents - 1; pos >= 0; pos--) {
             ParseNode* cur = parents[pos];
             if (cur->is<AssignmentNode>()) {
                 return cur;
             }
 
             switch (cur->getKind()) {
+              case ParseNodeKind::PrivateName:
               case ParseNodeKind::Name:     return cur;  /* found the initialized declaration */
               case ParseNodeKind::This:     return cur;  /* Setting a property of 'this'. */
               case ParseNodeKind::Function: return nullptr; /* won't find an assignment or declaration */
 
               case ParseNodeKind::Return:
                 /*
                  * Normally the relevant parent of a node is its direct parent, but
                  * sometimes with code like:
@@ -442,16 +444,17 @@ class NameResolver
             MOZ_ASSERT(cur->is<BreakStatement>());
             break;
 
           case ParseNodeKind::Continue:
             MOZ_ASSERT(cur->is<ContinueStatement>());
             break;
 
           case ParseNodeKind::ObjectPropertyName:
+          case ParseNodeKind::PrivateName: // TODO(khyperia): Implement private field access.
           case ParseNodeKind::String:
           case ParseNodeKind::TemplateString:
             MOZ_ASSERT(cur->is<NameNode>());
             break;
 
           case ParseNodeKind::RegExp:
             MOZ_ASSERT(cur->is<RegExpLiteral>());
             break;
@@ -535,16 +538,31 @@ class NameResolver
                 return false;
             }
             if (!resolve(node->right(), prefix)) {
                 return false;
             }
             break;
           }
 
+          case ParseNodeKind::ClassField: {
+            ClassField* node = &cur->as<ClassField>();
+            if (!resolve(&node->name(), prefix)) {
+                return false;
+            }
+
+            if (node->hasInitializer()) {
+                if (!resolve(&node->initializer(), prefix)) {
+                    return false;
+                }
+            }
+
+            break;
+          }
+
           case ParseNodeKind::Elem: {
             PropertyByValue* elem = &cur->as<PropertyByValue>();
             if (!elem->isSuper() && !resolve(&elem->expression(), prefix)) {
                 return false;
             }
             if (!resolve(&elem->key(), prefix)) {
                 return false;
             }
@@ -696,17 +714,17 @@ class NameResolver
                 MOZ_ASSERT(!innerBinding->initializer());
             }
 #endif
             if (ParseNode* heritage = classNode->heritage()) {
                 if (!resolve(heritage, prefix)) {
                     return false;
                 }
             }
-            if (!resolve(classNode->methodList(), prefix)) {
+            if (!resolve(classNode->memberList(), prefix)) {
                 return false;
             }
             break;
           }
 
           // The condition and consequent are non-optional, but the alternative
           // might be omitted.
           case ParseNodeKind::If: {
@@ -804,17 +822,17 @@ class NameResolver
             for (ParseNode* element : cur->as<ListNode>().contents()) {
                 if (!resolve(element, prefix)) {
                     return false;
                 }
             }
             break;
 
           case ParseNodeKind::Object:
-          case ParseNodeKind::ClassMethodList:
+          case ParseNodeKind::ClassMemberList:
             for (ParseNode* element : cur->as<ListNode>().contents()) {
                 if (!resolve(element, prefix)) {
                     return false;
                 }
             }
             break;
 
           // A template string list's contents alternate raw template string
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -169,16 +169,19 @@ ParseNode::dump(GenericPrinter& out, int
         as<CodeNode>().dump(out, indent);
         return;
       case PN_LIST:
         as<ListNode>().dump(out, indent);
         return;
       case PN_NAME:
         as<NameNode>().dump(out, indent);
         return;
+      case PN_FIELD:
+        as<ClassField>().dump(out, indent);
+        return;
       case PN_NUMBER:
         as<NumericLiteral>().dump(out, indent);
         return;
       case PN_REGEXP:
         as<RegExpLiteral>().dump(out, indent);
         return;
       case PN_LOOP:
         as<LoopControlStatement>().dump(out, indent);
@@ -342,16 +345,17 @@ NameNode::dump(GenericPrinter& out, int 
     switch (getKind()) {
       case ParseNodeKind::String:
       case ParseNodeKind::TemplateString:
       case ParseNodeKind::ObjectPropertyName:
         atom()->dumpCharsNoNewline(out);
         return;
 
       case ParseNodeKind::Name:
+      case ParseNodeKind::PrivateName: // atom() already includes the '#', no need to specially include it.
       case ParseNodeKind::PropertyName:
         if (!atom()) {
             out.put("#<null name>");
         } else if (getOp() == JSOP_GETARG && atom()->length() == 0) {
             // Dump destructuring parameter.
             static const char ZeroLengthPrefix[] = "(#<zero-length name> ";
             constexpr size_t ZeroLengthPrefixLength = ArrayLength(ZeroLengthPrefix) - 1;
             out.put(ZeroLengthPrefix);
@@ -384,16 +388,31 @@ NameNode::dump(GenericPrinter& out, int 
         DumpParseTree(initializer(), out, indent);
         out.printf(")");
         return;
       }
     }
 }
 
 void
+ClassField::dump(GenericPrinter& out, int indent)
+{
+    out.printf("(");
+    if (hasInitializer()) {
+        indent += 2;
+    }
+    DumpParseTree(&name(), out, indent);
+    if (hasInitializer()) {
+        IndentNewLine(out, indent);
+        DumpParseTree(&initializer(), out, indent);
+    }
+    out.printf(")");
+}
+
+void
 LexicalScopeNode::dump(GenericPrinter& out, int indent)
 {
     const char* name = parseNodeNames[size_t(getKind())];
     out.printf("(%s [", name);
     int nameIndent = indent + strlen(name) + 3;
     if (!isEmptyScope()) {
         LexicalScope::Data* bindings = scopeBindings();
         for (uint32_t i = 0; i < bindings->length; i++) {
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -63,16 +63,17 @@ class ObjectBox;
     F(Elision, PN_NULLARY) \
     F(StatementList, PN_LIST) \
     F(Label, PN_NAME) \
     F(Object, PN_LIST) \
     F(Call, PN_BINARY) \
     F(Arguments, PN_LIST) \
     F(Name, PN_NAME) \
     F(ObjectPropertyName, PN_NAME) \
+    F(PrivateName, PN_NAME) \
     F(ComputedName, PN_UNARY) \
     F(Number, PN_NUMBER) \
     F(String, PN_NAME) \
     F(TemplateStringList, PN_LIST) \
     F(TemplateString, PN_NAME) \
     F(TaggedTemplate, PN_BINARY) \
     F(CallSiteObj, PN_LIST) \
     F(RegExp, PN_REGEXP) \
@@ -123,17 +124,18 @@ class ObjectBox;
     F(ForIn, PN_TERNARY) \
     F(ForOf, PN_TERNARY) \
     F(ForHead, PN_TERNARY) \
     F(ParamsBody, PN_LIST) \
     F(Spread, PN_UNARY) \
     F(MutateProto, PN_UNARY) \
     F(Class, PN_TERNARY) \
     F(ClassMethod, PN_BINARY) \
-    F(ClassMethodList, PN_LIST) \
+    F(ClassField, PN_FIELD) \
+    F(ClassMemberList, PN_LIST) \
     F(ClassNames, PN_BINARY) \
     F(NewTarget, PN_BINARY) \
     F(PosHolder, PN_NULLARY) \
     F(SuperBase, PN_UNARY) \
     F(SuperCall, PN_BINARY) \
     F(SetThis, PN_BINARY) \
     F(ImportMeta, PN_BINARY) \
     F(CallImport, PN_BINARY) \
@@ -248,25 +250,25 @@ IsTypeofKind(ParseNodeKind kind)
  *           * Return for expression closure
  *   count: number of formal parameters + 1
  * Spread (UnaryNode)
  *   kid: expression being spread
  * Class (ClassNode)
  *   kid1: ClassNames for class name. can be null for anonymous class.
  *   kid2: expression after `extends`. null if no expression
  *   kid3: either of
- *           * ClassMethodList, if anonymous class
- *           * LexicalScopeNode which contains ClassMethodList as scopeBody,
+ *           * ClassMemberList, if anonymous class
+ *           * LexicalScopeNode which contains ClassMemberList as scopeBody,
  *             if named class
  * ClassNames (ClassNames)
  *   left: Name node for outer binding, or null if the class is an expression
  *         that doesn't create an outer binding
  *   right: Name node for inner binding
- * ClassMethodList (ListNode)
- *   head: list of N ClassMethod nodes
+ * ClassMemberList (ListNode)
+ *   head: list of N ClassMethod or ClassField nodes
  *   count: N >= 0
  * ClassMethod (ClassMethod)
  *   name: propertyName
  *   method: methodDefinition
  * Module (CodeNode)
  *   funbox: ?
  *   body: ?
  *
@@ -515,28 +517,30 @@ enum ParseNodeArity
 {
     PN_NULLARY,                         /* 0 kids */
     PN_UNARY,                           /* one kid, plus a couple of scalars */
     PN_BINARY,                          /* two kids, plus a couple of scalars */
     PN_TERNARY,                         /* three kids */
     PN_CODE,                            /* module or function definition node */
     PN_LIST,                            /* generic singly linked list */
     PN_NAME,                            /* name, label, string */
+    PN_FIELD,                           /* field name, optional initializer */
     PN_NUMBER,                          /* numeric literal */
     PN_REGEXP,                          /* regexp literal */
     PN_LOOP,                            /* loop control (break/continue) */
     PN_SCOPE                            /* lexical scope */
 };
 
 // FIXME: Remove `*Type` (bug 1489008)
 #define FOR_EACH_PARSENODE_SUBCLASS(macro) \
     macro(BinaryNode, BinaryNodeType, asBinary) \
     macro(AssignmentNode, AssignmentNodeType, asAssignment) \
     macro(CaseClause, CaseClauseType, asCaseClause) \
     macro(ClassMethod, ClassMethodType, asClassMethod) \
+    macro(ClassField, ClassFieldType, asClassField) \
     macro(ClassNames, ClassNamesType, asClassNames) \
     macro(ForNode, ForNodeType, asFor) \
     macro(PropertyAccess, PropertyAccessType, asPropertyAccess) \
     macro(PropertyByValue, PropertyByValueType, asPropertyByValue) \
     macro(SwitchStatement, SwitchStatementType, asSwitchStatement) \
     \
     macro(CodeNode, CodeNodeType, asCode) \
     \
@@ -710,16 +714,22 @@ class ParseNode
           private:
             friend class NameNode;
             JSAtom*      atom;          /* lexical name or label atom */
             ParseNode*  initOrStmt;     /* var initializer, argument default,
                                          * or label statement target */
         } name;
         struct {
           private:
+            friend class ClassField;
+            ParseNode* name;
+            ParseNode* initializer;     /* field initializer - optional */
+        } field;
+        struct {
+          private:
             friend class RegExpLiteral;
             ObjectBox* objbox;
         } regexp;
         struct {
           private:
             friend class CodeNode;
             FunctionBox* funbox;        /* function object */
             ParseNode*  body;           /* module or function body */
@@ -967,17 +977,17 @@ class BinaryNode : public ParseNode
         return pn_u.binary.left;
     }
 
     ParseNode* right() const {
         return pn_u.binary.right;
     }
 
     // Methods used by FoldConstants.cpp.
-    // caller are responsible for keeping the list consistent.
+    // callers are responsible for keeping the list consistent.
     ParseNode** unsafeLeftReference() {
         return &pn_u.binary.left;
     }
 
     ParseNode** unsafeRightReference() {
         return &pn_u.binary.right;
     }
 };
@@ -1174,34 +1184,34 @@ class ListNode : public ParseNode
     MOZ_MUST_USE bool hasArrayHoleOrSpread() const {
         MOZ_ASSERT(isKind(ParseNodeKind::Array));
         return pn_u.list.xflags & hasArrayHoleOrSpreadBit;
     }
 
     MOZ_MUST_USE bool hasNonConstInitializer() const {
         MOZ_ASSERT(isKind(ParseNodeKind::Array) ||
                    isKind(ParseNodeKind::Object) ||
-                   isKind(ParseNodeKind::ClassMethodList));
+                   isKind(ParseNodeKind::ClassMemberList));
         return pn_u.list.xflags & hasNonConstInitializerBit;
     }
 
     void setHasTopLevelFunctionDeclarations() {
         MOZ_ASSERT(isKind(ParseNodeKind::StatementList));
         pn_u.list.xflags |= hasTopLevelFunctionDeclarationsBit;
     }
 
     void setHasArrayHoleOrSpread() {
         MOZ_ASSERT(isKind(ParseNodeKind::Array));
         pn_u.list.xflags |= hasArrayHoleOrSpreadBit;
     }
 
     void setHasNonConstInitializer() {
         MOZ_ASSERT(isKind(ParseNodeKind::Array) ||
                    isKind(ParseNodeKind::Object) ||
-                   isKind(ParseNodeKind::ClassMethodList));
+                   isKind(ParseNodeKind::ClassMemberList));
         pn_u.list.xflags |= hasNonConstInitializerBit;
     }
 
     /*
      * Compute a pointer to the last element in a singly-linked list. NB: list
      * must be non-empty -- this is asserted!
      */
     ParseNode* last() const {
@@ -1955,16 +1965,58 @@ class ClassMethod : public BinaryNode
         return right()->as<CodeNode>();
     }
 
     bool isStatic() const {
         return pn_u.binary.isStatic;
     }
 };
 
+class ClassField : public ParseNode
+{
+  public:
+    ClassField(ParseNode* name, ParseNode* initializer)
+      : ParseNode(ParseNodeKind::ClassField, JSOP_NOP, PN_FIELD,
+                  initializer == nullptr ? name->pn_pos : TokenPos::box(name->pn_pos, initializer->pn_pos))
+    {
+        pn_u.field.name = name;
+        pn_u.field.initializer = initializer;
+    }
+
+    static bool test(const ParseNode& node) {
+        return node.isKind(ParseNodeKind::ClassField);
+    }
+
+    ParseNode& name() const {
+        return *pn_u.field.name;
+    }
+
+    bool hasInitializer() const {
+        return pn_u.field.initializer != nullptr;
+    }
+
+    ParseNode& initializer() const {
+        return *pn_u.field.initializer;
+    }
+
+#ifdef DEBUG
+    void dump(GenericPrinter& out, int indent);
+#endif
+
+    // Methods used by FoldConstants.cpp.
+    // callers are responsible for keeping the list consistent.
+    ParseNode** unsafeNameReference() {
+        return &pn_u.field.name;
+    }
+
+    ParseNode** unsafeInitializerReference() {
+        return &pn_u.field.initializer;
+    }
+};
+
 class SwitchStatement : public BinaryNode
 {
   public:
     SwitchStatement(uint32_t begin, ParseNode* discriminant, LexicalScopeNode* lexicalForCaseList,
                     bool hasDefault)
       : BinaryNode(ParseNodeKind::Switch, JSOP_NOP,
                    TokenPos(begin, lexicalForCaseList->pn_pos.end),
                    discriminant, lexicalForCaseList)
@@ -2041,45 +2093,45 @@ class ClassNames : public BinaryNode
     NameNode* innerBinding() const {
         return &right()->as<NameNode>();
     }
 };
 
 class ClassNode : public TernaryNode
 {
   public:
-    ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* methodsOrBlock,
+    ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* membersOrBlock,
               const TokenPos& pos)
-      : TernaryNode(ParseNodeKind::Class, names, heritage, methodsOrBlock, pos)
+      : TernaryNode(ParseNodeKind::Class, names, heritage, membersOrBlock, pos)
     {
         MOZ_ASSERT_IF(names, names->is<ClassNames>());
-        MOZ_ASSERT(methodsOrBlock->is<LexicalScopeNode>() ||
-                   methodsOrBlock->isKind(ParseNodeKind::ClassMethodList));
+        MOZ_ASSERT(membersOrBlock->is<LexicalScopeNode>() ||
+                   membersOrBlock->isKind(ParseNodeKind::ClassMemberList));
     }
 
     static bool test(const ParseNode& node) {
         bool match = node.isKind(ParseNodeKind::Class);
         MOZ_ASSERT_IF(match, node.is<TernaryNode>());
         return match;
     }
 
     ClassNames* names() const {
         return kid1() ? &kid1()->as<ClassNames>() : nullptr;
     }
     ParseNode* heritage() const {
         return kid2();
     }
-    ListNode* methodList() const {
-        ParseNode* methodsOrBlock = kid3();
-        if (methodsOrBlock->isKind(ParseNodeKind::ClassMethodList)) {
-            return &methodsOrBlock->as<ListNode>();
+    ListNode* memberList() const {
+        ParseNode* membersOrBlock = kid3();
+        if (membersOrBlock->isKind(ParseNodeKind::ClassMemberList)) {
+            return &membersOrBlock->as<ListNode>();
         }
 
-        ListNode* list = &methodsOrBlock->as<LexicalScopeNode>().scopeBody()->as<ListNode>();
-        MOZ_ASSERT(list->isKind(ParseNodeKind::ClassMethodList));
+        ListNode* list = &membersOrBlock->as<LexicalScopeNode>().scopeBody()->as<ListNode>();
+        MOZ_ASSERT(list->isKind(ParseNodeKind::ClassMemberList));
         return list;
     }
     Handle<LexicalScope::Data*> scopeBindings() const {
         ParseNode* scope = kid3();
         return scope->as<LexicalScopeNode>().scopeBindings();
     }
 };
 
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -5012,17 +5012,17 @@ GeneralParser<ParseHandler, Unit>::objec
 
             if (!handler.addSpreadProperty(literal, begin, inner)) {
                 return null();
             }
         } else {
             TokenPos namePos = anyChars.nextToken().pos;
 
             PropertyType propType;
-            Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom);
+            Node propName = propertyName(yieldHandling, PropertyNameInPattern, declKind, literal, &propType, &propAtom);
             if (!propName) {
                 return null();
             }
 
             if (propType == PropertyType::Normal) {
                 // Handle e.g., |var {p: x} = o| and |var {p: x=0} = o|.
 
                 if (!tokenStream.getToken(&tt, TokenStream::Operand)) {
@@ -8022,18 +8022,18 @@ GeneralParser<ParseHandler, Unit>::class
         classHeritage = memberExpr(yieldHandling, TripledotProhibited, tt);
         if (!classHeritage) {
             return null();
         }
     }
 
     MUST_MATCH_TOKEN(TokenKind::LeftCurly, JSMSG_CURLY_BEFORE_CLASS);
 
-    ListNodeType classMethods = handler.newClassMethodList(pos().begin);
-    if (!classMethods) {
+    ListNodeType classMembers = handler.newClassMemberList(pos().begin);
+    if (!classMembers) {
         return null();
     }
 
     Maybe<DeclarationKind> declKind = Nothing();
     for (;;) {
         TokenKind tt;
         if (!tokenStream.getToken(&tt)) {
             return null();
@@ -8067,21 +8067,54 @@ GeneralParser<ParseHandler, Unit>::class
         }
 
         uint32_t nameOffset;
         if (!tokenStream.peekOffset(&nameOffset)) {
             return null();
         }
 
         PropertyType propType;
-        Node propName = propertyName(yieldHandling, declKind, classMethods, &propType, &propAtom);
+        Node propName = propertyName(yieldHandling, PropertyNameInClass, declKind, classMembers, &propType, &propAtom);
         if (!propName) {
             return null();
         }
 
+        if (propType == PropertyType::Field) {
+            if (isStatic) {
+                errorAt(nameOffset, JSMSG_BAD_METHOD_DEF);
+                return null();
+            }
+            if (!tokenStream.getToken(&tt)) {
+                return null();
+            }
+            Node initializer = null();
+            if (tt == TokenKind::Assign) {
+                initializer = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
+                if (!tokenStream.getToken(&tt)) {
+                    return null();
+                }
+            }
+
+            // TODO(khyperia): Implement ASI
+            if (tt != TokenKind::Semi) {
+                error(JSMSG_MISSING_SEMI_FIELD);
+                return null();
+            }
+
+            if (!handler.addClassFieldDefinition(classMembers, propName, initializer)) {
+                return null();
+            }
+
+            // TODO(khyperia): Change the below to `continue;` once fields are
+            // fully supported in the backend. We can't fail in BytecodeCompiler
+            // because of lazy parsing.
+            errorAt(nameOffset, JSMSG_FIELDS_NOT_SUPPORTED);
+            return null();
+        }
+
         if (propType != PropertyType::Getter && propType != PropertyType::Setter &&
             propType != PropertyType::Method && propType != PropertyType::GeneratorMethod &&
             propType != PropertyType::AsyncMethod &&
             propType != PropertyType::AsyncGeneratorMethod)
         {
             errorAt(nameOffset, JSMSG_BAD_METHOD_DEF);
             return null();
         }
@@ -8128,50 +8161,50 @@ GeneralParser<ParseHandler, Unit>::class
         // parsing and will be amended when class parsing finishes below.
         CodeNodeType funNode = methodDefinition(isConstructor ? classStartOffset : nameOffset,
                                                 propType, funName);
         if (!funNode) {
             return null();
         }
 
         AccessorType atype = ToAccessorType(propType);
-        if (!handler.addClassMethodDefinition(classMethods, propName, funNode, atype, isStatic)) {
+        if (!handler.addClassMethodDefinition(classMembers, propName, funNode, atype, isStatic)) {
             return null();
         }
     }
 
     // Amend the toStringEnd offset for the constructor now that we've
     // finished parsing the class.
     uint32_t classEndOffset = pos().end;
     if (FunctionBox* ctorbox = classStmt.constructorBox) {
         if (ctorbox->function()->isInterpretedLazy()) {
             ctorbox->function()->lazyScript()->setToStringEnd(classEndOffset);
         }
         ctorbox->toStringEnd = classEndOffset;
     }
 
     Node nameNode = null();
-    Node methodsOrBlock = classMethods;
+    Node membersOrBlock = classMembers;
     if (name) {
         // The inner name is immutable.
         if (!noteDeclaredName(name, DeclarationKind::Const, namePos)) {
             return null();
         }
 
         NameNodeType innerName = newName(name, namePos);
         if (!innerName) {
             return null();
         }
 
-        Node classBlock = finishLexicalScope(*innerScope, classMethods);
+        Node classBlock = finishLexicalScope(*innerScope, classMembers);
         if (!classBlock) {
             return null();
         }
 
-        methodsOrBlock = classBlock;
+        membersOrBlock = classBlock;
 
         // Pop the inner scope.
         innerScope.reset();
         innerScopeStmt.reset();
 
         NameNodeType outerName = null();
         if (classContext == ClassStatement) {
             // The outer name is mutable.
@@ -8188,17 +8221,17 @@ GeneralParser<ParseHandler, Unit>::class
         nameNode = handler.newClassNames(outerName, innerName, namePos);
         if (!nameNode) {
             return null();
         }
     }
 
     MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness));
 
-    return handler.newClass(nameNode, classHeritage, methodsOrBlock,
+    return handler.newClass(nameNode, classHeritage, membersOrBlock,
                             TokenPos(classStartOffset, classEndOffset));
 }
 
 bool
 ParserBase::nextTokenContinuesLetDeclaration(TokenKind next)
 {
     MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Let));
     MOZ_ASSERT(anyChars.nextToken().type == next);
@@ -9599,16 +9632,17 @@ GeneralParser<ParseHandler, Unit>::membe
             break;
         }
 
         Node nextMember;
         if (tt == TokenKind::Dot) {
             if (!tokenStream.getToken(&tt)) {
                 return null();
             }
+
             if (TokenKindIsPossibleIdentifierName(tt)) {
                 PropertyName* field = anyChars.currentName();
                 if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
                     error(JSMSG_BAD_SUPERPROP, "property");
                     return null();
                 }
 
                 NameNodeType name = handler.newPropertyName(field, pos());
@@ -9812,17 +9846,17 @@ GeneralParser<ParseHandler, Unit>::check
     TokenKind tt;
     if (hint == TokenKind::Limit) {
         tt = ReservedWordTokenKind(ident);
     } else {
         MOZ_ASSERT(hint == ReservedWordTokenKind(ident), "hint doesn't match actual token kind");
         tt = hint;
     }
 
-    if (tt == TokenKind::Name) {
+    if (tt == TokenKind::Name || tt == TokenKind::PrivateName) {
         return true;
     }
     if (TokenKindIsContextualKeyword(tt)) {
         if (tt == TokenKind::Yield) {
             if (yieldHandling == YieldIsKeyword) {
                 errorAt(offset, JSMSG_RESERVED_ID, "yield");
                 return false;
             }
@@ -10270,20 +10304,21 @@ GeneralParser<ParseHandler, Unit>::array
 
     handler.setEndPosition(literal, pos().end);
     return literal;
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::Node
 GeneralParser<ParseHandler, Unit>::propertyName(YieldHandling yieldHandling,
-                                                const Maybe<DeclarationKind>& maybeDecl,
-                                                ListNodeType propList,
-                                                PropertyType* propType,
-                                                MutableHandleAtom propAtom)
+                                                 PropertyNameContext propertyNameContext,
+                                                 const Maybe<DeclarationKind>& maybeDecl,
+                                                 ListNodeType propList,
+                                                 PropertyType* propType,
+                                                 MutableHandleAtom propAtom)
 {
     TokenKind ltok;
     if (!tokenStream.getToken(&ltok)) {
         return null();
     }
 
     MOZ_ASSERT(ltok != TokenKind::RightCurly, "caller should have handled TokenKind::RightCurly");
 
@@ -10444,16 +10479,26 @@ GeneralParser<ParseHandler, Unit>::prope
         if (isGenerator || isAsync) {
             error(JSMSG_BAD_PROP_ID);
             return null();
         }
         *propType = PropertyType::Normal;
         return propName;
     }
 
+    if (propertyNameContext == PropertyNameInClass && (tt == TokenKind::Semi || tt == TokenKind::Assign)) {
+        if (isGenerator || isAsync) {
+            error(JSMSG_BAD_PROP_ID);
+            return null();
+        }
+        anyChars.ungetToken();
+        *propType = PropertyType::Field;
+        return propName;
+    }
+
     if (TokenKindIsPossibleIdentifierName(ltok) &&
         (tt == TokenKind::Comma || tt == TokenKind::RightCurly || tt == TokenKind::Assign))
     {
         if (isGenerator || isAsync) {
             error(JSMSG_BAD_PROP_ID);
             return null();
         }
 
@@ -10562,17 +10607,17 @@ GeneralParser<ParseHandler, Unit>::objec
             }
             if (!handler.addSpreadProperty(literal, begin, inner)) {
                 return null();
             }
         } else {
             TokenPos namePos = anyChars.nextToken().pos;
 
             PropertyType propType;
-            Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom);
+            Node propName = propertyName(yieldHandling, PropertyNameInLiteral, declKind, literal, &propType, &propAtom);
             if (!propName) {
                 return null();
             }
 
             if (propType == PropertyType::Normal) {
                 TokenPos exprPos;
                 if (!tokenStream.peekTokenPos(&exprPos, TokenStream::Operand)) {
                     return null();
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -235,17 +235,18 @@ enum class PropertyType {
     CoverInitializedName,
     Getter,
     Setter,
     Method,
     GeneratorMethod,
     AsyncMethod,
     AsyncGeneratorMethod,
     Constructor,
-    DerivedConstructor
+    DerivedConstructor,
+    Field,
 };
 
 enum AwaitHandling : uint8_t { AwaitIsName, AwaitIsKeyword, AwaitIsModuleKeyword };
 
 template <class ParseHandler, typename Unit>
 class AutoAwaitIsKeyword;
 
 template <class ParseHandler, typename Unit>
@@ -1238,17 +1239,19 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE
                              uint32_t prevPos);
     bool notePositionalFormalParameter(CodeNodeType funNode, HandlePropertyName name,
                                        uint32_t beginPos,
                                        bool disallowDuplicateParams, bool* duplicatedParam);
 
     bool checkLexicalDeclarationDirectlyWithinBlock(ParseContext::Statement& stmt,
                                                     DeclarationKind kind, TokenPos pos);
 
+    enum PropertyNameContext { PropertyNameInLiteral, PropertyNameInPattern, PropertyNameInClass };
     Node propertyName(YieldHandling yieldHandling,
+                      PropertyNameContext propertyNameContext,
                       const mozilla::Maybe<DeclarationKind>& maybeDecl,
                       ListNodeType propList,
                       PropertyType* propType, MutableHandleAtom propAtom);
     UnaryNodeType computedPropertyName(YieldHandling yieldHandling,
                                        const mozilla::Maybe<DeclarationKind>& maybeDecl,
                                        ListNodeType literal);
     ListNodeType arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError);
     inline RegExpLiteralType newRegExp();
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -274,17 +274,17 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
 
     ListNodeType newArguments(const TokenPos& pos) { return NodeGeneric; }
     BinaryNodeType newCall(Node callee, Node args) { return NodeFunctionCall; }
 
     BinaryNodeType newSuperCall(Node callee, Node args) { return NodeGeneric; }
     BinaryNodeType newTaggedTemplate(Node tag, Node args) { return NodeGeneric; }
 
     ListNodeType newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; }
-    ListNodeType newClassMethodList(uint32_t begin) { return NodeGeneric; }
+    ListNodeType newClassMemberList(uint32_t begin) { return NodeGeneric; }
     ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) {
         return NodeGeneric;
     }
     ClassNodeType newClass(Node name, Node heritage, Node methodBlock, const TokenPos& pos) { return NodeGeneric; }
 
     LexicalScopeNodeType newLexicalScope(Node body) {
         return NodeLexicalDeclaration;
     }
@@ -300,21 +300,25 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
     void addPropertyDefinition(ListNodeType literal, BinaryNodeType propdef) {}
     MOZ_MUST_USE bool addPropertyDefinition(ListNodeType literal, Node key, Node expr) { return true; }
     MOZ_MUST_USE bool addShorthand(ListNodeType literal, NameNodeType name, NameNodeType expr) { return true; }
     MOZ_MUST_USE bool addSpreadProperty(ListNodeType literal, uint32_t begin, Node inner) { return true; }
     MOZ_MUST_USE bool addObjectMethodDefinition(ListNodeType literal, Node key,
                                                 CodeNodeType funNode, AccessorType atype) {
         return true;
     }
-    MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType methodList, Node key,
+    MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType memberList, Node key,
                                                CodeNodeType funNode, AccessorType atype,
                                                bool isStatic) {
         return true;
     }
+    MOZ_MUST_USE bool addClassFieldDefinition(ListNodeType memberList,
+                                              Node name, Node initializer) {
+        return true;
+    }
     UnaryNodeType newYieldExpression(uint32_t begin, Node value) { return NodeGeneric; }
     UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) { return NodeGeneric; }
     UnaryNodeType newAwaitExpression(uint32_t begin, Node value) { return NodeGeneric; }
 
     // Statements
 
     ListNodeType newStatementList(const TokenPos& pos) { return NodeGeneric; }
     void addStatementToList(ListNodeType list, Node stmt) {}
--- a/js/src/frontend/TokenKind.h
+++ b/js/src/frontend/TokenKind.h
@@ -68,16 +68,17 @@
     macro(TripleDot,    "'...'")  /* rest arguments and spread operator */ \
     macro(LeftBracket,  "'['") \
     macro(RightBracket, "']'") \
     macro(LeftCurly,    "'{'") \
     macro(RightCurly,   "'}'") \
     macro(LeftParen,    "'('") \
     macro(RightParen,   "')'") \
     macro(Name,         "identifier") \
+    macro(PrivateName,  "private identifier") \
     macro(Number,       "numeric literal") \
     macro(String,       "string literal") \
     \
     /* start of template literal with substitutions */ \
     macro(TemplateHead,    "'${'") \
     /* template literal without substitutions */ \
     macro(NoSubsTemplate, "template literal") \
     \
@@ -317,16 +318,17 @@ TokenKindIsReservedWord(TokenKind tt)
            TokenKindIsFutureReservedWord(tt) ||
            TokenKindIsReservedWordLiteral(tt);
 }
 
 inline MOZ_MUST_USE bool
 TokenKindIsPossibleIdentifier(TokenKind tt)
 {
     return tt == TokenKind::Name ||
+           tt == TokenKind::PrivateName ||
            TokenKindIsContextualKeyword(tt) ||
            TokenKindIsStrictReservedWord(tt);
 }
 
 inline MOZ_MUST_USE bool
 TokenKindIsPossibleIdentifierName(TokenKind tt)
 {
     return TokenKindIsPossibleIdentifier(tt) ||
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -114,46 +114,38 @@ FindReservedWord(const CharT* s, size_t 
 template <>
 MOZ_ALWAYS_INLINE const ReservedWordInfo*
 FindReservedWord<Utf8Unit>(const Utf8Unit* units, size_t length)
 {
     return FindReservedWord(Utf8AsUnsignedChars(units), length);
 }
 
 static const ReservedWordInfo*
-FindReservedWord(JSLinearString* str)
+FindReservedWord(JSLinearString* str, js::frontend::NameVisibility* visibility)
 {
     JS::AutoCheckCannotGC nogc;
-    return str->hasLatin1Chars()
-           ? FindReservedWord(str->latin1Chars(nogc), str->length())
-           : FindReservedWord(str->twoByteChars(nogc), str->length());
-}
-
-template <typename CharT>
-static bool
-IsIdentifierImpl(const CharT* chars, size_t length)
-{
-    using namespace js;
-
-    if (length == 0) {
-        return false;
+    if (str->hasLatin1Chars()) {
+        const JS::Latin1Char* chars = str->latin1Chars(nogc);
+        size_t length = str->length();
+        if (length > 0 && chars[0] == '#') {
+            *visibility = js::frontend::NameVisibility::Private;
+            return nullptr;
+        }
+        *visibility = js::frontend::NameVisibility::Public;
+        return FindReservedWord(chars, length);
     }
 
-    if (!unicode::IsIdentifierStart(char16_t(*chars))) {
-        return false;
+    const char16_t* chars = str->twoByteChars(nogc);
+    size_t length = str->length();
+    if (length > 0 && chars[0] == '#') {
+        *visibility = js::frontend::NameVisibility::Private;
+        return nullptr;
     }
-
-    const CharT* end = chars + length;
-    while (++chars != end) {
-        if (!unicode::IsIdentifierPart(char16_t(*chars))) {
-            return false;
-        }
-    }
-
-    return true;
+    *visibility = js::frontend::NameVisibility::Public;
+    return FindReservedWord(chars, length);
 }
 
 static uint32_t
 GetSingleCodePoint(const char16_t** p, const char16_t* end)
 {
     using namespace js;
 
     uint32_t codePoint;
@@ -166,25 +158,81 @@ GetSingleCodePoint(const char16_t** p, c
         }
     }
 
     codePoint = **p;
     (*p)++;
     return codePoint;
 }
 
-static bool
-IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length)
+namespace js {
+
+namespace frontend {
+
+bool
+IsIdentifier(JSLinearString* str)
+{
+    JS::AutoCheckCannotGC nogc;
+    MOZ_ASSERT(str);
+    if (str->hasLatin1Chars()) {
+        return IsIdentifier(str->latin1Chars(nogc), str->length());
+    }
+    return IsIdentifier(str->twoByteChars(nogc), str->length());
+}
+
+bool
+IsIdentifierNameOrPrivateName(JSLinearString* str)
+{
+    JS::AutoCheckCannotGC nogc;
+    MOZ_ASSERT(str);
+    if (str->hasLatin1Chars()) {
+        return IsIdentifierNameOrPrivateName(str->latin1Chars(nogc), str->length());
+    }
+    return IsIdentifierNameOrPrivateName(str->twoByteChars(nogc), str->length());
+}
+
+bool
+IsIdentifier(const Latin1Char* chars, size_t length)
 {
-    using namespace js;
-
-    if (IsIdentifierImpl(chars, length)) {
-        return true;
+    if (length == 0) {
+        return false;
+    }
+
+    if (!unicode::IsIdentifierStart(char16_t(*chars))) {
+        return false;
+    }
+
+    const Latin1Char* end = chars + length;
+    while (++chars != end) {
+        if (!unicode::IsIdentifierPart(char16_t(*chars))) {
+            return false;
+        }
     }
 
+    return true;
+}
+
+bool
+IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length)
+{
+    if (length == 0) {
+        return false;
+    }
+
+    if (char16_t(*chars) == '#') {
+        ++chars;
+        --length;
+    }
+
+    return IsIdentifier(chars, length);
+}
+
+bool
+IsIdentifier(const char16_t* chars, size_t length)
+{
     if (length == 0) {
         return false;
     }
 
     const char16_t* p = chars;
     const char16_t* end = chars + length;
     uint32_t codePoint;
 
@@ -198,67 +246,77 @@ IsIdentifierMaybeNonBMP(const char16_t* 
         if (!unicode::IsIdentifierPart(codePoint)) {
             return false;
         }
     }
 
     return true;
 }
 
-namespace js {
-
-namespace frontend {
-
 bool
-IsIdentifier(JSLinearString* str)
+IsIdentifierNameOrPrivateName(const char16_t* chars, size_t length)
 {
-    JS::AutoCheckCannotGC nogc;
-    MOZ_ASSERT(str);
-    if (str->hasLatin1Chars()) {
-        return ::IsIdentifierImpl(str->latin1Chars(nogc), str->length());
+    if (length == 0) {
+        return false;
     }
-    return ::IsIdentifierMaybeNonBMP(str->twoByteChars(nogc), str->length());
-}
-
-bool
-IsIdentifier(const char* chars, size_t length)
-{
-    return ::IsIdentifierImpl(chars, length);
-}
-
-bool
-IsIdentifier(const char16_t* chars, size_t length)
-{
-    return ::IsIdentifierImpl(chars, length);
+
+    const char16_t* p = chars;
+    const char16_t* end = chars + length;
+    uint32_t codePoint;
+
+    codePoint = GetSingleCodePoint(&p, end);
+    if (codePoint == '#') {
+        if (length == 1) {
+            return false;
+        }
+
+        codePoint = GetSingleCodePoint(&p, end);
+    }
+
+    if (!unicode::IsIdentifierStart(codePoint)) {
+        return false;
+    }
+
+    while (p < end) {
+        codePoint = GetSingleCodePoint(&p, end);
+        if (!unicode::IsIdentifierPart(codePoint)) {
+            return false;
+        }
+    }
+
+    return true;
 }
 
 bool
 IsKeyword(JSLinearString* str)
 {
-    if (const ReservedWordInfo* rw = FindReservedWord(str)) {
+    NameVisibility visibility;
+    if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility)) {
         return TokenKindIsKeyword(rw->tokentype);
     }
 
     return false;
 }
 
 TokenKind
 ReservedWordTokenKind(PropertyName* str)
 {
-    if (const ReservedWordInfo* rw = FindReservedWord(str)) {
+    NameVisibility visibility;
+    if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility)) {
         return rw->tokentype;
     }
 
-    return TokenKind::Name;
+    return visibility == NameVisibility::Private ? TokenKind::PrivateName : TokenKind::Name;
 }
 
 const char*
 ReservedWordToCharZ(PropertyName* str)
 {
-    if (const ReservedWordInfo* rw = FindReservedWord(str)) {
+    NameVisibility visibility;
+    if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility)) {
         return ReservedWordToCharZ(rw->tokentype);
     }
 
     return nullptr;
 }
 
 const char*
 ReservedWordToCharZ(TokenKind tt)
@@ -1656,16 +1714,52 @@ GeneralTokenStreamChars<Unit, AnyCharsAc
         this->sourceUnits.unskipCodeUnits(length);
     }
 
     MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('\\'));
     return false;
 }
 
 template<typename Unit, class AnyCharsAccess>
+MOZ_MUST_USE bool
+TokenStreamSpecific<Unit, AnyCharsAccess>::matchIdentifierStart(IdentifierEscapes* sawEscape)
+{
+    int32_t unit = getCodeUnit();
+    if (unicode::IsIdentifierStart(char16_t(unit))) {
+        *sawEscape = IdentifierEscapes::None;
+        return true;
+    }
+
+    if (unit == '\\') {
+        *sawEscape = IdentifierEscapes::SawUnicodeEscape;
+
+        uint32_t codePoint;
+        uint32_t escapeLength = matchUnicodeEscapeIdStart(&codePoint);
+        if (escapeLength != 0) {
+            return true;
+        }
+
+        // We could point "into" a mistyped escape, e.g. for "\u{41H}" we
+        // could point at the 'H'.  But we don't do that now, so the code
+        // unit after the '\' isn't necessarily bad, so just point at the
+        // start of the actually-invalid escape.
+        ungetCodeUnit('\\');
+        error(JSMSG_BAD_ESCAPE);
+        return false;
+    }
+
+    *sawEscape = IdentifierEscapes::None;
+
+    // NOTE: |unit| may be EOF here.
+    ungetCodeUnit(unit);
+    error(JSMSG_MISSING_PRIVATE_NAME);
+    return false;
+}
+
+template<typename Unit, class AnyCharsAccess>
 bool
 TokenStreamSpecific<Unit, AnyCharsAccess>::getDirectives(bool isMultiline,
                                                          bool shouldWarnDeprecated)
 {
     // Match directive comments used in debugging, such as "//# sourceURL" and
     // "//# sourceMappingURL". Use of "//@" instead of "//#" is deprecated.
     //
     // To avoid a crashing bug in IE, several JavaScript transpilers wrap single
@@ -1926,19 +2020,21 @@ TokenStreamSpecific<Unit, AnyCharsAccess
     } while (true);
 
     return true;
 }
 
 template<typename Unit, class AnyCharsAccess>
 MOZ_MUST_USE bool
 TokenStreamSpecific<Unit, AnyCharsAccess>::identifierName(TokenStart start,
-                                                          const Unit* identStart,
-                                                          IdentifierEscapes escaping,
-                                                          Modifier modifier, TokenKind* out)
+                                                           const Unit* identStart,
+                                                           IdentifierEscapes escaping,
+                                                           Modifier modifier,
+                                                           NameVisibility visibility,
+                                                           TokenKind* out)
 {
     // Run the bad-token code for every path out of this function except the
     // two success-cases.
     auto noteBadToken = MakeScopeExit([this]() {
         this->badToken();
     });
 
     // We've already consumed an initial code point in the identifer, to *know*
@@ -1990,31 +2086,43 @@ TokenStreamSpecific<Unit, AnyCharsAccess
         }
 
         atom = drainCharBufferIntoAtom(anyCharsAccess().cx);
     } else {
         // Escape-free identifiers can be created directly from sourceUnits.
         const Unit* chars = identStart;
         size_t length = this->sourceUnits.addressOfNextCodeUnit() - identStart;
 
-        // Represent reserved words lacking escapes as reserved word tokens.
-        if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) {
-            noteBadToken.release();
-            newSimpleToken(rw->tokentype, start, modifier, out);
-            return true;
+        // Private identifiers start with a '#', and so cannot be reserved words.
+        if (visibility == NameVisibility::Public) {
+            // Represent reserved words lacking escapes as reserved word tokens.
+            if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) {
+                noteBadToken.release();
+                newSimpleToken(rw->tokentype, start, modifier, out);
+                return true;
+            }
         }
 
         atom = atomizeSourceChars(anyCharsAccess().cx, MakeSpan(chars, length));
     }
     if (!atom) {
         return false;
     }
 
     noteBadToken.release();
-    newNameToken(atom->asPropertyName(), start, modifier, out);
+    if (visibility == NameVisibility::Private) {
+        MOZ_ASSERT(identStart[0] == static_cast<Unit>('#'), "Private identifier starts with #");
+        newPrivateNameToken(atom->asPropertyName(), start, modifier, out);
+
+        // TODO(khypera): Delete the below once private names are supported.
+        errorAt(start.offset(), JSMSG_FIELDS_NOT_SUPPORTED);
+        return false;
+    } else {
+        newNameToken(atom->asPropertyName(), start, modifier, out);
+    }
     return true;
 }
 
 enum FirstCharKind {
     // A char16_t has the 'OneChar' kind if it, by itself, constitutes a valid
     // token that cannot also be a prefix of a longer token.  E.g. ';' has the
     // OneChar kind, but '+' does not, because '++' and '+=' are valid longer tokens
     // that begin with '+'.
@@ -2435,17 +2543,17 @@ TokenStreamSpecific<Unit, AnyCharsAccess
 
             if (MOZ_LIKELY(unicode::IsUnicodeIDStart(cp))) {
                 this->sourceUnits.consumeKnownCodePoint(peeked);
                 MOZ_ASSERT(!IsLineTerminator(cp),
                            "IdentifierStart must guarantee !IsLineTerminator "
                            "or else we'll fail to maintain line-info/flags "
                            "for EOL here");
 
-                return identifierName(start, identStart, IdentifierEscapes::None, modifier, ttp);
+                return identifierName(start, identStart, IdentifierEscapes::None, modifier, NameVisibility::Public, ttp);
             }
 
             error(JSMSG_ILLEGAL_CHARACTER);
             return badToken();
         } // !isAsciiCodePoint(unit)
 
         consumeKnownCodeUnit(unit);
 
@@ -2484,17 +2592,18 @@ TokenStreamSpecific<Unit, AnyCharsAccess
             continue;
         }
 
         // Look for an identifier.
         //
         if (c1kind == Ident) {
             TokenStart start(this->sourceUnits, -1);
             return identifierName(start, this->sourceUnits.addressOfNextCodeUnit() - 1,
-                                  IdentifierEscapes::None, modifier, ttp);
+                                  IdentifierEscapes::None, modifier,
+                                  NameVisibility::Public, ttp);
         }
 
         // Look for a decimal number.
         //
         if (c1kind == Dec) {
             TokenStart start(this->sourceUnits, -1);
             const Unit* numStart = this->sourceUnits.addressOfNextCodeUnit() - 1;
             return decimalNumber(unit, start, numStart, modifier, ttp);
@@ -2677,16 +2786,26 @@ TokenStreamSpecific<Unit, AnyCharsAccess
 
             // NOTE: |unit| may be EOF here.  A stray '.' at EOF would be an
             //       error, but subsequent code will handle it.
             ungetCodeUnit(unit);
 
             simpleKind = TokenKind::Dot;
             break;
 
+          case '#': {
+            TokenStart start(this->sourceUnits, -1);
+            const Unit* identStart = this->sourceUnits.addressOfNextCodeUnit() - 1;
+            IdentifierEscapes sawEscape;
+            if (!matchIdentifierStart(&sawEscape)) {
+                return badToken();
+            }
+            return identifierName(start, identStart, sawEscape, modifier, NameVisibility::Private, ttp);
+          }
+
           case '=':
             if (matchCodeUnit('=')) {
                 simpleKind = matchCodeUnit('=') ? TokenKind::StrictEq : TokenKind::Eq;
             } else if (matchCodeUnit('>')) {
                 simpleKind = TokenKind::Arrow;
             } else {
                 simpleKind = TokenKind::Assign;
             }
@@ -2700,17 +2819,17 @@ TokenStreamSpecific<Unit, AnyCharsAccess
             }
             break;
 
           case '\\': {
             uint32_t codePoint;
             if (uint32_t escapeLength = matchUnicodeEscapeIdStart(&codePoint)) {
                 return identifierName(start,
                                       this->sourceUnits.addressOfNextCodeUnit() - escapeLength - 1,
-                                      IdentifierEscapes::SawUnicodeEscape, modifier, ttp);
+                                      IdentifierEscapes::SawUnicodeEscape, modifier, NameVisibility::Public, ttp);
             }
 
             // We could point "into" a mistyped escape, e.g. for "\u{41H}" we
             // could point at the 'H'.  But we don't do that now, so the code
             // unit after the '\' isn't necessarily bad, so just point at the
             // start of the actually-invalid escape.
             ungetCodeUnit('\\');
             error(JSMSG_BAD_ESCAPE);
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -284,16 +284,18 @@ enum class InvalidEscapeType {
     UnicodeOverflow,
     // An octal escape in a template token.
     Octal
 };
 
 // The only escapes found in IdentifierName are of the Unicode flavor.
 enum class IdentifierEscapes { None, SawUnicodeEscape };
 
+enum class NameVisibility { Public, Private };
+
 class TokenStreamShared;
 
 struct Token
 {
   private:
     // Sometimes the parser needs to inform the tokenizer to interpret
     // subsequent text in a particular manner: for example, to tokenize a
     // keyword as an identifier, not as the actual keyword, on the right-hand
@@ -401,17 +403,17 @@ struct Token
      * situations.
      */
     ModifierException modifierException;
 #endif
 
     // Mutators
 
     void setName(PropertyName* name) {
-        MOZ_ASSERT(type == TokenKind::Name);
+        MOZ_ASSERT(type == TokenKind::Name || type == TokenKind::PrivateName);
         u.name = name;
     }
 
     void setAtom(JSAtom* atom) {
         MOZ_ASSERT(type == TokenKind::String ||
                    type == TokenKind::TemplateHead ||
                    type == TokenKind::NoSubsTemplate);
         u.atom = atom;
@@ -427,17 +429,17 @@ struct Token
         MOZ_ASSERT(type == TokenKind::Number);
         u.number.value = n;
         u.number.decimalPoint = decimalPoint;
     }
 
     // Type-safe accessors
 
     PropertyName* name() const {
-        MOZ_ASSERT(type == TokenKind::Name);
+        MOZ_ASSERT(type == TokenKind::Name || type == TokenKind::PrivateName);
         return u.name->JSAtom::asPropertyName(); // poor-man's type verification
     }
 
     JSAtom* atom() const {
         MOZ_ASSERT(type == TokenKind::String ||
                    type == TokenKind::TemplateHead ||
                    type == TokenKind::NoSubsTemplate);
         return u.atom;
@@ -622,26 +624,26 @@ class TokenStreamAnyChars
 
     MOZ_MUST_USE bool checkOptions();
 
   private:
     PropertyName* reservedWordToPropertyName(TokenKind tt) const;
 
   public:
     PropertyName* currentName() const {
-        if (isCurrentTokenType(TokenKind::Name)) {
+        if (isCurrentTokenType(TokenKind::Name) || isCurrentTokenType(TokenKind::PrivateName)) {
             return currentToken().name();
         }
 
         MOZ_ASSERT(TokenKindIsPossibleIdentifierName(currentToken().type));
         return reservedWordToPropertyName(currentToken().type);
     }
 
     bool currentNameHasEscapes() const {
-        if (isCurrentTokenType(TokenKind::Name)) {
+        if (isCurrentTokenType(TokenKind::Name) || isCurrentTokenType(TokenKind::PrivateName)) {
             TokenPos pos = currentToken().pos;
             return (pos.end - pos.begin) != currentToken().name()->length();
         }
 
         MOZ_ASSERT(TokenKindIsPossibleIdentifierName(currentToken().type));
         return false;
     }
 
@@ -1922,16 +1924,25 @@ class GeneralTokenStreamChars
 
     void newNameToken(PropertyName* name, TokenStart start, TokenStreamShared::Modifier modifier,
                       TokenKind* out)
     {
         Token* token = newToken(TokenKind::Name, start, modifier, out);
         token->setName(name);
     }
 
+    void newPrivateNameToken(PropertyName* name,
+                             TokenStart start,
+                             TokenStreamShared::Modifier modifier,
+                             TokenKind* out)
+    {
+        Token* token = newToken(TokenKind::PrivateName, start, modifier, out);
+        token->setName(name);
+    }
+
     void newRegExpToken(RegExpFlag reflags, TokenStart start, TokenKind* out)
     {
         Token* token = newToken(TokenKind::RegExp, start, TokenStreamShared::Operand, out);
         token->setRegExpFlags(reflags);
     }
 
     MOZ_COLD bool badToken();
 
@@ -1995,16 +2006,17 @@ class GeneralTokenStreamChars
     }
 
     MOZ_MUST_USE MOZ_ALWAYS_INLINE bool updateLineInfoForEOL() {
         return anyCharsAccess().internalUpdateLineInfoForEOL(this->sourceUnits.offset());
     }
 
     uint32_t matchUnicodeEscapeIdStart(uint32_t* codePoint);
     bool matchUnicodeEscapeIdent(uint32_t* codePoint);
+    bool matchIdentifierStart();
 
     /**
      * If possible, compute a line of context for an otherwise-filled-in |err|
      * at the given offset in this token stream.
      *
      * This function is very-internal: almost certainly you should use one of
      * its callers instead.  It basically exists only to make those callers
      * more readable.
@@ -2330,16 +2342,17 @@ class MOZ_STACK_CLASS TokenStreamSpecifi
     using GeneralCharsBase::internalComputeLineOfContext;
     using TokenStreamCharsShared::isAsciiCodePoint;
     using CharsBase::matchCodeUnit;
     using CharsBase::matchLineTerminator;
     using GeneralCharsBase::matchUnicodeEscapeIdent;
     using GeneralCharsBase::matchUnicodeEscapeIdStart;
     using GeneralCharsBase::newAtomToken;
     using GeneralCharsBase::newNameToken;
+    using GeneralCharsBase::newPrivateNameToken;
     using GeneralCharsBase::newNumberToken;
     using GeneralCharsBase::newRegExpToken;
     using GeneralCharsBase::newSimpleToken;
     using CharsBase::peekCodeUnit;
     // Deliberately don't |using| |sourceUnits| because of bug 1472569.  :-(
     using CharsBase::toUnit;
     using GeneralCharsBase::ungetCodeUnit;
     using GeneralCharsBase::updateLineInfoForEOL;
@@ -2665,17 +2678,19 @@ class MOZ_STACK_CLASS TokenStreamSpecifi
     }
 
     const Unit* rawLimit() const {
         return this->sourceUnits.limit();
     }
 
     MOZ_MUST_USE bool identifierName(TokenStart start, const Unit* identStart,
                                      IdentifierEscapes escaping, Modifier modifier,
-                                     TokenKind* out);
+                                     NameVisibility visibility, TokenKind* out);
+
+    MOZ_MUST_USE bool matchIdentifierStart(IdentifierEscapes* sawEscape);
 
     MOZ_MUST_USE bool getTokenInternal(TokenKind* const ttp, const Modifier modifier);
 
     MOZ_MUST_USE bool getStringOrTemplateToken(char untilChar, Modifier modifier, TokenKind* out);
 
     MOZ_MUST_USE bool getDirectives(bool isMultiline, bool shouldWarnDeprecated);
     MOZ_MUST_USE bool getDirective(bool isMultiline, bool shouldWarnDeprecated,
                                    const char* directive, uint8_t directiveLength,
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -260,16 +260,17 @@ MSG_DEF(JSMSG_EXPORT_DECL_AT_TOP_LEVEL,0
 MSG_DEF(JSMSG_FINALLY_WITHOUT_TRY,     0, JSEXN_SYNTAXERR, "finally without try")
 MSG_DEF(JSMSG_FORBIDDEN_AS_STATEMENT,  1, JSEXN_SYNTAXERR, "{0} can't appear in single-statement context")
 MSG_DEF(JSMSG_FOR_AWAIT_OUTSIDE_ASYNC, 0, JSEXN_SYNTAXERR, "for await (... of ...) is only valid in async functions and async generators")
 MSG_DEF(JSMSG_FROM_AFTER_IMPORT_CLAUSE, 0, JSEXN_SYNTAXERR, "missing keyword 'from' after import clause")
 MSG_DEF(JSMSG_FROM_AFTER_EXPORT_STAR,  0, JSEXN_SYNTAXERR, "missing keyword 'from' after export *")
 MSG_DEF(JSMSG_GARBAGE_AFTER_INPUT,     2, JSEXN_SYNTAXERR, "unexpected garbage after {0}, starting with {1}")
 MSG_DEF(JSMSG_IDSTART_AFTER_NUMBER,    0, JSEXN_SYNTAXERR, "identifier starts immediately after numeric literal")
 MSG_DEF(JSMSG_BAD_ESCAPE,              0, JSEXN_SYNTAXERR, "invalid escape sequence")
+MSG_DEF(JSMSG_MISSING_PRIVATE_NAME,    0, JSEXN_SYNTAXERR, "'#' not followed by identifier")
 MSG_DEF(JSMSG_ILLEGAL_CHARACTER,       0, JSEXN_SYNTAXERR, "illegal character")
 MSG_DEF(JSMSG_IMPORT_META_OUTSIDE_MODULE, 0, JSEXN_SYNTAXERR, "import.meta may only appear in a module")
 MSG_DEF(JSMSG_IMPORT_DECL_AT_TOP_LEVEL, 0, JSEXN_SYNTAXERR, "import declarations may only appear at top level of a module")
 MSG_DEF(JSMSG_OF_AFTER_FOR_LOOP_DECL,  0, JSEXN_SYNTAXERR, "a declaration in the head of a for-of loop can't have an initializer")
 MSG_DEF(JSMSG_IN_AFTER_LEXICAL_FOR_DECL,0,JSEXN_SYNTAXERR, "a lexical declaration in the head of a for-in loop can't have an initializer")
 MSG_DEF(JSMSG_INVALID_FOR_IN_DECL_WITH_INIT,0,JSEXN_SYNTAXERR,"for-in loop head declarations may not have initializers")
 MSG_DEF(JSMSG_INVALID_ID,              1, JSEXN_SYNTAXERR, "{0} is an invalid identifier")
 MSG_DEF(JSMSG_LABEL_NOT_FOUND,         0, JSEXN_SYNTAXERR, "label not found")
@@ -350,16 +351,18 @@ MSG_DEF(JSMSG_VAR_HIDES_ARG,           1
 MSG_DEF(JSMSG_WHILE_AFTER_DO,          0, JSEXN_SYNTAXERR, "missing while after do-loop body")
 MSG_DEF(JSMSG_YIELD_IN_PARAMETER,      0, JSEXN_SYNTAXERR, "yield expression can't be used in parameter")
 MSG_DEF(JSMSG_YIELD_OUTSIDE_GENERATOR, 0, JSEXN_SYNTAXERR, "yield expression is only valid in generators")
 MSG_DEF(JSMSG_BAD_COLUMN_NUMBER,       0, JSEXN_RANGEERR, "column number out of range")
 MSG_DEF(JSMSG_COMPUTED_NAME_IN_PATTERN,0, JSEXN_SYNTAXERR, "computed property names aren't supported in this destructuring declaration")
 MSG_DEF(JSMSG_DEFAULT_IN_PATTERN,      0, JSEXN_SYNTAXERR, "destructuring defaults aren't supported in this destructuring declaration")
 MSG_DEF(JSMSG_BAD_NEWTARGET,           0, JSEXN_SYNTAXERR, "new.target only allowed within functions")
 MSG_DEF(JSMSG_ESCAPED_KEYWORD,         0, JSEXN_SYNTAXERR, "keywords must be written literally, without embedded escapes")
+MSG_DEF(JSMSG_MISSING_SEMI_FIELD,      0, JSEXN_SYNTAXERR, "missing ; after field definition")
+MSG_DEF(JSMSG_FIELDS_NOT_SUPPORTED,    0, JSEXN_SYNTAXERR, "fields are not currently supported")
 
 // UTF-8 source text encoding errors
 MSG_DEF(JSMSG_BAD_LEADING_UTF8_UNIT,   1, JSEXN_SYNTAXERR, "{0} byte doesn't begin a valid UTF-8 code point")
 MSG_DEF(JSMSG_NOT_ENOUGH_CODE_UNITS,   4, JSEXN_SYNTAXERR, "{0} byte in UTF-8 must be followed by {1} bytes, but {2} byte{3} present")
 MSG_DEF(JSMSG_BAD_TRAILING_UTF8_UNIT,  1, JSEXN_SYNTAXERR, "bad trailing UTF-8 byte {0} doesn't match the pattern 0b10xxxxxx")
 MSG_DEF(JSMSG_FORBIDDEN_UTF8_CODE_POINT,2,JSEXN_SYNTAXERR, "{0} isn't a valid code point because {1}")
 MSG_DEF(JSMSG_BAD_CODE_UNITS,          1, JSEXN_NOTE, "the code units comprising this invalid code point were: {0}")
 
--- a/js/src/jsast.tbl
+++ b/js/src/jsast.tbl
@@ -73,9 +73,10 @@ ASTDEF(AST_OBJECT_PATT,           "Objec
 ASTDEF(AST_PROP_PATT,             "Property",                       "propertyPattern")
 ASTDEF(AST_TEMPLATE_LITERAL,      "TemplateLiteral",                "templateLiteral")
 ASTDEF(AST_TAGGED_TEMPLATE,       "TaggedTemplate",                 "taggedTemplate")
 ASTDEF(AST_CALL_SITE_OBJ,         "CallSiteObject",                 "callSiteObject")
 ASTDEF(AST_COMPUTED_NAME,         "ComputedName",                   "computedName")
 
 ASTDEF(AST_CLASS_STMT,            "ClassStatement",                 "classStatement")
 ASTDEF(AST_CLASS_METHOD,          "ClassMethod",                    "classMethod")
+ASTDEF(AST_CLASS_FIELD,           "ClassField",                     "classField")
 /* AST_LIMIT = last + 1 */
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -456,16 +456,24 @@ skip script test262/intl402/RelativeTime
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1483548
 skip script test262/intl402/RelativeTimeFormat/prototype/format/value-non-finite.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1499933
 skip script test262/intl402/DateTimeFormat/prototype/resolvedOptions/order.js
 skip script test262/intl402/PluralRules/prototype/resolvedOptions/order.js
 skip script test262/intl402/NumberFormat/prototype/resolvedOptions/order.js
 
+# Fields are not fully implemented yet
+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
 
 ###########################################################
 # Tests disabled due to issues in test262 importer script #
 ###########################################################
 
 # test262 importer merges all includes in a per directory shell.js file, breaking this harness test case.
 skip script test262/harness/detachArrayBuffer.js
 
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/fields/access.js
@@ -0,0 +1,19 @@
+// * * * 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);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/fields/basic.js
@@ -0,0 +1,14 @@
+// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet
+
+class C {
+    x;
+    y = 2;
+}
+
+class D {
+    #x;
+    #y = 2;
+}
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/fields/error.js
@@ -0,0 +1,45 @@
+// * * * 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);
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/fields/field_types.js
@@ -0,0 +1,25 @@
+// * * * 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);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/fields/literal.js
@@ -0,0 +1,46 @@
+// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet
+
+source = `var y = {
+    x;
+}`;
+assertThrowsInstanceOf(() => 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);
+
+// 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);
+
+source = `var y = {
+    x = 2
+}`;
+assertThrowsInstanceOf(() => eval(source), SyntaxError);
+
+source = `var y = {
+    #x = 2;
+}`;
+assertThrowsInstanceOf(() => eval(source), SyntaxError);
+
+source = `var y = {
+    #x = 2
+}`;
+assertThrowsInstanceOf(() => eval(source), SyntaxError);
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/fields/mixed_methods.js
@@ -0,0 +1,11 @@
+// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet
+
+class C {
+    x;
+    y(){}
+    z = 2;
+    w(){};
+}
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/fields/quirks.js
@@ -0,0 +1,16 @@
+// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet
+
+class C {
+    x;;;;
+    y
+    ;
+}
+
+class D {
+    x = 5;
+    y = (x += 1);
+    // TODO: Assert values of x and y
+}
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
--- a/js/src/vm/CompilationAndEvaluation.cpp
+++ b/js/src/vm/CompilationAndEvaluation.cpp
@@ -320,17 +320,17 @@ JS::CompileFunction(JSContext* cx, AutoO
     if (name) {
         nameLen = strlen(name);
         nameAtom = Atomize(cx, name, nameLen);
         if (!nameAtom) {
             return false;
         }
 
         // If name is not valid identifier
-        if (!js::frontend::IsIdentifier(name, nameLen)) {
+        if (!js::frontend::IsIdentifier(reinterpret_cast<const Latin1Char*>(name), nameLen)) {
             isInvalidName = true;
         }
     }
 
     uint32_t parameterListEnd;
     StringBuffer funStr(cx);
     if (!BuildFunctionString(isInvalidName ? nullptr : name, nameLen, nargs, argnames, srcBuf,
                              &funStr, &parameterListEnd))
--- a/js/src/vm/StringType.cpp
+++ b/js/src/vm/StringType.cpp
@@ -2205,17 +2205,17 @@ js::EncodeAscii(JSContext* cx, JSString*
 }
 
 UniqueChars
 js::IdToPrintableUTF8(JSContext* cx, HandleId id, IdToPrintableBehavior behavior)
 {
     // ToString(<symbol>) throws a TypeError, therefore require that callers
     // request source representation when |id| is a property key.
     MOZ_ASSERT_IF(behavior == IdToPrintableBehavior::IdIsIdentifier,
-                  JSID_IS_ATOM(id) && frontend::IsIdentifier(JSID_TO_ATOM(id)));
+                  JSID_IS_ATOM(id) && frontend::IsIdentifierNameOrPrivateName(JSID_TO_ATOM(id)));
 
     RootedValue v(cx, IdToValue(id));
     JSString* str;
     if (behavior == IdToPrintableBehavior::IdIsPropertyKey) {
         str = ValueToSource(cx, v);
     } else {
         str = ToString<CanGC>(cx, v);
     }