diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/tests/non262/class | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/tests/non262/class')
99 files changed, 3907 insertions, 0 deletions
diff --git a/js/src/tests/non262/class/boundFunctionSubclassing.js b/js/src/tests/non262/class/boundFunctionSubclassing.js new file mode 100644 index 0000000000..be9058bb9e --- /dev/null +++ b/js/src/tests/non262/class/boundFunctionSubclassing.js @@ -0,0 +1,37 @@ +class func extends Function { } +let inst = new func("x", "return this.bar + x"); + +// First, ensure that we get sane prototype chains for the bound instance +let bound = inst.bind({bar: 3}, 4); +assertEq(bound instanceof func, true); +assertEq(bound(), 7); + +// Check the corner case for Function.prototype.bind where the function has +// a null [[Prototype]] +Object.setPrototypeOf(inst, null); +bound = Function.prototype.bind.call(inst, {bar:1}, 3); +assertEq(Object.getPrototypeOf(bound), null); +assertEq(bound(), 4); + +// Check that we actually pass the proper new.target when calling super() +function toBind() { } + +var boundArgs = []; +for (let i = 0; i < 5; i++) { + boundArgs.push(i); + let bound = toBind.bind(undefined, ...boundArgs); + + // We have to wire it up by hand to allow us to use a bound function + // as a superclass, but it's doable. + bound.prototype = {}; + class test extends bound { }; + let passedArgs = []; + for (let j = 0; j < 15; j++) { + passedArgs.push(j); + assertEq(Object.getPrototypeOf(new test(...passedArgs)), test.prototype); + } +} + + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/browser.js b/js/src/tests/non262/class/browser.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/js/src/tests/non262/class/browser.js diff --git a/js/src/tests/non262/class/bytecodePatternMatching.js b/js/src/tests/non262/class/bytecodePatternMatching.js new file mode 100644 index 0000000000..446184f805 --- /dev/null +++ b/js/src/tests/non262/class/bytecodePatternMatching.js @@ -0,0 +1,29 @@ +// Constructors can't be called so we can't pattern match +// them in replace and sort. +function a() { + var b = {a: "A"}; + + class X { + constructor(a) { + return b[a] + } + }; + + assertThrowsInstanceOf(() => "a".replace(/a/, X), TypeError); +} + +function b() { + class X { + constructor(x, y) { + return x - y; + } + } + + assertThrowsInstanceOf(() => [1, 2, 3].sort(X), TypeError); +} + +a(); +b(); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/classConstructorNoCall.js b/js/src/tests/non262/class/classConstructorNoCall.js new file mode 100644 index 0000000000..f59f87b9c5 --- /dev/null +++ b/js/src/tests/non262/class/classConstructorNoCall.js @@ -0,0 +1,21 @@ +// Class constructors don't have a [[Call]] +class Foo { + constructor() { } +} + +assertThrowsInstanceOf(Foo, TypeError); + +class Bar extends Foo { + constructor() { } +} + +assertThrowsInstanceOf(Bar, TypeError); + +assertThrowsInstanceOf(class { constructor() { } }, TypeError); +assertThrowsInstanceOf(class extends Foo { constructor() { } }, TypeError); + +assertThrowsInstanceOf(class foo { constructor() { } }, TypeError); +assertThrowsInstanceOf(class foo extends Foo { constructor() { } }, TypeError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/classHeritage.js b/js/src/tests/non262/class/classHeritage.js new file mode 100644 index 0000000000..478c23fc21 --- /dev/null +++ b/js/src/tests/non262/class/classHeritage.js @@ -0,0 +1,97 @@ +// It's an error to have a non-constructor as your heritage +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. +class basic { + constructor() { } +} +class nullExtends extends null { + constructor() { } +} +var nullExtendsExpr = class extends null { constructor() { } }; + +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; } + static staticMethod() { staticMethodCalled = true; } + override() { overrideCalled = "base" } +} +class derived extends base { + constructor() { super(); }; + override() { overrideCalled = "derived"; } +} +var derivedExpr = class extends base { + constructor() { super(); }; + override() { overrideCalled = "derived"; } +}; + +// Make sure we get the right object layouts. +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 extension()).method(); + assertEq(baseMethodCalled, true); + + // But we can still override them? + (new extension()).override(); + assertEq(overrideCalled, "derived"); + + // What about the statics? + extension.staticMethod(); + assertEq(staticMethodCalled, true); +} + +// Gotta extend an object, or null. +function nope() { + class Foo extends "Bar" { + 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"; +function stillNo() { + class Foo extends nope { + constructor() { } + } +} +function stillNoExpr() { + (class extends nope { + constructor() { } + }); +} +assertThrowsInstanceOf(stillNo, TypeError); +assertThrowsInstanceOf(stillNoExpr, TypeError); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/className.js b/js/src/tests/non262/class/className.js new file mode 100644 index 0000000000..e72510e18d --- /dev/null +++ b/js/src/tests/non262/class/className.js @@ -0,0 +1,248 @@ +function testName(C, name, hasValue, hasGetter, hasSetter, isFunction=false) { + if (isFunction) { + assertEq(typeof C.name, "function"); + } else { + assertEq(C.name, name); + } + + var desc = Object.getOwnPropertyDescriptor(C, "name"); + if (!hasValue && !hasGetter && !hasSetter) { + assertEq(desc, undefined); + return; + } + + if (hasValue) { + assertEq("value" in desc, true); + if (isFunction) { + assertEq(typeof desc.value, "function"); + } else { + assertEq(desc.value, name); + } + // FIXME: Methods defined in classes should not be enumerable + // (bug 1144630). + // assertEq(desc.enumerable, false); + assertEq(desc.configurable, true); + + assertEq("get" in desc, false); + assertEq("set" in desc, false); + + return; + } + + assertEq("value" in desc, false); + + if (hasGetter) { + assertEq("get" in desc, true); + assertEq(desc.get(), name); + // assertEq(desc.enumerable, false); + assertEq(desc.configurable, true); + } else { + assertEq("get" in desc, true); + assertEq(desc.get, undefined); + // assertEq(desc.enumerable, false); + assertEq(desc.configurable, true); + } + + if (hasSetter) { + assertEq("set" in desc, true); + assertEq(typeof desc.set, "function"); + // assertEq(desc.enumerable, false); + assertEq(desc.configurable, true); + } else { + assertEq("set" in desc, true); + assertEq(desc.set, undefined); + // assertEq(desc.enumerable, false); + assertEq(desc.configurable, true); + } +} + +// ---- declaration --- + +class Decl { + constructor() {} +} +testName(Decl, "Decl", true, false, false); + +class DeclWithFunc { + constructor() {} + static name() {} +} +testName(DeclWithFunc, "DeclWithFunc", true, false, false, true); + +class DeclWithGetter { + constructor() {} + static get name() { return "base"; } +} +testName(DeclWithGetter, "base", false, true, false); + +class DeclWithSetter { + constructor() {} + static set name(v) {} +} +testName(DeclWithSetter, undefined, false, false, true); + +class DeclWithGetterSetter { + constructor() {} + static get name() { return "base"; } + static set name(v) {} +} +testName(DeclWithGetterSetter, "base", false, true, true); + +function prop() { + return "name"; +} +class DeclWithComputed { + constructor() {} + static get [prop()]() { return "base"; } +} +testName(DeclWithGetterSetter, "base", false, true, true); + +class ExtendedDecl1 extends Decl { + constructor() {} +} +testName(ExtendedDecl1, "ExtendedDecl1", true, false, false); +delete ExtendedDecl1.name; +testName(ExtendedDecl1, "Decl", false, false, false); + +class ExtendedDecl2 extends DeclWithGetterSetter { + constructor() {} + static get name() { return "extend"; } +} +testName(ExtendedDecl2, "extend", false, true, false); +delete ExtendedDecl2.name; +testName(ExtendedDecl2, "base", false, false, false); + +class ExtendedDecl3 extends DeclWithGetterSetter { + constructor() {} + static set name(v) {} +} +testName(ExtendedDecl3, undefined, false, false, true); +delete ExtendedDecl3.name; +testName(ExtendedDecl3, "base", false, false, false); + +// ---- expression --- + +let Expr = class Expr { + constructor() {} +}; +testName(Expr, "Expr", true, false, false); + +let ExprWithGetter = class ExprWithGetter { + constructor() {} + static get name() { return "base"; } +}; +testName(ExprWithGetter, "base", false, true, false); + +let ExprWithSetter = class ExprWithSetter { + constructor() {} + static set name(v) {} +}; +testName(ExprWithSetter, undefined, false, false, true); + +let ExprWithGetterSetter = class ExprWithGetterSetter { + constructor() {} + static get name() { return "base"; } + static set name(v) {} +}; +testName(ExprWithGetterSetter, "base", false, true, true); + + +let ExtendedExpr1 = class ExtendedExpr1 extends Expr { + constructor() {} +}; +testName(ExtendedExpr1, "ExtendedExpr1", true, false, false); +delete ExtendedExpr1.name; +testName(ExtendedExpr1, "Expr", false, false, false); + +let ExtendedExpr2 = class ExtendedExpr2 extends ExprWithGetterSetter { + constructor() {} + static get name() { return "extend"; } +}; +testName(ExtendedExpr2, "extend", false, true, false); +delete ExtendedExpr2.name; +testName(ExtendedExpr2, "base", false, false, false); + +let ExtendedExpr3 = class ExtendedExpr3 extends ExprWithGetterSetter { + constructor() {} + static set name(v) {} +}; +testName(ExtendedExpr3, undefined, false, false, true); +delete ExtendedExpr3.name; +testName(ExtendedExpr3, "base", false, false, false); + +// ---- anonymous ---- + +// Anonymous class expressions use the empty string as the default name property. +// Use property assignment to avoid setting name property. +let tmp = {}; +let Anon = tmp.value = class { + constructor() {} +}; +testName(Anon, "", true, false, false); + +let AnonDefault = tmp.value = class { }; +testName(AnonDefault, "", true, false, false); + +let AnonWithGetter = tmp.value = class { + constructor() {} + static get name() { return "base"; } +}; +testName(AnonWithGetter, "base", false, true, false); + +let AnonWithSetter = tmp.value = class { + constructor() {} + static set name(v) {} +}; +testName(AnonWithSetter, undefined, false, false, true); + +let AnonWithGetterSetter = tmp.value = class { + constructor() {} + static get name() { return "base"; } + static set name(v) {} +}; +testName(AnonWithGetterSetter, "base", false, true, true); + + +let ExtendedAnon1 = tmp.value = class extends Anon { + constructor() {} +}; +testName(ExtendedAnon1, "", true, false, false); + +let ExtendedAnonDefault = tmp.value = class extends Anon { }; +testName(ExtendedAnonDefault, "", true, false, false); + +let ExtendedAnon2 = tmp.value = class extends AnonWithGetterSetter { + constructor() {} + static get name() { return "extend"; } +}; +testName(ExtendedAnon2, "extend", false, true, false); +delete ExtendedAnon2.name; +testName(ExtendedAnon2, "base", false, false, false); + +let ExtendedAnon3 = tmp.value = class extends AnonWithGetterSetter { + constructor() {} + static set name(v) {} +}; +testName(ExtendedAnon3, undefined, false, false, true); +delete ExtendedAnon3.name; +testName(ExtendedAnon3, "base", false, false, false); + +// ---- mixed ---- + +let ExtendedExpr4 = class ExtendedExpr4 extends Anon { + constructor() {} +} +testName(ExtendedExpr4, "ExtendedExpr4", true, false, false); +delete ExtendedExpr4.name; +testName(ExtendedExpr4, "", false, false, false); + +let ExtendedExpr5 = class ExtendedExpr5 extends AnonWithGetterSetter { + constructor() {} + static get name() { return "extend"; } +} +testName(ExtendedExpr5, "extend", false, true, false); +delete ExtendedExpr5.name; +testName(ExtendedExpr5, "base", false, false, false); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/classPrototype.js b/js/src/tests/non262/class/classPrototype.js new file mode 100644 index 0000000000..0d2296037c --- /dev/null +++ b/js/src/tests/non262/class/classPrototype.js @@ -0,0 +1,65 @@ +// The prototype of a class is a non-writable, non-configurable, non-enumerable data property. +class a { constructor() { } } +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 desiredPrototype = {}; + Object.defineProperty(desiredPrototype, "constructor", { writable: true, + configurable: true, + enumerable: false, + value: test }); + assertDeepEq(prototype, desiredPrototype); +} + +// As such, it should by a TypeError to try and overwrite "prototype" with a +// static member. The only way to try is with a computed property name; the rest +// are early errors. +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); + +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); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/compPropDestr.js b/js/src/tests/non262/class/compPropDestr.js new file mode 100644 index 0000000000..2bd8cbd49a --- /dev/null +++ b/js/src/tests/non262/class/compPropDestr.js @@ -0,0 +1,11 @@ +var BUGNUMBER = 924688; +var summary = 'Computed Property Names'; + +print(BUGNUMBER + ": " + summary); + +var key = "z"; +var { [key]: foo } = { z: "bar" }; +assertEq(foo, "bar"); + +if (typeof reportCompare === 'function') + reportCompare(0, 0, "ok"); diff --git a/js/src/tests/non262/class/compPropNames.js b/js/src/tests/non262/class/compPropNames.js new file mode 100644 index 0000000000..1c4064d8a5 --- /dev/null +++ b/js/src/tests/non262/class/compPropNames.js @@ -0,0 +1,243 @@ +var BUGNUMBER = 924688; +var summary = 'Computed Property Names'; + +print(BUGNUMBER + ": " + summary); + +// Function definitions. +function syntaxError (script) { + try { + Function(script); + } catch (e) { + if (e instanceof SyntaxError) { + return; + } + } + throw new Error('Expected syntax error: ' + script); +} + + +// Tests begin. + +assertThrowsInstanceOf(function() { var a = {[field1]: "a", [field2]: "b"}; }, ReferenceError); + +assertThrowsInstanceOf(function() { + field1 = 1; + var a = {[field1]: "a", [field2]: "b"}; + }, ReferenceError); + +var f1 = 1; +var f2 = 2; +var a = {[f1]: "a", [f2]: "b"}; +assertEq(a[1], "a"); +assertEq(a[2], "b"); + +var a = {[f1]: "a", f2: "b"}; +assertEq(a[1], "a"); +assertEq(a.f2, "b"); + +var a = {["f1"]: "a", [++f2]: "b"}; +assertEq(a.f1, "a"); +assertEq(a[3], "b"); + +var a = {["f1"]: "a", f2: "b"}; +assertEq(a.f1, "a"); +assertEq(a.f2, "b"); + + +var i = 0; +var a = { + ["foo" + ++i]: i, + ["foo" + ++i]: i, + ["foo" + ++i]: i +}; +assertEq(a.foo1, 1); +assertEq(a.foo2, 2); +assertEq(a.foo3, 3); + +var expr = "abc"; +syntaxError("({["); +syntaxError("({[expr"); +syntaxError("({[expr]"); +syntaxError("({[expr]})"); +syntaxError("({[expr] 0})"); +syntaxError("({[expr], 0})"); +syntaxError("[[expr]: 0]"); +syntaxError("({[expr]: name: 0})"); +syntaxError("({[1, 2]: 3})"); // because '1,2' is an Expression but not an AssignmentExpression +syntaxError("({[1;]: 1})"); // and not an ExpressionStatement +syntaxError("({[if (0) 0;]})"); // much less a Statement +syntaxError("function f() { {[x]: 1} }"); // that's not even an ObjectLiteral +syntaxError("function f() { [x]: 1 }"); // or that +syntaxError('a = {[f1@]: "a", [f2]: "b"}'); // unexpected symbol at end of AssignmentExpression +try { JSON.parse('{["a"]:4}'); } catch(e) { + if (!(e instanceof SyntaxError)) throw new Error('Expected syntax error'); +} + +// Property characteristics. +a = { ["b"] : 4 }; +b = Object.getOwnPropertyDescriptor(a, "b"); +assertEq(b.configurable, true); +assertEq(b.enumerable, true); +assertEq(b.writable, true); +assertEq(b.value, 4); + +// Setter and getter are not hit. +Object.defineProperty(Object.prototype, "x", { set: function (x) { throw "FAIL"; }, + get: function (x) { throw "FAIL"; } }); +var a = {["x"]: 0}; +assertEq(a.x, 0); + +a = {["x"]: 1, ["x"]: 2}; +assertEq(a.x, 2); +a = {x: 1, ["x"]: 2}; +assertEq(a.x, 2); +a = {["x"]: 1, x: 2}; +assertEq(a.x, 2); + +// Symbols +var unique_sym = Symbol("1"), registered_sym = Symbol.for("2"); +a = { [unique_sym] : 2, [registered_sym] : 3 }; +assertEq(a[unique_sym], 2); +assertEq(a[registered_sym], 3); + +// Same expression can be run several times to build objects with different property names. +a = []; +for (var i = 0; i < 3; i++) { + a[i] = {["foo" + i]: i}; +} +assertEq(a[0].foo0, 0); +assertEq(a[1].foo1, 1); +assertEq(a[2].foo2, 2); + +// Following are stored in object's elements rather than slots. +var i = 0; +a = { [++i] : i, + [++i] : i, + [++i] : i, + [++i] : i, + [++i] : i, + [++i] : i, + [++i] : i, + [++i] : i +} +for (var i = 1; i < 9; i++) + assertEq(a[i], i); +syntaxError("a.1"); +syntaxError("a.2"); +syntaxError("a.3"); +syntaxError("a.4"); +syntaxError("a.5"); +syntaxError("a.6"); +syntaxError("a.7"); +syntaxError("a.8"); + +// Adding a single large index. +var i = 0; +a = { [++i] : i, + [++i] : i, + [++i] : i, + [++i] : i, + [++i] : i, + [++i] : i, + [++i] : i, + [++i] : i, + [3000] : 2999 +} +for (var i = 1; i < 9; i++) + assertEq(a[i], i); +assertEq(a[3000], 2999); + +// Defining several properties using eval. +var code = "({"; +for (i = 0; i < 1000; i++) + code += "['foo' + " + i + "]: 'ok', " +code += "['bar']: 'ok'});"; +var obj = eval(code); +for (i = 0; i < 1000; i++) + assertEq(obj["foo" + i], "ok"); +assertEq(obj["bar"], "ok"); + +// Can yield in a computed property name which is in a generator. +function* g() { + var a = { [yield 1]: 2, [yield 2]: 3}; + return a; +} + +var it = g(); +var next = it.next(); +assertEq(next.done, false); +assertEq(next.value, 1); +next = it.next("hello"); +assertEq(next.done, false); +assertEq(next.value, 2); +next = it.next("world"); +assertEq(next.done, true); +assertEq(next.value.hello, 2); +assertEq(next.value.world, 3); + +// get and set. +expr = "abc"; +syntaxError("({get ["); +syntaxError("({get [expr()"); +syntaxError("({get [expr]()"); +syntaxError("({get [expr]()})"); +syntaxError("({get [expr] 0 ()})"); +syntaxError("({get [expr], 0(})"); +syntaxError("[get [expr]: 0()]"); +syntaxError("({get [expr](: name: 0})"); +syntaxError("({get [1, 2](): 3})"); +syntaxError("({get [1;](): 1})"); +syntaxError("({get [if (0) 0;](){}})"); +syntaxError("({set [(a)"); +syntaxError("({set [expr(a)"); +syntaxError("({set [expr](a){}"); +syntaxError("({set [expr]}(a)"); +syntaxError("({set [expr](a), 0})"); +syntaxError("[set [expr](a): 0]"); +syntaxError("({set [expr](a): name: 0})"); +syntaxError("({set [1, 2](a) {return 3;}})"); +syntaxError("({set [1;](a) {return 1}})"); +syntaxError("({set [if (0) 0;](a){}})"); +syntaxError("function f() { {get [x](): 1} }"); +syntaxError("function f() { get [x](): 1 }"); +syntaxError("function f() { {set [x](a): 1} }"); +syntaxError("function f() { set [x](a): 1 }"); +f1 = "abc"; +syntaxError('a = {get [f1@](){}, set [f1](a){}}'); // unexpected symbol at end of AssignmentExpression +syntaxError('a = {get@ [f1](){}, set [f1](a){}}'); // unexpected symbol after get +syntaxError('a = {get [f1](){}, set@ [f1](a){}}'); // unexpected symbol after set + +expr = "hey"; +a = {get [expr]() { return 3; }, set[expr](v) { throw 2; }}; +assertEq(a.hey, 3); +assertThrowsValue(() => { a.hey = 5; }, 2); + +// Symbols with duplicate get and set. +expr = Symbol("hey"); +a = {get [expr]() { return 3; }, set[expr](v) { throw 2; }, + set [expr] (w) { throw 4; }, get[expr](){return 5; }}; +assertEq(a[expr], 5); +assertThrowsValue(() => { a[expr] = 7; }, 4); + +// expressions with side effects are called in the right order +log = ""; +obj = { + "a": log += 'a', + get [log += 'b']() {}, + [log += 'c']: log += 'd', + set [log += 'e'](a) {} +}; +assertEq(log, "abcde"); + +// assignment expressions, objects and regex in computed names +obj = { + get [a = "hey"]() { return 1; }, + get [a = {b : 4}.b]() { return 2; }, + set [/x/.source](a) { throw 3; } +} +assertEq(obj.hey, 1); +assertEq(obj[4], 2); +assertThrowsValue(() => { obj.x = 7; }, 3); + +if (typeof reportCompare === 'function') + reportCompare(0, 0, "ok"); diff --git a/js/src/tests/non262/class/constructorCalled.js b/js/src/tests/non262/class/constructorCalled.js new file mode 100644 index 0000000000..144cc08c79 --- /dev/null +++ b/js/src/tests/non262/class/constructorCalled.js @@ -0,0 +1,45 @@ +// The constructor specified should get called, regardless of order, or +// other distractions + +var called = false; +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 (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/defaultConstructorBase.js b/js/src/tests/non262/class/defaultConstructorBase.js new file mode 100644 index 0000000000..2d977935dd --- /dev/null +++ b/js/src/tests/non262/class/defaultConstructorBase.js @@ -0,0 +1,18 @@ +class base { + method() { return 1; } + *gen() { return 2; } + static sMethod() { return 3; } + get answer() { return 42; } +} + +// Having a default constructor should work, and also not make you lose +// everything for no good reason + +assertEq(Object.getPrototypeOf(new base()), base.prototype); +assertEq(new base().method(), 1); +assertEq(new base().gen().next().value, 2); +assertEq(base.sMethod(), 3); +assertEq(new base().answer, 42); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/defaultConstructorDerivedSpread.js b/js/src/tests/non262/class/defaultConstructorDerivedSpread.js new file mode 100644 index 0000000000..4de18ec2fb --- /dev/null +++ b/js/src/tests/non262/class/defaultConstructorDerivedSpread.js @@ -0,0 +1,23 @@ +/* Make sure that the default derived class constructor has the required spread semantics. + * + * Test credit André Bargull + */ + +// <https://github.com/tc39/ecma262/pull/2216> changed default derived class +// constructors to no longer execute the spread iteration protocol. +Array.prototype[Symbol.iterator] = function*() { + throw new Error("unexpected call"); +}; + +class Base { + constructor(a, b) { + assertEq(a, 1); + assertEq(b, 2); + } +}; +class Derived extends Base {}; + +new Derived(1, 2); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/defaultConstructorNotCallable.js b/js/src/tests/non262/class/defaultConstructorNotCallable.js new file mode 100644 index 0000000000..558f8dcc2f --- /dev/null +++ b/js/src/tests/non262/class/defaultConstructorNotCallable.js @@ -0,0 +1,8 @@ +class badBase {} +assertThrowsInstanceOf(badBase, TypeError); + +class badSub extends (class {}) {} +assertThrowsInstanceOf(badSub, TypeError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorArrowEvalBinding.js b/js/src/tests/non262/class/derivedConstructorArrowEvalBinding.js new file mode 100644 index 0000000000..d6b15baab3 --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorArrowEvalBinding.js @@ -0,0 +1,12 @@ +// Make sure it doesn't matter when we make the arrow function +new class extends class { } { + constructor() { + let arrow = () => this; + assertThrowsInstanceOf(arrow, ReferenceError); + super(); + assertEq(arrow(), this); + } +}(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorArrowEvalClosed.js b/js/src/tests/non262/class/derivedConstructorArrowEvalClosed.js new file mode 100644 index 0000000000..431a73c92f --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorArrowEvalClosed.js @@ -0,0 +1,11 @@ +new class extends class { } { + constructor() { + let a1 = () => this; + let a2 = (() => super()); + assertThrowsInstanceOf(a1, ReferenceError); + assertEq(a2(), a1()); + } +}(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorArrowEvalEscape.js b/js/src/tests/non262/class/derivedConstructorArrowEvalEscape.js new file mode 100644 index 0000000000..2676d4b946 --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorArrowEvalEscape.js @@ -0,0 +1,13 @@ +let arrow; + +class foo extends class { } { + constructor() { + arrow = () => this; + super(); + } +} + +assertEq(new foo(), arrow()); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorArrowEvalEscapeUninitialized.js b/js/src/tests/non262/class/derivedConstructorArrowEvalEscapeUninitialized.js new file mode 100644 index 0000000000..970beb0436 --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorArrowEvalEscapeUninitialized.js @@ -0,0 +1,38 @@ +let superArrow; +let thisArrow; + +let thisStash; + +class base { + constructor() { + // We run this constructor twice as part of the double init check + if (!thisStash) + thisStash = {prop:45}; + return thisStash; + } +} + +class foo extends base { + constructor() { + superArrow = (()=>super()); + thisArrow = ()=>this; + } +} + +// Populate the arrow function saves. Since we never invoke super(), we throw +assertThrowsInstanceOf(()=>new foo(), ReferenceError); + +// No |this| binding in the closure, yet +assertThrowsInstanceOf(thisArrow, ReferenceError); + +// call super() +superArrow(); + +// Can't call it twice +assertThrowsInstanceOf(superArrow, ReferenceError); + +// Oh look, |this| is populated, now. +assertEq(thisArrow(), thisStash); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorArrowEvalGetThis.js b/js/src/tests/non262/class/derivedConstructorArrowEvalGetThis.js new file mode 100644 index 0000000000..26f9843d2c --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorArrowEvalGetThis.js @@ -0,0 +1,10 @@ +new class extends class { } { + constructor() { + super(); + assertEq(this, (()=>this)()); + assertEq(this, eval("this")); + } +}(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorArrowEvalNestedSuperCall.js b/js/src/tests/non262/class/derivedConstructorArrowEvalNestedSuperCall.js new file mode 100644 index 0000000000..d81f186485 --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorArrowEvalNestedSuperCall.js @@ -0,0 +1,34 @@ +new class extends class { } { + constructor() { + (()=>eval("super()"))(); + assertEq(this, eval("this")); + assertEq(this, (()=>this)()); + } +}(); + +new class extends class { } { + constructor() { + (()=>(()=>super())())(); + assertEq(this, eval("this")); + assertEq(this, (()=>this)()); + } +}(); + +new class extends class { } { + constructor() { + eval("(()=>super())()"); + assertEq(this, eval("this")); + assertEq(this, (()=>this)()); + } +}(); + +new class extends class { } { + constructor() { + eval("eval('super()')"); + assertEq(this, eval("this")); + assertEq(this, (()=>this)()); + } +}(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorArrowEvalSuperCall.js b/js/src/tests/non262/class/derivedConstructorArrowEvalSuperCall.js new file mode 100644 index 0000000000..0f60df7465 --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorArrowEvalSuperCall.js @@ -0,0 +1,18 @@ +new class extends class { } { + constructor() { + assertEq(eval("super(); this"), this); + assertEq(this, eval("this")); + assertEq(this, (()=>this)()); + } +}(); + +new class extends class { } { + constructor() { + (()=>super())(); + assertEq(this, eval("this")); + assertEq(this, (()=>this)()); + } +}(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorInlining.js b/js/src/tests/non262/class/derivedConstructorInlining.js new file mode 100644 index 0000000000..53b6a52f24 --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorInlining.js @@ -0,0 +1,19 @@ +// Since we (for now!) can't emit jitcode for derived class statements. Make +// sure we can correctly invoke derived class constructors. + +class foo extends null { + constructor() { + // Anything that tests |this| should throw, so just let it run off the + // end. + } +} + +function intermediate() { + new foo(); +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(intermediate, "this"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorName.js b/js/src/tests/non262/class/derivedConstructorName.js new file mode 100644 index 0000000000..13d47addb4 --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorName.js @@ -0,0 +1,11 @@ +class A { + constructor() { } +} + +class B extends A { } + +var b = new B(); +assertEq(b.constructor.name, "B"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorReturnPrimitive.js b/js/src/tests/non262/class/derivedConstructorReturnPrimitive.js new file mode 100644 index 0000000000..8b1594fa81 --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorReturnPrimitive.js @@ -0,0 +1,15 @@ +class foo extends null { + constructor() { + // Returning a primitive is a TypeError in derived constructors. This + // ensures that super() can take the return value directly, without + // checking it. Use |null| here, as a tricky check to make sure we + // didn't lump it in with the object check, somehow. + return null; + } +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(() => new foo(), "return"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorTDZExplicitThis.js b/js/src/tests/non262/class/derivedConstructorTDZExplicitThis.js new file mode 100644 index 0000000000..4a4922d57e --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorTDZExplicitThis.js @@ -0,0 +1,12 @@ +class foo extends null { + constructor() { + this; + assertEq(false, true); + } +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(() => new foo(), "this"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorTDZOffEdge.js b/js/src/tests/non262/class/derivedConstructorTDZOffEdge.js new file mode 100644 index 0000000000..28a2c377ca --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorTDZOffEdge.js @@ -0,0 +1,11 @@ +class foo extends null { + constructor() { + // Let it fall off the edge and throw. + } +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(() => new foo(), "this"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorTDZReturnAliasedTry.js b/js/src/tests/non262/class/derivedConstructorTDZReturnAliasedTry.js new file mode 100644 index 0000000000..3399c69b0c --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorTDZReturnAliasedTry.js @@ -0,0 +1,14 @@ +class base {} +class derived extends base { + constructor() { + try { + (function() { p1(eval()) }()) + } catch (e) { + return + } + } +} +assertThrowsInstanceOf(()=>new derived(), ReferenceError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorTDZReturnObject.js b/js/src/tests/non262/class/derivedConstructorTDZReturnObject.js new file mode 100644 index 0000000000..48206a881e --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorTDZReturnObject.js @@ -0,0 +1,13 @@ +class foo extends null { + constructor() { + // If you return an object, we don't care that |this| went + // uninitialized + return {}; + } +} + +for (let i = 0; i < 1100; i++) + new foo(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorTDZReturnTry.js b/js/src/tests/non262/class/derivedConstructorTDZReturnTry.js new file mode 100644 index 0000000000..96791c0e78 --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorTDZReturnTry.js @@ -0,0 +1,16 @@ +class base {} +class derived extends base { + constructor() { + try { + return; + } catch (e) { + try { + return; + } catch (e) {} + } + } +} +assertThrowsInstanceOf(() => new derived, ReferenceError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/derivedConstructorTDZReturnUndefined.js b/js/src/tests/non262/class/derivedConstructorTDZReturnUndefined.js new file mode 100644 index 0000000000..21b8a3a05c --- /dev/null +++ b/js/src/tests/non262/class/derivedConstructorTDZReturnUndefined.js @@ -0,0 +1,13 @@ +class foo extends null { + constructor() { + // Explicit returns of undefined should act the same as falling off the + // end of the function. That is to say, they should throw. + return undefined; + } +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(() => new foo(), "this"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/extendBuiltinConstructors.js b/js/src/tests/non262/class/extendBuiltinConstructors.js new file mode 100644 index 0000000000..f823d81343 --- /dev/null +++ b/js/src/tests/non262/class/extendBuiltinConstructors.js @@ -0,0 +1,110 @@ +function testBuiltinInstanceIsInstanceOf(instance, builtin, class_) { + assertEq(instance instanceof class_, true); + assertEq(instance instanceof builtin, true); + + if (builtin === Array) + assertEq(Array.isArray(instance), true); +} + +function testBuiltinInstance(builtin, ...args) { + class sub extends builtin { + constructor(...args) { + super(...args); + this.called = true; + } + } + + let instance = new sub(...args); + assertEq(instance.called, true); + testBuiltinInstanceIsInstanceOf(instance, builtin, sub); +} + +function testBuiltinMultipleSubclasses(builtin, ...args) { + function f(obj, prop) { + assertEq(obj.prop, prop); + } + + class sub1 extends builtin { }; + class sub2 extends builtin { }; + + const prop1 = "A"; + const prop2 = "B"; + + sub1.prototype.prop = prop1; + sub2.prototype.prop = prop2; + + let instance1 = new sub1(...args); + let instance2 = new sub2(...args); + + // Also make sure we get the properties we want with a default constructor + testBuiltinInstanceIsInstanceOf(instance1, builtin, sub1); + + for (let i = 0; i < 10; i++) { + f(instance1, prop1); + f(instance2, prop2); + } +} + +function testBuiltin(builtin, ...args) { + testBuiltinInstance(builtin, ...args); + testBuiltinMultipleSubclasses(builtin, ...args); +} + +function testBuiltinTypedArrays() { + let typedArrays = [Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array]; + + for (let array of typedArrays) { + testBuiltin(array); + testBuiltin(array, 5); + testBuiltin(array, new array()); + testBuiltin(array, new ArrayBuffer()); + } +} + +testBuiltin(Function); +testBuiltin(Object); +testBuiltin(Boolean); +testBuiltin(Error); +testBuiltin(EvalError); +testBuiltin(RangeError); +testBuiltin(ReferenceError); +testBuiltin(SyntaxError); +testBuiltin(TypeError); +testBuiltin(URIError); +testBuiltin(Number); +testBuiltin(Date); +testBuiltin(Date, 5); +testBuiltin(Date, 5, 10); +testBuiltin(RegExp); +testBuiltin(RegExp, /Regexp Argument/); +testBuiltin(RegExp, "String Argument"); +testBuiltin(Map); +testBuiltin(Set); +testBuiltin(WeakMap); +testBuiltin(WeakSet); +testBuiltin(ArrayBuffer); +testBuiltinTypedArrays(); +testBuiltin(DataView, new ArrayBuffer()); +testBuiltin(DataView, new (newGlobal().ArrayBuffer)()); +testBuiltin(String); +testBuiltin(Array); +testBuiltin(Array, 15); +testBuiltin(Array, 3.0); +testBuiltin(Array, "non-length one-arg"); +testBuiltin(Array, 5, 10, 15, "these are elements"); +// More Promise subclassing tests can be found in non262/Promise/promise-subclassing.js +testBuiltin(Promise, _=>{}); + +if (this.SharedArrayBuffer) + testBuiltin(SharedArrayBuffer); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/fields-instance-class-name-binding-eval.js b/js/src/tests/non262/class/fields-instance-class-name-binding-eval.js new file mode 100644 index 0000000000..6b6d3a0857 --- /dev/null +++ b/js/src/tests/non262/class/fields-instance-class-name-binding-eval.js @@ -0,0 +1,40 @@ +// Instance field initialisers can access the inner name binding for class definitions. +{ + class C { + field = eval("C"); + } + assertEq(new C().field, C); +} +{ + let C = class Inner { + field = eval("Inner"); + }; + assertEq(new C().field, C); +} + +// Instance field initialiser expressions always resolve the inner name binding. +{ + class C { + field = () => eval("C"); + } + assertEq(new C().field(), C); + + const D = C; + C = null; + + assertEq(new D().field(), D); +} +{ + let C = class Inner { + field = () => eval("Inner"); + } + assertEq(new C().field(), C); + + const D = C; + C = null; + + assertEq(new D().field(), D); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/class/fields-instance-class-name-binding.js b/js/src/tests/non262/class/fields-instance-class-name-binding.js new file mode 100644 index 0000000000..10cac12504 --- /dev/null +++ b/js/src/tests/non262/class/fields-instance-class-name-binding.js @@ -0,0 +1,40 @@ +// Instance field initialisers can access the inner name binding for class definitions. +{ + class C { + field = C; + } + assertEq(new C().field, C); +} +{ + let C = class Inner { + field = Inner; + }; + assertEq(new C().field, C); +} + +// Instance field initialiser expressions always resolve the inner name binding. +{ + class C { + field = () => C; + } + assertEq(new C().field(), C); + + const D = C; + C = null; + + assertEq(new D().field(), D); +} +{ + let C = class Inner { + field = () => Inner; + } + assertEq(new C().field(), C); + + const D = C; + C = null; + + assertEq(new D().field(), D); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/class/fields-static-class-name-binding-eval.js b/js/src/tests/non262/class/fields-static-class-name-binding-eval.js new file mode 100644 index 0000000000..34185e7ecd --- /dev/null +++ b/js/src/tests/non262/class/fields-static-class-name-binding-eval.js @@ -0,0 +1,59 @@ +// Static field initialisers can access the inner name binding for class definitions. +{ + class C { + static field = eval("C"); + } + assertEq(C.field, C); +} +{ + let C = class Inner { + static field = eval("Inner"); + }; + assertEq(C.field, C); +} + +// Static field initialisers can't access the outer name binding for class expressions +// before it has been initialised. +{ + assertThrowsInstanceOf(() => { + let C = class { + static field = eval("C"); + }; + }, ReferenceError); +} + +// Static field initialisers can access the outer name binding for class expressions after +// the binding has been initialised +{ + let C = class { + static field = () => eval("C"); + }; + assertEq(C.field(), C); +} + +// Static field initialiser expressions always resolve the inner name binding. +{ + class C { + static field = () => eval("C"); + } + assertEq(C.field(), C); + + const D = C; + C = null; + + assertEq(D.field(), D); +} +{ + let C = class Inner { + static field = () => eval("Inner"); + } + assertEq(C.field(), C); + + const D = C; + C = null; + + assertEq(D.field(), D); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/class/fields-static-class-name-binding.js b/js/src/tests/non262/class/fields-static-class-name-binding.js new file mode 100644 index 0000000000..54032b03ff --- /dev/null +++ b/js/src/tests/non262/class/fields-static-class-name-binding.js @@ -0,0 +1,59 @@ +// Static field initialisers can access the inner name binding for class definitions. +{ + class C { + static field = C; + } + assertEq(C.field, C); +} +{ + let C = class Inner { + static field = Inner; + }; + assertEq(C.field, C); +} + +// Static field initialisers can't access the outer name binding for class expressions +// before it has been initialised. +{ + assertThrowsInstanceOf(() => { + let C = class { + static field = C; + }; + }, ReferenceError); +} + +// Static field initialisers can access the outer name binding for class expressions after +// the binding has been initialised +{ + let C = class { + static field = () => C; + }; + assertEq(C.field(), C); +} + +// Static field initialiser expressions always resolve the inner name binding. +{ + class C { + static field = () => C; + } + assertEq(C.field(), C); + + const D = C; + C = null; + + assertEq(D.field(), D); +} +{ + let C = class Inner { + static field = () => Inner; + } + assertEq(C.field(), C); + + const D = C; + C = null; + + assertEq(D.field(), D); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/class/geterNoExprClosure.js b/js/src/tests/non262/class/geterNoExprClosure.js new file mode 100644 index 0000000000..e37cb287a9 --- /dev/null +++ b/js/src/tests/non262/class/geterNoExprClosure.js @@ -0,0 +1,20 @@ +// getter/setter with expression closure is allowed only in object literal. + +assertThrowsInstanceOf(() => eval(` + class foo { + constructor() {} + + get a() 1 + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + class foo { + constructor() {} + + set a(v) 1 + } +`), SyntaxError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/innerBinding.js b/js/src/tests/non262/class/innerBinding.js new file mode 100644 index 0000000000..b1afa177e0 --- /dev/null +++ b/js/src/tests/non262/class/innerBinding.js @@ -0,0 +1,86 @@ +// Named class definitions should create an immutable inner binding. +// Since all code in classes is in strict mode, attempts to mutate it +// should throw. +class Foof { constructor() { }; tryBreak() { Foof = 4; } } +for (let result of [Foof, class Bar { constructor() { }; tryBreak() { Bar = 4; } }]) + assertThrowsInstanceOf(() => new result().tryBreak(), TypeError); + +{ + class foo { constructor() { }; tryBreak() { foo = 4; } } + for (let result of [foo, class Bar { constructor() { }; tryBreak() { Bar = 4 } }]) + assertThrowsInstanceOf(() => new result().tryBreak(), TypeError); +} + +// TDZ applies to inner bindings +assertThrowsInstanceOf(()=>eval(`class Bar { + constructor() { }; + [Bar] () { }; + }`), ReferenceError); + +assertThrowsInstanceOf(()=>eval(`(class Bar { + constructor() { }; + [Bar] () { }; + })`), ReferenceError); + +// There's no magic "inner binding" global +{ + class Foo { + constructor() { }; + test() { + class Bar { + constructor() { } + test() { return Foo === Bar } + } + return new Bar().test(); + } + } + 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 +{ + class Foo { + constructor() { } + 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; + + class X { + constructor() { } + f() { assertEq(X, orig_X); } + } + + orig_X = X; + X = 13; + assertEq(X, 13); + new orig_X().f(); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/member-expr-after-super.js b/js/src/tests/non262/class/member-expr-after-super.js new file mode 100644 index 0000000000..fa965b24e5 --- /dev/null +++ b/js/src/tests/non262/class/member-expr-after-super.js @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +class Base { + get test() { + return "ok"; + } +} + +class SuperThenProperty extends Base { + constructor() { + var result = super().test; + return {result}; + } +} +assertEq(new SuperThenProperty().result, "ok"); + +class SuperThenMember extends Base { + constructor() { + var result = super()["tes" + String.fromCodePoint("t".codePointAt(0))]; + return {result}; + } +} +assertEq(new SuperThenMember().result, "ok"); + +class SuperThenCall extends Function { + constructor() { + var result = super("o, k", "return o + k;")("o", "k"); + return {result}; + } +} +assertEq(new SuperThenCall().result, "ok"); + +class SuperThenTemplateCall extends Function { + constructor() { + var result = super("cooked", "return cooked[0][0] + cooked.raw[0][1];")`ok`; + return {result}; + } +} +assertEq(new SuperThenTemplateCall().result, "ok"); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/class/methDefn.js b/js/src/tests/non262/class/methDefn.js new file mode 100644 index 0000000000..9513bdfb30 --- /dev/null +++ b/js/src/tests/non262/class/methDefn.js @@ -0,0 +1,204 @@ +var BUGNUMBER = 924672; +var summary = 'Method Definitions' + +print(BUGNUMBER + ": " + summary); + +// Function definitions. +function syntaxError (script) { + try { + Function(script); + } catch (e) { + if (e instanceof SyntaxError) { + return; + } + } + throw new Error('Expected syntax error: ' + script); +} + + +// Tests begin. + +syntaxError("{a(){}}"); +syntaxError("b = {a("); +syntaxError("b = {a)"); +syntaxError("b = {a(}"); +syntaxError("b = {a)}"); +syntaxError("b = {a()"); +syntaxError("b = {a()}"); +syntaxError("b = {a(){}"); +syntaxError("b = {a){}"); +syntaxError("b = {a}}"); +syntaxError("b = {a{}}"); +syntaxError("b = {a({}}"); +syntaxError("b = {a@(){}}"); +syntaxError("b = {a() => 0}"); +syntaxError("b = {a() void 0}"); +syntaxError("b = {a() 1}"); +syntaxError("b = {a() false}"); + +b = {a(){return 5;}}; +assertEq(b.a(), 5); + +b = {a(){return "hey";}}; +assertEq(b.a(), "hey"); + +b = {a(){return arguments;}}; +assertEq(b.a().length, 0); + +b = {a(c){return arguments;}}; +assertEq(b.a().length, 0); + +b = {a(c){return arguments;}}; +assertEq(b.a(1).length, 1); + +b = {a(c){return arguments;}}; +assertEq(b.a(1)[0], 1); + +b = {a(c,d){return arguments;}}; +assertEq(b.a(1,2).length, 2); + +b = {a(c,d){return arguments;}}; +assertEq(b.a(1,2)[0], 1); + +b = {a(c,d){return arguments;}}; +assertEq(b.a(1,2)[1], 2); + +// Methods along with other properties. +syntaxError("b = {,a(){}}"); +syntaxError("b = {@,a(){}}"); +syntaxError("b = {a(){},@}"); +syntaxError("b = {a : 5 , (){}}"); +syntaxError("b = {a : 5@ , a(){}}"); +syntaxError("b = {a : , a(){}}"); +syntaxError("b = {a : 5, a()}}"); +syntaxError("b = {a : 5, a({}}"); +syntaxError("b = {a : 5, a){}}"); +syntaxError("b = {a : 5, a(){}"); +var c = "d"; +b = { a : 5, + [c] : "hey", + e() {return 6;}, + get f() {return 7;}, + set f(g) {this.h = 9;} +} +assertEq(b.a, 5); +assertEq(b.d, "hey"); +assertEq(b.e(), 6); +assertEq(b.f, 7); +assertEq(b.h !== 9, true); +b.f = 15; +assertEq(b.h, 9); + + +var i = 0; +var a = { + foo0 : function (){return 0;}, + ["foo" + ++i](){return 1;}, + ["foo" + ++i](){return 2;}, + ["foo" + ++i](){return 3;}, + foo4(){return 4;} +}; +assertEq(a.foo0(), 0); +assertEq(a.foo1(), 1); +assertEq(a.foo2(), 2); +assertEq(a.foo3(), 3); +assertEq(a.foo4(), 4); + +// Symbols. +var unique_sym = Symbol("1"), registered_sym = Symbol.for("2"); +a = { [unique_sym](){return 2;}, [registered_sym](){return 3;} }; +assertEq(a[unique_sym](), 2); +assertEq(a[registered_sym](), 3); + +// Method characteristics. +a = { b(){ return 4;} }; +b = Object.getOwnPropertyDescriptor(a, "b"); +assertEq(b.configurable, true); +assertEq(b.enumerable, true); +assertEq(b.writable, true); +assertEq(b.value(), 4); + +// prototype property +assertEq(a.b.prototype, undefined); +assertEq(a.b.hasOwnProperty("prototype"), false); + +// Defining several methods using eval. +var code = "({"; +for (i = 0; i < 1000; i++) + code += "['foo' + " + i + "]() {return 'ok';}, " +code += "['bar']() {return 'all ok'}});"; +var obj = eval(code); +for (i = 0; i < 1000; i++) + assertEq(obj["foo" + i](), "ok"); +assertEq(obj["bar"](), "all ok"); + +// this +var obj = { + a : "hey", + meth(){return this.a;} +} +assertEq(obj.meth(), "hey"); + +// Inheritance +var obj2 = Object.create(obj); +assertEq(obj2.meth(), "hey"); + +var obj = { + a() { + return "hey"; + } +} +assertEq(obj.a.call(), "hey"); + +// Duplicates +var obj = { + meth : 3, + meth() { return 4; }, + meth() { return 5; } +} +assertEq(obj.meth(), 5); + +var obj = { + meth() { return 4; }, + meth() { return 5; }, + meth : 3 +} +assertEq(obj.meth, 3); +assertThrowsInstanceOf(function() {obj.meth();}, TypeError); + +// Strict mode +a = {b(c){"use strict";return c;}}; +assertEq(a.b(1), 1); +a = {["b"](c){"use strict";return c;}}; +assertEq(a.b(1), 1); + +// Allow strict-reserved names as methods in objects. +// (Bug 1124362) +a = { static() { return 4; } }; +assertEq(a.static(), 4); + +a = { get static() { return 4; } }; +assertEq(a.static, 4); + +a = { set static(x) { assertEq(x, 4); } }; +a.static = 4; + +function testStrictMode() { + "use strict"; + var obj = { static() { return 4; } }; + assertEq(obj.static(), 4); + + obj = { get static() { return 4; } }; + assertEq(obj.static, 4); + + obj = { set static(x) { assertEq(x, 4); } }; + obj.static = 4; +} +testStrictMode(); + +// Tests provided by benvie in the bug to distinguish from ES5 desugar. +assertEq(({ method() {} }).method.name, "method"); +assertThrowsInstanceOf(function() {({ method() { method() } }).method() }, ReferenceError); + +if (typeof reportCompare === 'function') + reportCompare(0, 0, "ok"); diff --git a/js/src/tests/non262/class/methDefnGen.js b/js/src/tests/non262/class/methDefnGen.js new file mode 100644 index 0000000000..3165ed4481 --- /dev/null +++ b/js/src/tests/non262/class/methDefnGen.js @@ -0,0 +1,83 @@ +var BUGNUMBER = 924672; +var summary = 'Method Definitions - Generators' + +print(BUGNUMBER + ": " + summary); + +// Function definitions. +function syntaxError (script) { + try { + Function(script); + } catch (e) { + if (e instanceof SyntaxError) { + return; + } + } + throw new Error('Expected syntax error: ' + script); +} + + +// Tests begin. + +syntaxError("{*a(){}}"); +syntaxError("b = {*(){}"); +syntaxError("b = {*{}"); +syntaxError("b = {*){}"); +syntaxError("b = {*({}"); +syntaxError("b = {*(){"); +syntaxError("b = {*()}"); +syntaxError("b = {*a("); +syntaxError("b = {*a)"); +syntaxError("b = {*a(}"); +syntaxError("b = {*a)}"); +syntaxError("b = {*a()"); +syntaxError("b = {*a()}"); +syntaxError("b = {*a(){}"); +syntaxError("b = {*a){}"); +syntaxError("b = {*a}}"); +syntaxError("b = {*a{}}"); +syntaxError("b = {*a({}}"); +syntaxError("b = {*a@(){}}"); +syntaxError("b = {*@(){}}"); +syntaxError("b = {*get a(){}}"); +syntaxError("b = {get *a(){}}"); +syntaxError("b = {get a*(){}}"); +syntaxError("b = {*set a(c){}}"); +syntaxError("b = {set *a(c){}}"); +syntaxError("b = {set a*(c){}}"); +syntaxError("b = {*a : 1}"); +syntaxError("b = {a* : 1}"); +syntaxError("b = {a :* 1}"); +syntaxError("b = {a*(){}}"); + +// Generator methods. +b = { * g() { + var a = { [yield 1]: 2, [yield 2]: 3}; + return a; +} } +var it = b.g(); +var next = it.next(); +assertEq(next.done, false); +assertEq(next.value, 1); +next = it.next("hello"); +assertEq(next.done, false); +assertEq(next.value, 2); +next = it.next("world"); +assertEq(next.done, true); +assertEq(next.value.hello, 2); +assertEq(next.value.world, 3); + +// prototype property +assertEq(b.g.hasOwnProperty("prototype"), true); + +// Strict mode +a = {*b(c){"use strict";yield c;}}; +assertEq(a.b(1).next().value, 1); +a = {*["b"](c){"use strict";return c;}}; +assertEq(a.b(1).next().value, 1); + +// Generators should not have [[Construct]] +a = {*g() { yield 1; }} +assertThrowsInstanceOf(() => { new a.g }, TypeError); + +if (typeof reportCompare === 'function') + reportCompare(0, 0, "ok"); diff --git a/js/src/tests/non262/class/method-named-static.js b/js/src/tests/non262/class/method-named-static.js new file mode 100644 index 0000000000..c5a2ed8827 --- /dev/null +++ b/js/src/tests/non262/class/method-named-static.js @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Instance method named "static", with and without escape sequence. +assertEq((new class { + static() { return "method-static-no-escape"; } +}).static(), "method-static-no-escape"); + +assertEq((new class { + st\u0061tic() { return "method-static-escape"; } +}).static(), "method-static-escape"); + +// Instance getter named "static", with and without escape sequence. +assertEq((new class { + get static() { return "getter-static-no-escape"; } +}).static, "getter-static-no-escape"); + +assertEq((new class { + get static() { return "getter-static-escape"; } +}).static, "getter-static-escape"); + +// Static method named "static", with and without escape sequence. +assertEq(class { + static static() { return "static-method-static-no-escape"; } +}.static(), "static-method-static-no-escape"); + +assertEq(class { + static st\u0061tic() { return "static-method-static-escape"; } +}.static(), "static-method-static-escape"); + +// Static getter named "static", with and without escape sequence. +assertEq(class { + static get static() { return "static-getter-static-no-escape"; } +}.static, "static-getter-static-no-escape"); + +assertEq(class { + static get st\u0061tic() { return "static-getter-static-escape"; } +}.static, "static-getter-static-escape"); + + +// The static modifier itself must not contain any escape sequences. +assertThrowsInstanceOf(() => eval(String.raw` + class C { + st\u0061tic m() {} + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(String.raw` + class C { + st\u0061tic get m() {} + } +`), SyntaxError); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "ok"); diff --git a/js/src/tests/non262/class/methodInstallation.js b/js/src/tests/non262/class/methodInstallation.js new file mode 100644 index 0000000000..bbb6c2f727 --- /dev/null +++ b/js/src/tests/non262/class/methodInstallation.js @@ -0,0 +1,101 @@ +// Do the things we write in classes actually appear as they are supposed to? + +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; } + 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"; } +} + +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"; } + }]) { + + methodCalled = false; + getterCalled = false; + setterCalled = false; + constructorCalled = false; + staticMethodCalled = false; + staticGetterCalled = false; + staticSetterCalled = false; + + var aConstDesc = Object.getOwnPropertyDescriptor(a.prototype, "constructor"); + assertEq(aConstDesc.writable, true); + assertEq(aConstDesc.configurable, true); + assertEq(aConstDesc.enumerable, false); + new 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, false); + aMethDesc.value(); + assertEq(methodCalled, true); + + var aGetDesc = Object.getOwnPropertyDescriptor(a.prototype, "getter"); + assertEq(aGetDesc.configurable, true); + assertEq(aGetDesc.enumerable, false); + aGetDesc.get(); + assertThrowsInstanceOf(() => new aGetDesc.get, TypeError); + assertEq(getterCalled, true); + + var aSetDesc = Object.getOwnPropertyDescriptor(a.prototype, "setter"); + assertEq(aSetDesc.configurable, true); + assertEq(aSetDesc.enumerable, false); + aSetDesc.set(); + assertThrowsInstanceOf(() => new aSetDesc.set, TypeError); + assertEq(setterCalled, true); + assertDeepEq(aSetDesc, Object.getOwnPropertyDescriptor(a.prototype, "setter")); + + assertEq(Object.getOwnPropertyDescriptor(new a(), "staticMethod"), undefined); + var aStaticMethDesc = Object.getOwnPropertyDescriptor(a, "staticMethod"); + assertEq(aStaticMethDesc.configurable, true); + assertEq(aStaticMethDesc.enumerable, false); + assertEq(aStaticMethDesc.writable, true); + aStaticMethDesc.value(); + assertThrowsInstanceOf(() => new aStaticMethDesc.value, TypeError); + assertEq(staticMethodCalled, true); + + assertEq(Object.getOwnPropertyDescriptor(new a(), "staticGetter"), undefined); + var aStaticGetDesc = Object.getOwnPropertyDescriptor(a, "staticGetter"); + assertEq(aStaticGetDesc.configurable, true); + assertEq(aStaticGetDesc.enumerable, false); + aStaticGetDesc.get(); + assertThrowsInstanceOf(() => new aStaticGetDesc.get, TypeError); + assertEq(staticGetterCalled, true); + + assertEq(Object.getOwnPropertyDescriptor(new a(), "staticSetter"), undefined); + var aStaticSetDesc = Object.getOwnPropertyDescriptor(a, "staticSetter"); + assertEq(aStaticSetDesc.configurable, true); + assertEq(aStaticSetDesc.enumerable, false); + aStaticSetDesc.set(); + assertThrowsInstanceOf(() => new aStaticSetDesc.set, TypeError); + assertEq(staticSetterCalled, true); + + assertEq([...new a()].join(), "cow,pig"); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/methodName.js b/js/src/tests/non262/class/methodName.js new file mode 100644 index 0000000000..02a726c105 --- /dev/null +++ b/js/src/tests/non262/class/methodName.js @@ -0,0 +1,40 @@ +class TestClass { + get getter() { } + set setter(x) { } + method() { } + + static get staticGetter() { } + static set staticSetter(x) { } + static staticMethod() { } + + get 1() { } + set 2(v) { } + 3() { } + + static get 4() { } + static set 5(x) { } + static 6() { } +} + +function name(obj, property, get) { + let desc = Object.getOwnPropertyDescriptor(obj, property); + return (get ? desc.get : desc.set).name; +} + +assertEq(name(TestClass.prototype, "getter", true), "get getter"); +assertEq(name(TestClass.prototype, "setter", false), "set setter"); +assertEq(TestClass.prototype.method.name, "method"); + +assertEq(name(TestClass, "staticGetter", true), "get staticGetter"); +assertEq(name(TestClass, "staticSetter", false), "set staticSetter"); +assertEq(TestClass.staticMethod.name, "staticMethod"); + +assertEq(name(TestClass.prototype, "1", true), "get 1"); +assertEq(name(TestClass.prototype, "2", false), "set 2"); +assertEq(TestClass.prototype[3].name, "3"); + +assertEq(name(TestClass, "4", true), "get 4"); +assertEq(name(TestClass, "5", false), "set 5"); +assertEq(TestClass[6].name, "6"); + +reportCompare(true, true); diff --git a/js/src/tests/non262/class/methodOverwrites.js b/js/src/tests/non262/class/methodOverwrites.js new file mode 100644 index 0000000000..c1c460a28c --- /dev/null +++ b/js/src/tests/non262/class/methodOverwrites.js @@ -0,0 +1,80 @@ +// Ensure that we can overwrite methods when more tha one is present. +{ + 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); + + class c { constructor() { } method() { result += 1 } get method() { result += 2 } } + new c().method; + assertEq(result, 8); + + class d { 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. + 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 (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/methodsPrototype.js b/js/src/tests/non262/class/methodsPrototype.js new file mode 100644 index 0000000000..07a565ceb6 --- /dev/null +++ b/js/src/tests/non262/class/methodsPrototype.js @@ -0,0 +1,39 @@ +class TestClass { + constructor() { } + method() { } + get getter() { } + set setter(x) { } + *generator() { } + static staticMethod() { } + static get staticGetter() { } + static set staticSetter(x) { } + static *staticGenerator() { } +} + +var test = new TestClass(); + +var hasPrototype = [ + test.constructor, + test.generator, + TestClass.staticGenerator +] + +for (var fun of hasPrototype) { + assertEq(fun.hasOwnProperty('prototype'), true); +} + +var hasNoPrototype = [ + test.method, + Object.getOwnPropertyDescriptor(test.__proto__, 'getter').get, + Object.getOwnPropertyDescriptor(test.__proto__, 'setter').set, + TestClass.staticMethod, + Object.getOwnPropertyDescriptor(TestClass, 'staticGetter').get, + Object.getOwnPropertyDescriptor(TestClass, 'staticSetter').set, +] + +for (var fun of hasNoPrototype) { + assertEq(fun.hasOwnProperty('prototype'), false); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/newTargetArgumentsIntact.js b/js/src/tests/non262/class/newTargetArgumentsIntact.js new file mode 100644 index 0000000000..d047085ea0 --- /dev/null +++ b/js/src/tests/non262/class/newTargetArgumentsIntact.js @@ -0,0 +1,44 @@ +// Since we put new.target at the end of the arguments vector, ensrue that it +// doesn't interact with the arguments object + +var argsContent; + +function argsWithNewTarget(foo) { + assertEq(arguments.length, argsContent.length); + for (let i = 0; i < arguments.length; i++) + assertEq(arguments[i], argsContent[i]); + let nt = new.target; + + // Assigning to the arguments object shouldn't infect new.target, either + arguments[arguments.length] = 42; + assertEq(new.target, nt); +} + +// Test constructing invocations, with under and overflow +argsContent = []; +for (let i = 0; i < 100; i++) + new argsWithNewTarget(); + +argsContent = [1]; +for (let i = 0; i < 100; i++) + new argsWithNewTarget(1); + +argsContent = [1,2,3]; +for (let i = 0; i < 100; i++) + new argsWithNewTarget(1, 2, 3); + +// Test spreadnew as well. +argsContent = []; +for (let i = 0; i < 100; i++) + new argsWithNewTarget(...[]); + +argsContent = [1]; +for (let i = 0; i < 100; i++) + new argsWithNewTarget(...[1]); + +argsContent = [1,2,3]; +for (let i = 0; i < 100; i++) + new argsWithNewTarget(...[1,2,3]); + +if (typeof reportCompare === "function") + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/newTargetArrow.js b/js/src/tests/non262/class/newTargetArrow.js new file mode 100644 index 0000000000..548f750475 --- /dev/null +++ b/js/src/tests/non262/class/newTargetArrow.js @@ -0,0 +1,24 @@ +// new.target is valid in any arrow function not in a global context. +new Function('(() => new.target)()'); + +// It's also good inside eval, but not global eval +assertThrowsInstanceOf(() => eval('() => new.target'), SyntaxError); + +function assertNewTarget(expected) { + assertEq((()=>new.target)(), expected); + assertEq(eval('()=>new.target')(), expected); + + // Make sure that arrow functions can escape their original context and + // still get the right answer. + return (() => new.target); +} + +const ITERATIONS = 550; +for (let i = 0; i < ITERATIONS; i++) + assertEq(assertNewTarget(undefined)(), undefined); + +for (let i = 0; i < ITERATIONS; i++) + assertEq(new assertNewTarget(assertNewTarget)(), assertNewTarget); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/newTargetBound.js b/js/src/tests/non262/class/newTargetBound.js new file mode 100644 index 0000000000..f65cfe3caf --- /dev/null +++ b/js/src/tests/non262/class/newTargetBound.js @@ -0,0 +1,16 @@ +function boundTarget(expected) { + assertEq(new.target, expected); +} + +let bound = boundTarget.bind(undefined); + +const TEST_ITERATIONS = 550; + +for (let i = 0; i < TEST_ITERATIONS; i++) + bound(undefined); + +for (let i = 0; i < TEST_ITERATIONS; i++) + new bound(boundTarget); + +if (typeof reportCompare === "function") + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/newTargetCCW.js b/js/src/tests/non262/class/newTargetCCW.js new file mode 100644 index 0000000000..9f5f870d40 --- /dev/null +++ b/js/src/tests/non262/class/newTargetCCW.js @@ -0,0 +1,10 @@ +// Make sure we wrap the new target on CCW construct calls. +var g = newGlobal(); + +let f = g.eval('(function (expected) { this.accept = new.target === expected; })'); + +for (let i = 0; i < 1100; i++) + assertEq(new f(f).accept, true); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/newTargetDVG.js b/js/src/tests/non262/class/newTargetDVG.js new file mode 100644 index 0000000000..1f9c7d7fc5 --- /dev/null +++ b/js/src/tests/non262/class/newTargetDVG.js @@ -0,0 +1,7 @@ +function thunk() { + new.target(); +} +assertThrownErrorContains(thunk, "new.target"); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/newTargetDefaults.js b/js/src/tests/non262/class/newTargetDefaults.js new file mode 100644 index 0000000000..d5a4c5640b --- /dev/null +++ b/js/src/tests/non262/class/newTargetDefaults.js @@ -0,0 +1,25 @@ +// Check that new.target works properly in defaults. + +function check(expected, actual = new.target) { assertEq(actual, expected); } +new check(check); +check(undefined); + +let evaldCheck = eval("(" + check.toString() + ")"); +new evaldCheck(evaldCheck); +evaldCheck(undefined); + +function testInFunction() { + function checkInFunction(expected, actual = new.target) { assertEq(actual, expected); } + new checkInFunction(checkInFunction); + checkInFunction(undefined); + + let evaldCheckInFunction = eval("(" + checkInFunction.toString() + ")"); + new evaldCheckInFunction(evaldCheckInFunction); + evaldCheckInFunction(undefined); +} + +testInFunction(); +new testInFunction(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/newTargetDirectInvoke.js b/js/src/tests/non262/class/newTargetDirectInvoke.js new file mode 100644 index 0000000000..d2cd1128c3 --- /dev/null +++ b/js/src/tests/non262/class/newTargetDirectInvoke.js @@ -0,0 +1,50 @@ +// new.target is valid inside Function() invocations +var func = new Function("new.target"); + +// Note that this will also test new.target in ion inlines. When the toplevel +// script is compiled, assertNewTarget will be inlined. +function assertNewTarget(expected, unused) { assertEq(new.target, expected); } + +// Test non-constructing invocations, with arg underflow, overflow, and correct +// numbers +for (let i = 0; i < 100; i++) + assertNewTarget(undefined, null); + +for (let i = 0; i < 100; i++) + assertNewTarget(undefined); + +for (let i = 0; i < 100; i++) + assertNewTarget(undefined, null, 1); + +// Test spread-call +for (let i = 0; i < 100; i++) + assertNewTarget(...[undefined]); + +for (let i = 0; i < 100; i++) + assertNewTarget(...[undefined, null]); + +for (let i = 0; i < 100; i++) + assertNewTarget(...[undefined, null, 1]); + +// Test constructing invocations, again with under and overflow +for (let i = 0; i < 100; i++) + new assertNewTarget(assertNewTarget, null); + +for (let i = 0; i < 100; i++) + new assertNewTarget(assertNewTarget); + +for (let i = 0; i < 100; i++) + new assertNewTarget(assertNewTarget, null, 1); + +// Test spreadnew as well. +for (let i = 0; i < 100; i++) + new assertNewTarget(...[assertNewTarget]); + +for (let i = 0; i < 100; i++) + new assertNewTarget(...[assertNewTarget, null]); + +for (let i = 0; i < 100; i++) + new assertNewTarget(...[assertNewTarget, null, 1]); + +if (typeof reportCompare === "function") + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/newTargetEval.js b/js/src/tests/non262/class/newTargetEval.js new file mode 100644 index 0000000000..f7581c5ab8 --- /dev/null +++ b/js/src/tests/non262/class/newTargetEval.js @@ -0,0 +1,40 @@ +// Eval of new.target is invalid outside functions. +try { + eval('new.target'); + assertEq(false, true); +} catch (e) { + if (!(e instanceof SyntaxError)) + throw e; +} + +// new.target is invalid inside eval inside top-level arrow functions +assertThrowsInstanceOf(() => eval('new.target'), SyntaxError); + +// new.target is invalid inside indirect eval. +let ieval = eval; +try { + (function () { return ieval('new.target'); })(); + assertEq(false, true); +} catch (e) { + if (!(e instanceof SyntaxError)) + throw e; +} + +function assertNewTarget(expected) { + assertEq(eval('new.target'), expected); + assertEq((()=>eval('new.target'))(), expected); + + // Also test nestings "by induction" + assertEq(eval('eval("new.target")'), expected); + assertEq(eval("eval('eval(`new.target`)')"), expected); +} + +const ITERATIONS = 550; +for (let i = 0; i < ITERATIONS; i++) + assertNewTarget(undefined); + +for (let i = 0; i < ITERATIONS; i++) + new assertNewTarget(assertNewTarget); + +if (typeof reportCompare === "function") + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/newTargetGenerators.js b/js/src/tests/non262/class/newTargetGenerators.js new file mode 100644 index 0000000000..614b1ab908 --- /dev/null +++ b/js/src/tests/non262/class/newTargetGenerators.js @@ -0,0 +1,14 @@ +function *generatorNewTarget(expected) { + assertEq(new.target, expected); + assertEq(eval('new.target'), expected); + assertEq((() => new.target)(), expected); + yield (() => new.target); +} + +const ITERATIONS = 25; + +for (let i = 0; i < ITERATIONS; i++) + assertEq(generatorNewTarget(undefined).next().value(), undefined); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/newTargetMethods.js b/js/src/tests/non262/class/newTargetMethods.js new file mode 100644 index 0000000000..2383dc8bd8 --- /dev/null +++ b/js/src/tests/non262/class/newTargetMethods.js @@ -0,0 +1,51 @@ +// Just like newTargetDirectInvoke, except to prove it works in functions +// defined with method syntax as well. Note that methods, getters, and setters +// are not constructible. + +let ol = { + olTest(arg) { assertEq(arg, 4); assertEq(new.target, undefined); }, + get ol() { assertEq(new.target, undefined); }, + set ol(arg) { assertEq(arg, 4); assertEq(new.target, undefined); } +} + +class cl { + constructor() { assertEq(new.target, cl); } + clTest(arg) { assertEq(arg, 4); assertEq(new.target, undefined); } + get cl() { assertEq(new.target, undefined); } + set cl(arg) { assertEq(arg, 4); assertEq(new.target, undefined); } + + static staticclTest(arg) { assertEq(arg, 4); assertEq(new.target, undefined); } + static get staticcl() { assertEq(new.target, undefined); } + static set staticcl(arg) { assertEq(arg, 4); assertEq(new.target, undefined); } +} + +const TEST_ITERATIONS = 150; + +for (let i = 0; i < TEST_ITERATIONS; i++) + ol.olTest(4); +for (let i = 0; i < TEST_ITERATIONS; i++) + ol.ol; +for (let i = 0; i < TEST_ITERATIONS; i++) + ol.ol = 4; + +for (let i = 0; i < TEST_ITERATIONS; i++) + cl.staticclTest(4); +for (let i = 0; i < TEST_ITERATIONS; i++) + cl.staticcl; +for (let i = 0; i < TEST_ITERATIONS; i++) + cl.staticcl = 4; + +for (let i = 0; i < TEST_ITERATIONS; i++) + new cl(); + +let clInst = new cl(); + +for (let i = 0; i < TEST_ITERATIONS; i++) + clInst.clTest(4); +for (let i = 0; i < TEST_ITERATIONS; i++) + clInst.cl; +for (let i = 0; i < TEST_ITERATIONS; i++) + clInst.cl = 4; + +if (typeof reportCompare === "function") + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/newTargetNonFunction.js b/js/src/tests/non262/class/newTargetNonFunction.js new file mode 100644 index 0000000000..59e6febef7 --- /dev/null +++ b/js/src/tests/non262/class/newTargetNonFunction.js @@ -0,0 +1,10 @@ +// Make sure that we can plumb new.target, even if the results are going to +// throw. + +assertThrowsInstanceOf(() => new ""(...Array()), TypeError); + +assertThrowsInstanceOf(() => new ""(), TypeError); +assertThrowsInstanceOf(() => new ""(1), TypeError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/newTargetProxyNative.js b/js/src/tests/non262/class/newTargetProxyNative.js new file mode 100644 index 0000000000..89cdd64ab1 --- /dev/null +++ b/js/src/tests/non262/class/newTargetProxyNative.js @@ -0,0 +1,5 @@ +var proxyToArray = new Proxy(Array, {}); +new proxyToArray(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/outerBinding.js b/js/src/tests/non262/class/outerBinding.js new file mode 100644 index 0000000000..19ffcb3e4a --- /dev/null +++ b/js/src/tests/non262/class/outerBinding.js @@ -0,0 +1,48 @@ +// |reftest| skip-if(!xulRuntime.shell) +// +// The above skip-if is because global lexicals aren't yet implemented. Remove +// that and the |evaluate| call below once they are. +// +// A class statement creates a mutable lexical outer binding. + +class Foo { constructor() { } } +assertEq(typeof Foo, "function"); +Foo = 5; +assertEq(Foo, 5); + +{ + class foo { constructor() { } } + assertEq(typeof foo, "function"); + foo = 4; + assertEq(foo, 4); +} + +{ + class PermanentBinding { constructor() { } } + delete PermanentBinding; + // That...didn't actually work, right? + assertEq(typeof PermanentBinding, "function"); +} + +evaluate("const globalConstant = 0; var earlyError = true;"); + +try { + evaluate("earlyError = false; class globalConstant { constructor() { } }"); +} catch (e) { + if (!(e instanceof SyntaxError)) + throw e; +} +assertEq(earlyError, true); + +function strictEvalShadows() { + "use strict"; + let x = 4; + eval(`class x { constructor() { } } + assertEq(typeof x, "function"); + `); + assertEq(x, 4); +} +strictEvalShadows() + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/parenExprToString.js b/js/src/tests/non262/class/parenExprToString.js new file mode 100644 index 0000000000..a93972ce92 --- /dev/null +++ b/js/src/tests/non262/class/parenExprToString.js @@ -0,0 +1,8 @@ +// Test that parenthesized class expressions don't get their toString offsets +// messed up. + +assertEq((class {}).toString(), "class {}"); +assertEq(((class {})).toString(), "class {}"); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/shell.js b/js/src/tests/non262/class/shell.js new file mode 100644 index 0000000000..1169b2c5f4 --- /dev/null +++ b/js/src/tests/non262/class/shell.js @@ -0,0 +1,10 @@ +function assertThrownErrorContains(thunk, substr) { + try { + thunk(); + } catch (e) { + if (e.message.indexOf(substr) !== -1) + return; + throw new Error("Expected error containing " + substr + ", got " + e); + } + throw new Error("Expected error containing " + substr + ", no exception thrown"); +} diff --git a/js/src/tests/non262/class/staticConstructor.js b/js/src/tests/non262/class/staticConstructor.js new file mode 100644 index 0000000000..bd79c22005 --- /dev/null +++ b/js/src/tests/non262/class/staticConstructor.js @@ -0,0 +1,22 @@ +class testBasic { + constructor() { } + static constructor() { } +} + +class testWithExtends extends null { + constructor() { }; + static constructor() { }; +} + +class testOrder { + static constructor() { }; + constructor() { }; +} + +class testOrderWithExtends extends null { + static constructor() { }; + constructor() { }; +} + +if (typeof reportCompare === "function") + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/staticMethods.js b/js/src/tests/non262/class/staticMethods.js new file mode 100644 index 0000000000..756e5abf56 --- /dev/null +++ b/js/src/tests/non262/class/staticMethods.js @@ -0,0 +1,15 @@ +// basic static method test +class X { + static count() { return ++this.hits; } + constructor() { } +} +X.hits = 0; +assertEq(X.count(), 1); + +// A static method is just a function. +assertEq(X.count instanceof Function, true); +assertEq(X.count.length, 0); +assertEq(X.count.bind({hits: 77})(), 78); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/strictExecution.js b/js/src/tests/non262/class/strictExecution.js new file mode 100644 index 0000000000..f459311e55 --- /dev/null +++ b/js/src/tests/non262/class/strictExecution.js @@ -0,0 +1,38 @@ +// Classes are always strict mode. Check computed property names and heritage +// expressions as well. + +class a { constructor() { Object.preventExtensions({}).prop = 0; } } +assertThrowsInstanceOf(() => new a(), TypeError); +var aExpr = class { constructor() { Object.preventExtensions().prop = 0; } }; +assertThrowsInstanceOf(() => new aExpr(), TypeError); + +function shouldThrowCPN() { + class b { + [Object.preventExtensions({}).prop = 4]() { } + constructor() { } + } +} +function shouldThrowCPNExpr() { + var b = class { + [Object.preventExtensions({}).prop = 4]() { } + constructor() { } + }; +} +assertThrowsInstanceOf(shouldThrowCPN, TypeError); +assertThrowsInstanceOf(shouldThrowCPNExpr, TypeError); + +function shouldThrowHeritage() { + class b extends (Object.preventExtensions({}).prop = 4) { + constructor() { } + } +} +function shouldThrowHeritageExpr() { + var b = class extends (Object.preventExtensions({}).prop = 4) { + constructor() { } + }; +} +assertThrowsInstanceOf(shouldThrowHeritage, TypeError); +assertThrowsInstanceOf(shouldThrowHeritageExpr, TypeError); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/non262/class/stringConstructor.js b/js/src/tests/non262/class/stringConstructor.js new file mode 100644 index 0000000000..4e8ae3331f --- /dev/null +++ b/js/src/tests/non262/class/stringConstructor.js @@ -0,0 +1,12 @@ +class A { + "constructor"() { return {}; } +} +assertEq(new A() instanceof A, false); + +class B extends class { } { + "constructor"() { return {}; } +} +assertEq(new B() instanceof B, false); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/subclassedArrayUnboxed.js b/js/src/tests/non262/class/subclassedArrayUnboxed.js new file mode 100644 index 0000000000..eb27a960d0 --- /dev/null +++ b/js/src/tests/non262/class/subclassedArrayUnboxed.js @@ -0,0 +1,22 @@ +class foo extends Array { } + +function testArrs(arrs) { + for (let arr of arrs) { + assertEq(Object.getPrototypeOf(arr), foo.prototype); + } +} + +var arrs = []; +for (var i = 0; i < 25; i++) + arrs.push(new foo(1)); + +testArrs(arrs); + +arrs[0].nonIndexedProp = "uhoh"; + +arrs.push(new foo(1)); + +testArrs(arrs); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superCallBadDynamicSuperClass.js b/js/src/tests/non262/class/superCallBadDynamicSuperClass.js new file mode 100644 index 0000000000..f5b3ac3c0c --- /dev/null +++ b/js/src/tests/non262/class/superCallBadDynamicSuperClass.js @@ -0,0 +1,12 @@ +class base { constructor() { } } + +class inst extends base { constructor() { super(); } } +Object.setPrototypeOf(inst, Math.sin); +assertThrowsInstanceOf(() => new inst(), TypeError); + +class defaultInst extends base { } +Object.setPrototypeOf(inst, Math.sin); +assertThrowsInstanceOf(() => new inst(), TypeError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superCallBadNewTargetPrototype.js b/js/src/tests/non262/class/superCallBadNewTargetPrototype.js new file mode 100644 index 0000000000..43d1eb60d1 --- /dev/null +++ b/js/src/tests/non262/class/superCallBadNewTargetPrototype.js @@ -0,0 +1,25 @@ +class base { constructor() { } } + +// lies and the lying liars who tell them +function lies() { } +lies.prototype = 4; + +assertThrowsInstanceOf(()=>Reflect.consruct(base, [], lies), TypeError); + +// lie a slightly different way +function get(target, property, receiver) { + if (property === "prototype") + return 42; + return Reflect.get(target, property, receiver); +} + +class inst extends base { + constructor() { super(); } +} +assertThrowsInstanceOf(()=>new new Proxy(inst, {get})(), TypeError); + +class defaultInst extends base {} +assertThrowsInstanceOf(()=>new new Proxy(defaultInst, {get})(), TypeError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superCallBaseInvoked.js b/js/src/tests/non262/class/superCallBaseInvoked.js new file mode 100644 index 0000000000..b66f9be170 --- /dev/null +++ b/js/src/tests/non262/class/superCallBaseInvoked.js @@ -0,0 +1,55 @@ +function testBase(base) { + class instance extends base { + constructor(inst, one) { + super(inst, one); + } + } + + let inst = new instance(instance, 1); + assertEq(Object.getPrototypeOf(inst), instance.prototype); + assertEq(inst.calledBase, true); + + class defaultInstance extends base { } + let defInst = new defaultInstance(defaultInstance, 1); + assertEq(Object.getPrototypeOf(defInst), defaultInstance.prototype); + assertEq(defInst.calledBase, true); +} + +class base { + // Base class must be [[Construct]]ed, as you cannot [[Call]] a class + // constructor + constructor(nt, one) { + assertEq(new.target, nt); + + // Check argument ordering + assertEq(one, 1); + this.calledBase = true; + } +} + +testBase(base); +testBase(class extends base { + constructor(nt, one) { + // Every step of the way, new.target and args should be right + assertEq(new.target, nt); + assertEq(one, 1); + super(nt, one); + } + }); +function baseFunc(nt, one) { + assertEq(new.target, nt); + assertEq(one, 1); + this.calledBase = true; +} + +testBase(baseFunc); + +let handler = {}; +let p = new Proxy(baseFunc, handler); +testBase(p); + +handler.construct = (target, args, nt) => Reflect.construct(target, args, nt); +testBase(p); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superCallIllegal.js b/js/src/tests/non262/class/superCallIllegal.js new file mode 100644 index 0000000000..8b7b36397d --- /dev/null +++ b/js/src/tests/non262/class/superCallIllegal.js @@ -0,0 +1,7 @@ +// super() invalid outside derived class constructors, including in dynamic +// functions and eval +assertThrowsInstanceOf(() => new Function("super();"), SyntaxError); +assertThrowsInstanceOf(() => eval("super()"), SyntaxError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superCallInvalidBase.js b/js/src/tests/non262/class/superCallInvalidBase.js new file mode 100644 index 0000000000..86b24f96c5 --- /dev/null +++ b/js/src/tests/non262/class/superCallInvalidBase.js @@ -0,0 +1,9 @@ +class instance extends null { + constructor() { super(); } +} + +assertThrowsInstanceOf(() => new instance(), TypeError); +assertThrowsInstanceOf(() => new class extends null { }(), TypeError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superCallOrder.js b/js/src/tests/non262/class/superCallOrder.js new file mode 100644 index 0000000000..46a1b0e57c --- /dev/null +++ b/js/src/tests/non262/class/superCallOrder.js @@ -0,0 +1,28 @@ +function base() { } + +class beforeSwizzle extends base { + constructor() { + super(Object.setPrototypeOf(beforeSwizzle, null)); + } +} + +new beforeSwizzle(); + +function MyError() {} + +// Again, testing both dynamic prototype dispatch, and that we verify the function +// is a constructor after evaluating args +class beforeThrow extends base { + constructor() { + function thrower() { throw new MyError(); } + super(thrower()); + } +} + +Object.setPrototypeOf(beforeThrow, Math.sin); + +// Won't throw that Math.sin is not a constructor before evaluating the args +assertThrowsInstanceOf(() => new beforeThrow(), MyError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superCallProperBase.js b/js/src/tests/non262/class/superCallProperBase.js new file mode 100644 index 0000000000..cc03f57d08 --- /dev/null +++ b/js/src/tests/non262/class/superCallProperBase.js @@ -0,0 +1,34 @@ +class base1 { + constructor() { + this.base = 1; + } +} + +class base2 { + constructor() { + this.base = 2; + } +} + +class inst extends base1 { + constructor() { + super(); + } +} + +assertEq(new inst().base, 1); + +Object.setPrototypeOf(inst, base2); + +assertEq(new inst().base, 2); + +// Still works with default constructor + +class defaultInst extends base1 { } + +assertEq(new defaultInst().base, 1); +Object.setPrototypeOf(defaultInst, base2); +assertEq(new defaultInst().base, 2); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superCallSpreadCall.js b/js/src/tests/non262/class/superCallSpreadCall.js new file mode 100644 index 0000000000..1045f1a22a --- /dev/null +++ b/js/src/tests/non262/class/superCallSpreadCall.js @@ -0,0 +1,27 @@ +class base { + constructor(a, b, c) { + assertEq(a, 1); + assertEq(b, 2); + assertEq(c, 3); + this.calledBase = true; + } +} + +class doTest extends base { + constructor(arr) { + super(...arr); + } +} + +assertEq(new doTest([1,2,3]).calledBase, true); + +class testRest extends base { + constructor(...args) { + super(...args); + } +} + +assertEq(new testRest(1,2,3).calledBase, true); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superCallThisInit.js b/js/src/tests/non262/class/superCallThisInit.js new file mode 100644 index 0000000000..935f6b69ff --- /dev/null +++ b/js/src/tests/non262/class/superCallThisInit.js @@ -0,0 +1,48 @@ +function base() { this.prop = 42; } + +class testInitialize extends base { + constructor() { + // A poor man's assertThrowsInstanceOf, as arrow functions are currently + // disabled in this context + try { + this; + throw new Error(); + } catch (e) { + if (!(e instanceof ReferenceError)) + throw e; + } + super(); + assertEq(this.prop, 42); + } +} +assertEq(new testInitialize().prop, 42); + +// super() twice is a no-go. +class willThrow extends base { + constructor() { + super(); + super(); + } +} +assertThrowsInstanceOf(()=>new willThrow(), ReferenceError); + +// This is determined at runtime, not the syntax level. +class willStillThrow extends base { + constructor() { + for (let i = 0; i < 3; i++) { + super(); + } + } +} +assertThrowsInstanceOf(()=>new willStillThrow(), ReferenceError); + +class canCatchThrow extends base { + constructor() { + super(); + try { super(); } catch(e) { } + } +} +assertEq(new canCatchThrow().prop, 42); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superElemDelete.js b/js/src/tests/non262/class/superElemDelete.js new file mode 100644 index 0000000000..74d2c94e15 --- /dev/null +++ b/js/src/tests/non262/class/superElemDelete.js @@ -0,0 +1,68 @@ +// Make sure we get the proper side effects. +// |delete super[expr]| applies ToPropertyKey on |expr| before throwing. + +class base { + constructor() { } +} + +class derived extends base { + constructor() { super(); } + testDeleteElem() { + let sideEffect = 0; + let key = { + toString() { + sideEffect++; + return ""; + } + }; + assertThrowsInstanceOf(() => delete super[key], ReferenceError); + assertEq(sideEffect, 1); + } + testDeleteElemPropValFirst() { + // The deletion error is a reference error, even after munging the prototype + // chain. + let key = { + toString() { + Object.setPrototypeOf(derived.prototype, null); + return ""; + } + }; + delete super[key]; + } +} + +class derivedTestDeleteElem extends base { + constructor() { + let sideEffect = 0; + let key = { + toString() { + sideEffect++; + return ""; + } + }; + + assertThrowsInstanceOf(() => delete super[key], ReferenceError); + assertEq(sideEffect, 0); + + super(); + + assertThrowsInstanceOf(() => delete super[key], ReferenceError); + assertEq(sideEffect, 1); + + Object.setPrototypeOf(derivedTestDeleteElem.prototype, null); + + assertThrowsInstanceOf(() => delete super[key], ReferenceError); + assertEq(sideEffect, 2); + + return {}; + } +} + +var d = new derived(); +d.testDeleteElem(); +assertThrowsInstanceOf(() => d.testDeleteElemPropValFirst(), ReferenceError); + +new derivedTestDeleteElem(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropBasicCalls.js b/js/src/tests/non262/class/superPropBasicCalls.js new file mode 100644 index 0000000000..c05d0ec26c --- /dev/null +++ b/js/src/tests/non262/class/superPropBasicCalls.js @@ -0,0 +1,27 @@ +// Super property (and calls) works in non-extending classes and object +// litterals. +class toStringTest { + constructor() { + // Install a property to make it plausible that it's the same this + this.foo = "rhinoceros"; + } + + test() { + assertEq(super.toString(), super["toString"]()); + assertEq(super.toString(), this.toString()); + } +} + +new toStringTest().test(); + +let toStrOL = { + test() { + assertEq(super.toString(), super["toString"]()); + assertEq(super.toString(), this.toString()); + } +} + +toStrOL.test(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropBasicChain.js b/js/src/tests/non262/class/superPropBasicChain.js new file mode 100644 index 0000000000..12bf84820e --- /dev/null +++ b/js/src/tests/non262/class/superPropBasicChain.js @@ -0,0 +1,11 @@ +var o = { + access() { + super.foo.bar; + } +}; + +// Delazify +assertThrowsInstanceOf(o.access, TypeError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropBasicGetter.js b/js/src/tests/non262/class/superPropBasicGetter.js new file mode 100644 index 0000000000..fe1f4eeee8 --- /dev/null +++ b/js/src/tests/non262/class/superPropBasicGetter.js @@ -0,0 +1,36 @@ +class base { + constructor() {} + + getValue() { + return this._prop; + } + + setValue(v) { + this._prop = v; + } +} + +class derived extends base { + constructor() { super(); } + + get a() { return super.getValue(); } + set a(v) { super.setValue(v); } + + get b() { return eval('super.getValue()'); } + set b(v) { eval('super.setValue(v);'); } + + test() { + this.a = 15; + assertEq(this.a, 15); + + assertEq(this.b, 15); + this.b = 30; + assertEq(this.b, 30); + } +} + +var derivedInstance = new derived(); +derivedInstance.test(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropBasicNew.js b/js/src/tests/non262/class/superPropBasicNew.js new file mode 100644 index 0000000000..e1ca9b4ae1 --- /dev/null +++ b/js/src/tests/non262/class/superPropBasicNew.js @@ -0,0 +1,17 @@ +class Base { + constructor() {} +} +class Mid extends Base { + constructor() { super(); } + f() { return new super.constructor(); } +} +class Derived extends Mid { + constructor() { super(); } +} + +let d = new Derived(); +var df = d.f(); +assertEq(df.constructor, Base); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropChains.js b/js/src/tests/non262/class/superPropChains.js new file mode 100644 index 0000000000..e8c0de05cd --- /dev/null +++ b/js/src/tests/non262/class/superPropChains.js @@ -0,0 +1,58 @@ +// First, let's test the trivial. A chain of three works. +class base { + constructor() { } + testChain() { + this.baseCalled = true; + } +} + +class middle extends base { + constructor() { super(); } + testChain() { + this.middleCalled = true; + super.testChain(); + } +} + +class derived extends middle { + constructor() { super(); } + testChain() { + super.testChain(); + assertEq(this.middleCalled, true); + assertEq(this.baseCalled, true); + } +} + +new derived().testChain(); + +// Super even chains in a wellbehaved fashion with normal functions. +function bootlegMiddle() { } +bootlegMiddle.prototype = middle.prototype; + +new class extends bootlegMiddle { + constructor() { super(); } + testChain() { + super.testChain(); + assertEq(this.middleCalled, true); + assertEq(this.baseCalled, true); + } + }().testChain(); + +// Now let's try out some "long" chains +base.prototype.x = "yeehaw"; + +let chain = class extends base { constructor() { super(); } } + +const CHAIN_LENGTH = 100; +for (let i = 0; i < CHAIN_LENGTH; i++) + chain = class extends chain { constructor() { super(); } } + +// Now we poke the chain +let inst = new chain(); +inst.testChain(); +assertEq(inst.baseCalled, true); + +assertEq(inst.x, "yeehaw"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropDVG.js b/js/src/tests/non262/class/superPropDVG.js new file mode 100644 index 0000000000..f187cb6ea1 --- /dev/null +++ b/js/src/tests/non262/class/superPropDVG.js @@ -0,0 +1,17 @@ +// Super property accesses should play nice with the pretty printer. +class testNonExistent { + constructor() { + super["prop"](); + } +} +// Should fold to super.prop +assertThrownErrorContains(() => new testNonExistent(), 'super.prop'); + +var ol = { testNonExistent() { super.prop(); } }; +assertThrownErrorContains(() => ol.testNonExistent(), "super.prop"); + +var olElem = { testNonExistent() { var prop = "prop"; super[prop](); } }; +assertThrownErrorContains(() => olElem.testNonExistent(), "super[prop]"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropDelete.js b/js/src/tests/non262/class/superPropDelete.js new file mode 100644 index 0000000000..e0b71613ea --- /dev/null +++ b/js/src/tests/non262/class/superPropDelete.js @@ -0,0 +1,64 @@ +// Make sure we get the proper side effects. +// |delete super.prop| and |delete super[expr]| throw universally. + +class base { + constructor() { } +} + +class derived extends base { + constructor() { super(); } + testDeleteProp() { delete super.prop; } + testDeleteElem() { + let sideEffect = 0; + assertThrowsInstanceOf(() => delete super[sideEffect = 1], ReferenceError); + assertEq(sideEffect, 1); + } + testDeleteElemPropValFirst() { + // The deletion error is a reference error, but by munging the prototype + // chain, we can force a typeerror from JSOP_SUPERBASE + delete super[Object.setPrototypeOf(derived.prototype, null)]; + } +} + +var d = new derived(); +assertThrowsInstanceOf(() => d.testDeleteProp(), ReferenceError); +d.testDeleteElem(); +assertThrowsInstanceOf(() => d.testDeleteElemPropValFirst(), TypeError); + +// |delete super.x| does not delete anything before throwing. +var thing1 = { + go() { delete super.toString; } +}; +let saved = Object.prototype.toString; +assertThrowsInstanceOf(() => thing1.go(), ReferenceError); +assertEq(Object.prototype.toString, saved); + +// |delete super.x| does not tell the prototype to delete anything, when it's a proxy. +var thing2 = { + go() { delete super.prop; } +}; +Object.setPrototypeOf(thing2, new Proxy({}, { + deleteProperty(x) { throw "FAIL"; } +})); +assertThrowsInstanceOf(() => thing2.go(), ReferenceError); + +class derivedTestDeleteProp extends base { + constructor() { + // The deletion error is a reference error, even after munging the prototype + // chain. + Object.setPrototypeOf(derivedTestDeleteProp.prototype, null); + + assertThrowsInstanceOf(() => delete super.prop, ReferenceError); + + super(); + + assertThrowsInstanceOf(() => delete super.prop, ReferenceError); + + return {}; + } +} + +new derivedTestDeleteProp(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropDerivedCalls.js b/js/src/tests/non262/class/superPropDerivedCalls.js new file mode 100644 index 0000000000..1a8a036057 --- /dev/null +++ b/js/src/tests/non262/class/superPropDerivedCalls.js @@ -0,0 +1,77 @@ +let derivedInstance; + +class base { + constructor() { } + method(a, b, c) { + assertEq(this, derivedInstance); + this.methodCalled = true; + assertEq(a, 1); + assertEq(b, 2); + assertEq(c, 3); + } + + get prop() { + assertEq(this, derivedInstance); + this.getterCalled = true; + return this._prop; + } + + set prop(val) { + assertEq(this, derivedInstance); + this.setterCalled = true; + this._prop = val; + } +} + +class derived extends base { + constructor() { super(); } + + // |super| actually checks the chain, not |this| + method() { throw "FAIL"; } + get prop() { throw "FAIL"; } + set prop(v) { throw "FAIL"; } + + test() { + this.reset(); + // While we're here. Let's check on super spread calls... + let spread = [1,2,3]; + super.method(...spread); + super.prop++; + this.asserts(); + } + + testInEval() { + this.reset(); + eval("super.method(1,2,3); super.prop++"); + this.asserts(); + } + + testInArrow() { + this.reset(); + (() => (super.method(1,2,3), super.prop++))(); + this.asserts(); + } + + reset() { + this._prop = 0; + this.methodCalled = false; + this.setterCalled = false; + this.getterCalled = false; + } + + asserts() { + assertEq(this.methodCalled, true); + assertEq(this.getterCalled, true); + assertEq(this.setterCalled, true); + assertEq(this._prop, 1); + } + +} + +derivedInstance = new derived(); +derivedInstance.test(); +derivedInstance.testInEval(); +derivedInstance.testInArrow(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropDestructuring.js b/js/src/tests/non262/class/superPropDestructuring.js new file mode 100644 index 0000000000..84d1bb517a --- /dev/null +++ b/js/src/tests/non262/class/superPropDestructuring.js @@ -0,0 +1,43 @@ +class base { + constructor() { } +} + +let seenValues; +Object.defineProperty(base.prototype, "minutes", + { + set(x) { + assertEq(x, 525600); + seenValues.push(x); + } + }); +Object.defineProperty(base.prototype, "intendent", + { + set(x) { + assertEq(x, "Fred"); + seenValues.push(x) + } + }); + +const testArr = [525600, "Fred"]; +class derived extends base { + constructor() { super(); } + prepForTest() { seenValues = []; } + testAsserts() { assertDeepEq(seenValues, testArr); } + testProps() { + this.prepForTest(); + [super.minutes, super.intendent] = testArr; + this.testAsserts(); + } + testElems() { + this.prepForTest(); + [super["minutes"], super["intendent"]] = testArr; + this.testAsserts(); + } +} + +let d = new derived(); +d.testProps(); +d.testElems(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropEvalInsideArrow.js b/js/src/tests/non262/class/superPropEvalInsideArrow.js new file mode 100644 index 0000000000..882f93ba0a --- /dev/null +++ b/js/src/tests/non262/class/superPropEvalInsideArrow.js @@ -0,0 +1,11 @@ +class foo { + constructor() { } + + method() { + return (() => eval('super.toString')); + } +} +assertEq(new foo().method()(), Object.prototype.toString); + +if (typeof reportCompare === "function") + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropEvalInsideNested.js b/js/src/tests/non262/class/superPropEvalInsideNested.js new file mode 100644 index 0000000000..04cdab0499 --- /dev/null +++ b/js/src/tests/non262/class/superPropEvalInsideNested.js @@ -0,0 +1,13 @@ +// It's invalid to eval super.prop inside a nested non-method, even if it +// appears inside a method definition +assertThrowsInstanceOf(() => +({ + method() { + (function () { + eval('super.toString'); + })(); + } +}).method(), SyntaxError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropFor.js b/js/src/tests/non262/class/superPropFor.js new file mode 100644 index 0000000000..23b222cb3e --- /dev/null +++ b/js/src/tests/non262/class/superPropFor.js @@ -0,0 +1,25 @@ +class testForIn { + constructor() { + let hits = 0; + for (super.prop in { prop1: 1, prop2: 2 }) + hits++; + assertEq(this.prop, "prop2"); + assertEq(hits, 2); + } +} + +new testForIn(); + + +({ + testForOf() { + let hits = 0; + for (super["prop"] of [1, 2]) + hits++; + assertEq(this.prop, 2); + assertEq(hits, 2); + } +}).testForOf(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropHeavyweightArrow.js b/js/src/tests/non262/class/superPropHeavyweightArrow.js new file mode 100644 index 0000000000..ce30690d53 --- /dev/null +++ b/js/src/tests/non262/class/superPropHeavyweightArrow.js @@ -0,0 +1,12 @@ +class foo { + constructor() { } + + method() { + return (() => (eval(''), super.toString)); + } +} + +assertEq(new foo().method()(), Object.prototype.toString); + +if (typeof reportCompare === "function") + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropHomeObject.js b/js/src/tests/non262/class/superPropHomeObject.js new file mode 100644 index 0000000000..b23c115dd5 --- /dev/null +++ b/js/src/tests/non262/class/superPropHomeObject.js @@ -0,0 +1,61 @@ +// This is super weird. A super property reference in the spec contains two +// things. The first is the object to do the lookup on, the super base. This +// should be unchanged, no matter what's going on: I can move the method to +// another object. I can pull it out as its own function. I can put it on my +// head and run around the front yard. No changes. The other half, the |this| +// for invoked calls, is the this at the time of referencing the property, which +// means it's gonna vary wildly as stuff gets moved around. + +class base { + constructor() { } + test(expectedThis) { assertEq(this, expectedThis); } +} + +class derived extends base { + constructor() { super(); } + test(expected) { super.test(expected); } + testArrow() { return (() => super.test(this)); } + ["testCPN"](expected) { super.test(expected); } +} + +let derivedInstance = new derived(); +derivedInstance.test(derivedInstance); +derivedInstance.testCPN(derivedInstance); + +let obj = { test: derivedInstance.test }; +obj.test(obj); + +// Classes are strict, so primitives are not boxed/turned into globals +let testSolo = derivedInstance.test; +testSolo(undefined); + +let anotherObject = { }; +derivedInstance.test.call(anotherObject, anotherObject); + +let strThis = "this is not an object!"; +derivedInstance.test.call(strThis, strThis); + +// You can take the arrow function out of the super, ... or something like that +let arrowTest = derivedInstance.testArrow(); +arrowTest(); + +// There's no magic "super script index" per code location. +class base1 { + constructor() { } + test() { return "llama"; } +} +class base2 { + constructor() { } + test() { return "alpaca"; } +} + +let animals = []; +for (let exprBase of [base1, base2]) + new class extends exprBase { + constructor() { super(); } + test() { animals.push(super["test"]()); } + }().test(); +assertDeepEq(animals, ["llama", "alpaca"]); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropIncDecElem.js b/js/src/tests/non262/class/superPropIncDecElem.js new file mode 100644 index 0000000000..a7b93eb371 --- /dev/null +++ b/js/src/tests/non262/class/superPropIncDecElem.js @@ -0,0 +1,24 @@ +// #1 +function base() { } + +base.prototype = { + test() { + --super[1]; + } +} + +var d = new base(); +d.test(); + +// #2 +class test2 { + test() { + super[1]++; + } +} + +var d = new test2(); +d.test() + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropLazyInnerFunction.js b/js/src/tests/non262/class/superPropLazyInnerFunction.js new file mode 100644 index 0000000000..d7a441f93b --- /dev/null +++ b/js/src/tests/non262/class/superPropLazyInnerFunction.js @@ -0,0 +1,19 @@ +testcase(); +function testcase() { + var tokenCodes = { + get try() { + try { + super.actual(); + } catch (e) {} + } + }; + var arr = [ + 'try', + ]; + for (var i = 0; i < arr.length; i++) { + if (tokenCodes[arr[i]] !== i) {}; + } +} + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropNoOverwriting.js b/js/src/tests/non262/class/superPropNoOverwriting.js new file mode 100644 index 0000000000..db44b509c5 --- /dev/null +++ b/js/src/tests/non262/class/superPropNoOverwriting.js @@ -0,0 +1,58 @@ +class X { + constructor() { + Object.defineProperty(this, "prop1", { + configurable: true, + writable: false, + value: 1 + }); + + Object.defineProperty(this, "prop2", { + configurable: true, + get: function() { return 15; } + }); + + Object.defineProperty(this, "prop3", { + configurable: true, + set: function(a) { } + }); + + Object.defineProperty(this, "prop4", { + configurable: true, + get: function() { return 20; }, + set: function(a) { } + }); + } + + f1() { + super.prop1 = 2; + } + + f2() { + super.prop2 = 3; + } + + f3() { + super.prop3 = 4; + } + + f4() { + super.prop4 = 5; + } +} + +var x = new X(); + +assertThrowsInstanceOf(() => x.f1(), TypeError); +assertEq(x.prop1, 1); + +assertThrowsInstanceOf(() => x.f2(), TypeError); +assertEq(x.prop2, 15); + +assertThrowsInstanceOf(() => x.f3(), TypeError); +assertEq(x.prop3, undefined); + +assertThrowsInstanceOf(() => x.f4(), TypeError); +assertEq(x.prop4, 20); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropOrdering.js b/js/src/tests/non262/class/superPropOrdering.js new file mode 100644 index 0000000000..1374d52c1c --- /dev/null +++ b/js/src/tests/non262/class/superPropOrdering.js @@ -0,0 +1,93 @@ +class base { + constructor() { } + method() { this.methodCalled++; } +} + +class derived extends base { + constructor() { super(); this.methodCalled = 0; } + + // Test orderings of various evaluations relative to the superbase + + // Unlike in regular element evaluation, the propVal is evaluated before + // checking the starting object ([[HomeObject]].[[Prototype]]) + testElem() { super[ruin()]; } + + // The starting object for looking up super.method is determined before + // ruin() is called. + testProp() { super.method(ruin()); } + + // The entire super.method property lookup has concluded before the args + // are evaluated + testPropCallDeleted() { super.method(()=>delete base.prototype.method); } + + // The starting object for looking up super["prop"] is determined before + // ruin() is called. + testElemAssign() { super["prop"] = ruin(); } + + // Test the normal assignment gotchas + testAssignElemPropValChange() { + let x = "prop1"; + super[x] = (()=>(x = "prop2", 0))(); + assertEq(this.prop1, 0); + assertEq(this.prop2, undefined); + } + + testAssignProp() { + Object.defineProperty(base.prototype, "piggy", + { + configurable: true, + set() { throw "WEE WEE WEE WEE"; } + }); + + // The property lookup is noted, but not actually evaluated, until the + // right hand side is. Please don't make the piggy cry. + super.piggy = (() => delete base.prototype.piggy)(); + } + testCompoundAssignProp() { + let getterCalled = false; + Object.defineProperty(base.prototype, "horse", + { + configurable: true, + get() { getterCalled = true; return "Of course"; }, + set() { throw "NO!"; } + }); + super.horse += (()=>(delete base.prototype.horse, ", of course!"))(); + assertEq(getterCalled, true); + + // So, is a horse a horse? + assertEq(this.horse, "Of course, of course!"); + } +} + +function ruin() { + Object.setPrototypeOf(derived.prototype, null); + return 5; +} + +function reset() { + Object.setPrototypeOf(derived.prototype, base.prototype); +} + +let instance = new derived(); +assertThrowsInstanceOf(() => instance.testElem(), TypeError); +reset(); + +instance.testProp(); +assertEq(instance.methodCalled, 1); +reset(); + +instance.testPropCallDeleted(); +assertEq(instance.methodCalled, 2); + +instance.testElemAssign(); +assertEq(instance.prop, 5); +reset(); + +instance.testAssignElemPropValChange(); + +instance.testAssignProp(); + +instance.testCompoundAssignProp(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropProtoChanges.js b/js/src/tests/non262/class/superPropProtoChanges.js new file mode 100644 index 0000000000..d16ca16659 --- /dev/null +++ b/js/src/tests/non262/class/superPropProtoChanges.js @@ -0,0 +1,22 @@ +class base { + constructor() { } + test() { + return false; + } +} + +let standin = { test() { return true; } }; + +class derived extends base { + constructor() { super(); } + test() { + assertEq(super.test(), false); + Object.setPrototypeOf(derived.prototype, standin); + assertEq(super["test"](), true); + } +} + +new derived().test(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropProxies.js b/js/src/tests/non262/class/superPropProxies.js new file mode 100644 index 0000000000..3ec43b279c --- /dev/null +++ b/js/src/tests/non262/class/superPropProxies.js @@ -0,0 +1,83 @@ +class base { + constructor() { } +} + +let midStaticHandler = { }; + +// We shouldn't use the |this.called| strategy here, since we have proxies +// snooping property accesses. +let getterCalled, setterCalled; + +class mid extends new Proxy(base, midStaticHandler) { + constructor() { super(); } + testSuperInProxy() { + super.prop = "looking"; + assertEq(setterCalled, true); + assertEq(super.prop, "found"); + assertEq(getterCalled, true); + } +} + +class child extends mid { + constructor() { super(); } + static testStaticLookups() { + // This funtion is called more than once. + this.called = false; + super.prop; + assertEq(this.called, true); + } +} + +let midInstance = new mid(); + +// Make sure proxies are searched properly on the prototype chain +let baseHandler = { + get(target, p, receiver) { + assertEq(receiver, midInstance); + getterCalled = true; + return "found"; + }, + + set(t,p,val,receiver) { + assertEq(receiver, midInstance); + assertEq(val, "looking"); + setterCalled = true; + return true; + } +} +Object.setPrototypeOf(base.prototype, new Proxy(Object.prototype, baseHandler)); + +// make sure subclasses are not searched on static or super lookups. +let childHandler = { + get() { throw "NO!"; }, + set() { throw "NO!"; } +} +Object.setPrototypeOf(child.prototype, new Proxy(mid.prototype, childHandler)); + +midInstance.testSuperInProxy(); + +// Don't do this earlier to avoid the lookup of .prototype during class creation +function midGet(target, p, receiver) { + assertEq(receiver, child); + receiver.called = true; +} +midStaticHandler.get = midGet; + +child.testStaticLookups(); + +// Hey does super work in a proxy? +assertEq(new Proxy(({ method() { return super.hasOwnProperty("method"); } }), {}).method(), true); + +// What about a CCW? +var g = newGlobal(); +var wrappedSuper = g.eval("({ method() { return super.hasOwnProperty('method'); } })"); +assertEq(wrappedSuper.method(), true); + +// With a CCW on the proto chain? +var wrappedBase = g.eval("({ method() { return this.__secretProp__; } })"); +var unwrappedDerived = { __secretProp__: 42, method() { return super.method(); } } +Object.setPrototypeOf(unwrappedDerived, wrappedBase); +assertEq(unwrappedDerived.method(), 42); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropSkips.js b/js/src/tests/non262/class/superPropSkips.js new file mode 100644 index 0000000000..c9587c72f1 --- /dev/null +++ b/js/src/tests/non262/class/superPropSkips.js @@ -0,0 +1,45 @@ +// Ensure that super lookups and sets skip over properties on the |this| object. +// That is, super lookups start with the superclass, not the current class. + +// The whole point: an empty superclass +class base { + constructor() { } +} + +class derived extends base { + constructor() { super(); this.prop = "flamingo"; } + + toString() { throw "No!"; } + + testSkipGet() { + assertEq(super.prop, undefined); + } + + testSkipDerivedOverrides() { + assertEq(super["toString"](), Object.prototype.toString.call(this)); + } + + testSkipSet() { + // since there's no prop on the chain, we should set the data property + // on the receiver, |this| + super.prop = "rat"; + assertEq(this.prop, "rat"); + + // Since the receiver is the instance, we can overwrite inherited + // properties of the instance, even non-writable ones, as they could be + // skipped in the super lookup. + assertEq(this.nonWritableProp, "pony"); + super.nonWritableProp = "bear"; + assertEq(this.nonWritableProp, "bear"); + } +} + +Object.defineProperty(derived.prototype, "nonWritableProp", { writable: false, value: "pony" }); + +let instance = new derived(); +instance.testSkipGet(); +instance.testSkipDerivedOverrides(); +instance.testSkipSet(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropStatics.js b/js/src/tests/non262/class/superPropStatics.js new file mode 100644 index 0000000000..32624b5d28 --- /dev/null +++ b/js/src/tests/non262/class/superPropStatics.js @@ -0,0 +1,34 @@ +class base { + constructor() { } + static found() { + this.foundCalled = true; + } + static get accessor() { + assertEq(this, derived); + return 45; + } + notFound() { } +} + +class derived extends base { + constructor() { } + + static found() { throw "NO!"; } + static get accessor() { throw "NO!"; } + + static test() { + assertEq(super["notFound"], undefined); + super.found(); + + // foundCalled is set on |derived| specifically. + let calledDesc = Object.getOwnPropertyDescriptor(derived, "foundCalled"); + assertEq(calledDesc.value, true); + + assertEq(super.accessor, 45); + } +} + +derived.test(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superPropStrictAssign.js b/js/src/tests/non262/class/superPropStrictAssign.js new file mode 100644 index 0000000000..4b5444a3cf --- /dev/null +++ b/js/src/tests/non262/class/superPropStrictAssign.js @@ -0,0 +1,23 @@ +// While |super| is common in classes, it also works in object litterals, +// where there is no guarantee of strict mode. Check that we do not somehow +// get strict mode semantics when they were not called for + +// |undefined|, writable: false +Object.defineProperty(Object.prototype, "prop", { writable: false }); + +class strictAssignmentTest { + constructor() { + // Strict mode. Throws. + super.prop = 14; + } +} + +assertThrowsInstanceOf(()=>new strictAssignmentTest(), TypeError); + +// Non-strict. Silent failure. +({ test() { super.prop = 14; } }).test(); + +assertEq(Object.prototype.prop, undefined); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/class/superThisStrictNoBoxing.js b/js/src/tests/non262/class/superThisStrictNoBoxing.js new file mode 100644 index 0000000000..f479ac036a --- /dev/null +++ b/js/src/tests/non262/class/superThisStrictNoBoxing.js @@ -0,0 +1,31 @@ +class C { + get prop_this() { return this; } +} + +var g_prop_this = 'prop_this'; +class D extends C { + super_prop() { return super.prop_this; } + super_elem() { return super[g_prop_this]; } +} + +var barsym = Symbol("bar"); + +// Test that primitive |this| values are not boxed, and undefined/null are not +// globals for super.property. +assertEq(new D().super_prop.call(3), 3); +assertEq(new D().super_prop.call("foo"), "foo"); +assertEq(new D().super_prop.call(true), true); +assertEq(new D().super_prop.call(barsym), barsym); +assertEq(new D().super_prop.call(null), null); +assertEq(new D().super_prop.call(undefined), undefined); + +// Ditto for super[elem] +assertEq(new D().super_elem.call(3), 3); +assertEq(new D().super_elem.call("foo"), "foo"); +assertEq(new D().super_elem.call(true), true); +assertEq(new D().super_elem.call(barsym), barsym); +assertEq(new D().super_elem.call(null), null); +assertEq(new D().super_elem.call(undefined), undefined); + +if (typeof reportCompare === 'function') + reportCompare(0, 0); diff --git a/js/src/tests/non262/class/uninitializedThisError.js b/js/src/tests/non262/class/uninitializedThisError.js new file mode 100644 index 0000000000..658ef8002c --- /dev/null +++ b/js/src/tests/non262/class/uninitializedThisError.js @@ -0,0 +1,53 @@ +function checkErr(f) { + var expected = "must call super constructor before using 'this' in derived class constructor"; + try { + f(); + assertEq(0, 1); + } catch (e) { + assertEq(e.name, "ReferenceError"); + assertEq(e.message, expected); + } +} +class TestNormal extends class {} { + constructor() { this; } +} +checkErr(() => new TestNormal()); + +class TestEval extends class {} { + constructor() { eval("this") } +} +checkErr(() => new TestEval()); + +class TestNestedEval extends class {} { + constructor() { eval("eval('this')") } +} +checkErr(() => new TestNestedEval()); + +checkErr(() => { + new class extends class {} { + constructor() { eval("this") } + } +}); + +class TestArrow extends class {} { + constructor() { (() => this)(); } +} +checkErr(() => new TestArrow()); + +class TestArrowEval extends class {} { + constructor() { (() => eval("this"))(); } +} +checkErr(() => new TestArrowEval()); + +class TestEvalArrow extends class {} { + constructor() { eval("(() => this)()"); } +} +checkErr(() => new TestEvalArrow()); + +class TestTypeOf extends class {} { + constructor() { eval("typeof this"); } +} +checkErr(() => new TestTypeOf()); + +if (typeof reportCompare === 'function') + reportCompare(0, 0); |