| // |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); |