Bug 1066233 - Tests. (r=jorendorff)

This commit is contained in:
Eric Faust 2015-03-11 00:44:41 -07:00
parent 3ba28c5305
commit ad9c2fb296
9 changed files with 478 additions and 237 deletions

View File

@ -4,6 +4,9 @@ var test = `
assertThrowsInstanceOf(() => eval(\`class a extends Math.sin {
constructor() { }
}\`), TypeError);
assertThrowsInstanceOf(() => eval(\`(class a extends Math.sin {
constructor() { }
})\`), TypeError);
// Unless it's null, in which case it works like a normal class, except that
// the prototype object does not inherit from Object.prototype.
@ -13,14 +16,19 @@ class basic {
class nullExtends extends null {
constructor() { }
}
assertEq(Object.getPrototypeOf(basic), Function.prototype);
assertEq(Object.getPrototypeOf(nullExtends), Function.prototype);
assertEq(Object.getPrototypeOf(basic.prototype), Object.prototype);
assertEq(Object.getPrototypeOf(nullExtends.prototype), null);
var nullExtendsExpr = class extends null { constructor() { } };
var baseMethodCalled = false;
var staticMethodCalled = false;
var overrideCalled = "";
assertEq(Object.getPrototypeOf(basic), Function.prototype);
assertEq(Object.getPrototypeOf(basic.prototype), Object.prototype);
for (let extension of [nullExtends, nullExtendsExpr]) {
assertEq(Object.getPrototypeOf(extension), Function.prototype);
assertEq(Object.getPrototypeOf(extension.prototype), null);
}
var baseMethodCalled;
var staticMethodCalled;
var overrideCalled;
class base {
constructor() { };
method() { baseMethodCalled = true; }
@ -31,22 +39,32 @@ class derived extends base {
constructor() { };
override() { overrideCalled = "derived"; }
}
var derivedExpr = class extends base {
constructor() { };
override() { overrideCalled = "derived"; }
};
// Make sure we get the right object layouts.
assertEq(Object.getPrototypeOf(derived), base);
assertEq(Object.getPrototypeOf(derived.prototype), base.prototype);
for (let extension of [derived, derivedExpr]) {
baseMethodCalled = false;
staticMethodCalled = false;
overrideCalled = "";
// Make sure we get the right object layouts.
assertEq(Object.getPrototypeOf(extension), base);
assertEq(Object.getPrototypeOf(extension.prototype), base.prototype);
// We do inherit the methods, right?
(new derived()).method();
assertEq(baseMethodCalled, true);
// We do inherit the methods, right?
(new extension()).method();
assertEq(baseMethodCalled, true);
// But we can still override them?
(new derived()).override();
assertEq(overrideCalled, "derived");
// But we can still override them?
(new extension()).override();
assertEq(overrideCalled, "derived");
// What about the statics?
derived.staticMethod();
assertEq(staticMethodCalled, true);
// What about the statics?
extension.staticMethod();
assertEq(staticMethodCalled, true);
}
// Gotta extend an object, or null.
function nope() {
@ -54,7 +72,13 @@ function nope() {
constructor() { }
}
}
function nopeExpr() {
(class extends "Bar" {
constructor() { }
});
}
assertThrowsInstanceOf(nope, TypeError);
assertThrowsInstanceOf(nopeExpr, TypeError);
// The .prototype of the extension must be an object, or null.
nope.prototype = "not really, no";
@ -63,7 +87,13 @@ function stillNo() {
constructor() { }
}
}
function stillNoExpr() {
(class extends nope {
constructor() { }
});
}
assertThrowsInstanceOf(stillNo, TypeError);
assertThrowsInstanceOf(stillNoExpr, TypeError);
`;

View File

@ -2,22 +2,25 @@ var test = `
// The prototype of a class is a non-writable, non-configurable, non-enumerable data property.
class a { constructor() { } }
var protoDesc = Object.getOwnPropertyDescriptor(a, "prototype");
assertEq(protoDesc.writable, false);
assertEq(protoDesc.configurable, false);
assertEq(protoDesc.enumerable, false);
let b = class { constructor() { } };
for (let test of [a,b]) {
var protoDesc = Object.getOwnPropertyDescriptor(test, "prototype");
assertEq(protoDesc.writable, false);
assertEq(protoDesc.configurable, false);
assertEq(protoDesc.enumerable, false);
var prototype = protoDesc.value;
assertEq(typeof prototype, "object");
assertEq(Object.getPrototypeOf(prototype), Object.prototype);
assertEq(Object.isExtensible(prototype), true);
var prototype = protoDesc.value;
assertEq(typeof prototype, "object");
assertEq(Object.getPrototypeOf(prototype), Object.prototype);
assertEq(Object.isExtensible(prototype), true);
var desiredPrototype = {};
Object.defineProperty(desiredPrototype, "constructor", { writable: true,
configurable: true,
enumerable: false,
value: a });
assertDeepEq(prototype, desiredPrototype);
var desiredPrototype = {};
Object.defineProperty(desiredPrototype, "constructor", { writable: true,
configurable: true,
enumerable: false,
value: test });
assertDeepEq(prototype, desiredPrototype);
}
try {
eval(\`class a {
@ -50,6 +53,25 @@ assertThrowsInstanceOf(() => eval(\`
static set ["prototype"](x) { }
}
\`), TypeError);
assertThrowsInstanceOf(() => eval(\`(
class a {
constructor() { };
static ["prototype"]() { }
}
)\`), TypeError);
assertThrowsInstanceOf(() => eval(\`(
class a {
constructor() { };
static get ["prototype"]() { }
}
)\`), TypeError);
assertThrowsInstanceOf(() => eval(\`(
class a {
constructor() { };
static set ["prototype"](x) { }
}
)\`), TypeError);
*/
}
`;

