Skip to content

Commit

Permalink
Bug 1566143 - Add tests for Optional Chaining r=arai
Browse files Browse the repository at this point in the history
Differential Revision: https://phabricator.services.mozilla.com/D59870

--HG--
extra : moz-landing-system : lando
  • Loading branch information
codehag committed Jan 20, 2020
1 parent 311ade1 commit 364f826
Show file tree
Hide file tree
Showing 46 changed files with 1,031 additions and 39 deletions.
15 changes: 7 additions & 8 deletions js/src/tests/non262/expressions/nullish-coalescing.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,13 @@ shouldNotThrow('0 || 1 && 2 | 3 ^ 4 & 5 == 6 != 7 === 8 !== 9 < 0 > 1 <= 2 >= 3
shouldThrowSyntaxError('0 || 1 && 2 | 3 ^ 4 & 5 == 6 != 7 === 8 !== 9 < 0 > 1 <= 2 >= 3 << 4 >> 5 >>> 6 + 7 - 8 * 9 / 0 % 1 ** 2 ?? 3');
shouldThrowSyntaxError('3 ?? 2 ** 1 % 0 / 9 * 8 - 7 + 6 >>> 5 >> 4 << 3 >= 2 <= 1 > 0 < 9 !== 8 === 7 != 6 == 5 & 4 ^ 3 | 2 && 1 || 0');

// Ignore these tests for now as they use optional chaining
// shouldBe(null?.x ?? 3, 3);
// shouldBe(({})?.x ?? 3, 3);
// shouldBe(({ x: 0 })?.x ?? 3, 0);
// shouldBe(null?.() ?? 3, 3);
// shouldBe((() => 0)?.() ?? 3, 0);
// shouldBe(({ x: 0 })?.[null?.a ?? 'x'] ?? 3, 0);
// shouldBe((() => 0)?.(null?.a ?? 'x') ?? 3, 0);
shouldBe(null?.x ?? 3, 3);
shouldBe(({})?.x ?? 3, 3);
shouldBe(({ x: 0 })?.x ?? 3, 0);
shouldBe(null?.() ?? 3, 3);
shouldBe((() => 0)?.() ?? 3, 0);
shouldBe(({ x: 0 })?.[null?.a ?? 'x'] ?? 3, 0);
shouldBe((() => 0)?.(null?.a ?? 'x') ?? 3, 0);

if (typeof reportCompare === "function")
reportCompare(true, true);
Expand Down
226 changes: 226 additions & 0 deletions js/src/tests/non262/expressions/optional-chain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
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!');

if (!error.message.startsWith(messagePrefix))
throw new Error(`TypeError has wrong message!, expected ${messagePrefix} but got ${error.message}`);
}
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");
1 change: 1 addition & 0 deletions js/src/tests/test262-update.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"Intl.DisplayNames",
"Intl.Segmenter",
"optional-chaining",
"WeakRef",
"top-level-await",
])
FEATURE_CHECK_NEEDED = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// |reftest| skip error:SyntaxError -- optional-chaining is not supported
// |reftest| error:SyntaxError
// This file was procedurally generated from the following sources:
// - src/dstr-assignment/array-elem-nested-memberexpr-optchain-prop-ref-init.case
// - src/dstr-assignment/syntax/assignment-expr.template
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// |reftest| error:SyntaxError
// This file was procedurally generated from the following sources:
// - src/dstr-assignment/array-elem-nested-memberexpr-optchain-prop-ref.case
// - src/dstr-assignment/syntax/assignment-expr.template
/*---
description: It is a Syntax Error if LeftHandSideExpression of an DestructuringAssignmentTarget is neither an ObjectLiteral nor an ArrayLiteral and AssignmentTargetType(LeftHandSideExpression) is not simple Using Object (MemberExpression OptionalChain .IdentifierName) (AssignmentExpression)
esid: sec-variable-statement-runtime-semantics-evaluation
features: [optional-chaining, destructuring-binding]
flags: [generated]
negative:
phase: parse
type: SyntaxError
info: |
VariableDeclaration : BindingPattern Initializer
1. Let rhs be the result of evaluating Initializer.
2. Let rval be GetValue(rhs).
3. ReturnIfAbrupt(rval).
4. Return the result of performing BindingInitialization for
BindingPattern passing rval and undefined as arguments.
Syntax
AssignmentElement : DestructuringAssignmentTarget Initializer_opt
DestructuringAssignmentTarget : LeftHandSideExpression
Static Semantics: Early Errors
OptionalExpression:
MemberExpression OptionalChain
CallExpression OptionalChain
OptionalExpression OptionalChain
OptionalChain:
?. [ Expression ]
?. IdentifierName
?. Arguments
?. TemplateLiteral
OptionalChain [ Expression ]
OptionalChain .IdentifierName
OptionalChain Arguments
OptionalChain TemplateLiteral
DestructuringAssignmentTarget : LeftHandSideExpression
- It is a Syntax Error if LeftHandSideExpression is neither an ObjectLiteral nor an ArrayLiteral and IsValidSimpleAssignmentTarget(LeftHandSideExpression) is not true.
Static Semantics: IsValidSimpleAssignmentTarget
LeftHandSideExpression : OptionalExpression
1. Return false.
---*/
$DONOTEVALUATE();
var x = {};

