blob: 360b065f17db62b000fce8ff63202286fb131a5d [file] [log] [blame]
// 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";
// Basic private method test
{
let calledWith;
class C {
#a(arg) { calledWith = arg; }
callA(arg) { this.#a(arg); }
}
const c = new C;
assertEquals(undefined, c.a);
assertEquals(undefined, calledWith);
c.callA(1);
assertEquals(1, calledWith);
}
// Call private method in another instance
{
class C {
#a(arg) { this.calledWith = arg; }
callAIn(obj, arg) { obj.#a(arg); }
}
const c = new C;
const c2 = new C;
assertEquals(undefined, c.a);
assertEquals(undefined, c.calledWith);
assertEquals(undefined, c2.calledWith);
c2.callAIn(c, 'fromC2');
assertEquals('fromC2', c.calledWith);
assertEquals(undefined, c2.calledWith);
c2.callAIn(c2, 'c2');
assertEquals('fromC2', c.calledWith);
assertEquals('c2', c2.calledWith);
assertThrows(() => { c2.callAIn({}); }, TypeError);
}
// Private methods and private fields
{
class C {
#a;
constructor(a) {
this.#a = a;
}
#getAPlus1() {
return this.#a + 1;
}
equals(obj) {
return this.#getAPlus1() === obj.#getAPlus1();
}
}
const c = new C(0);
const c2 = new C(2);
const c3 = new C(2);
assertEquals(true, c2.equals(c3));
assertEquals(false, c2.equals(c));
assertEquals(false, c3.equals(c));
}
// Class inheritance
{
class A {
#val;
constructor(a) {
this.#val = a;
}
#a() { return this.#val; }
getA() { return this.#a(); }
}
class B extends A {
constructor(b) {
super(b);
}
b() { return this.getA() }
}
const b = new B(1);
assertEquals(1, b.b());
}
// Private members should be accessed according to the class the
// invoked method is in.
{
class A {
#val;
constructor(a) {
this.#val = a;
}
#getVal() { return this.#val; }
getA() { return this.#getVal(); }
getVal() { return this.#getVal(); }
}
class B extends A {
#val;
constructor(a, b) {
super(a);
this.#val = b;
}
#getVal() { return this.#val; }
getB() { return this.#getVal(); }
getVal() { return this.#getVal(); }
}
const b = new B(1, 2);
assertEquals(1, b.getA());
assertEquals(2, b.getB());
assertEquals(1, A.prototype.getVal.call(b));
assertEquals(2, B.prototype.getVal.call(b));
const a = new A(1);
assertEquals(1, a.getA());
assertThrows(() => B.prototype.getB.call(a), TypeError);
}
// Private methods in nested classes.
{
class C {
#b() {
class B {
#foo(arg) { return arg; }
callFoo(arg) { return this.#foo(arg); }
}
return new B();
}
createB() { return this.#b(); }
}
const c = new C;
const b = c.createB();
assertEquals(1, b.callFoo(1));
}
// Private methods in nested classes with inheritance.
{
class C {
#b() {
class B extends C {
#foo(arg) { return arg; }
callFoo(arg) { return this.#foo(arg); }
}
return new B();
}
createB() { return this.#b(); }
}
const c = new C;
const b = c.createB();
assertEquals(1, b.callFoo(1));
const b2 = b.createB();
assertEquals(1, b2.callFoo(1));
}
// Class expressions.
{
const C = class {
#a() { return 1; }
callA(obj) { return obj.#a() }
};
const c = new C;
const c2 = new C;
assertEquals(1, c.callA(c));
assertEquals(1, c.callA(c2));
}
// Nested class expressions.
{
const C = class {
#b() {
const B = class {
#foo(arg) { return arg; }
callFoo(arg) { return this.#foo(arg); }
};
return new B();
}
createB() { return this.#b(); }
};
const c = new C;
const b = c.createB();
assertEquals(1, b.callFoo(1));
}
// Nested class expressions with hierarchy.
{
const C = class {
#b() {
const B = class extends C {
#foo(arg) { return arg; }
callFoo(arg) { return this.#foo(arg); }
}
return new B();
}
createB() { return this.#b(); }
}
const c = new C;
const b = c.createB();
assertEquals(1, b.callFoo(1));
const b2 = b.createB();
assertEquals(1, b2.callFoo(1));
}
// Adding the brand twice on the same object should throw.
{
class A {
constructor(arg) {
return arg;
}
}
class C extends A {
#x() { }
constructor(arg) {
super(arg);
}
}
let c1 = new C({});
assertThrows(() => new C(c1), TypeError);
}
// Private methods should be not visible to proxies.
{
class X {
#x() {}
x() { this.#x(); };
callX(obj) { obj.#x(); }
}
let handlerCalled = false;
const x = new X();
let p = new Proxy(new X, {
apply(target, thisArg, argumentsList) {
handlerCalled = true;
Reflect.apply(target, thisArg, argumentsList);
}
});
assertThrows(() => p.x(), TypeError);
assertThrows(() => x.callX(p), TypeError);
assertThrows(() => X.prototype.x.call(p), TypeError);
assertThrows(() => X.prototype.callX(p), TypeError);
assertEquals(false, handlerCalled);
}
// Reference outside of class.
{
class C {
#a() {}
}
assertThrows('new C().#a()');
}
// Duplicate private names.
{
assertThrows('class C { #a = 1; #a() {} }');
}
{
// TODO(v8:9177): test extending a class expression that does not have
// a private method.
class D extends class {
#c() {}
} {
#d() {}
}
class E extends D {
#e() {}
}
new D;
new E;
}