[class] parse private accessors

This patch adds:

- VariableMode::kPrivateMethod
- VariableMode::kPrivateSetterOnly
- VariableMode::kPrivateGetterOnly
- VariableMode::kPrivateGetterAndSetter

And replace the previous RequiresBrandCheckFlag by inferring
whether the brand check is required from these VariableModes.
It is then possible to check duplicate non-complementary
accessors in the parsers and throw early errors, and allow
complementary accessors to be associated with the same
private name variable.

This patch also adds the following AssignType:

- PRIVATE_METHOD
- PRIVATE_GETTER_ONLY
- PRIVATE_SETTER_ONLY
- PRIVATE_GETTER_AND_SETTER

corresponding to the new VariableModes so that it's possible
to generate specialized code for different type of
private accessor declarations.

Design doc: https://docs.google.com/document/d/10W4begYfs7lmldSqBoQBBt_BKamgT8igqxF9u50RGrI/edit

Bug: v8:8330
Change-Id: I0fb61b1be248630d1eadd74fb16d7d64a421f4c4
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1695204
Commit-Queue: Joyee Cheung <joyee@igalia.com>
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62988}
diff --git a/src/ast/ast.h b/src/ast/ast.h
index 85c8c32..6622eda 100644
--- a/src/ast/ast.h
+++ b/src/ast/ast.h
@@ -1605,12 +1605,15 @@
 // Otherwise, the assignment is to a non-property (a global, a local slot, a
 // parameter slot, or a destructuring pattern).
 enum AssignType {
-  NON_PROPERTY,          // destructuring
-  NAMED_PROPERTY,        // obj.key
-  KEYED_PROPERTY,        // obj[key]
-  NAMED_SUPER_PROPERTY,  // super.key
-  KEYED_SUPER_PROPERTY,  // super[key]
-  PRIVATE_METHOD         // obj.#key: #key is a private method
+  NON_PROPERTY,              // destructuring
+  NAMED_PROPERTY,            // obj.key
+  KEYED_PROPERTY,            // obj[key]
+  NAMED_SUPER_PROPERTY,      // super.key
+  KEYED_SUPER_PROPERTY,      // super[key]
+  PRIVATE_METHOD,            // obj.#key: #key is a private method
+  PRIVATE_GETTER_ONLY,       // obj.#key: #key only has a getter defined
+  PRIVATE_SETTER_ONLY,       // obj.#key: #key only has a setter defined
+  PRIVATE_GETTER_AND_SETTER  // obj.#key: #key has both accessors defined
 };
 
 class Property final : public Expression {
@@ -1631,8 +1634,21 @@
       VariableProxy* proxy = property->key()->AsVariableProxy();
       DCHECK_NOT_NULL(proxy);
       Variable* var = proxy->var();
-      // Use KEYED_PROPERTY for private fields.
-      return var->requires_brand_check() ? PRIVATE_METHOD : KEYED_PROPERTY;
+
+      switch (var->mode()) {
+        case VariableMode::kPrivateMethod:
+          return PRIVATE_METHOD;
+        case VariableMode::kConst:
+          return KEYED_PROPERTY;  // Use KEYED_PROPERTY for private fields.
+        case VariableMode::kPrivateGetterOnly:
+          return PRIVATE_GETTER_ONLY;
+        case VariableMode::kPrivateSetterOnly:
+          return PRIVATE_SETTER_ONLY;
+        case VariableMode::kPrivateGetterAndSetter:
+          return PRIVATE_GETTER_AND_SETTER;
+        default:
+          UNREACHABLE();
+      }
     }
     bool super_access = property->IsSuperAccess();
     return (property->key()->IsPropertyName())
diff --git a/src/ast/prettyprinter.cc b/src/ast/prettyprinter.cc
index c0fe3ba..8883a20 100644
--- a/src/ast/prettyprinter.cc
+++ b/src/ast/prettyprinter.cc
@@ -1289,6 +1289,18 @@
       PrintIndentedVisit("PRIVATE_METHOD", node->key());
       break;
     }
+    case PRIVATE_GETTER_ONLY: {
+      PrintIndentedVisit("PRIVATE_GETTER_ONLY", node->key());
+      break;
+    }
+    case PRIVATE_SETTER_ONLY: {
+      PrintIndentedVisit("PRIVATE_SETTER_ONLY", node->key());
+      break;
+    }
+    case PRIVATE_GETTER_AND_SETTER: {
+      PrintIndentedVisit("PRIVATE_GETTER_AND_SETTER", node->key());
+      break;
+    }
     case KEYED_PROPERTY:
     case KEYED_SUPER_PROPERTY: {
       PrintIndentedVisit("KEY", node->key());
diff --git a/src/ast/scopes.cc b/src/ast/scopes.cc
index 237d98e..f8da533 100644
--- a/src/ast/scopes.cc
+++ b/src/ast/scopes.cc
@@ -40,7 +40,6 @@
                                VariableKind kind,
                                InitializationFlag initialization_flag,
                                MaybeAssignedFlag maybe_assigned_flag,
-                               RequiresBrandCheckFlag requires_brand_check,
                                bool* was_added) {
   // AstRawStrings are unambiguous, i.e., the same string is always represented
   // by the same AstRawString*.
@@ -52,9 +51,8 @@
   if (*was_added) {
     // The variable has not been declared yet -> insert it.
     DCHECK_EQ(name, p->key);
-    Variable* variable =
-        new (zone) Variable(scope, name, mode, kind, initialization_flag,
-                            maybe_assigned_flag, requires_brand_check);
+    Variable* variable = new (zone) Variable(
+        scope, name, mode, kind, initialization_flag, maybe_assigned_flag);
     p->value = variable;
   }
   return reinterpret_cast<Variable*>(p->value);
@@ -787,13 +785,11 @@
   VariableMode mode;
   InitializationFlag init_flag;
   MaybeAssignedFlag maybe_assigned_flag;
-  RequiresBrandCheckFlag requires_brand_check = kNoBrandCheck;
 
   {
     location = VariableLocation::CONTEXT;
     index = ScopeInfo::ContextSlotIndex(*scope_info_, name_handle, &mode,
-                                        &init_flag, &maybe_assigned_flag,
-                                        &requires_brand_check);
+                                        &init_flag, &maybe_assigned_flag);
     found = index >= 0;
   }
 
@@ -818,9 +814,9 @@
   }
 
   bool was_added;
-  Variable* var = cache->variables_.Declare(
-      zone(), this, name, mode, NORMAL_VARIABLE, init_flag, maybe_assigned_flag,
-      requires_brand_check, &was_added);
+  Variable* var =
+      cache->variables_.Declare(zone(), this, name, mode, NORMAL_VARIABLE,
+                                init_flag, maybe_assigned_flag, &was_added);
   DCHECK(was_added);
   var->AllocateTo(location, index);
   return var;
