blob: 10bedf43d7fea76ada331e89169e651f431b6438 [file] [log] [blame]
// |reftest| skip-if(!xulRuntime.shell)
// Classes
function testClasses() {
function methodFun(id, kind, generator, args, body = []) {
assertEq(generator && kind === "method", generator);
assertEq(typeof id === 'string' || id === null, true);
let idN = typeof id === 'string' ? ident(id) : null;
let methodName = kind !== "method" ? null : idN;
return generator
? genFunExpr("es6", methodName, args.map(ident), blockStmt(body))
: funExpr(methodName, args.map(ident), blockStmt(body));
}
function simpleMethod(id, kind, generator, args=[], isStatic=false) {
return classMethod(ident(id),
methodFun(id, kind, generator, args),
kind, isStatic);
}
function ctorWithName(id, body = []) {
return classMethod(ident("constructor"),
methodFun(id, "method", false, [], body),
"method", false);
}
function emptyCPNMethod(id, isStatic) {
return classMethod(computedName(lit(id)),
funExpr(null, [], blockStmt([])),
"method", isStatic);
}
function assertClassExpr(str, methods, heritage=null, name=null) {
let template = classExpr(name, heritage, methods);
assertExpr("(" + str + ")", template);
}
// FunctionExpression of constructor has class name as its id.
// FIXME: Implement ES6 function "name" property semantics (bug 883377).
let ctorPlaceholder = {};
function assertClass(str, methods, heritage=null, constructorBody=[]) {
let namelessStr = str.replace("NAME", "");
let namedStr = str.replace("NAME", "Foo");
let namedCtor = ctorWithName("Foo", constructorBody);
let namelessCtor = ctorWithName(null, constructorBody);
let namelessMethods = methods.map(x => x == ctorPlaceholder ? namelessCtor : x);
let namedMethods = methods.map(x => x == ctorPlaceholder ? namedCtor : x);
assertClassExpr(namelessStr, namelessMethods, heritage);
assertClassExpr(namedStr, namedMethods, heritage, ident("Foo"));
let template = classStmt(ident("Foo"), heritage, namedMethods);
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);
}
/* Trivial classes */
// Unnamed class statements are forbidden, but unnamed class expressions are
// just fine.
assertError("class { constructor() { } }", SyntaxError);
assertClass("class NAME { constructor() { } }", [ctorPlaceholder]);
// 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
assertClass("class NAME { constructor() { } method() { } }",
[ctorPlaceholder, simpleMethod("method", "method", false)]);
assertClass("class NAME { constructor() { } get method() { } }",
[ctorPlaceholder, simpleMethod("method", "get", false)]);
assertClass("class NAME { constructor() { } set method(x) { } }",
[ctorPlaceholder, simpleMethod("method", "set", false, ["x"])]);
/* Static */
assertClass(`class NAME {
constructor() { };
static method() { };
static *methodGen() { };
static get getter() { };
static set setter(x) { }
}`,
[ctorPlaceholder,
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.
assertClass("class NAME { constructor() { } static() { } }",
[ctorPlaceholder, simpleMethod("static", "method", false)]);
assertClass("class NAME { static static() { }; constructor() { } }",
[simpleMethod("static", "method", false, [], true), ctorPlaceholder]);
assertClass("class NAME { static get static() { }; constructor() { } }",
[simpleMethod("static", "get", false, [], true), ctorPlaceholder]);
assertClass("class NAME { constructor() { }; static set static(x) { } }",
[ctorPlaceholder, simpleMethod("static", "set", false, ["x"], true)]);
// You do, however, have to put static in the right spot
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
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
assertClass("class NAME { constructor() { }; static [\"prototype\"]() { } }",
[ctorPlaceholder, emptyCPNMethod("prototype", true)]);
/* Constructor */
// Allow default constructors
assertClass("class NAME { }", []);
assertClass("class NAME extends null { }", [], lit(null));
// Derived class constructor must have curly brackets
assertClassError("class NAME extends null { constructor() 1 }", SyntaxError);
// 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.
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)],
["set method(x) { }", simpleMethod("method", "set", false, ["x"])],
["static method() { }", simpleMethod("method", "method", false, [], true)],
["static *method() { }", simpleMethod("method", "method", true, [], true)],
["static get method() { }", simpleMethod("method", "get", false, [], true)],
["static set method(x) { }", simpleMethod("method", "set", false, ["x"], true)]];
let i,j;
for (i=0; i < methods.length; i++) {
for (j=0; j < methods.length; j++) {
let str = "class NAME { constructor() { } " +
methods[i][0] + " " + methods[j][0] +
" }";
assertClass(str, [ctorPlaceholder, 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'
assertClass("class NAME { constructor () { } [\"constructor\"] () { } }",
[ctorPlaceholder, emptyCPNMethod("constructor", false)]);
// It is an error to have a generator or accessor named constructor
assertClassError("class NAME { *constructor() { } }", SyntaxError);
assertClassError("class NAME { get constructor() { } }", SyntaxError);
assertClassError("class NAME { set constructor() { } }", SyntaxError);
/* Semicolons */
// Allow Semicolons in Class Definitions
assertClass("class NAME { constructor() { }; }", [ctorPlaceholder]);
// Allow more than one semicolon, even in otherwise trivial classses
assertClass("class NAME { ;;; constructor() { } }", [ctorPlaceholder]);
// Semicolons are optional, even if the methods share a line
assertClass("class NAME { method() { } constructor() { } }",
[simpleMethod("method", "method", false), ctorPlaceholder]);
/* Generators */
// No yield as a class name inside a generator
assertError(`function *foo() {
class yield {
constructor() { }
}
}`, SyntaxError);
assertError(`function *foo() {
(class yield {
constructor() { }
})
}`, SyntaxError);
// Methods may be generators, but not accessors
assertClassError("class NAME { constructor() { } *get foo() { } }", SyntaxError);
assertClassError("class NAME { constructor() { } *set foo() { } }", SyntaxError);
assertClass("class NAME { *method() { } constructor() { } }",
[simpleMethod("method", "method", true), ctorPlaceholder]);
/* Strictness */
// yield is a strict-mode keyword, and class definitions are always strict.
assertClassError("class NAME { constructor() { var yield; } }", SyntaxError);
// Beware of the strictness of computed property names. Here use bareword
// deletion (a deprecated action) to check.
assertClassError("class NAME { constructor() { } [delete bar]() { }}", SyntaxError);
/* Bindings */
// Class statements bind lexically, so they should collide with other
// in-block lexical bindings, but class expressions don't.
let FooCtor = ctorWithName("Foo");
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, [FooCtor]))]));
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, [FooCtor]))]));
assertError("{ class Foo { constructor() { } } class Foo { constructor() { } } }", TypeError);
assertStmt(`{
(class Foo {
constructor() { }
},
class Foo {
constructor() { }
});
}`,
blockStmt([exprStmt(seqExpr([classExpr(ident("Foo"), null, [FooCtor]),
classExpr(ident("Foo"), null, [FooCtor])]))]));
assertStmt(`{
var x = class Foo { constructor() { } };
class Foo { constructor() { } }
}`,
blockStmt([varDecl([{ id: ident("x"),
init: classExpr(ident("Foo"), null, [FooCtor])
}]),
classStmt(ident("Foo"), null, [FooCtor])]));
// Can't make a lexical binding without a block.
assertError("if (1) class Foo { constructor() { } }", SyntaxError);
/* Heritage Expressions */
// It's illegal to have things that look like "multiple inheritance":
// non-parenthesized comma expressions.
assertClassError("class NAME extends null, undefined { constructor() { } }", SyntaxError);
// Again check for strict-mode in heritage expressions
assertClassError("class NAME extends (delete x) { constructor() { } }", SyntaxError);
// You must specify an inheritance if you say "extends"
assertClassError("class NAME extends { constructor() { } }", SyntaxError);
// "extends" is still a valid name for a method
assertClass("class NAME { constructor() { }; extends() { } }",
[ctorPlaceholder, simpleMethod("extends", "method", false)]);
// Immediate expression
assertClass("class NAME extends null { constructor() { } }",
[ctorPlaceholder], lit(null));
// Sequence expresson
assertClass("class NAME extends (undefined, undefined) { constructor() { } }",
[ctorPlaceholder], seqExpr([ident("undefined"), ident("undefined")]));
// Function expression
let emptyFunction = funExpr(null, [], blockStmt([]));
assertClass("class NAME extends function(){ } { constructor() { } }",
[ctorPlaceholder], emptyFunction);
// New expression
assertClass("class NAME extends new function(){ }() { constructor() { } }",
[ctorPlaceholder], newExpr(emptyFunction, []));
// Call expression
assertClass("class NAME extends function(){ }() { constructor() { } }",
[ctorPlaceholder], callExpr(emptyFunction, []));
// Dot expression
assertClass("class NAME extends {}.foo { constructor() { } }",
[ctorPlaceholder], dotExpr(objExpr([]), ident("foo")));
// Member expression
assertClass("class NAME extends {}[foo] { constructor() { } }",
[ctorPlaceholder], memExpr(objExpr([]), ident("foo")));
/* SuperProperty */
// NOTE: Some of these tests involve object literals, as SuperProperty is a
// valid production in any method definition, including in objectl
// litterals. These tests still fall here, as |super| is not implemented in
// any form without classes.
function assertValidSuperProps(assertion, makeStr, makeExpr, type, generator, args, static,
extending)
{
let superProperty = superProp(ident("prop"));
let superMember = superElem(lit("prop"));
let situations = [
["super.prop", superProperty],
["super['prop']", superMember],
["super.prop()", callExpr(superProperty, [])],
["super['prop']()", callExpr(superMember, [])],
["new super.prop()", newExpr(superProperty, [])],
["new super['prop']()", newExpr(superMember, [])],
["delete super.prop", unExpr("delete", superProperty)],
["delete super['prop']", unExpr("delete", superMember)],
["++super.prop", updExpr("++", superProperty, true)],
["super['prop']--", updExpr("--", superMember, false)],
["super.prop = 4", aExpr("=", superProperty, lit(4))],
["super['prop'] = 4", aExpr("=", superMember, lit(4))],
["super.prop += 4", aExpr("+=", superProperty, lit(4))],
["super['prop'] += 4", aExpr("+=", superMember, lit(4))],
["super.prop -= 4", aExpr("-=", superProperty, lit(4))],
["super['prop'] -= 4", aExpr("-=", superMember, lit(4))],
["super.prop *= 4", aExpr("*=", superProperty, lit(4))],
["super['prop'] *= 4", aExpr("*=", superMember, lit(4))],
["super.prop /= 4", aExpr("/=", superProperty, lit(4))],
["super['prop'] /= 4", aExpr("/=", superMember, lit(4))],
["super.prop %= 4", aExpr("%=", superProperty, lit(4))],
["super['prop'] %= 4", aExpr("%=", superMember, lit(4))],
["super.prop <<= 4", aExpr("<<=", superProperty, lit(4))],
["super['prop'] <<= 4", aExpr("<<=", superMember, lit(4))],
["super.prop >>= 4", aExpr(">>=", superProperty, lit(4))],
["super['prop'] >>= 4", aExpr(">>=", superMember, lit(4))],
["super.prop >>>= 4", aExpr(">>>=", superProperty, lit(4))],
["super['prop'] >>>= 4", aExpr(">>>=", superMember, lit(4))],
["super.prop |= 4", aExpr("|=", superProperty, lit(4))],
["super['prop'] |= 4", aExpr("|=", superMember, lit(4))],
["super.prop ^= 4", aExpr("^=", superProperty, lit(4))],
["super['prop'] ^= 4", aExpr("^=", superMember, lit(4))],
["super.prop &= 4", aExpr("&=", superProperty, lit(4))],
["super['prop'] &= 4", aExpr("&=", superMember, lit(4))],
// We can also use super from inside arrow functions in method
// definitions
["()=>super.prop", arrowExpr([], superProperty)],
["()=>super['prop']", arrowExpr([], superMember)]];
for (let situation of situations) {
let sitStr = situation[0];
let sitExpr = situation[1];
let fun = methodFun("method", type, generator, args, [exprStmt(sitExpr)]);
let str = makeStr(sitStr, type, generator, args, static, extending);
assertion(str, makeExpr(fun, type, static), extending);
}
}
function assertValidSuperPropTypes(assertion, makeStr, makeExpr, static, extending) {
for (let type of ["method", "get", "set"]) {
if (type === "method") {
// methods can also be generators
assertValidSuperProps(assertion, makeStr, makeExpr, type, true, [], static, extending);
assertValidSuperProps(assertion, makeStr, makeExpr, type, false, [], static, extending);
continue;
}
// Setters require 1 argument, and getters require 0
assertValidSuperProps(assertion, makeStr, makeExpr, type, false,
type === "set" ? ["x"] : [], static, extending);
}
}
function makeSuperPropMethodStr(propStr, type, generator, args) {
return `${type === "method" ? "" : type} ${generator ? "*" : ""} method(${args.join(",")})
{
${propStr};
}`;
}
function makeClassSuperPropStr(propStr, type, generator, args, static, extending) {
return `class NAME ${extending ? "extends null" : ""} {
constructor() { };
${static ? "static" : ""} ${makeSuperPropMethodStr(propStr, type, generator, args)}
}`;
}
function makeClassSuperPropExpr(fun, type, static) {
// We are going right into assertClass, so we don't have to build the
// entire statement.
return [ctorPlaceholder,
classMethod(ident("method"), fun, type, static)];
}
function doClassSuperPropAssert(str, expr, extending) {
assertClass(str, expr, extending ? lit(null) : null);
}
function assertValidClassSuperPropExtends(extending) {
// super.prop and super[prop] are valid, regardless of whether the
// method is static or not
assertValidSuperPropTypes(doClassSuperPropAssert, makeClassSuperPropStr, makeClassSuperPropExpr,
false, extending);
assertValidSuperPropTypes(doClassSuperPropAssert, makeClassSuperPropStr, makeClassSuperPropExpr,
true, extending);
}
function assertValidClassSuperProps() {
// super.prop and super[prop] are valid, regardless of class heritage
assertValidClassSuperPropExtends(false);
assertValidClassSuperPropExtends(true);
}
function makeOLSuperPropStr(propStr, type, generator, args) {
let str = `({ ${makeSuperPropMethodStr(propStr, type, generator, args)} })`;
return str;
}
function makeOLSuperPropExpr(fun) {
return objExpr([{ type: "Property", key: ident("method"), value: fun}]);
}
function assertValidOLSuperProps() {
assertValidSuperPropTypes(assertExpr, makeOLSuperPropStr, makeOLSuperPropExpr);
}
// Check all valid uses of SuperProperty
assertValidClassSuperProps();
assertValidOLSuperProps();
// SuperProperty is forbidden outside of method definitions.
assertError("function foo () { super.prop; }", SyntaxError);
assertError("(function () { super.prop; }", SyntaxError);
assertError("(()=>super.prop)", SyntaxError);
assertError("function *foo() { super.prop; }", SyntaxError);
assertError("super.prop", SyntaxError);
assertError("function foo () { super['prop']; }", SyntaxError);
assertError("(function () { super['prop']; }", SyntaxError);
assertError("(()=>super['prop'])", SyntaxError);
assertError("function *foo() { super['prop']; }", SyntaxError);
assertError("super['prop']", SyntaxError);
// Or inside functions inside method definitions...
assertClassError("class NAME { constructor() { function nested() { super.prop; }}}", SyntaxError);
// Bare super is forbidden
assertError("super", SyntaxError);
// Even where super is otherwise allowed
assertError("{ foo() { super } }", SyntaxError);
assertClassError("class NAME { constructor() { super; } }", SyntaxError);
/* SuperCall */
// SuperCall is invalid outside derived class constructors.
assertError("super()", SyntaxError);
assertError("(function() { super(); })", SyntaxError);
// SuperCall is invalid in generator comprehensions, even inside derived
// class constructors
assertError("(super() for (x in y))", SyntaxError);
assertClassError("class NAME { constructor() { (super() for (x in y))", SyntaxError);
// Even in class constructors
assertClassError("class NAME { constructor() { super(); } }", SyntaxError);
function superConstructor(args) {
return classMethod(ident("constructor"),
methodFun("NAME", "method", false,
[], [exprStmt(superCallExpr(args))]),
"method", false);
}
function superCallBody(args) {
return [exprStmt(superCallExpr(args))];
}
// SuperCall works with various argument configurations.
assertClass("class NAME extends null { constructor() { super() } }",
[ctorPlaceholder], lit(null), superCallBody([]));
assertClass("class NAME extends null { constructor() { super(1) } }",
[ctorPlaceholder], lit(null), superCallBody([lit(1)]));
assertClass("class NAME extends null { constructor() { super(1, a) } }",
[ctorPlaceholder], lit(null), superCallBody([lit(1), ident("a")]));
assertClass("class NAME extends null { constructor() { super(...[]) } }",
[ctorPlaceholder], lit(null), superCallBody([spread(arrExpr([]))]));
/* EOF */
// Clipped classes should throw a syntax error
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);
assertClassError("class NAME { constructor() { super", SyntaxError);
assertClassError("class NAME { constructor() { super.", SyntaxError);
assertClassError("class NAME { constructor() { super.x", SyntaxError);
assertClassError("class NAME { constructor() { super.m(", SyntaxError);
assertClassError("class NAME { constructor() { super[", SyntaxError);
assertClassError("class NAME { constructor() { super(", SyntaxError);
// Can not omit curly brackets
assertClassError("class NAME { constructor() ({}) }", SyntaxError);
assertClassError("class NAME { constructor() void 0 }", SyntaxError);
assertClassError("class NAME { constructor() 1 }", SyntaxError);
assertClassError("class NAME { constructor() false }", SyntaxError);
assertClassError("class NAME { constructor() {} a() ({}) }", SyntaxError);
assertClassError("class NAME { constructor() {} a() void 0 }", SyntaxError);
assertClassError("class NAME { constructor() {} a() 1 }", SyntaxError);
assertClassError("class NAME { constructor() {} a() false }", SyntaxError);
}
runtest(testClasses);