// |reftest| slow 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 methodName; switch (kind) { case "method": methodName = typeof id === 'string' ? ident(id) : null; break; case "get": case "set": methodName = ident(`${kind} ${typeof id === 'string' ? id : ""}`); break; default: methodName = null; break; } 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); // No legacy generators for methods. assertClassError(`class NAME { constructor() { yield 2; } }`, SyntaxError); assertClassError(`class NAME { method() { yield 2; } }`, SyntaxError); assertClassError(`class NAME { get method() { yield 2; } }`, SyntaxError); assertClassError(`class NAME { set method() { yield 2; } }`, 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() { } } }", SyntaxError); 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() { } } }", SyntaxError); 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() { } } }", SyntaxError); 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"))); // #constructor is an invalid private method name. assertClassError("class NAME { #constructor() { } }", SyntaxError); const method = ["#method() { }", simpleMethod("#method", "method", false)]; const generator = ["*#method() { }", simpleMethod("#method", "method", true)]; const getter = ["get #method() { }", simpleMethod("#method", "get", false)]; const setter = ["set #method(x) { }", simpleMethod("#method", "set", false, ["x"])]; for (const [source, parsed] of [method, generator, getter, setter]) { assertClass(`class NAME { constructor() { } ${source} }`, [ctorPlaceholder, parsed]); } // Private getters and setters of the same name are allowed. assertClass(`class NAME { constructor() { } ${getter[0]} ${setter[0]} }`, [ctorPlaceholder, getter[1], setter[1]]); assertClass(`class NAME { constructor() { } ${setter[0]} ${getter[0]} }`, [ctorPlaceholder, setter[1], getter[1]]); // Private method names can't be used multiple times, other than for a getter/setter pair. for (const [source1, _] of [method, generator, getter, setter]) { for (const [source2, _] of [method, generator]) { assertClassError(`class NAME { constructor() { } ${source1} ${source2} }`, SyntaxError); } } assertClassError(`class NAME { constructor() { } ${setter[0]} ${setter[0]} }`, SyntaxError); assertClassError(`class NAME { constructor() { } ${getter[0]} ${getter[0]} }`, SyntaxError); /* 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); // 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 { static ;", 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);