@@ -873,6 +869,8 @@
                               VariableKind kind, bool* was_added,
                               InitializationFlag init_flag) {
   DCHECK(!already_resolved_);
+  // Private methods should be declared with ClassScope::DeclarePrivateName()
+  DCHECK(!IsPrivateMethodOrAccessorVariableMode(mode));
   // This function handles VariableMode::kVar, VariableMode::kLet, and
   // VariableMode::kConst modes.  VariableMode::kDynamic variables are
   // introduced during variable allocation, and VariableMode::kTemporary
@@ -905,6 +903,8 @@
     VariableMode mode, VariableKind kind, InitializationFlag init,
     bool* was_added, bool* sloppy_mode_block_scope_function_redefinition,
     bool* ok) {
+  // Private methods should be declared with ClassScope::DeclarePrivateName()
+  DCHECK(!IsPrivateMethodOrAccessorVariableMode(mode));
   DCHECK(IsDeclaredVariableMode(mode));
   DCHECK(!already_resolved_);
   DCHECK(!GetDeclarationScope()->is_being_lazily_parsed());
@@ -990,7 +990,8 @@
   DCHECK(IsDeclaredVariableMode(mode));
   DCHECK(!already_resolved_);
   DCHECK(GetDeclarationScope()->is_being_lazily_parsed());
-
+  // Private methods should be declared with ClassScope::DeclarePrivateName()
+  DCHECK(!IsPrivateMethodOrAccessorVariableMode(mode));
   if (mode == VariableMode::kVar && !is_declaration_scope()) {
     return GetDeclarationScope()->DeclareVariableName(name, mode, was_added,
                                                       kind);
@@ -1044,7 +1045,7 @@
   bool was_added;
   return cache->variables_.Declare(
       zone(), this, name, VariableMode::kDynamicGlobal, kind,
-      kCreatedInitialized, kNotAssigned, kNoBrandCheck, &was_added);
+      kCreatedInitialized, kNotAssigned, &was_added);
   // TODO(neis): Mark variable as maybe-assigned?
 }
 
@@ -1596,10 +1597,6 @@
     if (comma) PrintF(", ");
     PrintF("hole initialization elided");
   }
-  if (var->requires_brand_check()) {
-    if (comma) PrintF(", ");
-    PrintF("requires brand check");
-  }
   PrintF("\n");
 }
 
@@ -1774,9 +1771,9 @@
   // Declare a new non-local.
   DCHECK(IsDynamicVariableMode(mode));
   bool was_added;
-  Variable* var = variables_.Declare(zone(), this, name, mode, NORMAL_VARIABLE,
-                                     kCreatedInitialized, kNotAssigned,
-                                     kNoBrandCheck, &was_added);
+  Variable* var =
+      variables_.Declare(zone(), this, name, mode, NORMAL_VARIABLE,
+                         kCreatedInitialized, kNotAssigned, &was_added);
   // Allocate it by giving it a dynamic lookup.
   var->AllocateTo(VariableLocation::LOOKUP, -1);
   return var;
@@ -2081,11 +2078,14 @@
   //
   // Temporary variables are always stack-allocated.  Catch-bound variables are
   // always context-allocated.
-  if (var->mode() == VariableMode::kTemporary) return false;
+  VariableMode mode = var->mode();
+  if (mode == VariableMode::kTemporary) return false;
   if (is_catch_scope()) return true;
-  if ((is_script_scope() || is_eval_scope()) &&
-      IsLexicalVariableMode(var->mode())) {
-    return true;
+  if (is_script_scope() || is_eval_scope()) {
+    if (IsLexicalVariableMode(mode) ||
+        IsPrivateMethodOrAccessorVariableMode(mode)) {
+      return true;
+    }
   }
   return var->has_forced_context_allocation() || inner_scope_calls_eval_;
 }
@@ -2326,15 +2326,28 @@
          (is_function_var_in_context ? 1 : 0);
 }
 
-Variable* ClassScope::DeclarePrivateName(
-    const AstRawString* name, RequiresBrandCheckFlag requires_brand_check,
-    bool* was_added) {
+bool IsComplementaryAccessorPair(VariableMode a, VariableMode b) {
+  switch (a) {
+    case VariableMode::kPrivateGetterOnly:
+      return b == VariableMode::kPrivateSetterOnly;
+    case VariableMode::kPrivateSetterOnly:
+      return b == VariableMode::kPrivateGetterOnly;
+    default:
+      return false;
+  }
+}
+
+Variable* ClassScope::DeclarePrivateName(const AstRawString* name,
+                                         VariableMode mode, bool* was_added) {
   Variable* result = EnsureRareData()->private_name_map.Declare(
-      zone(), this, name, VariableMode::kConst, NORMAL_VARIABLE,
+      zone(), this, name, mode, NORMAL_VARIABLE,
       InitializationFlag::kNeedsInitialization,
-      MaybeAssignedFlag::kMaybeAssigned, requires_brand_check, was_added);
+      MaybeAssignedFlag::kMaybeAssigned, was_added);
   if (*was_added) {
     locals_.Add(result);
+  } else if (IsComplementaryAccessorPair(result->mode(), mode)) {
+    *was_added = true;
+    result->set_mode(VariableMode::kPrivateGetterAndSetter);
   }
   result->ForceContextAllocation();
   return result;
@@ -2416,22 +2429,20 @@
   VariableMode mode;
   InitializationFlag init_flag;
   MaybeAssignedFlag maybe_assigned_flag;
-  RequiresBrandCheckFlag requires_brand_check;
-  int index =
-      ScopeInfo::ContextSlotIndex(*scope_info_, name_handle, &mode, &init_flag,
-                                  &maybe_assigned_flag, &requires_brand_check);
+  int index = ScopeInfo::ContextSlotIndex(*scope_info_, name_handle, &mode,
+                                          &init_flag, &maybe_assigned_flag);
   if (index < 0) {
     return nullptr;
   }
 
-  DCHECK_EQ(mode, VariableMode::kConst);
+  DCHECK(IsConstVariableMode(mode));
   DCHECK_EQ(init_flag, InitializationFlag::kNeedsInitialization);
   DCHECK_EQ(maybe_assigned_flag, MaybeAssignedFlag::kMaybeAssigned);
 
   // Add the found private name to the map to speed up subsequent
   // lookups for the same name.
   bool was_added;
-  Variable* var = DeclarePrivateName(name, requires_brand_check, &was_added);
+  Variable* var = DeclarePrivateName(name, mode, &was_added);
   DCHECK(was_added);
   var->AllocateTo(VariableLocation::CONTEXT, index);
   return var;
diff --git a/src/ast/scopes.h b/src/ast/scopes.h
index 932d5c7..76480b2 100644
--- a/src/ast/scopes.h
+++ b/src/ast/scopes.h
@@ -42,7 +42,6 @@
                     VariableMode mode, VariableKind kind,
                     InitializationFlag initialization_flag,
                     MaybeAssignedFlag maybe_assigned_flag,
-                    RequiresBrandCheckFlag requires_brand_check,
                     bool* was_added);
 
   V8_EXPORT_PRIVATE Variable* Lookup(const AstRawString* name);
