diff options
Diffstat (limited to 'js/src/tests/non262/expressions')
7 files changed, 365 insertions, 0 deletions
diff --git a/js/src/tests/non262/expressions/browser.js b/js/src/tests/non262/expressions/browser.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/js/src/tests/non262/expressions/browser.js diff --git a/js/src/tests/non262/expressions/optional-chain-class-heritage.js b/js/src/tests/non262/expressions/optional-chain-class-heritage.js new file mode 100644 index 0000000000..14a99c6392 --- /dev/null +++ b/js/src/tests/non262/expressions/optional-chain-class-heritage.js @@ -0,0 +1,10 @@ +// Optional expression can be part of a class heritage expression. + +var a = {b: null}; + +class C extends a?.b {} + +assertEq(Object.getPrototypeOf(C.prototype), null); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/expressions/optional-chain-first-expression.js b/js/src/tests/non262/expressions/optional-chain-first-expression.js new file mode 100644 index 0000000000..89912aec4a --- /dev/null +++ b/js/src/tests/non262/expressions/optional-chain-first-expression.js @@ -0,0 +1,92 @@ +// Verify bytecode emitter accepts all valid optional chain first expressions. + +const expressions = [ + // https://tc39.es/ecma262/#sec-primary-expression + "this", + "ident", + "null", + "true", + "false", + "123", + "123n", + "'str'", + "[]", + "{}", + "function(){}", + "class{}", + "function*(){}", + "async function(){}", + "async function*(){}", + "/a/", + "`str`", + "(a + b)", + + // https://tc39.es/ecma262/#sec-left-hand-side-expressions + "a[b]", + "a.b", + "a``", + "super[a]", + "super.a", + "new.target", + "import.meta", + "new C()", + "new C", + "f()", + "super()", + "a?.b", + "a?.[b]", + "a?.()", + "a?.``", +]; + +function tryParse(s, f = Function) { + try { f(s); } catch {} +} + +function tryRun(s, f = Function) { + try { f(s)(); } catch {} +} + +for (let expr of expressions) { + // Evaluate in an expression context. + tryRun(`void (${expr}?.());`); + tryRun(`void (${expr}?.p());`); + + // Also try parenthesized. + tryRun(`void ((${expr})?.());`); + tryRun(`void ((${expr})?.p());`); +} + +function inClassConstructor(s) { + return `class C { constructor() { ${s} } }`; +} + +for (let expr of ["super[a]", "super.a", "super()"]) { + // Evaluate in an expression context. + tryRun(inClassConstructor(`void (${expr}?.());`)); + tryRun(inClassConstructor(`void (${expr}?.p());`)); + + // Also try parenthesized. + tryRun(inClassConstructor(`void ((${expr})?.());`)); + tryRun(inClassConstructor(`void ((${expr})?.p());`)); +} + +if (typeof parseModule === "function") { + const expressions = [ + "import.meta", + "import('')", + ]; + + for (let expr of expressions) { + // Evaluate in an expression context. + tryParse(`void (${expr}?.());`, parseModule); + tryParse(`void (${expr}?.p());`, parseModule); + + // Also try parenthesized. + tryParse(`void ((${expr})?.());`, parseModule); + tryParse(`void ((${expr})?.p());`, parseModule); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/expressions/optional-chain-super-elem.js b/js/src/tests/non262/expressions/optional-chain-super-elem.js new file mode 100644 index 0000000000..3b912a9ada --- /dev/null +++ b/js/src/tests/non262/expressions/optional-chain-super-elem.js @@ -0,0 +1,12 @@ +// Don't assert. + +var obj = { + m() { + super[0]?.a + } +}; + +obj.m(); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/expressions/optional-chain-tdz.js b/js/src/tests/non262/expressions/optional-chain-tdz.js new file mode 100644 index 0000000000..e12d0fb860 --- /dev/null +++ b/js/src/tests/non262/expressions/optional-chain-tdz.js @@ -0,0 +1,28 @@ +// Test TDZ for optional chaining. + +// TDZ for lexical |let| bindings with optional chaining. +{ + assertThrowsInstanceOf(() => { + const Null = null; + Null?.[b]; + b = 0; + let b; + }, ReferenceError); + + assertThrowsInstanceOf(() => { + const Null = null; + Null?.[b](); + b = 0; + let b; + }, ReferenceError); + + assertThrowsInstanceOf(() => { + const Null = null; + delete Null?.[b]; + b = 0; + let b; + }, ReferenceError); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/expressions/optional-chain.js b/js/src/tests/non262/expressions/optional-chain.js new file mode 100644 index 0000000000..04e0909359 --- /dev/null +++ b/js/src/tests/non262/expressions/optional-chain.js @@ -0,0 +1,223 @@ +var BUGNUMBER = 1566143; +var summary = "Implement the Optional Chain operator (?.) proposal"; + +print(BUGNUMBER + ": " + summary); + +// These tests are originally from webkit. +// webkit specifics have been removed and error messages have been updated. +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`expected ${expected} but got ${actual}`); +} + +function shouldThrowSyntaxError(script) { + let error; + try { + eval(script); + } catch (e) { + error = e; + } + + if (!(error instanceof SyntaxError)) + throw new Error('Expected SyntaxError!'); +} + +function shouldNotThrowSyntaxError(script) { + let error; + try { + eval(script); + } catch (e) { + error = e; + } + + if ((error instanceof SyntaxError)) + throw new Error('Unxpected SyntaxError!'); +} + +function shouldThrowTypeError(func, messagePrefix) { + let error; + try { + func(); + } catch (e) { + error = e; + } + + if (!(error instanceof TypeError)) + throw new Error('Expected TypeError!'); +} +function testBasicSuccessCases() { + shouldBe(undefined?.valueOf(), undefined); + shouldBe(null?.valueOf(), undefined); + shouldBe(true?.valueOf(), true); + shouldBe(false?.valueOf(), false); + shouldBe(0?.valueOf(), 0); + shouldBe(1?.valueOf(), 1); + shouldBe(''?.valueOf(), ''); + shouldBe('hi'?.valueOf(), 'hi'); + shouldBe(({})?.constructor, Object); + shouldBe(({ x: 'hi' })?.x, 'hi'); + shouldBe([]?.length, 0); + shouldBe(['hi']?.length, 1); + + shouldBe(undefined?.['valueOf'](), undefined); + shouldBe(null?.['valueOf'](), undefined); + shouldBe(true?.['valueOf'](), true); + shouldBe(false?.['valueOf'](), false); + shouldBe(0?.['valueOf'](), 0); + shouldBe(1?.['valueOf'](), 1); + shouldBe(''?.['valueOf'](), ''); + shouldBe('hi'?.['valueOf'](), 'hi'); + shouldBe(({})?.['constructor'], Object); + shouldBe(({ x: 'hi' })?.['x'], 'hi'); + shouldBe([]?.['length'], 0); + shouldBe(['hi']?.[0], 'hi'); + + shouldBe(undefined?.(), undefined); + shouldBe(null?.(), undefined); + shouldBe((() => 3)?.(), 3); +} + +function testBasicFailureCases() { + shouldThrowTypeError(() => true?.(), 'true is not a function'); + shouldThrowTypeError(() => false?.(), 'false is not a function'); + shouldThrowTypeError(() => 0?.(), '0 is not a function'); + shouldThrowTypeError(() => 1?.(), '1 is not a function'); + shouldThrowTypeError(() => ''?.(), '"" is not a function'); + shouldThrowTypeError(() => 'hi'?.(), '"hi" is not a function'); + shouldThrowTypeError(() => ({})?.(), '({}) is not a function'); + shouldThrowTypeError(() => ({ x: 'hi' })?.(), '({x:"hi"}) is not a function'); + shouldThrowTypeError(() => []?.(), '[] is not a function'); + shouldThrowTypeError(() => ['hi']?.(), '[...] is not a function'); +} + +testBasicSuccessCases(); + +testBasicFailureCases(); + +shouldThrowTypeError(() => ({})?.i(), '(intermediate value).i is not a function'); +shouldBe(({}).i?.(), undefined); +shouldBe(({})?.i?.(), undefined); +shouldThrowTypeError(() => ({})?.['i'](), '(intermediate value)["i"] is not a function'); +shouldBe(({})['i']?.(), undefined); +shouldBe(({})?.['i']?.(), undefined); + +shouldThrowTypeError(() => ({})?.a['b'], 'can\'t access property "b", (intermediate value).a is undefined'); +shouldBe(({})?.a?.['b'], undefined); +shouldBe(null?.a['b']().c, undefined); +shouldThrowTypeError(() => ({})?.['a'].b, 'can\'t access property "b", (intermediate value)["a"] is undefined'); +shouldBe(({})?.['a']?.b, undefined); +shouldBe(null?.['a'].b()['c'], undefined); +shouldBe(null?.()().a['b'], undefined); + +const o0 = { a: { b() { return this._b.bind(this); }, _b() { return this.__b; }, __b: { c: 42 } } }; +shouldBe(o0?.a?.['b']?.()?.()?.c, 42); +shouldBe(o0?.i?.['j']?.()?.()?.k, undefined); +shouldBe((o0.a?._b)?.().c, 42); +shouldBe((o0.a?._b)().c, 42); +shouldBe((o0.a?.b?.())?.().c, 42); +shouldBe((o0.a?.['b']?.())?.().c, 42); + +shouldBe(({ undefined: 3 })?.[null?.a], 3); +shouldBe((() => 3)?.(null?.a), 3); + +const o1 = { count: 0, get x() { this.count++; return () => {}; } }; +o1.x?.y; +shouldBe(o1.count, 1); +o1.x?.['y']; +shouldBe(o1.count, 2); +o1.x?.(); +shouldBe(o1.count, 3); +null?.(o1.x); +shouldBe(o1.count, 3); + +shouldBe(delete undefined?.foo, true); +shouldBe(delete null?.foo, true); +shouldBe(delete undefined?.['foo'], true); +shouldBe(delete null?.['foo'], true); +shouldBe(delete undefined?.(), true); +shouldBe(delete null?.(), true); +shouldBe(delete ({}).a?.b?.b, true); +shouldBe(delete ({a : {b: undefined}}).a?.b?.b, true); +shouldBe(delete ({a : {b: undefined}}).a?.["b"]?.["b"], true); + +const o2 = { x: 0, y: 0, z() {} }; +shouldBe(delete o2?.x, true); +shouldBe(o2.x, undefined); +shouldBe(o2.y, 0); +shouldBe(delete o2?.x, true); +shouldBe(delete o2?.['y'], true); +shouldBe(o2.y, undefined); +shouldBe(delete o2?.['y'], true); +shouldBe(delete o2.z?.(), true); + +function greet(name) { return `hey, ${name}${this.suffix ?? '.'}`; } +shouldBe(eval?.('greet("world")'), 'hey, world.'); +shouldBe(greet?.call({ suffix: '!' }, 'world'), 'hey, world!'); +shouldBe(greet.call?.({ suffix: '!' }, 'world'), 'hey, world!'); +shouldBe(null?.call({ suffix: '!' }, 'world'), undefined); +shouldBe(({}).call?.({ suffix: '!' }, 'world'), undefined); +shouldBe(greet?.apply({ suffix: '?' }, ['world']), 'hey, world?'); +shouldBe(greet.apply?.({ suffix: '?' }, ['world']), 'hey, world?'); +shouldBe(null?.apply({ suffix: '?' }, ['world']), undefined); +shouldBe(({}).apply?.({ suffix: '?' }, ['world']), undefined); +shouldThrowSyntaxError('class C {} class D extends C { foo() { return super?.bar; } }'); +shouldThrowSyntaxError('class C {} class D extends C { foo() { return super?.["bar"]; } }'); +shouldThrowSyntaxError('class C {} class D extends C { constructor() { super?.(); } }'); +shouldThrowSyntaxError('const o = { C: class {} }; new o?.C();') +shouldThrowSyntaxError('const o = { C: class {} }; new o?.["C"]();') +shouldThrowSyntaxError('class C {} new C?.();') +shouldThrowSyntaxError('function foo() { new?.target; }'); +shouldThrowSyntaxError('function tag() {} tag?.``;'); +shouldThrowSyntaxError('const o = { tag() {} }; o?.tag``;'); + +// NOT an optional chain +shouldBe(false?.4:5, 5); + +function testSideEffectCountFunction() { + let count = 0; + let a = { + b: { + c: { + d: () => { + count++; + return a; + } + } + } + } + + a.b.c.d?.()?.b?.c?.d + + shouldBe(count, 1); +} + +function testSideEffectCountGetters() { + let count = 0; + let a = { + get b() { + count++; + return { c: {} }; + } + } + + a.b?.c?.d; + shouldBe(count, 1); + a.b?.c?.d; + shouldBe(count, 2); +} + +testSideEffectCountFunction(); +testSideEffectCountGetters(); + +// stress test SM +shouldBe(({a : {b: undefined}}).a.b?.()()(), undefined); +shouldBe(({a : {b: undefined}}).a.b?.()?.()(), undefined); +shouldBe(({a : {b: () => undefined}}).a.b?.()?.(), undefined); +shouldThrowTypeError(() => delete ({a : {b: undefined}}).a?.b.b.c, 'can\'t access property "b", (intermediate value).a.b is undefined'); +shouldBe(delete ({a : {b: undefined}}).a?.["b"]?.["b"], true); +shouldThrowTypeError(() => (({a : {b: () => undefined}}).a.b?.())(), 'undefined is not a function'); + +if (typeof reportCompare === "function") + reportCompare(true, true); + +print("Tests complete"); diff --git a/js/src/tests/non262/expressions/shell.js b/js/src/tests/non262/expressions/shell.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/js/src/tests/non262/expressions/shell.js |