View File

@ -8,20 +8,40 @@ class a { constructor(x) { assertEq(x, 4); called = true } }
new a(4);
assertEq(called, true);
called = false;
var aExpr = class { constructor(x) { assertEq(x, 4); called = true } };
new aExpr(4);
assertEq(called, true);
called = false;
class b { constructor() { called = true } method() { } }
new b();
assertEq(called, true);
called = false;
var bExpr = class { constructor() { called = true } method() { } };
new bExpr();
assertEq(called, true);
called = false;
class c { method() { } constructor() { called = true; } }
new c();
assertEq(called, true);
called = false;
var cExpr = class { method() { } constructor() { called = true; } }
new cExpr();
assertEq(called, true);
called = false;
class d { [\"constructor\"]() { throw new Error(\"NO\"); } constructor() { called = true; } }
new d();
assertEq(called, true);
called = false;
var dExpr = class { [\"constructor\"]() { throw new Error(\"NO\"); } constructor() { called = true; } }
new dExpr();
assertEq(called, true);
`;
if (classesEnabled())

View File

@ -1,23 +1,36 @@
// Class statements should create an immutable inner binding. Since all code in
// classes is in strict mode, attempts to mutate it should throw.
// Named class definitions should create an immutable inner binding.
// Since all code in classes is in strict mode, attempts to mutate it
// should throw.
if (classesEnabled()) {
// XXXefaust Because we currently try to do assignment to const as an early error,
// this is a syntax error. It is specced to be a TypeError
// XXXefaust Because we currently try to do assignment to const as an early
// error, sometimes, maybe, this is almost sometimes a syntax error.
// It is specced to be a TypeError
function syntaxWrapper() {
function statementWrapper() {
eval("class Foo { constructor() { } tryBreak() { Foo = 4; } }");
}
assertThrowsInstanceOf(syntaxWrapper, SyntaxError);
function expressionWrapper() {
// Mmmmm. Lazy parseing means we don't see this as an error until later.
eval(`var x = class Foo { constructor() { }; tryBreak() { Foo = 4; } };
new x().tryBreak();`);
}
assertThrowsInstanceOf(statementWrapper, SyntaxError);
assertThrowsInstanceOf(expressionWrapper, TypeError);
/*
var test = `
class Foo { constructor() { }; tryBreak() { Foo = 4; } }
assertThrowsInstanceOf(() => new Foo().tryBreak(), TypeError);
for (let result of [Foo, class Bar { constructor() { }; tryBreak() { Bar = 4; } }])
assertThrowsInstanceOf(() => new result().tryBreak(), TypeError);
{
class foo { constructor() { }; tryBreak() { foo = 4; } }
assertThrowsInstanceOf(() => new foo().tryBreak(), TypeError);
for (let result of [foo, class Bar { constructor() { }; tryBreak() { Bar = 4 }])
assertThrowsInstanceOf(() => new result().tryBreak(), TypeError);
}
`;
*/
@ -30,6 +43,11 @@ assertThrowsInstanceOf(()=>eval(\`class Bar {
[Bar] () { };
}\`), ReferenceError);
assertThrowsInstanceOf(()=>eval(\`(class Bar {
constructor() { };
[Bar] () { };
})\`), ReferenceError);
// There's no magic "inner binding" global
{
class Foo {
@ -43,6 +61,15 @@ assertThrowsInstanceOf(()=>eval(\`class Bar {
}
}
assertEq(new Foo().test(), false);
assertEq(new class foo {
constructor() { };
test() {
return new class bar {
constructor() { }
test() { return foo === bar }
}().test();
}
}().test(), false);
}
// Inner bindings are shadowable
@ -52,8 +79,20 @@ assertThrowsInstanceOf(()=>eval(\`class Bar {
test(Foo) { return Foo; }
}
assertEq(new Foo().test(4), 4);
assertEq(new class foo {
constructor() { };
test(foo) { return foo }
}().test(4), 4);
}
// Inner bindings in expressions should shadow even existing names.
class Foo { constructor() { } static method() { throw new Error("NO!"); } }
assertEq(new class Foo {
constructor() { };
static method() { return 4; };
test() { return Foo.method(); }
}().test(), 4);
// The outer binding is distinct from the inner one
{
let orig_X;
@ -68,6 +107,8 @@ assertThrowsInstanceOf(()=>eval(\`class Bar {
assertEq(X, 13);
new orig_X().f();
}
`;
eval(test);

View File

@ -2,14 +2,14 @@
var test= `
var methodCalled = false;
var getterCalled = false;
var setterCalled = false;
var constructorCalled = false;
var staticMethodCalled = false;
var staticGetterCalled = false;
var staticSetterCalled = false;
class a {
var methodCalled;
var getterCalled;
var setterCalled;
var constructorCalled;
var staticMethodCalled;
var staticGetterCalled;
var staticSetterCalled;
class testClass {
constructor() { constructorCalled = true; }
__proto__() { methodCalled = true }
get getter() { getterCalled = true; }
@ -19,58 +19,80 @@ class a {
static set staticSetter(x) { staticSetterCalled = true; }
*[Symbol.iterator]() { yield "cow"; yield "pig"; }
}
var aConstDesc = Object.getOwnPropertyDescriptor(a.prototype, \"constructor\");
assertEq(aConstDesc.writable, true);
assertEq(aConstDesc.configurable, true);
assertEq(aConstDesc.enumerable, false);
aConstDesc.value();
assertEq(constructorCalled, true);
// __proto__ is just an identifier for classes. No prototype changes are made.
assertEq(Object.getPrototypeOf(a.prototype), Object.prototype);
var aMethDesc = Object.getOwnPropertyDescriptor(a.prototype, \"__proto__\");
assertEq(aMethDesc.writable, true);
assertEq(aMethDesc.configurable, true);
assertEq(aMethDesc.enumerable, true);
aMethDesc.value();
assertEq(methodCalled, true);
for (let a of [testClass,
class {
constructor() { constructorCalled = true; }
__proto__() { methodCalled = true }
get getter() { getterCalled = true; }
set setter(x) { setterCalled = true; }
static staticMethod() { staticMethodCalled = true; }
static get staticGetter() { staticGetterCalled = true; }
static set staticSetter(x) { staticSetterCalled = true; }
*[Symbol.iterator]() { yield "cow"; yield "pig"; }
}]) {
var aGetDesc = Object.getOwnPropertyDescriptor(a.prototype, \"getter\");
assertEq(aGetDesc.configurable, true);
assertEq(aGetDesc.enumerable, true);
aGetDesc.get();
assertEq(getterCalled, true);
methodCalled = false;
getterCalled = false;
setterCalled = false;
constructorCalled = false;
staticMethodCalled = false;
staticGetterCalled = false;
staticSetterCalled = false;
var aSetDesc = Object.getOwnPropertyDescriptor(a.prototype, \"setter\");
assertEq(aSetDesc.configurable, true);
assertEq(aSetDesc.enumerable, true);
aSetDesc.set();
assertEq(setterCalled, true);
assertDeepEq(aSetDesc, Object.getOwnPropertyDescriptor(a.prototype, \"setter\"));
var aConstDesc = Object.getOwnPropertyDescriptor(a.prototype, \"constructor\");
assertEq(aConstDesc.writable, true);
assertEq(aConstDesc.configurable, true);
assertEq(aConstDesc.enumerable, false);
aConstDesc.value();
assertEq(constructorCalled, true);
assertEq(Object.getOwnPropertyDescriptor(new a(), \"staticMethod\"), undefined);
var aStaticMethDesc = Object.getOwnPropertyDescriptor(a, \"staticMethod\");
assertEq(aStaticMethDesc.configurable, true);
assertEq(aStaticMethDesc.enumerable, true);
assertEq(aStaticMethDesc.writable, true);
aStaticMethDesc.value();
assertEq(staticMethodCalled, true);
// __proto__ is just an identifier for classes. No prototype changes are made.
assertEq(Object.getPrototypeOf(a.prototype), Object.prototype);
var aMethDesc = Object.getOwnPropertyDescriptor(a.prototype, \"__proto__\");
assertEq(aMethDesc.writable, true);
assertEq(aMethDesc.configurable, true);
assertEq(aMethDesc.enumerable, true);
aMethDesc.value();
assertEq(methodCalled, true);
assertEq(Object.getOwnPropertyDescriptor(new a(), \"staticGetter\"), undefined);
var aStaticGetDesc = Object.getOwnPropertyDescriptor(a, \"staticGetter\");
assertEq(aStaticGetDesc.configurable, true);
assertEq(aStaticGetDesc.enumerable, true);
aStaticGetDesc.get();
assertEq(staticGetterCalled, true);
var aGetDesc = Object.getOwnPropertyDescriptor(a.prototype, \"getter\");
assertEq(aGetDesc.configurable, true);
assertEq(aGetDesc.enumerable, true);
aGetDesc.get();
assertEq(getterCalled, true);
assertEq(Object.getOwnPropertyDescriptor(new a(), \"staticSetter\"), undefined);
var aStaticSetDesc = Object.getOwnPropertyDescriptor(a, \"staticSetter\");
assertEq(aStaticSetDesc.configurable, true);
assertEq(aStaticSetDesc.enumerable, true);
aStaticSetDesc.set();
assertEq(staticSetterCalled, true);
var aSetDesc = Object.getOwnPropertyDescriptor(a.prototype, \"setter\");
assertEq(aSetDesc.configurable, true);
assertEq(aSetDesc.enumerable, true);
aSetDesc.set();
assertEq(setterCalled, true);
assertDeepEq(aSetDesc, Object.getOwnPropertyDescriptor(a.prototype, \"setter\"));
assertEq([...new a()].join(), "cow,pig");
assertEq(Object.getOwnPropertyDescriptor(new a(), \"staticMethod\"), undefined);
var aStaticMethDesc = Object.getOwnPropertyDescriptor(a, \"staticMethod\");
assertEq(aStaticMethDesc.configurable, true);
assertEq(aStaticMethDesc.enumerable, true);
assertEq(aStaticMethDesc.writable, true);
aStaticMethDesc.value();
assertEq(staticMethodCalled, true);
assertEq(Object.getOwnPropertyDescriptor(new a(), \"staticGetter\"), undefined);
var aStaticGetDesc = Object.getOwnPropertyDescriptor(a, \"staticGetter\");
assertEq(aStaticGetDesc.configurable, true);
assertEq(aStaticGetDesc.enumerable, true);
aStaticGetDesc.get();
assertEq(staticGetterCalled, true);
assertEq(Object.getOwnPropertyDescriptor(new a(), \"staticSetter\"), undefined);
var aStaticSetDesc = Object.getOwnPropertyDescriptor(a, \"staticSetter\");
assertEq(aStaticSetDesc.configurable, true);
assertEq(aStaticSetDesc.enumerable, true);
aStaticSetDesc.set();
assertEq(staticSetterCalled, true);
assertEq([...new a()].join(), "cow,pig");
}
`;
if (classesEnabled())

View File

@ -1,41 +1,83 @@
// Ensure that we can overwrite methods when more tha one is present.
var test = `
var result = 0;
// Regardless of order, the constructor is overridden by any CPN, because it's
// processed seperately.
class a { [\"constructor\"]() { result += 1; }; constructor() { result += 2; } }
var aInst = new a();
assertEq(result, 2);
aInst.constructor();
assertEq(result, 3);
class b { constructor() { result += 2; } [\"constructor\"]() { result += 1; }; }
var bInst = new b();
assertEq(result, 5);
bInst.constructor();
assertEq(result, 6);
{
var result = 0;
// Regardless of order, the constructor is overridden by any CPN, because it's
// processed seperately.
class a { [\"constructor\"]() { result += 1; }; constructor() { result += 2; } }
var aInst = new a();
assertEq(result, 2);
aInst.constructor();
assertEq(result, 3);
class c { constructor() { } method() { result += 1 } get method() { result += 2 } }
new c().method;
assertEq(result, 8);
class b { constructor() { result += 2; } [\"constructor\"]() { result += 1; }; }
var bInst = new b();
assertEq(result, 5);
bInst.constructor();
assertEq(result, 6);
class d { constructor() { } get method() { result += 1 } method() { result += 2 } }
new d().method();
assertEq(result, 10);
class c { constructor() { } method() { result += 1 } get method() { result += 2 } }
new c().method;
assertEq(result, 8);
// getters and setter should not overwrite each other, but merge cleanly.
class e { constructor() { } get method() { result += 1 } set method(x) { } }
new e().method;
assertEq(result, 11);
class d { constructor() { } get method() { result += 1 } method() { result += 2 } }
new d().method();
assertEq(result, 10);
class f { constructor() { }
set method(x) { throw "NO"; }
method() { throw "NO" }
get method() { return new Function("result += 1"); }
}
new f().method();
assertEq(result, 12);
// getters and setter should not overwrite each other, but merge cleanly.
class e { constructor() { } get method() { result += 1 } set method(x) { } }
new e().method;
assertEq(result, 11);
class f { constructor() { }
set method(x) { throw "NO"; }
method() { throw "NO" }
get method() { return new Function("result += 1"); }
}
new f().method();
assertEq(result, 12);
}
// Try again with expressions.
{
var result = 0;
// Regardless of order, the constructor is overridden by any CPN, because it's
// processed seperately.
let a = class { [\"constructor\"]() { result += 1; }; constructor() { result += 2; } };
var aInst = new a();
assertEq(result, 2);
aInst.constructor();
assertEq(result, 3);
let b = class { constructor() { result += 2; } [\"constructor\"]() { result += 1; }; };
var bInst = new b();
assertEq(result, 5);
bInst.constructor();
assertEq(result, 6);
let c = class { constructor() { } method() { result += 1 } get method() { result += 2 } };
new c().method;
assertEq(result, 8);
let d = class { constructor() { } get method() { result += 1 } method() { result += 2 } };
new d().method();
assertEq(result, 10);
// getters and setter should not overwrite each other, but merge cleanly.
let e = class { constructor() { } get method() { result += 1 } set method(x) { } };
new e().method;
assertEq(result, 11);
let f = class { constructor() { }
set method(x) { throw "NO"; }
method() { throw "NO" }
get method() { return new Function("result += 1"); }
};
new f().method();
assertEq(result, 12);
}
`;
if (classesEnabled())

View File

@ -1,4 +1,4 @@
// A class creates a mutable lexical outer binding.
// A class statement creates a mutable lexical outer binding.
var test = `
class Foo { constructor() { } }

View File

@ -4,21 +4,36 @@
var test = `
class a { constructor() { Object.preventExtensions({}).prop = 0; } }
assertThrowsInstanceOf(() => new a(), TypeError);
var aExpr = class { constructor() { Object.preventExtensions().prop = 0; } };
assertThrowsInstanceOf(() => new aExpr(), TypeError);
function shouldThrow() {
function shouldThrowCPN() {
class b {
[Object.preventExtensions({}).prop = 4]() { }
constructor() { }
}
}
assertThrowsInstanceOf(shouldThrow, TypeError);
function shouldThrowCPNExpr() {
var b = class {
[Object.preventExtensions({}).prop = 4]() { }
constructor() { }
};
}
assertThrowsInstanceOf(shouldThrowCPN, TypeError);
assertThrowsInstanceOf(shouldThrowCPNExpr, TypeError);
function shouldThrow2() {
function shouldThrowHeritage() {
class b extends (Object.preventExtensions({}).prop = 4) {
constructor() { }
}
}
assertThrowsInstanceOf(shouldThrow2, TypeError);
function shouldThrowHeritageExpr() {
var b = class extends (Object.preventExtensions({}).prop = 4) {
constructor() { }
};
}
assertThrowsInstanceOf(shouldThrowHeritage, TypeError);
assertThrowsInstanceOf(shouldThrowHeritageExpr, TypeError);
`;
if (classesEnabled())

View File

@ -72,6 +72,10 @@ function classStmt(id, heritage, body) Pattern({ type: "ClassStatement",
name: id,
heritage: heritage,
body: body})
function classExpr(id, heritage, body) Pattern({ type: "ClassExpression",
name: id,
heritage: heritage,
body: body})
function classMethod(id, body, kind, static) Pattern({ type: "ClassMethod",
name: id,
body: body,
@ -1158,9 +1162,6 @@ function classesEnabled() {
}
function testClasses() {
// No unnamed class statements.
assertError("class { constructor() { } }", SyntaxError);
function simpleMethod(id, kind, generator, args=[], isStatic=false) {
assertEq(generator && kind === "method", generator);
let idN = ident(id);
@ -1175,76 +1176,98 @@ function testClasses() {
funExpr(null, [], blockStmt([])),
"method", isStatic);
}
function setClassMethods(class_, methods) {
class_.template.body = methods;
function assertClassExpr(str, methods, heritage=null, name=null) {
let template = classExpr(name, heritage, methods);
assertExpr("(" + str + ")", template);
}
function setClassHeritage(class_, heritage) {
class_.template.heritage = heritage;
function assertClass(str, methods, heritage=null) {
let namelessStr = str.replace("NAME", "");
let namedStr = str.replace("NAME", "Foo");
assertClassExpr(namelessStr, methods, heritage);
assertClassExpr(namedStr, methods, heritage, ident("Foo"));
let template = classStmt(ident("Foo"), heritage, methods);
assertStmt(namedStr, template);
}
function assertNamedClassError(str, error) {
assertError(str, error);
assertError("(" + str + ")", error);
}
function assertClassError(str, error) {
assertNamedClassError(str, error);
assertError("(" + str.replace("NAME", "") + ")", error);
}
let simpleConstructor = simpleMethod("constructor", "method", false);
let emptyFooClass = classStmt(ident("Foo"), null, [simpleConstructor]);
/* Trivial classes */
assertStmt("class Foo { constructor() { } }", emptyFooClass);
// Unnamed class statements are forbidden, but unnamed class expressions are
// just fine.
assertError("class { constructor() { } }", SyntaxError);
assertClass("class NAME { constructor() { } }", [simpleConstructor]);
// A class name must actually be a name
assertNamedClassError("class x.y { constructor() {} }", SyntaxError);
assertNamedClassError("class [] { constructor() {} }", SyntaxError);
assertNamedClassError("class {x} { constructor() {} }", SyntaxError);
assertNamedClassError("class for { constructor() {} }", SyntaxError);
// Allow methods and accessors
let stmt = classStmt(ident("Foo"), null,
[simpleConstructor, simpleMethod("method", "method", false)]);
assertStmt("class Foo { constructor() { } method() { } }", stmt);
assertClass("class NAME { constructor() { } method() { } }",
[simpleConstructor, simpleMethod("method", "method", false)]);
setClassMethods(stmt, [simpleConstructor, simpleMethod("method", "get", false)]);
assertStmt("class Foo { constructor() { } get method() { } }", stmt);
assertClass("class NAME { constructor() { } get method() { } }",
[simpleConstructor, simpleMethod("method", "get", false)]);
setClassMethods(stmt, [simpleConstructor, simpleMethod("method", "set", false, ["x"])]);
assertStmt("class Foo { constructor() { } set method(x) { } }", stmt);
assertClass("class NAME { constructor() { } set method(x) { } }",
[simpleConstructor, simpleMethod("method", "set", false, ["x"])]);
/* Static */
setClassMethods(stmt, [simpleConstructor,
simpleMethod("method", "method", false, [], true),
simpleMethod("methodGen", "method", true, [], true),
simpleMethod("getter", "get", false, [], true),
simpleMethod("setter", "set", false, ["x"], true)]);
assertStmt(`class Foo {
constructor() { };
static method() { };
static *methodGen() { };
static get getter() { };
static set setter(x) { }
}`, stmt);
assertClass(`class NAME {
constructor() { };
static method() { };
static *methodGen() { };
static get getter() { };
static set setter(x) { }
}`,
[simpleConstructor,
simpleMethod("method", "method", false, [], true),
simpleMethod("methodGen", "method", true, [], true),
simpleMethod("getter", "get", false, [], true),
simpleMethod("setter", "set", false, ["x"], true)]);
// It's not an error to have a method named static, static, or not.
setClassMethods(stmt, [simpleConstructor, simpleMethod("static", "method", false, [], false)]);
assertStmt("class Foo{ constructor() { } static() { } }", stmt);
setClassMethods(stmt, [simpleMethod("static", "method", false, [], true), simpleConstructor]);
assertStmt("class Foo{ static static() { }; constructor() { } }", stmt);
setClassMethods(stmt, [simpleMethod("static", "get", false, [], true), simpleConstructor]);
assertStmt("class Foo { static get static() { }; constructor() { } }", stmt);
setClassMethods(stmt, [simpleConstructor, simpleMethod("static", "set", false, ["x"], true)]);
assertStmt("class Foo { constructor() { }; static set static(x) { } }", stmt);
assertClass("class NAME { constructor() { } static() { } }",
[simpleConstructor, simpleMethod("static", "method", false)]);
assertClass("class NAME { static static() { }; constructor() { } }",
[simpleMethod("static", "method", false, [], true), simpleConstructor]);
assertClass("class NAME { static get static() { }; constructor() { } }",
[simpleMethod("static", "get", false, [], true), simpleConstructor]);
assertClass("class NAME { constructor() { }; static set static(x) { } }",
[simpleConstructor, simpleMethod("static", "set", false, ["x"], true)]);
// You do, however, have to put static in the right spot
assertError("class Foo { constructor() { }; get static foo() { } }", SyntaxError);
assertClassError("class NAME { constructor() { }; get static foo() { } }", SyntaxError);
// Spec disallows "prototype" as a static member in a class, since that
// one's important to make the desugaring work
assertError("class Foo { constructor() { } static prototype() { } }", SyntaxError);
assertError("class Foo { constructor() { } static *prototype() { } }", SyntaxError);
assertError("class Foo { static get prototype() { }; constructor() { } }", SyntaxError);
assertError("class Foo { static set prototype(x) { }; constructor() { } }", SyntaxError);
assertClassError("class NAME { constructor() { } static prototype() { } }", SyntaxError);
assertClassError("class NAME { constructor() { } static *prototype() { } }", SyntaxError);
assertClassError("class NAME { static get prototype() { }; constructor() { } }", SyntaxError);
assertClassError("class NAME { static set prototype(x) { }; constructor() { } }", SyntaxError);
// You are, however, allowed to have a CPN called prototype as a static
setClassMethods(stmt, [simpleConstructor, emptyCPNMethod("prototype", true)]);
assertStmt("class Foo { constructor() { }; static [\"prototype\"]() { } }", stmt);
assertClass("class NAME { constructor() { }; static [\"prototype\"]() { } }",
[simpleConstructor, emptyCPNMethod("prototype", true)]);
/* Constructor */
// Currently, we do not allow default constructors
assertError("class Foo { }", TypeError);
assertClassError("class NAME { }", TypeError);
// It is an error to have two methods named constructor, but not other
// names, regardless if one is an accessor or a generator or static.
assertError("class Foo { constructor() { } constructor(a) { } }", SyntaxError);
assertClassError("class NAME { constructor() { } constructor(a) { } }", SyntaxError);
let methods = [["method() { }", simpleMethod("method", "method", false)],
["*method() { }", simpleMethod("method", "method", true)],
["get method() { }", simpleMethod("method", "get", false)],
@ -1256,66 +1279,93 @@ function testClasses() {
let i,j;
for (i=0; i < methods.length; i++) {
for (j=0; j < methods.length; j++) {
setClassMethods(stmt,
[simpleConstructor,
methods[i][1],
methods[j][1]]);
let str = "class Foo { constructor() { } " +
let str = "class NAME { constructor() { } " +
methods[i][0] + " " + methods[j][0] +
" }";
assertStmt(str, stmt);
assertClass(str, [simpleConstructor, methods[i][1], methods[j][1]]);
}
}
// It is, however, not an error to have a constructor, and a method with a
// computed property name 'constructor'
setClassMethods(stmt, [simpleConstructor, emptyCPNMethod("constructor", false)]);
assertStmt("class Foo { constructor () { } [\"constructor\"] () { } }", stmt);
assertClass("class NAME { constructor () { } [\"constructor\"] () { } }",
[simpleConstructor, emptyCPNMethod("constructor", false)]);
// It is an error to have a generator or accessor named constructor
assertError("class Foo { *constructor() { } }", SyntaxError);
assertError("class Foo { get constructor() { } }", SyntaxError);
assertError("class Foo { set constructor() { } }", SyntaxError);
assertClassError("class NAME { *constructor() { } }", SyntaxError);
assertClassError("class NAME { get constructor() { } }", SyntaxError);
assertClassError("class NAME { set constructor() { } }", SyntaxError);
/* Semicolons */
// Allow Semicolons in Class Definitions
assertStmt("class Foo { constructor() { }; }", emptyFooClass);
assertClass("class NAME { constructor() { }; }", [simpleConstructor]);
// Allow more than one semicolon, even in otherwise trivial classses
assertStmt("class Foo { ;;; constructor() { } }", emptyFooClass);
assertClass("class NAME { ;;; constructor() { } }", [simpleConstructor]);
// Semicolons are optional, even if the methods share a line
setClassMethods(stmt, [simpleMethod("method", "method", false), simpleConstructor]);
assertStmt("class Foo { method() { } constructor() { } }", stmt);
assertClass("class NAME { method() { } constructor() { } }",
[simpleMethod("method", "method", false), simpleConstructor]);
/* Generators */
// No yield as a class name inside a generator
assertError("function *foo() {\
class yield {\
constructor() { } \
}\
}", SyntaxError);
assertError(`function *foo() {
class yield {
constructor() { }
}
}`, SyntaxError);
assertError(`function *foo() {
(class yield {
constructor() { }
})
}`, SyntaxError);
// Methods may be generators, but not accessors
assertError("class Foo { constructor() { } *get foo() { } }", SyntaxError);
assertError("class Foo { constructor() { } *set foo() { } }", SyntaxError);
assertClassError("class NAME { constructor() { } *get foo() { } }", SyntaxError);
assertClassError("class NAME { constructor() { } *set foo() { } }", SyntaxError);
setClassMethods(stmt, [simpleMethod("method", "method", true), simpleConstructor]);
assertStmt("class Foo { *method() { } constructor() { } }", stmt);
assertClass("class NAME { *method() { } constructor() { } }",
[simpleMethod("method", "method", true), simpleConstructor]);
/* Strictness */
// yield is a strict-mode keyword, and class definitions are always strict.
assertError("class Foo { constructor() { var yield; } }", SyntaxError);
assertClassError("class NAME { constructor() { var yield; } }", SyntaxError);
// Beware of the strictness of computed property names. Here use bareword
// deletion (a deprecated action) to check.
assertError("class Foo { constructor() { } [delete bar]() { }}", SyntaxError);
assertClassError("class NAME { constructor() { } [delete bar]() { }}", SyntaxError);
/* Bindings */
// Classes should bind lexically, so they should collide with other in-block
// lexical bindings
// Class statements bind lexically, so they should collide with other
// in-block lexical bindings, but class expressions don't.
assertError("{ let Foo; class Foo { constructor() { } } }", TypeError);
assertStmt("{ let Foo; (class Foo { constructor() { } }) }",
blockStmt([letDecl([{id: ident("Foo"), init: null}]),
exprStmt(classExpr(ident("Foo"), null, [simpleConstructor]))]));
assertError("{ const Foo = 0; class Foo { constructor() { } } }", TypeError);
assertStmt("{ const Foo = 0; (class Foo { constructor() { } }) }",
blockStmt([constDecl([{id: ident("Foo"), init: lit(0)}]),
exprStmt(classExpr(ident("Foo"), null, [simpleConstructor]))]));
assertError("{ class Foo { constructor() { } } class Foo { constructor() { } } }", TypeError);
assertStmt(`{
(class Foo {
constructor() { }
},
class Foo {
constructor() { }
});
}`,
blockStmt([exprStmt(seqExpr([classExpr(ident("Foo"), null, [simpleConstructor]),
classExpr(ident("Foo"), null, [simpleConstructor])]))]));
assertStmt(`{
var x = class Foo { constructor() { } };
class Foo { constructor() { } }
}`,
blockStmt([varDecl([{ id: ident("x"),
init: classExpr(ident("Foo"), null, [simpleConstructor])
}]),
classStmt(ident("Foo"), null, [simpleConstructor])]));
// Can't make a lexical binding inside a block.
assertError("if (1) class Foo { constructor() { } }", SyntaxError);
@ -1323,65 +1373,64 @@ function testClasses() {
/* Heritage Expressions */
// It's illegal to have things that look like "multiple inheritance":
// non-parenthesized comma expressions.
assertError("class Foo extends null, undefined { constructor() { } }", SyntaxError);
assertClassError("class NAME extends null, undefined { constructor() { } }", SyntaxError);
// Again check for strict-mode in heritage expressions
assertError("class Foo extends (delete x) { constructor() { } }", SyntaxError);
assertClassError("class NAME extends (delete x) { constructor() { } }", SyntaxError);
// You must specify an inheritance if you say "extends"
assertError("class Foo extends { constructor() { } }", SyntaxError);
assertClassError("class NAME extends { constructor() { } }", SyntaxError);
// "extends" is still a valid name for a method
setClassMethods(stmt, [simpleConstructor, simpleMethod("extends", "method", false)]);
assertStmt("class Foo { constructor() { }; extends() { } }", stmt);
assertClass("class NAME { constructor() { }; extends() { } }",
[simpleConstructor, simpleMethod("extends", "method", false)]);
// Immediate expression
setClassMethods(stmt, [simpleConstructor]);
setClassHeritage(stmt, lit(null));
assertStmt("class Foo extends null { constructor() { } }", stmt);
assertClass("class NAME extends null { constructor() { } }",
[simpleConstructor], lit(null));
// Sequence expresson
setClassHeritage(stmt, seqExpr([ident("undefined"), ident("undefined")]));
assertStmt("class Foo extends (undefined, undefined) { constructor() { } }", stmt);
assertClass("class NAME extends (undefined, undefined) { constructor() { } }",
[simpleConstructor], seqExpr([ident("undefined"), ident("undefined")]));
// Function expression
let emptyFunction = funExpr(null, [], blockStmt([]));
setClassHeritage(stmt, emptyFunction);
assertStmt("class Foo extends function(){ } { constructor() { } }", stmt);
assertClass("class NAME extends function(){ } { constructor() { } }",
[simpleConstructor], emptyFunction);
// New expression
setClassHeritage(stmt, newExpr(emptyFunction, []));
assertStmt("class Foo extends new function(){ }() { constructor() { } }", stmt);
assertClass("class NAME extends new function(){ }() { constructor() { } }",
[simpleConstructor], newExpr(emptyFunction, []));
// Call expression
setClassHeritage(stmt, callExpr(emptyFunction, []));
assertStmt("class Foo extends function(){ }() { constructor() { } }", stmt);
assertClass("class NAME extends function(){ }() { constructor() { } }",
[simpleConstructor], callExpr(emptyFunction, []));
// Dot expression
setClassHeritage(stmt, dotExpr(objExpr([]), ident("foo")));
assertStmt("class Foo extends {}.foo { constructor() { } }", stmt);
assertClass("class NAME extends {}.foo { constructor() { } }",
[simpleConstructor], dotExpr(objExpr([]), ident("foo")));
// Member expression
setClassHeritage(stmt, memExpr(objExpr([]), ident("foo")));
assertStmt("class Foo extends {}[foo] { constructor() { } }", stmt);
assertClass("class NAME extends {}[foo] { constructor() { } }",
[simpleConstructor], memExpr(objExpr([]), ident("foo")));
/* EOF */
// Clipped classes should throw a syntax error
assertError("class Foo {", SyntaxError);
assertError("class Foo {;", SyntaxError);
assertError("class Foo { constructor", SyntaxError);
assertError("class Foo { constructor(", SyntaxError);
assertError("class Foo { constructor()", SyntaxError);
assertError("class Foo { constructor()", SyntaxError);
assertError("class Foo { constructor() {", SyntaxError);
assertError("class Foo { constructor() { }", SyntaxError);
assertError("class Foo { static", SyntaxError);
assertError("class Foo { static y", SyntaxError);
assertError("class Foo { static *", SyntaxError);
assertError("class Foo { static *y", SyntaxError);
assertError("class Foo { static get", SyntaxError);
assertError("class Foo { static get y", SyntaxError);
assertError("class Foo extends", SyntaxError);
assertClassError("class NAME {", SyntaxError);
assertClassError("class NAME {;", SyntaxError);
assertClassError("class NAME { constructor", SyntaxError);
assertClassError("class NAME { constructor(", SyntaxError);
assertClassError("class NAME { constructor()", SyntaxError);
assertClassError("class NAME { constructor()", SyntaxError);
assertClassError("class NAME { constructor() {", SyntaxError);
assertClassError("class NAME { constructor() { }", SyntaxError);
assertClassError("class NAME { static", SyntaxError);
assertClassError("class NAME { static y", SyntaxError);
assertClassError("class NAME { static *", SyntaxError);
assertClassError("class NAME { static *y", SyntaxError);
assertClassError("class NAME { static get", SyntaxError);
assertClassError("class NAME { static get y", SyntaxError);
assertClassError("class NAME extends", SyntaxError);
}