@@ -558,7 +557,7 @@
                     MaybeAssignedFlag maybe_assigned_flag, bool* was_added) {
     Variable* result =
         variables_.Declare(zone, this, name, mode, kind, initialization_flag,
-                           maybe_assigned_flag, kNoBrandCheck, was_added);
+                           maybe_assigned_flag, was_added);
     if (*was_added) locals_.Add(result);
     return result;
   }
@@ -1175,8 +1174,7 @@
 
   // Declare a private name in the private name map and add it to the
   // local variables of this scope.
-  Variable* DeclarePrivateName(const AstRawString* name,
-                               RequiresBrandCheckFlag requires_brand_check,
+  Variable* DeclarePrivateName(const AstRawString* name, VariableMode mode,
                                bool* was_added);
 
   void AddUnresolvedPrivateName(VariableProxy* proxy);
diff --git a/src/ast/variables.h b/src/ast/variables.h
index c7b47b1..3358915 100644
--- a/src/ast/variables.h
+++ b/src/ast/variables.h
@@ -21,8 +21,7 @@
  public:
   Variable(Scope* scope, const AstRawString* name, VariableMode mode,
            VariableKind kind, InitializationFlag initialization_flag,
-           MaybeAssignedFlag maybe_assigned_flag = kNotAssigned,
-           RequiresBrandCheckFlag requires_brand_check = kNoBrandCheck)
+           MaybeAssignedFlag maybe_assigned_flag = kNotAssigned)
       : scope_(scope),
         name_(name),
         local_if_not_shadowed_(nullptr),
@@ -32,7 +31,6 @@
         bit_field_(MaybeAssignedFlagField::encode(maybe_assigned_flag) |
                    InitializationFlagField::encode(initialization_flag) |
                    VariableModeField::encode(mode) |
-                   RequiresBrandCheckField::encode(requires_brand_check) |
                    IsUsedField::encode(false) |
                    ForceContextAllocationField::encode(false) |
                    ForceHoleInitializationField::encode(false) |
@@ -58,6 +56,9 @@
   Handle<String> name() const { return name_->string(); }
   const AstRawString* raw_name() const { return name_; }
   VariableMode mode() const { return VariableModeField::decode(bit_field_); }
+  void set_mode(VariableMode mode) {
+    bit_field_ = VariableModeField::update(bit_field_, mode);
+  }
   bool has_forced_context_allocation() const {
     return ForceContextAllocationField::decode(bit_field_);
   }
@@ -85,17 +86,8 @@
     set_maybe_assigned();
   }
 
-  RequiresBrandCheckFlag get_requires_brand_check_flag() const {
-    return RequiresBrandCheckField::decode(bit_field_);
-  }
-
   bool requires_brand_check() const {
-    return get_requires_brand_check_flag() == kRequiresBrandCheck;
-  }
-
-  void set_requires_brand_check() {
-    bit_field_ =
-        RequiresBrandCheckField::update(bit_field_, kRequiresBrandCheck);
+    return IsPrivateMethodOrAccessorVariableMode(mode());
   }
 
   int initializer_position() { return initializer_position_; }
@@ -125,7 +117,8 @@
   // declaration time. Only returns valid results after scope analysis.
   bool binding_needs_init() const {
     DCHECK_IMPLIES(initialization_flag() == kNeedsInitialization,
-                   IsLexicalVariableMode(mode()));
+                   IsLexicalVariableMode(mode()) ||
+                       IsPrivateMethodOrAccessorVariableMode(mode()));
     DCHECK_IMPLIES(ForceHoleInitializationField::decode(bit_field_),
                    initialization_flag() == kNeedsInitialization);
 
@@ -149,7 +142,8 @@
   // be required at runtime.
   void ForceHoleInitialization() {
     DCHECK_EQ(kNeedsInitialization, initialization_flag());
-    DCHECK(IsLexicalVariableMode(mode()));
+    DCHECK(IsLexicalVariableMode(mode()) ||
+           IsPrivateMethodOrAccessorVariableMode(mode()));
     bit_field_ = ForceHoleInitializationField::update(bit_field_, true);
   }
 
@@ -243,7 +237,7 @@
     bit_field_ = MaybeAssignedFlagField::update(bit_field_, kMaybeAssigned);
   }
 
-  using VariableModeField = BitField16<VariableMode, 0, 3>;
+  using VariableModeField = BitField16<VariableMode, 0, 4>;
   using VariableKindField =
       BitField16<VariableKind, VariableModeField::kNext, 3>;
   using LocationField =
@@ -256,8 +250,7 @@
       BitField16<bool, InitializationFlagField::kNext, 1>;
   using MaybeAssignedFlagField =
       BitField16<MaybeAssignedFlag, ForceHoleInitializationField::kNext, 1>;
-  using RequiresBrandCheckField =
-      BitField16<RequiresBrandCheckFlag, MaybeAssignedFlagField::kNext, 1>;
+
   Variable** next() { return &next_; }
   friend List;
   friend base::ThreadedListTraits<Variable>;
diff --git a/src/common/globals.h b/src/common/globals.h
index 8d1bf5d..eeeaa4f 100644
--- a/src/common/globals.h
+++ b/src/common/globals.h
@@ -1060,6 +1060,25 @@
                   // has been shadowed by an eval-introduced
                   // variable
 
+  // Variables for private methods or accessors whose access require
+  // brand check. Declared only in class scopes by the compiler
+  // and allocated only in class contexts:
+  kPrivateMethod,  // Does not coexist with any other variable with the same
+                   // name in the same scope.
+
+  kPrivateSetterOnly,  // Incompatible with variables with the same name but
+                       // any mode other than kPrivateGetterOnly. Transition to
+                       // kPrivateGetterAndSetter if a later declaration for the
+                       // same name with kPrivateGetterOnly is made.
+
+  kPrivateGetterOnly,  // Incompatible with variables with the same name but
+                       // any mode other than kPrivateSetterOnly. Transition to
+                       // kPrivateGetterAndSetter if a later declaration for the
+                       // same name with kPrivateSetterOnly is made.
+
+  kPrivateGetterAndSetter,  // Does not coexist with any other variable with the
+                            // same name in the same scope.
+
   kLastLexicalVariableMode = kConst,
 };
 
@@ -1071,6 +1090,14 @@
       return "VAR";
     case VariableMode::kLet:
       return "LET";
+    case VariableMode::kPrivateGetterOnly:
+      return "PRIVATE_GETTER_ONLY";
+    case VariableMode::kPrivateSetterOnly:
+      return "PRIVATE_SETTER_ONLY";
+    case VariableMode::kPrivateMethod:
+      return "PRIVATE_METHOD";
+    case VariableMode::kPrivateGetterAndSetter:
+      return "PRIVATE_GETTER_AND_SETTER";
     case VariableMode::kConst:
       return "CONST";
     case VariableMode::kDynamic:
@@ -1104,6 +1131,21 @@
   return mode <= VariableMode::kVar;
 }
 
