[class] implement private accessors

This patch implements the access of private accessors by loading the
referenced component from the AccessorPair associated with private
name variables. It also makes the error messages for invalid kind
of private accessor access more specific.

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

Change-Id: I6d441cffb85f8d9cd0417ec9b6ae20f3e34ef418
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1695205
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Commit-Queue: Joyee Cheung <joyee@igalia.com>
Cr-Commit-Position: refs/heads/master@{#63474}
diff --git a/src/common/message-template.h b/src/common/message-template.h
index 9f7a5de..6a8c315 100644
--- a/src/common/message-template.h
+++ b/src/common/message-template.h
@@ -413,11 +413,15 @@
   T(InvalidOrUnexpectedToken, "Invalid or unexpected token")                   \
   T(InvalidPrivateFieldResolution,                                             \
     "Private field '%' must be declared in an enclosing class")                \
-  T(InvalidPrivateFieldRead,                                                   \
-    "Read of private field % from an object which did not contain the field")  \
-  T(InvalidPrivateFieldWrite,                                                  \
-    "Write of private field % to an object which did not contain the field")   \
+  T(InvalidPrivateMemberRead,                                                  \
+    "Cannot read private member % from an object whose class did not declare " \
+    "it")                                                                      \
+  T(InvalidPrivateMemberWrite,                                                 \
+    "Cannot write private member % to an object whose class did not declare "  \
+    "it")                                                                      \
   T(InvalidPrivateMethodWrite, "Private method '%' is not writable")           \
+  T(InvalidPrivateGetterAccess, "'%' was defined without a getter")            \
+  T(InvalidPrivateSetterAccess, "'%' was defined without a setter")            \
   T(JsonParseUnexpectedEOS, "Unexpected end of JSON input")                    \
   T(JsonParseUnexpectedToken, "Unexpected token % in JSON at position %")      \
   T(JsonParseUnexpectedTokenNumber, "Unexpected number in JSON at position %") \
diff --git a/src/ic/ic.cc b/src/ic/ic.cc
index 372214d..a491b8d 100644
--- a/src/ic/ic.cc
+++ b/src/ic/ic.cc
@@ -412,7 +412,7 @@
     if (name->IsPrivateName() && !it.IsFound()) {
       Handle<String> name_string(String::cast(Symbol::cast(*name).name()),
                                  isolate());
-      return TypeError(MessageTemplate::kInvalidPrivateFieldRead, object,
+      return TypeError(MessageTemplate::kInvalidPrivateMemberRead, object,
                        name_string);
     }
 
@@ -1411,7 +1411,7 @@
     if (name->IsPrivateName() && !it.IsFound()) {
       Handle<String> name_string(String::cast(Symbol::cast(*name).name()),
                                  isolate());
-      return TypeError(MessageTemplate::kInvalidPrivateFieldWrite, object,
+      return TypeError(MessageTemplate::kInvalidPrivateMemberWrite, object,
                        name_string);
     }
 
diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
index 96f9498..e88d5d0 100644
--- a/src/interpreter/bytecode-generator.cc
+++ b/src/interpreter/bytecode-generator.cc
@@ -2329,12 +2329,13 @@
   }
 }
 
-void BytecodeGenerator::BuildThrowPrivateMethodWriteError(
-    const AstRawString* name) {
+void BytecodeGenerator::BuildInvalidPropertyAccess(MessageTemplate tmpl,
+                                                   Property* property) {
   RegisterAllocationScope register_scope(this);
+  const AstRawString* name = property->key()->AsVariableProxy()->raw_name();
   RegisterList args = register_allocator()->NewRegisterList(2);
   builder()
-      ->LoadLiteral(Smi::FromEnum(MessageTemplate::kInvalidPrivateMethodWrite))
+      ->LoadLiteral(Smi::FromEnum(tmpl))
       .StoreAccumulatorInRegister(args[0])
       .LoadLiteral(name)
       .StoreAccumulatorInRegister(args[1])
@@ -3887,15 +3888,28 @@
       break;
     }
     case PRIVATE_METHOD: {
-      BuildThrowPrivateMethodWriteError(
-          lhs_data.expr()->AsProperty()->key()->AsVariableProxy()->raw_name());
+      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateMethodWrite,
+                                 lhs_data.expr()->AsProperty());
       break;
     }
-    case PRIVATE_GETTER_ONLY:
+    case PRIVATE_GETTER_ONLY: {
+      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateSetterAccess,
+                                 lhs_data.expr()->AsProperty());
+      break;
+    }
     case PRIVATE_SETTER_ONLY:
     case PRIVATE_GETTER_AND_SETTER: {
-      // TODO(joyee): implement private accessors.
-      return;
+      Register value = register_allocator()->NewRegister();
+      builder()->StoreAccumulatorInRegister(value);
+      Property* property = lhs_data.expr()->AsProperty();
+      Register object = VisitForRegisterValue(property->obj());
+      Register key = VisitForRegisterValue(property->key());
+      BuildPrivateBrandCheck(property, object);
+      BuildPrivateSetterAccess(object, key, value);
+      if (!execution_result()->IsEffect()) {
+        builder()->LoadAccumulatorWithRegister(value);
+      }
+      break;
     }
   }
 }
@@ -3942,16 +3956,13 @@
                              lhs_data.super_property_args().Truncate(3));
       break;
     }
-    case PRIVATE_METHOD: {
-      BuildThrowPrivateMethodWriteError(
-          lhs_data.expr()->AsProperty()->key()->AsVariableProxy()->raw_name());
-      break;
-    }
-    case PRIVATE_SETTER_ONLY:
+    case PRIVATE_METHOD:
     case PRIVATE_GETTER_ONLY:
+    case PRIVATE_SETTER_ONLY:
     case PRIVATE_GETTER_AND_SETTER: {
-      // TODO(joyee): implement private accessors.
-      return;
+      // ({ #foo: name } = obj) is currently syntactically invalid.
+      UNREACHABLE();
+      break;
     }
   }
 
@@ -4418,32 +4429,70 @@
     case KEYED_SUPER_PROPERTY:
       VisitKeyedSuperPropertyLoad(property, Register::invalid_value());
       break;
+    case PRIVATE_SETTER_ONLY: {
+      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateGetterAccess,
+                                 property);
+      break;
+    }
+    case PRIVATE_GETTER_ONLY:
+    case PRIVATE_GETTER_AND_SETTER: {
+      Register key = VisitForRegisterValue(property->key());
+      BuildPrivateBrandCheck(property, obj);
+      BuildPrivateGetterAccess(obj, key);
+      break;
+    }
     case PRIVATE_METHOD: {
-      Variable* private_name = property->key()->AsVariableProxy()->var();
-
-      // Perform the brand check.
-      DCHECK(private_name->requires_brand_check());
-      ClassScope* scope = private_name->scope()->AsClassScope();
-      Variable* brand = scope->brand();
-      BuildVariableLoadForAccumulatorValue(brand, HoleCheckMode::kElided);
-      builder()->SetExpressionPosition(property);
-      builder()->LoadKeyedProperty(
-          obj, feedback_index(feedback_spec()->AddKeyedLoadICSlot()));
-
+      BuildPrivateBrandCheck(property, obj);
       // In the case of private methods, property->key() is the function to be
       // loaded (stored in a context slot), so load this directly.
       VisitForAccumulatorValue(property->key());
       break;
     }