0, [x?.y] = [23];
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// |reftest| skip error:SyntaxError -- optional-chaining is not supported
// |reftest| error:SyntaxError
// This file was procedurally generated from the following sources:
// - src/dstr-assignment/array-elem-put-obj-literal-optchain-prop-ref-init.case
// - src/dstr-assignment/syntax/assignment-expr.template
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// |reftest| error:SyntaxError
// This file was procedurally generated from the following sources:
// - src/dstr-assignment/array-elem-put-obj-literal-optchain-prop-ref.case
// - src/dstr-assignment/syntax/assignment-expr.template
/*---
description: It is a Syntax Error if LeftHandSideExpression of an DestructuringAssignmentTarget is neither an ObjectLiteral nor an ArrayLiteral and AssignmentTargetType(LeftHandSideExpression) is not simple Using Object (AssignmentExpression)
esid: sec-variable-statement-runtime-semantics-evaluation
features: [optional-chaining, destructuring-binding]
flags: [generated]
negative:
phase: parse
type: SyntaxError
info: |
VariableDeclaration : BindingPattern Initializer
1. Let rhs be the result of evaluating Initializer.
2. Let rval be GetValue(rhs).
3. ReturnIfAbrupt(rval).
4. Return the result of performing BindingInitialization for
BindingPattern passing rval and undefined as arguments.
Syntax
AssignmentElement : DestructuringAssignmentTarget Initializer_opt
DestructuringAssignmentTarget : LeftHandSideExpression
Static Semantics: Early Errors
OptionalExpression:
MemberExpression OptionalChain
CallExpression OptionalChain
OptionalExpression OptionalChain
OptionalChain:
?. [ Expression ]
?. IdentifierName
?. Arguments
?. TemplateLiteral
OptionalChain [ Expression ]
OptionalChain .IdentifierName
OptionalChain Arguments
OptionalChain TemplateLiteral
DestructuringAssignmentTarget : LeftHandSideExpression
- It is a Syntax Error if LeftHandSideExpression is neither an ObjectLiteral nor an ArrayLiteral and IsValidSimpleAssignmentTarget(LeftHandSideExpression) is not true.
Static Semantics: IsValidSimpleAssignmentTarget
LeftHandSideExpression : OptionalExpression
1. Return false.
---*/
$DONOTEVALUATE();

0, [{
set y(val) {
throw new Test262Error('The property should not be accessed.');
}
}?.y] = [23];
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// |reftest| skip error:SyntaxError -- optional-chaining is not supported
// |reftest| error:SyntaxError
// This file was procedurally generated from the following sources:
// - src/dstr-assignment/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.case
// - src/dstr-assignment/syntax/assignment-expr.template
Expand Down
Loading

0 comments on commit 364f826

Please sign in to comment.