+inline bool IsPrivateMethodOrAccessorVariableMode(VariableMode mode) {
+  return mode >= VariableMode::kPrivateMethod &&
+         mode <= VariableMode::kPrivateGetterAndSetter;
+}
+
+inline bool IsSerializableVariableMode(VariableMode mode) {
+  return IsDeclaredVariableMode(mode) ||
+         IsPrivateMethodOrAccessorVariableMode(mode);
+}
+
+inline bool IsConstVariableMode(VariableMode mode) {
+  return mode == VariableMode::kConst ||
+         IsPrivateMethodOrAccessorVariableMode(mode);
+}
+
 inline bool IsLexicalVariableMode(VariableMode mode) {
   STATIC_ASSERT(static_cast<uint8_t>(VariableMode::kLet) ==
                 0);  // Implies that mode >= VariableMode::kLet.
@@ -1168,8 +1210,6 @@
 
 enum MaybeAssignedFlag : uint8_t { kNotAssigned, kMaybeAssigned };
 
-enum RequiresBrandCheckFlag : uint8_t { kNoBrandCheck, kRequiresBrandCheck };
-
 enum class InterpreterPushArgsMode : unsigned {
   kArrayFunction,
   kWithFinalSpread,
diff --git a/src/debug/debug-frames.cc b/src/debug/debug-frames.cc
index 4fe062b..78c4c32 100644
--- a/src/debug/debug-frames.cc
+++ b/src/debug/debug-frames.cc
@@ -93,10 +93,8 @@
   VariableMode mode;
   InitializationFlag init_flag;
   MaybeAssignedFlag maybe_assigned_flag;
-  RequiresBrandCheckFlag requires_brand_check;
   return ScopeInfo::ContextSlotIndex(*info, *parameter_name, &mode, &init_flag,
-                                     &maybe_assigned_flag,
-                                     &requires_brand_check) != -1;
+                                     &maybe_assigned_flag) != -1;
 }
 
 RedirectActiveFunctions::RedirectActiveFunctions(SharedFunctionInfo shared,
diff --git a/src/debug/debug-scopes.cc b/src/debug/debug-scopes.cc
index 1091e3a..c9c859c 100644
--- a/src/debug/debug-scopes.cc
+++ b/src/debug/debug-scopes.cc
@@ -884,10 +884,9 @@
   VariableMode mode;
   InitializationFlag flag;
   MaybeAssignedFlag maybe_assigned_flag;
-  RequiresBrandCheckFlag requires_brand_check;
-  int slot_index = ScopeInfo::ContextSlotIndex(
-      context_->scope_info(), *variable_name, &mode, &flag,
-      &maybe_assigned_flag, &requires_brand_check);
+  int slot_index =
+      ScopeInfo::ContextSlotIndex(context_->scope_info(), *variable_name, &mode,
+                                  &flag, &maybe_assigned_flag);
   if (slot_index < 0) return false;
 
   context_->set(slot_index, *new_value);
diff --git a/src/debug/debug-stack-trace-iterator.cc b/src/debug/debug-stack-trace-iterator.cc
index a0c6fa9..4f691e6 100644
--- a/src/debug/debug-stack-trace-iterator.cc
+++ b/src/debug/debug-stack-trace-iterator.cc
@@ -98,10 +98,9 @@
     VariableMode mode;
     InitializationFlag flag;
     MaybeAssignedFlag maybe_assigned_flag;
-    RequiresBrandCheckFlag requires_brand_check;
     int slot_index = ScopeInfo::ContextSlotIndex(
         context->scope_info(), ReadOnlyRoots(isolate_->heap()).this_string(),
-        &mode, &flag, &maybe_assigned_flag, &requires_brand_check);
+        &mode, &flag, &maybe_assigned_flag);
     if (slot_index < 0) return v8::MaybeLocal<v8::Value>();
     Handle<Object> value = handle(context->get(slot_index), isolate_);
     if (value->IsTheHole(isolate_)) return v8::MaybeLocal<v8::Value>();
diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
index d3b27b4..3091111 100644
--- a/src/interpreter/bytecode-generator.cc
+++ b/src/interpreter/bytecode-generator.cc
@@ -2005,9 +2005,11 @@
                               HoleCheckMode::kElided);
       break;
     }
-    default:
+    case ClassLiteral::Property::GETTER:
+    case ClassLiteral::Property::SETTER: {
       // TODO(joyee): Private accessors are not yet supported.
-      UNREACHABLE();
+      // Make them noops for now.
+    }
   }
 }
 
@@ -3017,6 +3019,7 @@
 
 void BytecodeGenerator::BuildHoleCheckForVariableAssignment(Variable* variable,
                                                             Token::Value op) {
+  DCHECK(!IsPrivateMethodOrAccessorVariableMode(variable->mode()));
   if (variable->is_this() && variable->mode() == VariableMode::kConst &&
       op == Token::INIT) {
     // Perform an initialization check for 'this'. 'this' variable is the
@@ -3201,10 +3204,10 @@
 }
 // static
 BytecodeGenerator::AssignmentLhsData
-BytecodeGenerator::AssignmentLhsData::PrivateMethod(Register object,
-                                                    const AstRawString* name) {
-  return AssignmentLhsData(PRIVATE_METHOD, nullptr, RegisterList(), object,
-                           Register(), nullptr, name);
+BytecodeGenerator::AssignmentLhsData::PrivateMethodOrAccessor(
+    AssignType type, Property* property) {
+  return AssignmentLhsData(type, property, RegisterList(), Register(),
+                           Register(), nullptr, nullptr);
 }
 // static
 BytecodeGenerator::AssignmentLhsData
@@ -3237,12 +3240,12 @@
       Register key = VisitForRegisterValue(property->key());
       return AssignmentLhsData::KeyedProperty(object, key);
     }
-    case PRIVATE_METHOD: {
+    case PRIVATE_METHOD:
+    case PRIVATE_GETTER_ONLY:
+    case PRIVATE_SETTER_ONLY:
+    case PRIVATE_GETTER_AND_SETTER: {
       DCHECK(!property->IsSuperAccess());
-      AccumulatorPreservingScope scope(this, accumulator_preserving_mode);
-      Register object = VisitForRegisterValue(property->obj());
-      const AstRawString* name = property->key()->AsVariableProxy()->raw_name();
-      return AssignmentLhsData::PrivateMethod(object, name);
+      return AssignmentLhsData::PrivateMethodOrAccessor(assign_type, property);
     }
     case NAMED_SUPER_PROPERTY: {
       AccumulatorPreservingScope scope(this, accumulator_preserving_mode);
@@ -3799,9 +3802,16 @@
       break;
     }
     case PRIVATE_METHOD: {
-      BuildThrowPrivateMethodWriteError(lhs_data.name());
+      BuildThrowPrivateMethodWriteError(
+          lhs_data.expr()->AsProperty()->key()->AsVariableProxy()->raw_name());
       break;
     }
+    case PRIVATE_GETTER_ONLY:
+    case PRIVATE_SETTER_ONLY:
+    case PRIVATE_GETTER_AND_SETTER: {
+      // TODO(joyee): implement private accessors.
+      return;
+    }
   }
 }
 
@@ -3848,10 +3858,18 @@
       break;
     }
     case PRIVATE_METHOD: {
-      BuildThrowPrivateMethodWriteError(lhs_data.name());
+      BuildThrowPrivateMethodWriteError(
+          lhs_data.expr()->AsProperty()->key()->AsVariableProxy()->raw_name());
       break;
     }