-    case PRIVATE_SETTER_ONLY:
-    case PRIVATE_GETTER_ONLY:
-    case PRIVATE_GETTER_AND_SETTER: {
-      // TODO(joyee): implement private accessors.
-      return;
-    }
   }
 }
 
+void BytecodeGenerator::BuildPrivateGetterAccess(Register object,
+                                                 Register accessor_pair) {
+  RegisterAllocationScope scope(this);
+  Register accessor = register_allocator()->NewRegister();
+  RegisterList args = register_allocator()->NewRegisterList(1);
+
+  builder()
+      ->CallRuntime(Runtime::kLoadPrivateGetter, accessor_pair)
+      .StoreAccumulatorInRegister(accessor)
+      .MoveRegister(object, args[0])
+      .CallProperty(accessor, args,
+                    feedback_index(feedback_spec()->AddCallICSlot()));
+}
+
+void BytecodeGenerator::BuildPrivateSetterAccess(Register object,
+                                                 Register accessor_pair,
+                                                 Register value) {
+  RegisterAllocationScope scope(this);
+  Register accessor = register_allocator()->NewRegister();
+  RegisterList args = register_allocator()->NewRegisterList(2);
+
+  builder()
+      ->CallRuntime(Runtime::kLoadPrivateSetter, accessor_pair)
+      .StoreAccumulatorInRegister(accessor)
+      .MoveRegister(object, args[0])
+      .MoveRegister(value, args[1])
+      .CallProperty(accessor, args,
+                    feedback_index(feedback_spec()->AddCallICSlot()));
+}
+
+void BytecodeGenerator::BuildPrivateBrandCheck(Property* property,
+                                               Register object) {
+  Variable* private_name = property->key()->AsVariableProxy()->var();
+  DCHECK(private_name->requires_brand_check());
+  ClassScope* scope = private_name->scope()->AsClassScope();
+  Variable* brand = scope->brand();
+  BuildVariableLoadForAccumulatorValue(brand, HoleCheckMode::kElided);
+  builder()->SetExpressionPosition(property);
+  builder()->LoadKeyedProperty(
+      object, feedback_index(feedback_spec()->AddKeyedLoadICSlot()));
+}
+
 void BytecodeGenerator::VisitPropertyLoadForRegister(Register obj,
                                                      Property* expr,
                                                      Register destination) {
@@ -5030,16 +5079,27 @@
       break;
     }
     case PRIVATE_METHOD: {
-      BuildThrowPrivateMethodWriteError(
-          property->key()->AsVariableProxy()->raw_name());
-      break;
-    }
-    case PRIVATE_GETTER_ONLY:
-    case PRIVATE_SETTER_ONLY:
-    case PRIVATE_GETTER_AND_SETTER: {
-      // TODO(joyee): implement private accessors.
+      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateMethodWrite,
+                                 property);
       return;
     }
+    case PRIVATE_GETTER_ONLY: {
+      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateSetterAccess,
+                                 property);
+      return;
+    }
+    case PRIVATE_SETTER_ONLY: {
+      BuildInvalidPropertyAccess(MessageTemplate::kInvalidPrivateGetterAccess,
+                                 property);
+      return;
+    }
+    case PRIVATE_GETTER_AND_SETTER: {
+      object = VisitForRegisterValue(property->obj());
+      key = VisitForRegisterValue(property->key());
+      BuildPrivateBrandCheck(property, object);
+      BuildPrivateGetterAccess(object, key);
+      break;
+    }
   }
 
   // Save result for postfix expressions.
@@ -5106,17 +5166,20 @@
           .CallRuntime(Runtime::kStoreKeyedToSuper, super_property_args);
       break;
     }
-    case PRIVATE_METHOD: {
-      BuildThrowPrivateMethodWriteError(
-          property->key()->AsVariableProxy()->raw_name());
-      break;
-    }
-    case PRIVATE_GETTER_ONLY:
     case PRIVATE_SETTER_ONLY:
-    case PRIVATE_GETTER_AND_SETTER: {
-      // TODO(joyee): implement private accessors.
+    case PRIVATE_GETTER_ONLY:
+    case PRIVATE_METHOD: {
       UNREACHABLE();
     }
+    case PRIVATE_GETTER_AND_SETTER: {
+      Register value = register_allocator()->NewRegister();
+      builder()->StoreAccumulatorInRegister(value);
+      BuildPrivateSetterAccess(object, key, value);
+      if (!execution_result()->IsEffect()) {
+        builder()->LoadAccumulatorWithRegister(value);
+      }
+      break;
+    }
   }
 
   // Restore old value for postfix expressions.
diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
index e47546e..2794883 100644
--- a/src/interpreter/bytecode-generator.h
+++ b/src/interpreter/bytecode-generator.h
@@ -302,7 +302,11 @@
   void VisitArgumentsObject(Variable* variable);
   void VisitRestArgumentsArray(Variable* rest);
   void VisitCallSuper(Call* call);
-  void BuildThrowPrivateMethodWriteError(const AstRawString* name);
+  void BuildInvalidPropertyAccess(MessageTemplate tmpl, Property* property);
+  void BuildPrivateBrandCheck(Property* property, Register object);
+  void BuildPrivateGetterAccess(Register obj, Register access_pair);
+  void BuildPrivateSetterAccess(Register obj, Register access_pair,
+                                Register value);
   void BuildClassLiteral(ClassLiteral* expr, Register name);
   void VisitClassLiteral(ClassLiteral* expr, Register name);
   void VisitNewTargetVariable(Variable* variable);
diff --git a/src/runtime/runtime-object.cc b/src/runtime/runtime-object.cc
index 9c486a0..01356be 100644
--- a/src/runtime/runtime-object.cc
+++ b/src/runtime/runtime-object.cc
@@ -46,7 +46,7 @@
     Handle<Object> name_string(Symbol::cast(*key).name(), isolate);
     DCHECK(name_string->IsString());
     THROW_NEW_ERROR(isolate,
-                    NewTypeError(MessageTemplate::kInvalidPrivateFieldRead,
+                    NewTypeError(MessageTemplate::kInvalidPrivateMemberRead,
                                  name_string, object),
                     Object);
   }
@@ -413,7 +413,7 @@
     Handle<Object> name_string(Symbol::cast(*key).name(), isolate);
     DCHECK(name_string->IsString());
     THROW_NEW_ERROR(isolate,
-                    NewTypeError(MessageTemplate::kInvalidPrivateFieldWrite,
+                    NewTypeError(MessageTemplate::kInvalidPrivateMemberWrite,
                                  name_string, object),
                     Object);
   }
diff --git a/test/cctest/interpreter/bytecode_expectations/PrivateAccessorAccess.golden b/test/cctest/interpreter/bytecode_expectations/PrivateAccessorAccess.golden
new file mode 100644
index 0000000..6fc0099
--- /dev/null
+++ b/test/cctest/interpreter/bytecode_expectations/PrivateAccessorAccess.golden
@@ -0,0 +1,192 @@
+#
+# Autogenerated by generate-bytecode-expectations.
+#
+
+---
+wrap: no
+test function name: test
+private methods: yes
+
+---
+snippet: "
+  class A {
+    get #a() { return 1; }
+    set #a(val) { }
+  
+    constructor() {
+      this.#a++;
+      this.#a = 1;
+      return this.#a;
+    }
+  }
+  var test = A;
+  new test;
+"
+frame size: 6
+parameter count: 1
+bytecode array length: 95
+bytecodes: [
+  /*   67 E> */ B(StackCheck),
+                B(LdaCurrentContextSlot), U8(5),
+                B(Star), R(1),
+                B(Mov), R(this), R(0),
+                B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(2),
+  /*   76 S> */ B(LdaCurrentContextSlot), U8(4),
+                B(Star), R(3),
+                B(LdaCurrentContextSlot), U8(5),
+  /*   81 E> */ B(LdaKeyedProperty), R(this), U8(0),
+                B(CallRuntime), U16(Runtime::kLoadPrivateGetter), R(3), U8(1),
+                B(Star), R(4),
+                B(CallProperty0), R(4), R(this), U8(2),
+                B(Inc), U8(4),
+                B(Star), R(4),
+  /*   83 E> */ B(CallRuntime), U16(Runtime::kLoadPrivateSetter), R(3), U8(1),
+                B(Star), R(5),
+                B(CallProperty1), R(5), R(this), R(4), U8(5),
+  /*   91 S> */ B(LdaSmi), I8(1),
+                B(Star), R(2),
+                B(LdaCurrentContextSlot), U8(4),
+                B(Star), R(4),
+                B(LdaCurrentContextSlot), U8(5),
+  /*   96 E> */ B(LdaKeyedProperty), R(this), U8(7),
+                B(CallRuntime), U16(Runtime::kLoadPrivateSetter), R(4), U8(1),
+                B(Star), R(5),
+                B(CallProperty1), R(5), R(this), R(2), U8(9),
+  /*  108 S> */ B(LdaCurrentContextSlot), U8(4),
+                B(Star), R(3),
+                B(LdaCurrentContextSlot), U8(5),
+  /*  120 E> */ B(LdaKeyedProperty), R(this), U8(11),
+                B(CallRuntime), U16(Runtime::kLoadPrivateGetter), R(3), U8(1),
+                B(Star), R(4),
+                B(CallProperty0), R(4), R(this), U8(13),
+  /*  123 S> */ B(Return),
+]
+constant pool: [
+]
+handlers: [
+]
+
+---
+snippet: "
+  class B {
+    get #b() { return 1; }
+    constructor() { this.#b++; }
+  }
+  var test = B;
+  new test;
+"
+frame size: 4
+parameter count: 1
+bytecode array length: 29
+bytecodes: [
+  /*   48 E> */ B(StackCheck),
+                B(LdaCurrentContextSlot), U8(5),
+                B(Star), R(1),
+                B(Mov), R(this), R(0),
+                B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(2),
+  /*   53 S> */ B(Wide), B(LdaSmi), I16(263),
+                B(Star), R(2),
+                B(LdaConstant), U8(0),
+                B(Star), R(3),
+                B(CallRuntime), U16(Runtime::kNewTypeError), R(2), U8(2),
+                B(Throw),
+]
+constant pool: [
+  ONE_BYTE_INTERNALIZED_STRING_TYPE ["#b"],
+]
+handlers: [
+]
+
+---
+snippet: "
+  class C {
+    set #c(val) { }
+    constructor() { this.#c++; }
+  }
+  var test = C;
+  new test;
+"
+frame size: 4
+parameter count: 1
+bytecode array length: 29
+bytecodes: [
+  /*   41 E> */ B(StackCheck),
+                B(LdaCurrentContextSlot), U8(5),
+                B(Star), R(1),
+                B(Mov), R(this), R(0),
+                B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(2),
+  /*   46 S> */ B(Wide), B(LdaSmi), I16(262),
+                B(Star), R(2),
+                B(LdaConstant), U8(0),
+                B(Star), R(3),
+                B(CallRuntime), U16(Runtime::kNewTypeError), R(2), U8(2),
+                B(Throw),
+]
+constant pool: [
+  ONE_BYTE_INTERNALIZED_STRING_TYPE ["#c"],
+]
+handlers: [
+]
+
+---
+snippet: "
+  class D {
+    get #d() { return 1; }
+    constructor() { this.#d = 1; }
+  }
+  var test = D;
+  new test;
+"
+frame size: 4
+parameter count: 1
+bytecode array length: 29
+bytecodes: [
+  /*   48 E> */ B(StackCheck),
+                B(LdaCurrentContextSlot), U8(5),
+                B(Star), R(1),
+                B(Mov), R(this), R(0),
+                B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(2),
+  /*   53 S> */ B(Wide), B(LdaSmi), I16(263),
+                B(Star), R(2),
+                B(LdaConstant), U8(0),
+                B(Star), R(3),
+  /*   61 E> */ B(CallRuntime), U16(Runtime::kNewTypeError), R(2), U8(2),
+                B(Throw),
+]
+constant pool: [
+  ONE_BYTE_INTERNALIZED_STRING_TYPE ["#d"],
+]
+handlers: [
+]
+
+---
+snippet: "
+  class E {
+    set #e(val) { }
+    constructor() { this.#e; }
+  }
+  var test = E;
+  new test;
+"
+frame size: 5
+parameter count: 1
+bytecode array length: 29
+bytecodes: [
+  /*   41 E> */ B(StackCheck),
+                B(LdaCurrentContextSlot), U8(5),
+                B(Star), R(1),
+                B(Mov), R(this), R(0),
+                B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(2),
+  /*   46 S> */ B(Wide), B(LdaSmi), I16(262),
+                B(Star), R(3),
+                B(LdaConstant), U8(0),
+                B(Star), R(4),
+                B(CallRuntime), U16(Runtime::kNewTypeError), R(3), U8(2),
+                B(Throw),
+]
+constant pool: [
+  ONE_BYTE_INTERNALIZED_STRING_TYPE ["#e"],
+]
+handlers: [
+]
+
diff --git a/test/cctest/interpreter/bytecode_expectations/PrivateAccessors.golden b/test/cctest/interpreter/bytecode_expectations/PrivateAccessorDeclaration.golden
similarity index 100%
rename from test/cctest/interpreter/bytecode_expectations/PrivateAccessors.golden
rename to test/cctest/interpreter/bytecode_expectations/PrivateAccessorDeclaration.golden
diff --git a/test/cctest/interpreter/bytecode_expectations/PrivateMethodAccess.golden b/test/cctest/interpreter/bytecode_expectations/PrivateMethodAccess.golden
new file mode 100644
index 0000000..d41b342
--- /dev/null
+++ b/test/cctest/interpreter/bytecode_expectations/PrivateMethodAccess.golden
@@ -0,0 +1,104 @@
+#
+# Autogenerated by generate-bytecode-expectations.
+#
+
+---
+wrap: no
+test function name: test
+private methods: yes
+
+---
+snippet: "
+  class A {
+    #a() { return 1; }
+    constructor() { return this.#a(); }
+  }
+  
+  var test = A;
+  new A;
+"
+frame size: 3
+parameter count: 1
+bytecode array length: 28
+bytecodes: [
+  /*   44 E> */ B(StackCheck),
+                B(LdaCurrentContextSlot), U8(5),
+                B(Star), R(1),
+                B(Mov), R(this), R(0),
+                B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(2),
+  /*   49 S> */ B(LdaCurrentContextSlot), U8(5),
+  /*   61 E> */ B(LdaKeyedProperty), R(this), U8(0),
+                B(LdaCurrentContextSlot), U8(4),
+                B(Star), R(2),
+  /*   63 E> */ B(CallAnyReceiver), R(2), R(this), U8(1), U8(2),
+  /*   66 S> */ B(Return),
+]
+constant pool: [
+]
+handlers: [
+]
+
+---
+snippet: "
+  class B {
+    #b() { return 1; }
+    constructor() { this.#b = 1; }
+  }
+  
+  var test = B;
+  new test;
+"
+frame size: 4
+parameter count: 1
+bytecode array length: 29
+bytecodes: [
+  /*   44 E> */ B(StackCheck),
+                B(LdaCurrentContextSlot), U8(5),
+                B(Star), R(1),
+                B(Mov), R(this), R(0),
+                B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(2),
+  /*   49 S> */ B(Wide), B(LdaSmi), I16(261),
+                B(Star), R(2),
+                B(LdaConstant), U8(0),
+                B(Star), R(3),
+  /*   57 E> */ B(CallRuntime), U16(Runtime::kNewTypeError), R(2), U8(2),
+                B(Throw),
+]
+constant pool: [
+  ONE_BYTE_INTERNALIZED_STRING_TYPE ["#b"],
+]
+handlers: [
+]
+
+---
+snippet: "
+  class C {
+    #c() { return 1; }
+    constructor() { this.#c++; }
+  }
+  
+  var test = C;
+  new test;
+"
+frame size: 4
+parameter count: 1
+bytecode array length: 29
+bytecodes: [
+  /*   44 E> */ B(StackCheck),
+                B(LdaCurrentContextSlot), U8(5),
+                B(Star), R(1),
+                B(Mov), R(this), R(0),
+                B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(2),
+  /*   49 S> */ B(Wide), B(LdaSmi), I16(261),
+                B(Star), R(2),
+                B(LdaConstant), U8(0),
+                B(Star), R(3),
+                B(CallRuntime), U16(Runtime::kNewTypeError), R(2), U8(2),
+                B(Throw),
+]
+constant pool: [
+  ONE_BYTE_INTERNALIZED_STRING_TYPE ["#c"],
+]
+handlers: [
+]
+
diff --git a/test/cctest/interpreter/bytecode_expectations/PrivateMethodDeclaration.golden b/test/cctest/interpreter/bytecode_expectations/PrivateMethodDeclaration.golden
new file mode 100644
index 0000000..d1aab34
--- /dev/null
+++ b/test/cctest/interpreter/bytecode_expectations/PrivateMethodDeclaration.golden
@@ -0,0 +1,198 @@
+#
+# Autogenerated by generate-bytecode-expectations.
+#
+
+---
+wrap: yes
+private methods: yes
+
+---
+snippet: "
+  {
+    class A {
+      #a() { return 1; }
+    }
+  }
+"
+frame size: 7
+parameter count: 1
+bytecode array length: 55
+bytecodes: [
+  /*   30 E> */ B(StackCheck),
+                B(CreateBlockContext), U8(0),
+                B(PushContext), R(2),
+                B(LdaTheHole),
+                B(Star), R(6),
+                B(CreateClosure), U8(2), U8(0), U8(2),
+                B(Star), R(3),
+                B(LdaConstant), U8(1),
+                B(Star), R(4),
+                B(CreateClosure), U8(3), U8(1), U8(2),
+                B(StaCurrentContextSlot), U8(4),
+                B(Mov), R(3), R(5),
+                B(CallRuntime), U16(Runtime::kDefineClass), R(4), U8(3),
+                B(Star), R(4),
+                B(Mov), R(5), R(1),
+                B(LdaConstant), U8(4),
+                B(Star), R(5),
+                B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(5), U8(1),
+                B(StaCurrentContextSlot), U8(5),
+                B(PopContext), R(2),
+                B(Mov), R(1), R(0),
+                B(LdaUndefined),
+  /*   77 S> */ B(Return),
+]
+constant pool: [
+  SCOPE_INFO_TYPE,
+  FIXED_ARRAY_TYPE,
+  SHARED_FUNCTION_INFO_TYPE,
+  SHARED_FUNCTION_INFO_TYPE,
+  ONE_BYTE_INTERNALIZED_STRING_TYPE ["A"],
+]
+handlers: [
+]
+
+---
+snippet: "
+  {
+    class D {
+      #d() { return 1; }
+    }
+    class E extends D {
+      #e() { return 2; }
+    }
+  }
+"
+frame size: 9
+parameter count: 1
+bytecode array length: 107
+bytecodes: [
+  /*   30 E> */ B(StackCheck),
+                B(CreateBlockContext), U8(0),
+                B(PushContext), R(4),
+                B(LdaTheHole),
+                B(Star), R(8),
+                B(CreateClosure), U8(2), U8(0), U8(2),
+                B(Star), R(5),
+                B(LdaConstant), U8(1),
+                B(Star), R(6),
+                B(CreateClosure), U8(3), U8(1), U8(2),
+                B(StaCurrentContextSlot), U8(4),
+                B(Mov), R(5), R(7),
+                B(CallRuntime), U16(Runtime::kDefineClass), R(6), U8(3),
+                B(Star), R(6),
+                B(Mov), R(7), R(3),
+                B(LdaConstant), U8(4),
+                B(Star), R(7),
+                B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(7), U8(1),
+                B(StaCurrentContextSlot), U8(5),
+                B(PopContext), R(4),
+                B(Mov), R(3), R(0),
+  /*   38 E> */ B(CreateBlockContext), U8(5),
+                B(PushContext), R(4),
+  /*   93 E> */ B(CreateClosure), U8(7), U8(2), U8(2),
+                B(Star), R(5),
+                B(LdaConstant), U8(6),
+                B(Star), R(6),
+                B(CreateClosure), U8(8), U8(3), U8(2),
+                B(StaCurrentContextSlot), U8(4),
+                B(Mov), R(5), R(7),
+                B(Mov), R(3), R(8),
+                B(CallRuntime), U16(Runtime::kDefineClass), R(6), U8(3),
+                B(Star), R(6),
+                B(Mov), R(7), R(2),
+                B(LdaConstant), U8(9),
+                B(Star), R(7),
+                B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(7), U8(1),
+                B(StaCurrentContextSlot), U8(5),
+                B(PopContext), R(4),
+                B(Mov), R(2), R(1),
+                B(LdaUndefined),
+  /*  126 S> */ B(Return),
+]
+constant pool: [
+  SCOPE_INFO_TYPE,
+  FIXED_ARRAY_TYPE,
+  SHARED_FUNCTION_INFO_TYPE,
+  SHARED_FUNCTION_INFO_TYPE,
+  ONE_BYTE_INTERNALIZED_STRING_TYPE ["D"],
+  SCOPE_INFO_TYPE,
+  FIXED_ARRAY_TYPE,
+  SHARED_FUNCTION_INFO_TYPE,
+  SHARED_FUNCTION_INFO_TYPE,
+  ONE_BYTE_INTERNALIZED_STRING_TYPE ["E"],
+]
+handlers: [
+]
+
+---
+snippet: "
+  {
+    class A { foo() {} }
+    class C extends A {
+      #m() { return super.foo; }
+    }
+  }
+"
+frame size: 10
+parameter count: 1
+bytecode array length: 106
+bytecodes: [
+  /*   30 E> */ B(StackCheck),
+                B(CreateBlockContext), U8(0),
+                B(PushContext), R(4),
+                B(LdaTheHole),
+                B(Star), R(8),
+                B(CreateClosure), U8(2), U8(0), U8(2),
+                B(Star), R(5),
+                B(LdaConstant), U8(1),
+                B(Star), R(6),
+                B(CreateClosure), U8(3), U8(1), U8(2),
+                B(Star), R(9),
+                B(Mov), R(5), R(7),
+                B(CallRuntime), U16(Runtime::kDefineClass), R(6), U8(4),
+                B(Star), R(6),
+                B(Mov), R(7), R(3),
+                B(PopContext), R(4),
+                B(Mov), R(3), R(0),
+  /*   38 E> */ B(CreateBlockContext), U8(4),
+                B(PushContext), R(4),
+  /*   77 E> */ B(CreateClosure), U8(6), U8(2), U8(2),
+                B(Star), R(5),
+                B(LdaConstant), U8(5),
+                B(Star), R(6),
+                B(CreateClosure), U8(7), U8(3), U8(2),
+                B(StaCurrentContextSlot), U8(4),
+                B(Mov), R(5), R(7),
+                B(Mov), R(3), R(8),
+                B(CallRuntime), U16(Runtime::kDefineClass), R(6), U8(3),
+                B(Star), R(6),
+                B(Mov), R(7), R(2),
+                B(LdaConstant), U8(8),
+                B(Star), R(7),
+                B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(7), U8(1),
+                B(StaCurrentContextSlot), U8(5),
+                B(LdaCurrentContextSlot), U8(4),
+                B(Star), R(8),
+                B(Ldar), R(6),
+                B(StaNamedProperty), R(8), U8(9), U8(0),
+                B(PopContext), R(4),
+                B(Mov), R(2), R(1),
+                B(LdaUndefined),
+  /*  118 S> */ B(Return),
+]
+constant pool: [
+  SCOPE_INFO_TYPE,
+  FIXED_ARRAY_TYPE,
+  SHARED_FUNCTION_INFO_TYPE,
+  SHARED_FUNCTION_INFO_TYPE,
+  SCOPE_INFO_TYPE,
+  FIXED_ARRAY_TYPE,
+  SHARED_FUNCTION_INFO_TYPE,
+  SHARED_FUNCTION_INFO_TYPE,
+  ONE_BYTE_INTERNALIZED_STRING_TYPE ["C"],
+  SYMBOL_TYPE,
+]
+handlers: [
+]
+
diff --git a/test/cctest/interpreter/bytecode_expectations/PrivateMethods.golden b/test/cctest/interpreter/bytecode_expectations/PrivateMethods.golden
deleted file mode 100644
index 18dd92d..0000000
--- a/test/cctest/interpreter/bytecode_expectations/PrivateMethods.golden
+++ /dev/null
@@ -1,243 +0,0 @@
-#
-# Autogenerated by generate-bytecode-expectations.
-#
-
----
-wrap: yes
-private methods: yes
-
----
-snippet: "
-  {
-    class A {
-      #a() { return 1; }
-      callA() { return this.#a(); }
-    }
-  
-    const a = new A;
-    a.callA();
-  }
-"
-frame size: 9
-parameter count: 1
-bytecode array length: 80
-bytecodes: [
-  /*   30 E> */ B(StackCheck),
-                B(CreateBlockContext), U8(0),
-                B(PushContext), R(3),
-                B(LdaTheHole),
-                B(Star), R(7),
-                B(CreateClosure), U8(2), U8(0), U8(2),
-                B(Star), R(4),
-                B(LdaConstant), U8(1),
-                B(Star), R(5),
-                B(CreateClosure), U8(3), U8(1), U8(2),
-                B(StaCurrentContextSlot), U8(4),
-                B(CreateClosure), U8(4), U8(2), U8(2),
-                B(Star), R(8),
-                B(Mov), R(4), R(6),
-                B(CallRuntime), U16(Runtime::kDefineClass), R(5), U8(4),
-                B(Star), R(5),
-                B(Mov), R(6), R(2),
-                B(LdaConstant), U8(5),
-                B(Star), R(6),
-                B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(6), U8(1),
-                B(StaCurrentContextSlot), U8(5),
-                B(PopContext), R(3),
-                B(Mov), R(2), R(0),
-  /*  122 S> */ B(Ldar), R(0),
-  /*  122 E> */ B(Construct), R(0), R(0), U8(0), U8(0),
-                B(Star), R(1),
-  /*  133 S> */ B(LdaNamedProperty), R(1), U8(6), U8(2),
-                B(Star), R(3),
-  /*  133 E> */ B(CallProperty0), R(3), R(1), U8(4),
-                B(LdaUndefined),
-  /*  144 S> */ B(Return),
-]
-constant pool: [
-  SCOPE_INFO_TYPE,
-  FIXED_ARRAY_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  ONE_BYTE_INTERNALIZED_STRING_TYPE ["A"],
-  ONE_BYTE_INTERNALIZED_STRING_TYPE ["callA"],
-]
-handlers: [
-]
-
----
-snippet: "
-  {
-    class D {
-      #d() { return 1; }
-      callD() { return this.#d(); }
-    }
-  
-    class E extends D {
-      #e() { return 2; }
-      callE() { return this.callD() + this.#e(); }
-    }
-  
-    const e = new E;
-    e.callE();
-  }
-"
-frame size: 11
-parameter count: 1
-bytecode array length: 138
-bytecodes: [
-  /*   30 E> */ B(StackCheck),
-                B(CreateBlockContext), U8(0),
-                B(PushContext), R(5),
-                B(LdaTheHole),
-                B(Star), R(9),
-                B(CreateClosure), U8(2), U8(0), U8(2),
-                B(Star), R(6),
-                B(LdaConstant), U8(1),
-                B(Star), R(7),
-                B(CreateClosure), U8(3), U8(1), U8(2),
-                B(StaCurrentContextSlot), U8(4),
-                B(CreateClosure), U8(4), U8(2), U8(2),
-                B(Star), R(10),
-                B(Mov), R(6), R(8),
-                B(CallRuntime), U16(Runtime::kDefineClass), R(7), U8(4),
-                B(Star), R(7),
-                B(Mov), R(8), R(4),
-                B(LdaConstant), U8(5),
-                B(Star), R(8),
-                B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(8), U8(1),
-                B(StaCurrentContextSlot), U8(5),
-                B(PopContext), R(5),
-                B(Mov), R(4), R(0),
-  /*   38 E> */ B(CreateBlockContext), U8(6),
-                B(PushContext), R(5),
-  /*  128 E> */ B(CreateClosure), U8(8), U8(3), U8(2),
-                B(Star), R(6),
-                B(LdaConstant), U8(7),
-                B(Star), R(7),
-                B(CreateClosure), U8(9), U8(4), U8(2),
-                B(StaCurrentContextSlot), U8(4),
-                B(CreateClosure), U8(10), U8(5), U8(2),
-                B(Star), R(10),
-                B(Mov), R(6), R(8),
-                B(Mov), R(4), R(9),
-                B(CallRuntime), U16(Runtime::kDefineClass), R(7), U8(4),
-                B(Star), R(7),
-                B(Mov), R(8), R(3),
-                B(LdaConstant), U8(11),
-                B(Star), R(8),
-                B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(8), U8(1),
-                B(StaCurrentContextSlot), U8(5),
-                B(PopContext), R(5),
-                B(Mov), R(3), R(1),
-  /*  221 S> */ B(Ldar), R(1),
-  /*  221 E> */ B(Construct), R(1), R(0), U8(0), U8(0),
-                B(Star), R(2),
-  /*  232 S> */ B(LdaNamedProperty), R(2), U8(12), U8(2),
-                B(Star), R(5),
-  /*  232 E> */ B(CallProperty0), R(5), R(2), U8(4),
-                B(LdaUndefined),
-  /*  243 S> */ B(Return),
-]
-constant pool: [
-  SCOPE_INFO_TYPE,
-  FIXED_ARRAY_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  ONE_BYTE_INTERNALIZED_STRING_TYPE ["D"],
-  SCOPE_INFO_TYPE,
-  FIXED_ARRAY_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  ONE_BYTE_INTERNALIZED_STRING_TYPE ["E"],
-  ONE_BYTE_INTERNALIZED_STRING_TYPE ["callE"],
-]
-handlers: [
-]
-
----
-snippet: "
-  {
-    class A { foo() {} }
-    class C extends A {
-      #m() { return super.foo; }
-      fn() { return this.#m(); }
-    }
-    new C().fn();
-  }
-"
-frame size: 10
-parameter count: 1
-bytecode array length: 131
-bytecodes: [
-  /*   30 E> */ B(StackCheck),
-                B(CreateBlockContext), U8(0),
-                B(PushContext), R(4),
-                B(LdaTheHole),
-                B(Star), R(8),
-                B(CreateClosure), U8(2), U8(0), U8(2),
-                B(Star), R(5),
-                B(LdaConstant), U8(1),
-                B(Star), R(6),
-                B(CreateClosure), U8(3), U8(1), U8(2),
-                B(Star), R(9),
-                B(Mov), R(5), R(7),
-                B(CallRuntime), U16(Runtime::kDefineClass), R(6), U8(4),
-                B(Star), R(6),
-                B(Mov), R(7), R(3),
-                B(PopContext), R(4),
-                B(Mov), R(3), R(0),
-  /*   38 E> */ B(CreateBlockContext), U8(4),
-                B(PushContext), R(4),
-  /*   77 E> */ B(CreateClosure), U8(6), U8(2), U8(2),
-                B(Star), R(5),
-                B(LdaConstant), U8(5),
-                B(Star), R(6),
-                B(CreateClosure), U8(7), U8(3), U8(2),
-                B(StaCurrentContextSlot), U8(4),
-                B(CreateClosure), U8(8), U8(4), U8(2),
-                B(Star), R(9),
-                B(Mov), R(5), R(7),
-                B(Mov), R(3), R(8),
-                B(CallRuntime), U16(Runtime::kDefineClass), R(6), U8(4),
-                B(Star), R(6),
-                B(Mov), R(7), R(2),
-                B(LdaConstant), U8(9),
-                B(Star), R(7),
-                B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(7), U8(1),
-                B(StaCurrentContextSlot), U8(5),
-                B(LdaCurrentContextSlot), U8(4),
-                B(Star), R(8),
-                B(Ldar), R(6),
-                B(StaNamedProperty), R(8), U8(10), U8(0),
-                B(PopContext), R(4),
-                B(Mov), R(2), R(1),
-  /*  149 S> */ B(Ldar), R(1),
-  /*  149 E> */ B(Construct), R(5), R(0), U8(0), U8(2),
-                B(Star), R(5),
-  /*  157 E> */ B(LdaNamedProperty), R(5), U8(11), U8(4),
-                B(Star), R(4),
-  /*  157 E> */ B(CallProperty0), R(4), R(5), U8(6),
-                B(LdaUndefined),
-  /*  165 S> */ B(Return),
-]
-constant pool: [
-  SCOPE_INFO_TYPE,
-  FIXED_ARRAY_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  SCOPE_INFO_TYPE,
-  FIXED_ARRAY_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  SHARED_FUNCTION_INFO_TYPE,
-  ONE_BYTE_INTERNALIZED_STRING_TYPE ["C"],
-  SYMBOL_TYPE,
-  ONE_BYTE_INTERNALIZED_STRING_TYPE ["fn"],
-]
-handlers: [
-]
-
diff --git a/test/cctest/interpreter/test-bytecode-generator.cc b/test/cctest/interpreter/test-bytecode-generator.cc
index 5afe8b3..fda0293 100644
--- a/test/cctest/interpreter/test-bytecode-generator.cc
+++ b/test/cctest/interpreter/test-bytecode-generator.cc
@@ -2758,7 +2758,7 @@
                      LoadGolden("PrivateClassFields.golden")));
 }
 
-TEST(PrivateMethods) {
+TEST(PrivateMethodDeclaration) {
   bool old_methods_flag = i::FLAG_harmony_private_methods;
   i::FLAG_harmony_private_methods = true;
   InitializedIgnitionHandleScope scope;
@@ -2768,43 +2768,124 @@
       "{\n"
       "  class A {\n"
       "    #a() { return 1; }\n"
-      "    callA() { return this.#a(); }\n"
       "  }\n"
-      "\n"
-      "  const a = new A;\n"
-      "  a.callA();\n"
       "}\n",
 
       "{\n"
       "  class D {\n"
       "    #d() { return 1; }\n"
-      "    callD() { return this.#d(); }\n"
       "  }\n"
-      "\n"
       "  class E extends D {\n"
       "    #e() { return 2; }\n"
-      "    callE() { return this.callD() + this.#e(); }\n"
       "  }\n"
-      "\n"
-      "  const e = new E;\n"
-      "  e.callE();\n"
       "}\n",
 
       "{\n"
       "  class A { foo() {} }\n"
       "  class C extends A {\n"
       "    #m() { return super.foo; }\n"
-      "    fn() { return this.#m(); }\n"
       "  }\n"
-      "  new C().fn();\n"
       "}\n"};
 
   CHECK(CompareTexts(BuildActual(printer, snippets),
-                     LoadGolden("PrivateMethods.golden")));
+                     LoadGolden("PrivateMethodDeclaration.golden")));
   i::FLAG_harmony_private_methods = old_methods_flag;
 }
 
-TEST(PrivateAccessors) {
+TEST(PrivateMethodAccess) {
+  bool old_methods_flag = i::FLAG_harmony_private_methods;
+  i::FLAG_harmony_private_methods = true;
+  InitializedIgnitionHandleScope scope;
+  BytecodeExpectationsPrinter printer(CcTest::isolate());
+  printer.set_wrap(false);
+  printer.set_test_function_name("test");
+
+  const char* snippets[] = {
+      "class A {\n"
+      "  #a() { return 1; }\n"
+      "  constructor() { return this.#a(); }\n"
+      "}\n"
+      "\n"
+      "var test = A;\n"
+      "new A;\n",
+
+      "class B {\n"
+      "  #b() { return 1; }\n"
+      "  constructor() { this.#b = 1; }\n"
+      "}\n"
+      "\n"
+      "var test = B;\n"
+      "new test;\n",
+
+      "class C {\n"
+      "  #c() { return 1; }\n"
+      "  constructor() { this.#c++; }\n"
+      "}\n"
+      "\n"
+      "var test = C;\n"
+      "new test;\n"};
+
+  CHECK(CompareTexts(BuildActual(printer, snippets),
+                     LoadGolden("PrivateMethodAccess.golden")));
+  i::FLAG_harmony_private_methods = old_methods_flag;
+}
+
+TEST(PrivateAccessorAccess) {
+  bool old_methods_flag = i::FLAG_harmony_private_methods;
+  i::FLAG_harmony_private_methods = true;
+  InitializedIgnitionHandleScope scope;
+  BytecodeExpectationsPrinter printer(CcTest::isolate());
+  printer.set_wrap(false);
+  printer.set_test_function_name("test");
+
+  const char* snippets[] = {
+      "class A {\n"
+      "  get #a() { return 1; }\n"
+      "  set #a(val) { }\n"
+      "\n"
+      "  constructor() {\n"
+      "    this.#a++;\n"
+      "    this.#a = 1;\n"
+      "    return this.#a;\n"
+      "  }\n"
+      "}\n"
+      "var test = A;\n"
+      "new test;\n",
+
+      "class B {\n"
+      "  get #b() { return 1; }\n"
+      "  constructor() { this.#b++; }\n"
+      "}\n"
+      "var test = B;\n"
+      "new test;\n",
+
+      "class C {\n"
+      "  set #c(val) { }\n"
+      "  constructor() { this.#c++; }\n"
+      "}\n"
+      "var test = C;\n"
+      "new test;\n",
+
+      "class D {\n"
+      "  get #d() { return 1; }\n"
+      "  constructor() { this.#d = 1; }\n"
+      "}\n"
+      "var test = D;\n"
+      "new test;\n",
+
+      "class E {\n"
+      "  set #e(val) { }\n"
+      "  constructor() { this.#e; }\n"
+      "}\n"
+      "var test = E;\n"
+      "new test;\n"};
+
+  CHECK(CompareTexts(BuildActual(printer, snippets),
+                     LoadGolden("PrivateAccessorAccess.golden")));
+  i::FLAG_harmony_private_methods = old_methods_flag;
+}
+
+TEST(PrivateAccessorDeclaration) {
   bool old_methods_flag = i::FLAG_harmony_private_methods;
   i::FLAG_harmony_private_methods = true;
   InitializedIgnitionHandleScope scope;
@@ -2859,7 +2940,7 @@
       "}\n"};
 
   CHECK(CompareTexts(BuildActual(printer, snippets),
-                     LoadGolden("PrivateAccessors.golden")));
+                     LoadGolden("PrivateAccessorDeclaration.golden")));
   i::FLAG_harmony_private_methods = old_methods_flag;
 }
 
diff --git a/test/message/fail/class-accessors-private-undefined-getter-count.js b/test/message/fail/class-accessors-private-undefined-getter-count.js
new file mode 100644
index 0000000..35f30ac
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-getter-count.js
@@ -0,0 +1,14 @@
+// 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
+
+class C {
+  set #foo(val) {}
+  constructor() {
+    this.#foo++;
+  }
+}
+
+new C();
diff --git a/test/message/fail/class-accessors-private-undefined-getter-count.out b/test/message/fail/class-accessors-private-undefined-getter-count.out
new file mode 100644
index 0000000..f6328b9
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-getter-count.out
@@ -0,0 +1,9 @@
+# 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.
+*%(basename)s:10: TypeError: '#foo' was defined without a getter
+    this.#foo++;
+    ^
+TypeError: '#foo' was defined without a getter
+    at new C (*%(basename)s:10:5)
+    at *%(basename)s:14:1
diff --git a/test/message/fail/class-accessors-private-undefined-getter-nested.js b/test/message/fail/class-accessors-private-undefined-getter-nested.js
new file mode 100644
index 0000000..680c1bb
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-getter-nested.js
@@ -0,0 +1,19 @@
+// 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
+
+class C {
+  set #a(val) {}
+  setA(obj, val) { obj.#a = val; }
+
+  constructor() {
+    class D {
+      get #a() {}
+    }
+    this.setA(new D(), 1);
+  }
+}
+
+new C;
diff --git a/test/message/fail/class-accessors-private-undefined-getter-nested.out b/test/message/fail/class-accessors-private-undefined-getter-nested.out
new file mode 100644
index 0000000..ca3154a
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-getter-nested.out
@@ -0,0 +1,10 @@
+# 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.
+*%(basename)s:9: TypeError: Cannot read private member C from an object whose class did not declare it
+  setA(obj, val) { obj.#a = val; }
+                       ^
+TypeError: Cannot read private member C from an object whose class did not declare it
+    at C.setA (*%(basename)s:9:24)
+    at new C (*%(basename)s:15:10)
+    at *%(basename)s:19:1
\ No newline at end of file
diff --git a/test/message/fail/class-accessors-private-undefined-getter.js b/test/message/fail/class-accessors-private-undefined-getter.js
new file mode 100644
index 0000000..9422739
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-getter.js
@@ -0,0 +1,13 @@
+// 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
+
+class C {
+  set #a(val) {}
+  constructor() {
+    const a = this.#a;
+  }
+}
+new C;
diff --git a/test/message/fail/class-accessors-private-undefined-getter.out b/test/message/fail/class-accessors-private-undefined-getter.out
new file mode 100644
index 0000000..c41451c
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-getter.out
@@ -0,0 +1,9 @@
+# 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.
+*%(basename)s:10: TypeError: '#a' was defined without a getter
+    const a = this.#a;
+              ^
+TypeError: '#a' was defined without a getter
+    at new C (*%(basename)s:10:15)
+    at *%(basename)s:13:1
diff --git a/test/message/fail/class-accessors-private-undefined-setter-compound.js b/test/message/fail/class-accessors-private-undefined-setter-compound.js
new file mode 100644
index 0000000..2a1f057
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-setter-compound.js
@@ -0,0 +1,13 @@
+// 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
+
+class C {
+  get #a() {}
+  constructor() {
+    this.#a = 1;
+  }
+}
+new C;
diff --git a/test/message/fail/class-accessors-private-undefined-setter-compound.out b/test/message/fail/class-accessors-private-undefined-setter-compound.out
new file mode 100644
index 0000000..a98f470
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-setter-compound.out
@@ -0,0 +1,9 @@
+# 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.
+*%(basename)s:10: TypeError: '#a' was defined without a setter
+    this.#a = 1;
+            ^
+TypeError: '#a' was defined without a setter
+    at new C (*%(basename)s:10:13)
+    at *%(basename)s:13:1
diff --git a/test/message/fail/class-accessors-private-undefined-setter-count.js b/test/message/fail/class-accessors-private-undefined-setter-count.js
new file mode 100644
index 0000000..33367c7
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-setter-count.js
@@ -0,0 +1,14 @@
+// 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
+
+class C {
+  get #foo() {}
+  constructor() {
+    this.#foo++;
+  }
+}
+
+new C();
diff --git a/test/message/fail/class-accessors-private-undefined-setter-count.out b/test/message/fail/class-accessors-private-undefined-setter-count.out
new file mode 100644
index 0000000..787a000
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-setter-count.out
@@ -0,0 +1,9 @@
+# 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.
+*%(basename)s:10: TypeError: '#foo' was defined without a setter
+    this.#foo++;
+    ^
+TypeError: '#foo' was defined without a setter
+    at new C (*%(basename)s:10:5)
+    at *%(basename)s:14:1
diff --git a/test/message/fail/class-accessors-private-undefined-setter-nested.js b/test/message/fail/class-accessors-private-undefined-setter-nested.js
new file mode 100644
index 0000000..2f28fc2
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-setter-nested.js
@@ -0,0 +1,19 @@
+// 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
+
+class C {
+  get #a() {}
+  getA(obj) { return obj.#a; }
+
+  constructor() {
+    class D {
+      set #a(val) {}
+    }
+    this.getA(new D());
+  }
+}
+
+new C;
diff --git a/test/message/fail/class-accessors-private-undefined-setter-nested.out b/test/message/fail/class-accessors-private-undefined-setter-nested.out
new file mode 100644
index 0000000..5f22848
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-setter-nested.out
@@ -0,0 +1,10 @@
+# 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.
+*%(basename)s:9: TypeError: Cannot read private member C from an object whose class did not declare it
+  getA(obj) { return obj.#a; }
+                         ^
+TypeError: Cannot read private member C from an object whose class did not declare it
+    at C.getA (*%(basename)s:9:26)
+    at new C (*%(basename)s:15:10)
+    at *%(basename)s:19:1
diff --git a/test/message/fail/class-accessors-private-undefined-setter.js b/test/message/fail/class-accessors-private-undefined-setter.js
new file mode 100644
index 0000000..2a1f057
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-setter.js
@@ -0,0 +1,13 @@
+// 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
+
+class C {
+  get #a() {}
+  constructor() {
+    this.#a = 1;
+  }
+}
+new C;
diff --git a/test/message/fail/class-accessors-private-undefined-setter.out b/test/message/fail/class-accessors-private-undefined-setter.out
new file mode 100644
index 0000000..a5c56ca
--- /dev/null
+++ b/test/message/fail/class-accessors-private-undefined-setter.out
@@ -0,0 +1,9 @@
+# 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.
+*%(basename)s:10: TypeError: '#a' was defined without a setter
+    this.#a = 1;
+            ^
+TypeError: '#a' was defined without a setter
+    at new C (*%(basename)s:10:13)
+    at *%(basename)s:13:1
\ No newline at end of file
diff --git a/test/message/fail/class-fields-private-source-positions.out b/test/message/fail/class-fields-private-source-positions.out
index 7c2f999..6be1fd8 100644
--- a/test/message/fail/class-fields-private-source-positions.out
+++ b/test/message/fail/class-fields-private-source-positions.out
@@ -1,5 +1,5 @@
-*%(basename)s:9: TypeError: Read of private field #a from an object which did not contain the field
+*%(basename)s:9: TypeError: Cannot read private member #a from an object whose class did not declare it
     [o.#a](){}
        ^
-TypeError: Read of private field #a from an object which did not contain the field
+TypeError: Cannot read private member #a from an object whose class did not declare it
     at *%(basename)s:9:8
diff --git a/test/message/fail/class-fields-private-throw-early-2.out b/test/message/fail/class-fields-private-throw-early-2.out
index 1cf7bb4..9731e7d 100644
--- a/test/message/fail/class-fields-private-throw-early-2.out
+++ b/test/message/fail/class-fields-private-throw-early-2.out
@@ -1,6 +1,6 @@
-*%(basename)s:8: TypeError: Write of private field #x to an object which did not contain the field
+*%(basename)s:8: TypeError: Cannot write private member #x to an object whose class did not declare it
     ({}).#x = 1;
             ^
-TypeError: Write of private field #x to an object which did not contain the field
+TypeError: Cannot write private member #x to an object whose class did not declare it
     at new X (*%(basename)s:8:13)
     at *%(basename)s:12:1
\ No newline at end of file
diff --git a/test/message/fail/class-fields-private-throw-read.out b/test/message/fail/class-fields-private-throw-read.out
index ec8dcf5..21823ed 100644
--- a/test/message/fail/class-fields-private-throw-read.out
+++ b/test/message/fail/class-fields-private-throw-read.out
@@ -1,6 +1,6 @@
-*%(basename)s:7: TypeError: Read of private field #x from an object which did not contain the field
+*%(basename)s:7: TypeError: Cannot read private member #x from an object whose class did not declare it
   eq(o) { return this.#x === o.#x; }
                                ^
-TypeError: Read of private field #x from an object which did not contain the field
+TypeError: Cannot read private member #x from an object whose class did not declare it
     at X.eq (*%(basename)s:7:32)
     at *%(basename)s:10:9
\ No newline at end of file
diff --git a/test/message/fail/class-fields-private-throw-write.out b/test/message/fail/class-fields-private-throw-write.out
index 8d9047c..81a73ca 100644
--- a/test/message/fail/class-fields-private-throw-write.out
+++ b/test/message/fail/class-fields-private-throw-write.out
@@ -1,6 +1,6 @@
-*%(basename)s:7: TypeError: Write of private field #x to an object which did not contain the field
+*%(basename)s:7: TypeError: Cannot write private member #x to an object whose class did not declare it
   setX(o, val) { o.#x = val; }
                       ^
-TypeError: Write of private field #x to an object which did not contain the field
+TypeError: Cannot write private member #x to an object whose class did not declare it
     at X.setX (*%(basename)s:7:23)
     at *%(basename)s:10:9
\ No newline at end of file
diff --git a/test/mjsunit/harmony/private-accessors.js b/test/mjsunit/harmony/private-accessors.js
index 29ad0c4..3a82811 100644
--- a/test/mjsunit/harmony/private-accessors.js
+++ b/test/mjsunit/harmony/private-accessors.js
@@ -8,12 +8,40 @@
 
 // Complementary private accessors.
 {
+  let store = 1;
   class C {
-    get #a() {  }
-    set #a(val) { }
+    get #a() { return store; }
+    set #a(val) { store = val; }
+    incA() { this.#a++; }  // CountOperation
+    setA(val) { this.#a = val; }
+    getA() { return this.#a; }
   }
 
-  new C;
+  const c = new C;
+  assertEquals(store, c.getA());
+  assertEquals(1, c.getA());
+  c.setA(2);
+  assertEquals(store, c.getA());
+  assertEquals(2, c.getA());
+  c.incA();
+  assertEquals(store, c.getA());
+  assertEquals(3, c.getA());
+}
+
+// Compound assignment.
+{
+  let store;
+  class A {
+    get #a() { return store; }
+    set #a(val) { store = val; }
+    getA() { return this.#a; }
+    constructor(val) {
+      ({ y: this.#a } = val);
+    }
+  }
+
+  const a = new A({y: 'test'});
+  assertEquals('test', a.getA());
 }
 
 // Accessing super in private accessors.
@@ -39,13 +67,20 @@
 // Nested private accessors.
 {
   class C {
-    a() { this.#a; }
     get #a() {
-      class D { get #a() { } }
+      let storeD = 'd';
+      class D {
+        // Shadows outer #a
+        get #a() { return storeD; }
+        getD() { return this.#a; }
+      }
       return new D;
     }
+    getA() {
+      return this.#a;
+    }
   }
-  new C().a();
+  assertEquals('d', new C().getA().getD());
 }
 
 // Duplicate private accessors.