+    case PRIVATE_SETTER_ONLY:
+    case PRIVATE_GETTER_ONLY:
+    case PRIVATE_GETTER_AND_SETTER: {
+      // TODO(joyee): implement private accessors.
+      return;
+    }
   }
+
   BinaryOperation* binop = expr->AsCompoundAssignment()->binary_operation();
   FeedbackSlot slot = feedback_spec()->AddBinaryOpICSlot();
   if (expr->value()->IsSmiLiteral()) {
@@ -4325,6 +4343,12 @@
       VisitForAccumulatorValue(property->key());
       break;
     }
+    case PRIVATE_SETTER_ONLY:
+    case PRIVATE_GETTER_ONLY:
+    case PRIVATE_GETTER_AND_SETTER: {
+      // TODO(joyee): implement private accessors.
+      return;
+    }
   }
 }
 
@@ -4879,6 +4903,12 @@
           property->key()->AsVariableProxy()->raw_name());
       break;
     }
+    case PRIVATE_GETTER_ONLY:
+    case PRIVATE_SETTER_ONLY:
+    case PRIVATE_GETTER_AND_SETTER: {
+      // TODO(joyee): implement private accessors.
+      return;
+    }
   }
 
   // Save result for postfix expressions.
@@ -4950,6 +4980,12 @@
           property->key()->AsVariableProxy()->raw_name());
       break;
     }
+    case PRIVATE_GETTER_ONLY:
+    case PRIVATE_SETTER_ONLY:
+    case PRIVATE_GETTER_AND_SETTER: {
+      // TODO(joyee): implement private accessors.
+      UNREACHABLE();
+    }
   }
 
   // Restore old value for postfix expressions.
diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
index b754d2c..5cbc566 100644
--- a/src/interpreter/bytecode-generator.h
+++ b/src/interpreter/bytecode-generator.h
@@ -84,8 +84,8 @@
                                            Register object,
                                            const AstRawString* name);
     static AssignmentLhsData KeyedProperty(Register object, Register key);
-    static AssignmentLhsData PrivateMethod(Register object,
-                                           const AstRawString* name);
+    static AssignmentLhsData PrivateMethodOrAccessor(AssignType type,
+                                                     Property* property);
     static AssignmentLhsData NamedSuperProperty(
         RegisterList super_property_args);
     static AssignmentLhsData KeyedSuperProperty(
@@ -93,7 +93,10 @@
 
     AssignType assign_type() const { return assign_type_; }
     Expression* expr() const {
-      DCHECK_EQ(assign_type_, NON_PROPERTY);
+      DCHECK(assign_type_ == NON_PROPERTY || assign_type_ == PRIVATE_METHOD ||
+             assign_type_ == PRIVATE_GETTER_ONLY ||
+             assign_type_ == PRIVATE_SETTER_ONLY ||
+             assign_type_ == PRIVATE_GETTER_AND_SETTER);
       return expr_;
     }
     Expression* object_expr() const {
@@ -101,8 +104,7 @@
       return object_expr_;
     }
     Register object() const {
-      DCHECK(assign_type_ == NAMED_PROPERTY || assign_type_ == KEYED_PROPERTY ||
-             assign_type_ == PRIVATE_METHOD);
+      DCHECK(assign_type_ == NAMED_PROPERTY || assign_type_ == KEYED_PROPERTY);
       return object_;
     }
     Register key() const {
@@ -110,7 +112,7 @@
       return key_;
     }
     const AstRawString* name() const {
-      DCHECK(assign_type_ == NAMED_PROPERTY || assign_type_ == PRIVATE_METHOD);
+      DCHECK(assign_type_ == NAMED_PROPERTY);
       return name_;
     }
     RegisterList super_property_args() const {
diff --git a/src/objects/contexts.cc b/src/objects/contexts.cc
index 861e06d..74fb447 100644
--- a/src/objects/contexts.cc
+++ b/src/objects/contexts.cc
@@ -44,7 +44,7 @@
     DCHECK(context.IsScriptContext());
     int slot_index = ScopeInfo::ContextSlotIndex(
         context.scope_info(), name, &result->mode, &result->init_flag,
-        &result->maybe_assigned_flag, &result->requires_brand_check);
+        &result->maybe_assigned_flag);
 
     if (slot_index >= 0) {
       result->context_index = i;
@@ -161,8 +161,8 @@
 }
 
 static PropertyAttributes GetAttributesForMode(VariableMode mode) {
-  DCHECK(IsDeclaredVariableMode(mode));
-  return mode == VariableMode::kConst ? READ_ONLY : NONE;
+  DCHECK(IsSerializableVariableMode(mode));
+  return IsConstVariableMode(mode) ? READ_ONLY : NONE;
 }
 
 // static
@@ -287,10 +287,8 @@
       VariableMode mode;
       InitializationFlag flag;
       MaybeAssignedFlag maybe_assigned_flag;
-      RequiresBrandCheckFlag requires_brand_check;
       int slot_index = ScopeInfo::ContextSlotIndex(scope_info, *name, &mode,
-                                                   &flag, &maybe_assigned_flag,
-                                                   &requires_brand_check);
+                                                   &flag, &maybe_assigned_flag);
       DCHECK(slot_index < 0 || slot_index >= MIN_CONTEXT_SLOTS);
       if (slot_index >= 0) {
         if (FLAG_trace_contexts) {
diff --git a/src/objects/contexts.h b/src/objects/contexts.h
index da400ee..1a0f885 100644
--- a/src/objects/contexts.h
+++ b/src/objects/contexts.h
@@ -361,7 +361,6 @@
     VariableMode mode;
     InitializationFlag init_flag;
     MaybeAssignedFlag maybe_assigned_flag;
-    RequiresBrandCheckFlag requires_brand_check;
   };
 
   inline int used() const;
diff --git a/src/objects/scope-info.cc b/src/objects/scope-info.cc
index eca8bc1..95d97f9 100644
--- a/src/objects/scope-info.cc
+++ b/src/objects/scope-info.cc
@@ -218,8 +218,6 @@
           uint32_t info =
               VariableModeField::encode(var->mode()) |
               InitFlagField::encode(var->initialization_flag()) |
-              RequiresBrandCheckField::encode(
-                  var->get_requires_brand_check_flag()) |
               MaybeAssignedFlagField::encode(var->maybe_assigned()) |
               ParameterNumberField::encode(ParameterNumberField::kMax);
           scope_info.set(context_local_base + local_index, *var->name(), mode);
@@ -236,8 +234,6 @@
               VariableModeField::encode(var->mode()) |
               InitFlagField::encode(var->initialization_flag()) |
               MaybeAssignedFlagField::encode(var->maybe_assigned()) |
-              RequiresBrandCheckField::encode(
-                  var->get_requires_brand_check_flag()) |
               ParameterNumberField::encode(ParameterNumberField::kMax);
           scope_info.set(module_var_entry + kModuleVariablePropertiesOffset,
                          Smi::FromInt(properties));
@@ -276,8 +272,6 @@
               VariableModeField::encode(var->mode()) |
               InitFlagField::encode(var->initialization_flag()) |
               MaybeAssignedFlagField::encode(var->maybe_assigned()) |
-              RequiresBrandCheckField::encode(
-                  var->get_requires_brand_check_flag()) |
               ParameterNumberField::encode(ParameterNumberField::kMax);
           scope_info.set(context_local_base + local_index, *var->name(), mode);
           scope_info.set(context_local_info_base + local_index,
@@ -451,7 +445,6 @@
         VariableModeField::encode(VariableMode::kConst) |
         InitFlagField::encode(kCreatedInitialized) |
         MaybeAssignedFlagField::encode(kNotAssigned) |
-        RequiresBrandCheckField::encode(kNoBrandCheck) |
         ParameterNumberField::encode(ParameterNumberField::kMax);
     scope_info->set(index++, Smi::FromInt(value));
   }
@@ -708,14 +701,6 @@
   return MaybeAssignedFlagField::decode(value);
 }
 
-RequiresBrandCheckFlag ScopeInfo::RequiresBrandCheck(int var) const {
-  DCHECK_LE(0, var);
-  DCHECK_LT(var, ContextLocalCount());
-  int info_index = ContextLocalInfosIndex() + var;
-  int value = Smi::ToInt(get(info_index));
-  return RequiresBrandCheckField::decode(value);
-}
-
 // static
 bool ScopeInfo::VariableIsSynthetic(String name) {
   // There's currently no flag stored on the ScopeInfo to indicate that a
@@ -755,8 +740,7 @@
 int ScopeInfo::ContextSlotIndex(ScopeInfo scope_info, String name,
                                 VariableMode* mode,
                                 InitializationFlag* init_flag,
-                                MaybeAssignedFlag* maybe_assigned_flag,
-                                RequiresBrandCheckFlag* requires_brand_check) {
+                                MaybeAssignedFlag* maybe_assigned_flag) {
   DisallowHeapAllocation no_gc;
   DCHECK(name.IsInternalizedString());
   DCHECK_NOT_NULL(mode);
@@ -773,7 +757,6 @@
     *mode = scope_info.ContextLocalMode(var);
     *init_flag = scope_info.ContextLocalInitFlag(var);
     *maybe_assigned_flag = scope_info.ContextLocalMaybeAssignedFlag(var);
-    *requires_brand_check = scope_info.RequiresBrandCheck(var);
     int result = Context::MIN_CONTEXT_SLOTS + var;
 
     DCHECK_LT(result, scope_info.ContextLength());
diff --git a/src/objects/scope-info.h b/src/objects/scope-info.h
index 746322e..7631676 100644
--- a/src/objects/scope-info.h
+++ b/src/objects/scope-info.h
@@ -130,9 +130,6 @@
   // Return the initialization flag of the given context local.
   MaybeAssignedFlag ContextLocalMaybeAssignedFlag(int var) const;
 
-  // Return whether access to the variable requries a brand check.
-  RequiresBrandCheckFlag RequiresBrandCheck(int var) const;
-
   // Return true if this local was introduced by the compiler, and should not be
   // exposed to the user in a debugger.
   static bool VariableIsSynthetic(String name);
@@ -144,8 +141,7 @@
   // mode for that variable.
   static int ContextSlotIndex(ScopeInfo scope_info, String name,
                               VariableMode* mode, InitializationFlag* init_flag,
-                              MaybeAssignedFlag* maybe_assigned_flag,
-                              RequiresBrandCheckFlag* requires_brand_check);
+                              MaybeAssignedFlag* maybe_assigned_flag);
 
   // Lookup metadata of a MODULE-allocated variable.  Return 0 if there is no
   // module variable with the given name (the index value of a MODULE variable
@@ -316,13 +312,13 @@
   static const int kPositionInfoEntries = 2;
 
   // Properties of variables.
-  using VariableModeField = BitField<VariableMode, 0, 3>;
-  using InitFlagField = BitField<InitializationFlag, 3, 1>;
-  using MaybeAssignedFlagField = BitField<MaybeAssignedFlag, 4, 1>;
-  using RequiresBrandCheckField =
-      BitField<RequiresBrandCheckFlag, MaybeAssignedFlagField::kNext, 1>;
+  using VariableModeField = BitField<VariableMode, 0, 4>;
+  using InitFlagField =
+      BitField<InitializationFlag, VariableModeField::kNext, 1>;
+  using MaybeAssignedFlagField =
+      BitField<MaybeAssignedFlag, InitFlagField::kNext, 1>;
   using ParameterNumberField =
-      BitField<uint32_t, RequiresBrandCheckField::kNext, 16>;
+      BitField<uint32_t, MaybeAssignedFlagField::kNext, 16>;
 
   friend class ScopeIterator;
   friend std::ostream& operator<<(std::ostream& os,
diff --git a/src/parsing/parser-base.h b/src/parsing/parser-base.h
index 737be1f..14bc428 100644
--- a/src/parsing/parser-base.h
+++ b/src/parsing/parser-base.h
@@ -625,9 +625,17 @@
     }
   }
 
-  RequiresBrandCheckFlag RequiresBrandCheck(ClassLiteralProperty::Kind kind) {
-    return kind == ClassLiteralProperty::Kind::FIELD ? kNoBrandCheck
-                                                     : kRequiresBrandCheck;
+  VariableMode GetVariableMode(ClassLiteralProperty::Kind kind) {
+    switch (kind) {
+      case ClassLiteralProperty::Kind::FIELD:
+        return VariableMode::kConst;
+      case ClassLiteralProperty::Kind::METHOD:
+        return VariableMode::kPrivateMethod;
+      case ClassLiteralProperty::Kind::GETTER:
+        return VariableMode::kPrivateGetterOnly;
+      case ClassLiteralProperty::Kind::SETTER:
+        return VariableMode::kPrivateSetterOnly;
+    }
   }
 
   const AstRawString* ClassFieldVariableName(AstValueFactory* ast_value_factory,
diff --git a/src/parsing/parser.cc b/src/parsing/parser.cc
index a0f2a80..43d3b87 100644
--- a/src/parsing/parser.cc
+++ b/src/parsing/parser.cc
@@ -2782,15 +2782,15 @@
   return proxy->var();
 }
 
-Variable* Parser::CreatePrivateNameVariable(
-    ClassScope* scope, RequiresBrandCheckFlag requires_brand_check,
-    const AstRawString* name) {
+Variable* Parser::CreatePrivateNameVariable(ClassScope* scope,
+                                            VariableMode mode,
+                                            const AstRawString* name) {
   DCHECK_NOT_NULL(name);
   int begin = position();
   int end = end_position();
   bool was_added = false;
-  Variable* var =
-      scope->DeclarePrivateName(name, requires_brand_check, &was_added);
+  DCHECK(IsConstVariableMode(mode));
+  Variable* var = scope->DeclarePrivateName(name, mode, &was_added);
   if (!was_added) {
     Scanner::Location loc(begin, end);
     ReportMessageAt(loc, MessageTemplate::kVarRedeclaration, var->raw_name());
@@ -2825,14 +2825,8 @@
                                        ClassLiteralProperty* property,
                                        ClassLiteralProperty::Kind kind,
                                        bool is_static, ClassInfo* class_info) {
-  DCHECK_IMPLIES(kind == ClassLiteralProperty::Kind::METHOD,
+  DCHECK_IMPLIES(kind != ClassLiteralProperty::Kind::FIELD,
                  allow_harmony_private_methods());
-  // TODO(joyee): We do not support private accessors yet (which allow
-  // declaring the same private name twice). Make them noops.
-  if (kind != ClassLiteralProperty::Kind::FIELD &&
-      kind != ClassLiteralProperty::Kind::METHOD) {
-    return;
-  }
 
   if (kind == ClassLiteralProperty::Kind::FIELD) {
     if (is_static) {
@@ -2843,7 +2837,7 @@
   }
 
   Variable* private_name_var =
-      CreatePrivateNameVariable(scope, RequiresBrandCheck(kind), property_name);
+      CreatePrivateNameVariable(scope, GetVariableMode(kind), property_name);
   int pos = property->value()->position();
   if (pos == kNoSourcePosition) {
     pos = property->key()->position();
diff --git a/src/parsing/parser.h b/src/parsing/parser.h
index e20c153..2a22e19 100644
--- a/src/parsing/parser.h
+++ b/src/parsing/parser.h
@@ -297,9 +297,8 @@
                              VariableKind kind, int beg_pos, int end_pos,
                              ZonePtrList<const AstRawString>* names);
   Variable* CreateSyntheticContextVariable(const AstRawString* synthetic_name);
-  Variable* CreatePrivateNameVariable(
-      ClassScope* scope, RequiresBrandCheckFlag requires_brand_check,
-      const AstRawString* name);
+  Variable* CreatePrivateNameVariable(ClassScope* scope, VariableMode mode,
+                                      const AstRawString* name);
   FunctionLiteral* CreateInitializerFunction(
       const char* name, DeclarationScope* scope,
       ZonePtrList<ClassLiteral::Property>* fields);
diff --git a/src/parsing/preparse-data.cc b/src/parsing/preparse-data.cc
index d66cbc4..fbb5658 100644
--- a/src/parsing/preparse-data.cc
+++ b/src/parsing/preparse-data.cc
@@ -265,7 +265,7 @@
   }
   if (!scope->is_hidden()) {
     for (Variable* var : *scope->locals()) {
-      if (IsDeclaredVariableMode(var->mode())) return true;
+      if (IsSerializableVariableMode(var->mode())) return true;
     }
   }
   for (Scope* inner = scope->inner_scope(); inner != nullptr;
@@ -368,7 +368,7 @@
   }
 
   for (Variable* var : *scope->locals()) {
-    if (IsDeclaredVariableMode(var->mode())) SaveDataForVariable(var);
+    if (IsSerializableVariableMode(var->mode())) SaveDataForVariable(var);
   }
 
   SaveDataForInnerScopes(scope);
@@ -611,7 +611,7 @@
   }
 
   for (Variable* var : *scope->locals()) {
-    if (IsDeclaredVariableMode(var->mode())) RestoreDataForVariable(var);
+    if (IsSerializableVariableMode(var->mode())) RestoreDataForVariable(var);
   }
 
   RestoreDataForInnerScopes(scope);
diff --git a/src/parsing/preparser.h b/src/parsing/preparser.h
index e49f2a8..8ea625c 100644
--- a/src/parsing/preparser.h
+++ b/src/parsing/preparser.h
@@ -1101,16 +1101,18 @@
     // Don't bother actually binding the proxy.
   }
 
-  Variable* DeclarePrivateVariableName(
-      const AstRawString* name, ClassScope* scope,
-      RequiresBrandCheckFlag requires_brand_check, bool* was_added) {
-    return scope->DeclarePrivateName(name, requires_brand_check, was_added);
+  Variable* DeclarePrivateVariableName(const AstRawString* name,
+                                       ClassScope* scope, VariableMode mode,
+                                       bool* was_added) {
+    DCHECK(IsConstVariableMode(mode));
+    return scope->DeclarePrivateName(name, mode, was_added);
   }
 
   Variable* DeclareVariableName(const AstRawString* name, VariableMode mode,
                                 Scope* scope, bool* was_added,
                                 int position = kNoSourcePosition,
                                 VariableKind kind = NORMAL_VARIABLE) {
+    DCHECK(!IsPrivateMethodOrAccessorVariableMode(mode));
     Variable* var = scope->DeclareVariableName(name, mode, was_added, kind);
     if (var == nullptr) {
       ReportUnidentifiableError();
@@ -1250,16 +1252,10 @@
       ClassScope* scope, const PreParserIdentifier& property_name,
       const PreParserExpression& property, ClassLiteralProperty::Kind kind,
       bool is_static, ClassInfo* class_info) {
-    // TODO(joyee): We do not support private accessors yet (which allow
-    // declaring the same private name twice). Make them noops.
-    if (kind != ClassLiteralProperty::Kind::FIELD &&
-        kind != ClassLiteralProperty::Kind::METHOD) {
-      return;
-    }
     bool was_added;
 
     DeclarePrivateVariableName(property_name.string_, scope,
-                               RequiresBrandCheck(kind), &was_added);
+                               GetVariableMode(kind), &was_added);
     if (!was_added) {
       Scanner::Location loc(property.position(), property.position() + 1);
       ReportMessageAt(loc, MessageTemplate::kVarRedeclaration,
diff --git a/test/cctest/test-parsing.cc b/test/cctest/test-parsing.cc
index ba69b6d..4630ab0 100644
--- a/test/cctest/test-parsing.cc
+++ b/test/cctest/test-parsing.cc
@@ -5542,7 +5542,10 @@
     "async #['a']() { }",
     "async *#['a]() { }",
 
-    // TODO(joyee): check duplicate accessors
+    "get #a() {} get #a() {}",
+    "get #a() {} get #['a']() {}",
+    "set #a(val) {} set #a(val) {}",
+    "set #a(val) {} set #['a'](val) {}",
 
     "#a\n#",
     "#a() c",
@@ -5572,8 +5575,60 @@
                     private_methods, arraysize(private_methods));
 }
 
+// Test that private members parse in class bodies nested in object literals
+TEST(PrivateMembersNestedInObjectLiteralsNoErrors) {
+  // clang-format off
+  const char* context_data[][2] = {{"({", "})"},
+                                   {"'use strict'; ({", "});"},
+                                   {nullptr, nullptr}};
+  const char* class_body_data[] = {
+    "a: class { #a = 1 }",
+    "a: class { #a = () => {} }",
+    "a: class { #a }",
+    "a: class { #a() { } }",
+    "a: class { get #a() { } }",
+    "a: class { set #a(foo) { } }",
+    "a: class { *#a() { } }",
+    "a: class { async #a() { } }",
+    "a: class { async *#a() { } }",
+    nullptr
+  };
+  // clang-format on
+
+  static const ParserFlag private_methods[] = {kAllowHarmonyPrivateMethods};
+  RunParserSyncTest(context_data, class_body_data, kSuccess, nullptr, 0,
+                    private_methods, arraysize(private_methods));
+}
+
+// Test that private members parse in class bodies nested in classes
+TEST(PrivateMembersInNestedClassNoErrors) {
+  // clang-format off
+  const char* context_data[][2] = {{"(class {", "});"},
+                                   {"(class extends Base {", "});"},
+                                   {"class C {", "}"},
+                                   {"class C extends Base {", "}"},
+                                   {nullptr, nullptr}};
+  const char* class_body_data[] = {
+    "a = class { #a = 1 }",
+    "a = class { #a = () => {} }",
+    "a = class { #a }",
+    "a = class { #a() { } }",
+    "a = class { get #a() { } }",
+    "a = class { set #a(foo) { } }",
+    "a = class { *#a() { } }",
+    "a = class { async #a() { } }",
+    "a = class { async *#a() { } }",
+    nullptr
+  };
+  // clang-format on
+
+  static const ParserFlag private_methods[] = {kAllowHarmonyPrivateMethods};
+  RunParserSyncTest(context_data, class_body_data, kSuccess, nullptr, 0,
+                    private_methods, arraysize(private_methods));
+}
+
 // Test that private members do not parse outside class bodies
-TEST(PrivateMembersInNonClassNoErrors) {
+TEST(PrivateMembersInNonClassErrors) {
   // clang-format off
   const char* context_data[][2] = {{"", ""},
                                    {"({", "})"},
@@ -5605,6 +5660,122 @@
                     private_methods, arraysize(private_methods));
 }
 
+// Test that nested private members parse
+TEST(PrivateMembersNestedNoErrors) {
+  // clang-format off
+  const char* context_data[][2] = {{"(class { get #a() { ", "} });"},
+                                   {
+                                     "(class { set #a(val) {} get #a() { ",
+                                     "} });"
+                                    },
+                                   {"(class { set #a(val) {", "} });"},
+                                   {"(class { #a() { ", "} });"},
+                                   {nullptr, nullptr}};
+  const char* class_body_data[] = {
+    "class C { #a() {} }",
+    "class C { get #a() {} }",
+    "class C { get #a() {} set #a(val) {} }",
+    "class C { set #a(val) {} }",
+    nullptr
+  };
+  // clang-format on
+
+  static const ParserFlag private_methods[] = {kAllowHarmonyPrivateMethods};
+  RunParserSyncTest(context_data, class_body_data, kSuccess, nullptr, 0,
+                    private_methods, arraysize(private_methods));
+}
+
+// Test that acessing undeclared private members result in early errors
+TEST(PrivateMembersEarlyErrors) {
+  // clang-format off
+  const char* context_data[][2] = {{"(class {", "});"},
+                                   {"(class extends Base {", "});"},
+                                   {"class C {", "}"},
+                                   {"class C extends Base {", "}"},
+                                   {nullptr, nullptr}};
+  const char* class_body_data[] = {
+    "set #b(val) { this.#a = val; }",
+    "get #b() { return this.#a; }",
+    "foo() { return this.#a; }",
+    "foo() { this.#a = 1; }",
+    nullptr
+  };
+  // clang-format on
+
+  RunParserSyncTest(context_data, class_body_data, kError);
+
+  static const ParserFlag private_methods[] = {kAllowHarmonyPrivateMethods};
+  RunParserSyncTest(context_data, class_body_data, kError, nullptr, 0,
+                    private_methods, arraysize(private_methods));
+}
+
+// Test that acessing wrong kind private members do not error early.
+// Instead these should be runtime errors.
+TEST(PrivateMembersWrongAccessNoEarlyErrors) {
+  // clang-format off
+  const char* context_data[][2] = {{"(class {", "});"},
+                                   {"(class extends Base {", "});"},
+                                   {"class C {", "}"},
+                                   {"class C extends Base {", "}"},
+                                   {nullptr, nullptr}};
+  const char* class_body_data[] = {
+    // Private setter only
+    "set #b(val) {} fn() { return this.#b; }",
+    "set #b(val) {} fn() { this.#b++; }",
+    // Nested private setter only
+    R"(get #b() {}
+    fn() {
+      return new class { set #b(val) {} fn() { this.#b++; } };
+    })",
+    R"(get #b() {}
+    fn() {
+      return new class { set #b(val) {} fn() { return this.#b; } };
+    })",
+
+    // Private getter only
+    "get #b() { } fn() { this.#b = 1; }",
+    "get #b() { } fn() { this.#b++; }",
+    "get #b() { } fn(obj) { ({ y: this.#b } = obj); }",
+    // Nested private getter only
+    R"(set #b(val) {}
+    fn() {
+      return new class { get #b() {} fn() { this.#b++; } };
+    })",
+    R"(set #b(val) {}
+    fn() {
+      return new class { get #b() {} fn() { this.#b = 1; } };
+    })",
+    R"(set #b(val) {}
+    fn() {
+      return new class { get #b() {} fn() { ({ y: this.#b } = obj); } };
+    })",
+
+    // Writing to private methods
+    "#b() { } fn() { this.#b = 1; }",
+    "#b() { } fn() { this.#b++; }",
+    "#b() {} fn(obj) { ({ y: this.#b } = obj); }",
+    // Writing to nested private methods
+    R"(#b() {}
+    fn() {
+      return new class { get #b() {} fn() { this.#b++; } };
+    })",
+    R"(#b() {}
+    fn() {
+      return new class { get #b() {} fn() { this.#b = 1; } };
+    })",
+    R"(#b() {}
+    fn() {
+      return new class { get #b() {} fn() { ({ y: this.#b } = obj); } };
+    })",
+    nullptr
+  };
+  // clang-format on
+
+  static const ParserFlag private_methods[] = {kAllowHarmonyPrivateMethods};
+  RunParserSyncTest(context_data, class_body_data, kSuccess, nullptr, 0,
+                    private_methods, arraysize(private_methods));
+}
+
 TEST(PrivateClassFieldsNoErrors) {
   // clang-format off
   // Tests proposed class fields syntax.
diff --git a/test/mjsunit/harmony/private-accessors.js b/test/mjsunit/harmony/private-accessors.js
new file mode 100644
index 0000000..719cf23
--- /dev/null
+++ b/test/mjsunit/harmony/private-accessors.js
@@ -0,0 +1,36 @@
+// Copyright 2019 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --harmony-private-methods
+
+"use strict";
+
+// Complementary private accessors.
+{
+  class C {
+    get #a() {  }
+    set #a(val) { }
+  }
+
+  new C;
+}
+
+// Nested private accessors.
+{
+  class C {
+    a() { this.#a; }
+    get #a() {
+      class D { get #a() { } }
+      return new D;
+    }
+  }
+  new C().a();
+}
+
+// Duplicate private accessors.
+// https://tc39.es/proposal-private-methods/#sec-static-semantics-early-errors
+{
+  assertThrows('class C { get #a() {} get #a() {} }', SyntaxError);
+  assertThrows('class C { set #a(val) {} set #a(val) {} }', SyntaxError);
+}