From 4dad98d63c279b989fdb48006fbd1db8ee27bc7f Mon Sep 17 00:00:00 2001 From: Maximilian Goldstein Date: Fri, 9 Apr 2021 15:03:20 +0200 Subject: Add optional chaining tests Change-Id: Ia87313a2756dfbc3d90128ac77cd4dc6c8c137df Reviewed-by: Ulf Hermann (cherry picked from commit 7d1cf05c547a636495b2731c4fc2842a01849064) Reviewed-by: Fabian Kosmale --- .../call-expression-super-no-base.js | 23 +++++ .../optional-chaining/call-expression.js | 75 +++++++++++++++ ...rs-tail-position-null-op-template-string-esi.js | 25 +++++ ...errors-tail-position-null-op-template-string.js | 22 +++++ ...l-position-null-optchain-template-string-esi.js | 25 +++++ ...-tail-position-null-optchain-template-string.js | 22 +++++ ...-errors-tail-position-op-template-string-esi.js | 27 ++++++ ...arly-errors-tail-position-op-template-string.js | 24 +++++ ...s-tail-position-optchain-template-string-esi.js | 27 ++++++ ...rrors-tail-position-optchain-template-string.js | 24 +++++ .../optional-chaining/eval-optional-call.js | 39 ++++++++ .../optional-chaining/iteration-statement-do.js | 18 ++++ .../iteration-statement-for-await-of.js | 35 +++++++ .../iteration-statement-for-in.js | 22 +++++ .../iteration-statement-for-of-type-error.js | 28 ++++++ .../optional-chaining/iteration-statement-for.js | 43 +++++++++ .../optional-chaining/iteration-statement-while.js | 18 ++++ .../member-expression-async-identifier.js | 32 +++++++ .../member-expression-async-literal.js | 19 ++++ .../member-expression-async-this.js | 20 ++++ .../optional-chaining/member-expression.js | 104 +++++++++++++++++++++ .../optional-chaining/new-target-optional-call.js | 30 ++++++ .../optional-call-preserves-this.js | 27 ++++++ ...l-chain-async-optional-chain-square-brackets.js | 28 ++++++ .../optional-chain-async-square-brackets.js | 24 +++++ ...ptional-chain-expression-optional-expression.js | 20 ++++ .../optional-chain-prod-arguments.js | 19 ++++ .../optional-chain-prod-expression.js | 42 +++++++++ .../optional-chain-prod-identifiername.js | 38 ++++++++ .../optional-chaining/optional-chain.js | 50 ++++++++++ .../optional-chaining/optional-expression.js | 27 ++++++ .../punctuator-decimal-lookahead.js | 15 +++ .../runtime-semantics-evaluation.js | 18 ++++ .../optional-chaining/short-circuiting.js | 22 +++++ .../static-semantics-simple-assignment.js | 23 +++++ .../super-property-optional-call.js | 30 ++++++ .../optional-chaining/update-expression-postfix.js | 23 +++++ .../optional-chaining/update-expression-prefix.js | 23 +++++ 38 files changed, 1131 insertions(+) create mode 100644 test/language/expressions/optional-chaining/call-expression-super-no-base.js create mode 100644 test/language/expressions/optional-chaining/call-expression.js create mode 100644 test/language/expressions/optional-chaining/early-errors-tail-position-null-op-template-string-esi.js create mode 100644 test/language/expressions/optional-chaining/early-errors-tail-position-null-op-template-string.js create mode 100644 test/language/expressions/optional-chaining/early-errors-tail-position-null-optchain-template-string-esi.js create mode 100644 test/language/expressions/optional-chaining/early-errors-tail-position-null-optchain-template-string.js create mode 100644 test/language/expressions/optional-chaining/early-errors-tail-position-op-template-string-esi.js create mode 100644 test/language/expressions/optional-chaining/early-errors-tail-position-op-template-string.js create mode 100644 test/language/expressions/optional-chaining/early-errors-tail-position-optchain-template-string-esi.js create mode 100644 test/language/expressions/optional-chaining/early-errors-tail-position-optchain-template-string.js create mode 100644 test/language/expressions/optional-chaining/eval-optional-call.js create mode 100644 test/language/expressions/optional-chaining/iteration-statement-do.js create mode 100644 test/language/expressions/optional-chaining/iteration-statement-for-await-of.js create mode 100644 test/language/expressions/optional-chaining/iteration-statement-for-in.js create mode 100644 test/language/expressions/optional-chaining/iteration-statement-for-of-type-error.js create mode 100644 test/language/expressions/optional-chaining/iteration-statement-for.js create mode 100644 test/language/expressions/optional-chaining/iteration-statement-while.js create mode 100644 test/language/expressions/optional-chaining/member-expression-async-identifier.js create mode 100644 test/language/expressions/optional-chaining/member-expression-async-literal.js create mode 100644 test/language/expressions/optional-chaining/member-expression-async-this.js create mode 100644 test/language/expressions/optional-chaining/member-expression.js create mode 100644 test/language/expressions/optional-chaining/new-target-optional-call.js create mode 100644 test/language/expressions/optional-chaining/optional-call-preserves-this.js create mode 100644 test/language/expressions/optional-chaining/optional-chain-async-optional-chain-square-brackets.js create mode 100644 test/language/expressions/optional-chaining/optional-chain-async-square-brackets.js create mode 100644 test/language/expressions/optional-chaining/optional-chain-expression-optional-expression.js create mode 100644 test/language/expressions/optional-chaining/optional-chain-prod-arguments.js create mode 100644 test/language/expressions/optional-chaining/optional-chain-prod-expression.js create mode 100644 test/language/expressions/optional-chaining/optional-chain-prod-identifiername.js create mode 100644 test/language/expressions/optional-chaining/optional-chain.js create mode 100644 test/language/expressions/optional-chaining/optional-expression.js create mode 100644 test/language/expressions/optional-chaining/punctuator-decimal-lookahead.js create mode 100644 test/language/expressions/optional-chaining/runtime-semantics-evaluation.js create mode 100644 test/language/expressions/optional-chaining/short-circuiting.js create mode 100644 test/language/expressions/optional-chaining/static-semantics-simple-assignment.js create mode 100644 test/language/expressions/optional-chaining/super-property-optional-call.js create mode 100644 test/language/expressions/optional-chaining/update-expression-postfix.js create mode 100644 test/language/expressions/optional-chaining/update-expression-prefix.js diff --git a/test/language/expressions/optional-chaining/call-expression-super-no-base.js b/test/language/expressions/optional-chaining/call-expression-super-no-base.js new file mode 100644 index 000000000..c42cbf5e4 --- /dev/null +++ b/test/language/expressions/optional-chaining/call-expression-super-no-base.js @@ -0,0 +1,23 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + should not suppress error if super called on class with no base +info: | + Left-Hand-Side Expressions + OptionalExpression: + SuperCall OptionalChain +features: [optional-chaining] +negative: + type: SyntaxError + phase: parse +---*/ + +$DONOTEVALUATE(); + +class C { + constructor () { + super()?.a; + } +} diff --git a/test/language/expressions/optional-chaining/call-expression.js b/test/language/expressions/optional-chaining/call-expression.js new file mode 100644 index 000000000..4cc1a73fc --- /dev/null +++ b/test/language/expressions/optional-chaining/call-expression.js @@ -0,0 +1,75 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain on call expression +info: | + Left-Hand-Side Expressions + OptionalExpression: + CallExpression OptionalChain +features: [optional-chaining] +---*/ + +// CallExpression CoverCallExpressionAndAsyncArrowHead +function fn () { + return {a: 33}; +}; +const obj = { + fn () { + return 44; + } +} +assert.sameValue(33, fn()?.a); +assert.sameValue(undefined, fn()?.b); +assert.sameValue(44, obj?.fn()); + +// CallExpression SuperCall +class A {} +class B extends A { + constructor () { + assert.sameValue(undefined, super()?.a); + } +} +new B(); + +// CallExpression Arguments +function fn2 () { + return () => { + return {a: 66}; + }; +} +function fn3 () { + return () => { + return null; + }; +} +assert.sameValue(66, fn2()()?.a); +assert.sameValue(undefined, fn3()()?.a); + +// CallExpression [Expression] +function fn4 () { + return [{a: 77}]; +} +function fn5 () { + return []; +} +assert.sameValue(77, fn4()[0]?.a); +assert.sameValue(undefined, fn5()[0]?.a); + +// CallExpression .IdentifierName +function fn6 () { + return { + a: { + b: 88 + } + }; +} +assert.sameValue(88, fn6().a?.b); +assert.sameValue(undefined, fn6().b?.c); + +// CallExpression TemplateLiteral +function fn7 () { + return () => {}; +} +assert.sameValue(undefined, fn7()`hello`?.a); diff --git a/test/language/expressions/optional-chaining/early-errors-tail-position-null-op-template-string-esi.js b/test/language/expressions/optional-chaining/early-errors-tail-position-null-op-template-string-esi.js new file mode 100644 index 000000000..0a315116e --- /dev/null +++ b/test/language/expressions/optional-chaining/early-errors-tail-position-null-op-template-string-esi.js @@ -0,0 +1,25 @@ +// Copyright 2020 Salesforce.com, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + template string passed to tail position of optional chain +info: | + Static Semantics: Early Errors + OptionalChain: + ?.TemplateLiteral + OptionalChain TemplateLiteral + + It is a Syntax Error if any code matches this production. +features: [optional-chaining] +negative: + type: SyntaxError + phase: parse +---*/ + +$DONOTEVALUATE(); + +// This production exists in order to prevent automatic semicolon +// insertion rules. +null?. + `hello` diff --git a/test/language/expressions/optional-chaining/early-errors-tail-position-null-op-template-string.js b/test/language/expressions/optional-chaining/early-errors-tail-position-null-op-template-string.js new file mode 100644 index 000000000..1bf4f43cc --- /dev/null +++ b/test/language/expressions/optional-chaining/early-errors-tail-position-null-op-template-string.js @@ -0,0 +1,22 @@ +// Copyright 2020 Salesforce.com, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + template string passed to tail position of optional chain +info: | + Static Semantics: Early Errors + OptionalChain: + ?.TemplateLiteral + OptionalChain TemplateLiteral + + It is a Syntax Error if any code matches this production. +features: [optional-chaining] +negative: + type: SyntaxError + phase: parse +---*/ + +$DONOTEVALUATE(); + +null?.`hello`; diff --git a/test/language/expressions/optional-chaining/early-errors-tail-position-null-optchain-template-string-esi.js b/test/language/expressions/optional-chaining/early-errors-tail-position-null-optchain-template-string-esi.js new file mode 100644 index 000000000..897cf434f --- /dev/null +++ b/test/language/expressions/optional-chaining/early-errors-tail-position-null-optchain-template-string-esi.js @@ -0,0 +1,25 @@ +// Copyright 2020 Salesforce.com, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + template string passed to tail position of optional chain +info: | + Static Semantics: Early Errors + OptionalChain: + ?.TemplateLiteral + OptionalChain TemplateLiteral + + It is a Syntax Error if any code matches this production. +features: [optional-chaining] +negative: + type: SyntaxError + phase: parse +---*/ + +$DONOTEVALUATE(); + +// This production exists in order to prevent automatic semicolon +// insertion rules. +null?.fn + `hello` diff --git a/test/language/expressions/optional-chaining/early-errors-tail-position-null-optchain-template-string.js b/test/language/expressions/optional-chaining/early-errors-tail-position-null-optchain-template-string.js new file mode 100644 index 000000000..d59e71ee5 --- /dev/null +++ b/test/language/expressions/optional-chaining/early-errors-tail-position-null-optchain-template-string.js @@ -0,0 +1,22 @@ +// Copyright 2020 Salesforce.com, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + template string passed to tail position of optional chain +info: | + Static Semantics: Early Errors + OptionalChain: + ?.TemplateLiteral + OptionalChain TemplateLiteral + + It is a Syntax Error if any code matches this production. +features: [optional-chaining] +negative: + type: SyntaxError + phase: parse +---*/ + +$DONOTEVALUATE(); + +null?.fn`hello`; diff --git a/test/language/expressions/optional-chaining/early-errors-tail-position-op-template-string-esi.js b/test/language/expressions/optional-chaining/early-errors-tail-position-op-template-string-esi.js new file mode 100644 index 000000000..82c9477f0 --- /dev/null +++ b/test/language/expressions/optional-chaining/early-errors-tail-position-op-template-string-esi.js @@ -0,0 +1,27 @@ +// Copyright 2020 Salesforce.com, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + template string passed to tail position of optional chain +info: | + Static Semantics: Early Errors + OptionalChain: + ?.TemplateLiteral + OptionalChain TemplateLiteral + + It is a Syntax Error if any code matches this production. +features: [optional-chaining] +negative: + type: SyntaxError + phase: parse +---*/ + +$DONOTEVALUATE(); + +const a = function() {}; + +// This production exists in order to prevent automatic semicolon +// insertion rules. +a?. + `hello` diff --git a/test/language/expressions/optional-chaining/early-errors-tail-position-op-template-string.js b/test/language/expressions/optional-chaining/early-errors-tail-position-op-template-string.js new file mode 100644 index 000000000..eaeffedfb --- /dev/null +++ b/test/language/expressions/optional-chaining/early-errors-tail-position-op-template-string.js @@ -0,0 +1,24 @@ +// Copyright 2020 Salesforce.com, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + template string passed to tail position of optional chain +info: | + Static Semantics: Early Errors + OptionalChain: + ?.TemplateLiteral + OptionalChain TemplateLiteral + + It is a Syntax Error if any code matches this production. +features: [optional-chaining] +negative: + type: SyntaxError + phase: parse +---*/ + +$DONOTEVALUATE(); + +const a = function() {}; + +a?.`hello`; diff --git a/test/language/expressions/optional-chaining/early-errors-tail-position-optchain-template-string-esi.js b/test/language/expressions/optional-chaining/early-errors-tail-position-optchain-template-string-esi.js new file mode 100644 index 000000000..bbb98ea83 --- /dev/null +++ b/test/language/expressions/optional-chaining/early-errors-tail-position-optchain-template-string-esi.js @@ -0,0 +1,27 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + template string passed to tail position of optional chain +info: | + Static Semantics: Early Errors + OptionalChain: + ?.TemplateLiteral + OptionalChain TemplateLiteral + + It is a Syntax Error if any code matches this production. +features: [optional-chaining] +negative: + type: SyntaxError + phase: parse +---*/ + +$DONOTEVALUATE(); + +const a = {fn() {}}; + +// This production exists in order to prevent automatic semicolon +// insertion rules. +a?.fn + `hello` diff --git a/test/language/expressions/optional-chaining/early-errors-tail-position-optchain-template-string.js b/test/language/expressions/optional-chaining/early-errors-tail-position-optchain-template-string.js new file mode 100644 index 000000000..302ac35cf --- /dev/null +++ b/test/language/expressions/optional-chaining/early-errors-tail-position-optchain-template-string.js @@ -0,0 +1,24 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + template string passed to tail position of optional chain +info: | + Static Semantics: Early Errors + OptionalChain: + ?.TemplateLiteral + OptionalChain TemplateLiteral + + It is a Syntax Error if any code matches this production. +features: [optional-chaining] +negative: + type: SyntaxError + phase: parse +---*/ + +$DONOTEVALUATE(); + +const a = {fn() {}}; + +a?.fn`hello`; diff --git a/test/language/expressions/optional-chaining/eval-optional-call.js b/test/language/expressions/optional-chaining/eval-optional-call.js new file mode 100644 index 000000000..3fa59bcbc --- /dev/null +++ b/test/language/expressions/optional-chaining/eval-optional-call.js @@ -0,0 +1,39 @@ +// Copyright 2020 Toru Nagashima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-optional-chaining-chain-evaluation +description: optional call invoked on eval function should be indirect eval. +info: | + Runtime Semantics: ChainEvaluation + OptionalChain: ?. Arguments + 1. Let thisChain be this OptionalChain. + 2. Let tailCall be IsInTailPosition(thisChain). + 3. Return ? EvaluateCall(baseValue, baseReference, Arguments, tailCall). + + Runtime Semantics: EvaluateCall ( func, ref, arguments, tailPosition ) + + ... + 7. Let result be Call(func, thisValue, argList). + ... + + eval ( x ) + + ... + 4. Return ? PerformEval(x, callerRealm, false, false). + + Runtime Semantics: PerformEval ( x, callerRealm, strictCaller, direct ) +features: [optional-chaining] +---*/ + +const a = 'global'; + +function fn() { + const a = 'local'; + return eval?.('a'); +} + +assert.sameValue(fn(), 'global', 'fn() returns "global" value from indirect eval'); + +const b = (a => eval?.('a'))('local'); + +assert.sameValue(b, 'global', 'b is "global", from indirect eval not observing parameter'); diff --git a/test/language/expressions/optional-chaining/iteration-statement-do.js b/test/language/expressions/optional-chaining/iteration-statement-do.js new file mode 100644 index 000000000..5a82cc23c --- /dev/null +++ b/test/language/expressions/optional-chaining/iteration-statement-do.js @@ -0,0 +1,18 @@ +// Copyright 2019 Google, LLC. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain in test portion of do while statement +info: | + IterationStatement + do Statement while (OptionalExpression) +features: [optional-chaining] +---*/ +let count = 0; +const obj = {a: true}; +do { + count++; + break; +} while (obj?.a); +assert.sameValue(1, count); diff --git a/test/language/expressions/optional-chaining/iteration-statement-for-await-of.js b/test/language/expressions/optional-chaining/iteration-statement-for-await-of.js new file mode 100644 index 000000000..6d414965a --- /dev/null +++ b/test/language/expressions/optional-chaining/iteration-statement-for-await-of.js @@ -0,0 +1,35 @@ +// Copyright 2019 Google, LLC. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain RHS of for await statement +info: | + IterationStatement + for await (LeftHandSideExpression of AssignmentExpression) Statement +features: [optional-chaining] +flags: [async] +---*/ +const obj = { + iterable: { + [Symbol.asyncIterator]() { + return { + i: 0, + next() { + if (this.i < 3) { + return Promise.resolve({ value: this.i++, done: false }); + } + return Promise.resolve({ done: true }); + } + }; + } + } +}; +async function checkAssertions() { + let count = 0; + for await (const num of obj?.iterable) { + count += num; + } + assert.sameValue(3, count); +} +checkAssertions().then($DONE, $DONE); diff --git a/test/language/expressions/optional-chaining/iteration-statement-for-in.js b/test/language/expressions/optional-chaining/iteration-statement-for-in.js new file mode 100644 index 000000000..2823b8053 --- /dev/null +++ b/test/language/expressions/optional-chaining/iteration-statement-for-in.js @@ -0,0 +1,22 @@ +// Copyright 2019 Google, LLC. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain in test portion of do while statement +info: | + IterationStatement + for (LeftHandSideExpression in Expression) Statement +features: [optional-chaining] +---*/ +const obj = { + inner: { + a: 1, + b: 2 + } +}; +let str = ''; +for (const key in obj?.inner) { + str += key; +} +assert.sameValue('ab', str); diff --git a/test/language/expressions/optional-chaining/iteration-statement-for-of-type-error.js b/test/language/expressions/optional-chaining/iteration-statement-for-of-type-error.js new file mode 100644 index 000000000..3cd18cd48 --- /dev/null +++ b/test/language/expressions/optional-chaining/iteration-statement-for-of-type-error.js @@ -0,0 +1,28 @@ +// Copyright 2019 Google, LLC. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain returning undefined in RHS of for of statement +info: | + IterationStatement + for (LeftHandSideExpression of Expression) Statement +features: [optional-chaining] +---*/ + +assert.throws(TypeError, function() { + for (const key of {}?.a) ; +}); + +assert.throws(TypeError, function() { + for (const key of {}?.a) {} +}); + +const obj = undefined; +assert.throws(TypeError, function() { + for (const key of obj?.a) {} +}); + +assert.throws(TypeError, function() { + for (const key of obj?.a); +}); diff --git a/test/language/expressions/optional-chaining/iteration-statement-for.js b/test/language/expressions/optional-chaining/iteration-statement-for.js new file mode 100644 index 000000000..c6d78a318 --- /dev/null +++ b/test/language/expressions/optional-chaining/iteration-statement-for.js @@ -0,0 +1,43 @@ +// Copyright 2019 Google, LLC. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain in init/test/update of for statement +info: | + IterationStatement + for (Expression; Expression; Expression) Statement +features: [optional-chaining] +---*/ + +// OptionalExpression in test. +let count; +const obj = {a: true}; +for (count = 0; obj?.a; count++) { + if (count > 0) break; +} +assert.sameValue(count, 1); + +// OptionalExpression in init/test/update. +let count2 = 0; +const obj2 = undefined; + +for (obj?.a; obj2?.a; obj?.a) { count2++; } +assert.sameValue(count2, 0); + +for (obj?.a; undefined?.a; obj?.a) { count2++; } +assert.sameValue(count2, 0); + +// Short-circuiting +let touched = 0; +const obj3 = { + get a() { + count++; + return undefined; // explicit for clarity + } +}; +for (count = 0; true; obj3?.a?.[touched++]) { + if (count > 0) { break; } +} +assert.sameValue(count, 1); +assert.sameValue(touched, 0); diff --git a/test/language/expressions/optional-chaining/iteration-statement-while.js b/test/language/expressions/optional-chaining/iteration-statement-while.js new file mode 100644 index 000000000..80fffa929 --- /dev/null +++ b/test/language/expressions/optional-chaining/iteration-statement-while.js @@ -0,0 +1,18 @@ +// Copyright 2019 Google, LLC. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain in test portion of while statement +info: | + IterationStatement + while (Expression) Statement +features: [optional-chaining] +---*/ +let count = 0; +const obj = {a: true}; +while (obj?.a) { + count++; + break; +} +assert.sameValue(1, count); diff --git a/test/language/expressions/optional-chaining/member-expression-async-identifier.js b/test/language/expressions/optional-chaining/member-expression-async-identifier.js new file mode 100644 index 000000000..19f9385eb --- /dev/null +++ b/test/language/expressions/optional-chaining/member-expression-async-identifier.js @@ -0,0 +1,32 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain on member expression in async context +info: | + Left-Hand-Side Expressions + OptionalExpression + MemberExpression [PrimaryExpression identifier] OptionalChain +features: [optional-chaining] +flags: [async] +---*/ + +const a = undefined; +const c = {d: Promise.resolve(11)}; +async function checkAssertions() { + assert.sameValue(await a?.b, undefined); + assert.sameValue(await c?.d, 11); + + Promise.prototype.x = 42; + var res = await Promise.resolve(undefined)?.x; + assert.sameValue(res, 42, 'await unwraps the evaluation of the whole optional chaining expression #1'); + + Promise.prototype.y = 43; + var res = await Promise.reject(undefined)?.y; + assert.sameValue(res, 43, 'await unwraps the evaluation of the whole optional chaining expression #2'); + + c.e = Promise.resolve(39); + assert.sameValue(await c?.e, 39, 'await unwraps the promise given after the evaluation of the OCE'); +} +checkAssertions().then($DONE, $DONE); diff --git a/test/language/expressions/optional-chaining/member-expression-async-literal.js b/test/language/expressions/optional-chaining/member-expression-async-literal.js new file mode 100644 index 000000000..90cb7da02 --- /dev/null +++ b/test/language/expressions/optional-chaining/member-expression-async-literal.js @@ -0,0 +1,19 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain on member expression in async context +info: | + Left-Hand-Side Expressions + OptionalExpression: + MemberExpression [PrimaryExpression literal] OptionalChain +features: [optional-chaining] +flags: [async] +---*/ + +async function checkAssertions() { + assert.sameValue(await "hello"?.[0], 'h'); + assert.sameValue(await null?.a, undefined); +} +checkAssertions().then($DONE, $DONE); diff --git a/test/language/expressions/optional-chaining/member-expression-async-this.js b/test/language/expressions/optional-chaining/member-expression-async-this.js new file mode 100644 index 000000000..988729a78 --- /dev/null +++ b/test/language/expressions/optional-chaining/member-expression-async-this.js @@ -0,0 +1,20 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain on member expression in async context +info: | + Left-Hand-Side Expressions + OptionalExpression: + MemberExpression [PrimaryExpression this] OptionalChain +features: [optional-chaining] +flags: [async] +---*/ + +async function thisFn() { + return await this?.a +} +thisFn.call({a: Promise.resolve(33)}).then(function(arg) { + assert.sameValue(33, arg); +}).then($DONE, $DONE); diff --git a/test/language/expressions/optional-chaining/member-expression.js b/test/language/expressions/optional-chaining/member-expression.js new file mode 100644 index 000000000..0651d393e --- /dev/null +++ b/test/language/expressions/optional-chaining/member-expression.js @@ -0,0 +1,104 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain on member expression +info: | + Left-Hand-Side Expressions + OptionalExpression: + MemberExpression OptionalChain +features: [optional-chaining] +---*/ + +// PrimaryExpression +// IdentifierReference +const a = {b: 22}; +assert.sameValue(22, a?.b); +// this +function fn () { + return this?.a +} +assert.sameValue(33, fn.call({a: 33})); +// Literal +assert.sameValue(undefined, "hello"?.a); +assert.sameValue(undefined, null?.a); +// ArrayLiteral +assert.sameValue(2, [1, 2]?.[1]); +// ObjectLiteral +assert.sameValue(44, {a: 44}?.a); +// FunctionExpression +assert.sameValue('a', (function a () {}?.name)); +// ClassExpression +assert.sameValue('Foo', (class Foo {}?.name)); +// GeneratorFunction +assert.sameValue('a', (function * a () {}?.name)); +// AsyncFunctionExpression +// assert.sameValue('a', (async function a () {}?.name)); // Not available in ES7 +// AsyncGeneratorExpression +// assert.sameValue('a', (async function * a () {}?.name)); // Not available in ES7 +// RegularExpressionLiteral +assert.sameValue(true, /[a-z]/?.test('a')); +// TemplateLiteral +assert.sameValue('h', `hello`?.[0]); +// CoverParenthesizedExpressionAndArrowParameterList +assert.sameValue(undefined, ({a: 33}, null)?.a); +assert.sameValue(33, (undefined, {a: 33})?.a); + +// MemberExpression [ Expression ] +const arr = [{a: 33}]; +assert.sameValue(33, arr[0]?.a); +assert.sameValue(undefined, arr[1]?.a); + +// MemberExpression .IdentifierName +const obj = {a: {b: 44}}; +assert.sameValue(44, obj.a?.b); +assert.sameValue(undefined, obj.c?.b); + +// MemberExpression TemplateLiteral +function f2 () { + return {a: 33}; +} +function f3 () {} +assert.sameValue(33, f2`hello world`?.a); +assert.sameValue(undefined, f3`hello world`?.a); + +// MemberExpression SuperProperty +class A { + a () {} + undf () { + return super.a?.c; + } +} +class B extends A { + dot () { + return super.a?.name; + } + expr () { + return super['a']?.name; + } + undf2 () { + return super.b?.c; + } +} +const subcls = new B(); +assert.sameValue('a', subcls.dot()); +assert.sameValue('a', subcls.expr()); +assert.sameValue(undefined, subcls.undf2()); +assert.sameValue(undefined, (new A()).undf()); + +// MemberExpression MetaProperty +class C { + constructor () { + assert.sameValue(undefined, new.target?.a); + } +} +new C(); + +// new MemberExpression Arguments +class D { + constructor (val) { + this.a = val; + } +} +assert.sameValue(99, new D(99)?.a); diff --git a/test/language/expressions/optional-chaining/new-target-optional-call.js b/test/language/expressions/optional-chaining/new-target-optional-call.js new file mode 100644 index 000000000..aae31b9c0 --- /dev/null +++ b/test/language/expressions/optional-chaining/new-target-optional-call.js @@ -0,0 +1,30 @@ +// Copyright 2019 Google, LLC. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional call invoked on new.target should be equivalent to call +info: | + OptionalExpression + MemberExpression OptionalChain + NewTarget OptionalChain +features: [optional-chaining] +---*/ + +const newTargetContext = (function() { return this; })(); + +let called = false; +// should be set to 'undefined' or global context, depending on whether +// mode is strict or sloppy. +let context = null; +function Base() { + called = true; + context = this; +} +function Foo(blerg) { + new.target?.(); +} + +Reflect.construct(Foo, [], Base); +assert(context === newTargetContext); +assert.sameValue(called, true); diff --git a/test/language/expressions/optional-chaining/optional-call-preserves-this.js b/test/language/expressions/optional-chaining/optional-call-preserves-this.js new file mode 100644 index 000000000..9e1eee629 --- /dev/null +++ b/test/language/expressions/optional-chaining/optional-call-preserves-this.js @@ -0,0 +1,27 @@ +// Copyright (C) 2019 Sony Interactive Entertainment Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-optional-chaining-chain-evaluation +description: > + optional call must preserve this context, as with a non-optional call +info: | + OptionalChain : ?. Arguments + 1. Let thisChain be this OptionalChain. + 2. Let tailCall be IsInTailPosition(thisChain). + 3. Return ? EvaluateCall(baseValue, baseReference, Arguments, tailCall). +features: [optional-chaining] +---*/ + +const a = { + b() { return this._b; }, + _b: { c: 42 } +}; + +assert.sameValue(a?.b().c, 42); +assert.sameValue((a?.b)().c, 42); + +assert.sameValue(a.b?.().c, 42); +assert.sameValue((a.b)?.().c, 42); + +assert.sameValue(a?.b?.().c, 42); +assert.sameValue((a?.b)?.().c, 42); diff --git a/test/language/expressions/optional-chaining/optional-chain-async-optional-chain-square-brackets.js b/test/language/expressions/optional-chaining/optional-chain-async-optional-chain-square-brackets.js new file mode 100644 index 000000000..5d64ad4d0 --- /dev/null +++ b/test/language/expressions/optional-chaining/optional-chain-async-optional-chain-square-brackets.js @@ -0,0 +1,28 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain expansions in an async context +info: | + Left-Hand-Side Expressions + OptionalExpression + MemberExpression [PrimaryExpression Identifier] OptionalChain + OptionalChain OptionalChain ?.[Expression] +features: [optional-chaining] +flags: [async] +---*/ + +async function checkAssertions() { + assert.sameValue(await {a: [11]}?.a[0], 11); + const b = {c: [22, 33]}; + assert.sameValue(b?.c[await Promise.resolve(1)], 33); + function e(val) { + return val; + } + assert.sameValue({d: e}?.d(await Promise.resolve([44, 55]))[1], 55); + assert.sameValue(undefined?.arr[ + await Promise.reject(new Error('unreachable')) + ], undefined); +} +checkAssertions().then($DONE, $DONE); diff --git a/test/language/expressions/optional-chaining/optional-chain-async-square-brackets.js b/test/language/expressions/optional-chaining/optional-chain-async-square-brackets.js new file mode 100644 index 000000000..0930d2eaf --- /dev/null +++ b/test/language/expressions/optional-chaining/optional-chain-async-square-brackets.js @@ -0,0 +1,24 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain expansions in an async context +info: | + Left-Hand-Side Expressions + OptionalExpression + MemberExpression [PrimaryExpression Identifier] OptionalChain + OptionalChain ?.[Expression] +features: [optional-chaining] +flags: [async] +---*/ + +async function checkAssertions() { + assert.sameValue(await [11]?.[0], 11); + assert.sameValue([22, 33]?.[await Promise.resolve(1)], 33); + assert.sameValue([44, await Promise.resolve(55)]?.[1], 55); + assert.sameValue(undefined?.[ + await Promise.reject(new Error('unreachable')) + ], undefined); +} +checkAssertions().then($DONE, $DONE); diff --git a/test/language/expressions/optional-chaining/optional-chain-expression-optional-expression.js b/test/language/expressions/optional-chaining/optional-chain-expression-optional-expression.js new file mode 100644 index 000000000..4391014d8 --- /dev/null +++ b/test/language/expressions/optional-chaining/optional-chain-expression-optional-expression.js @@ -0,0 +1,20 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain bracket notation containing optional expresion +info: | + OptionalChain: + ?. [OptionalExpression] +features: [optional-chaining] +---*/ +const a = undefined; +const b = {e: 0}; +const c = {}; +c[undefined] = 11; +const d = [22]; + +assert.sameValue(undefined, a?.[a?.b]); +assert.sameValue(11, c?.[a?.b]); +assert.sameValue(22, d?.[b?.e]); diff --git a/test/language/expressions/optional-chaining/optional-chain-prod-arguments.js b/test/language/expressions/optional-chaining/optional-chain-prod-arguments.js new file mode 100644 index 000000000..c07fc57cb --- /dev/null +++ b/test/language/expressions/optional-chaining/optional-chain-prod-arguments.js @@ -0,0 +1,19 @@ +// Copyright 2020 Salesforce.com, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + Productions for ?. Arguments +info: | + OptionalChain[Yield, Await]: + ?. Arguments +features: [optional-chaining] +---*/ + +function fn(arg1, arg2, arg3 = 0) { + return arg1 + arg2 + arg3; +} + +assert.sameValue(fn?.(10, 20), 30, 'regular'); +assert.sameValue(String?.(42), '42', 'built-in'); +assert.sameValue(fn ?. (...[10, 20, 40]), 70, 'spread'); diff --git a/test/language/expressions/optional-chaining/optional-chain-prod-expression.js b/test/language/expressions/optional-chaining/optional-chain-prod-expression.js new file mode 100644 index 000000000..bd9030584 --- /dev/null +++ b/test/language/expressions/optional-chaining/optional-chain-prod-expression.js @@ -0,0 +1,42 @@ +// Copyright 2020 Salesforce.com, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + Productions for ?. [Expression] +info: | + OptionalChain: + ?.[ Expression ] +features: [optional-chaining] +---*/ + +const $ = 'x'; +const arr = [39, 42]; + +arr.true = 'prop'; +arr[1.1] = 'other prop'; + +const obj = { + a: 'hello', + undefined: 40, + $: 0, + NaN: 41, + null: 42, + x: 43, + true: 44 +}; + +assert.sameValue(arr?.[0], 39, '[0]'); +assert.sameValue(arr?.[0, 1], 42, '[0, 1]'); +assert.sameValue(arr?.[1], 42, '[1]'); +assert.sameValue(arr?.[1, 0], 39, '[1, 0]'); +assert.sameValue(arr?.[{}, NaN, undefined, 2, 0, 10 / 10], 42, '[{}, NaN, undefined, 2, 0, 10 / 10]'); +assert.sameValue(arr?.[true], 'prop', '[true]'); +assert.sameValue(arr?.[1.1], 'other prop', '[1.1]'); + +assert.sameValue(obj?.[undefined], 40, '[undefined]'); +assert.sameValue(obj?.[NaN], 41, '[NaN]'); +assert.sameValue(obj?.[null], 42, '[null]'); +assert.sameValue(obj?.['$'], 0, '["$"]'); +assert.sameValue(obj?.[$], 43, '[$]'); +assert.sameValue(obj?.[true], 44, '[true]'); diff --git a/test/language/expressions/optional-chaining/optional-chain-prod-identifiername.js b/test/language/expressions/optional-chaining/optional-chain-prod-identifiername.js new file mode 100644 index 000000000..768af6beb --- /dev/null +++ b/test/language/expressions/optional-chaining/optional-chain-prod-identifiername.js @@ -0,0 +1,38 @@ +// Copyright 2020 Salesforce.com, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: prod-OptionalExpression +description: > + Productions for ?. IdentifierName +info: | + OptionalChain[Yield, Await]: + ?. IdentifierName +features: [optional-chaining] +---*/ + +const arr = [10, 11]; +const obj = { + a: 'hello' +}; + +assert.sameValue(obj?.a, 'hello'); +assert.sameValue(obj?.\u0061, 'hello'); +assert.sameValue(obj?.\u{0061}, 'hello'); + +assert.sameValue(obj?.\u0062, undefined); +assert.sameValue(obj?.\u{0062}, undefined); + +assert.sameValue(arr ?. length, 2); +assert.sameValue(arr ?. l\u0065ngth, 2); +assert.sameValue(arr ?. l\u{0065}ngth, 2); + +assert.sameValue(obj?.$, undefined); + +obj.$ = 42; +assert.sameValue(obj?.$, 42); + +assert.sameValue(obj?._, undefined); + +obj._ = 39; +assert.sameValue(obj?._, 39); diff --git a/test/language/expressions/optional-chaining/optional-chain.js b/test/language/expressions/optional-chaining/optional-chain.js new file mode 100644 index 000000000..fed70c0c7 --- /dev/null +++ b/test/language/expressions/optional-chaining/optional-chain.js @@ -0,0 +1,50 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + various optional chain expansions +info: | + OptionalChain[Yield, Await]: + ?.[Expression] + ?.IdentifierName + ?.Arguments + ?.TemplateLiteral + OptionalChain [Expression] + OptionalChain .IdentifierName + OptionalChain Arguments[?Yield, ?Await] + OptionalChain TemplateLiteral +features: [optional-chaining] +---*/ + +const arr = [10, 11]; +const obj = { + a: 'hello', + b: {val: 13}, + c(arg1) { + return arg1 * 2; + }, + arr: [11, 12] +}; +const i = 0; + +// OptionalChain: ?.[Expression] +assert.sameValue(11, arr?.[i + 1]); + +// OptionalChain: ?.IdentifierName +assert.sameValue('hello', obj?.a); + +// OptionalChain: ?.Arguments +const fn = (arg1, arg2) => { + return arg1 + arg2; +} +assert.sameValue(30, fn?.(10, 20)); + +// OptionalChain: OptionalChain [Expression] +assert.sameValue(12, obj?.arr[i + 1]); + +// OptionalChain: OptionalChain .IdentifierName +assert.sameValue(13, obj?.b.val); + +// OptionalChain: OptionalChain Arguments +assert.sameValue(20, obj?.c(10)); diff --git a/test/language/expressions/optional-chaining/optional-expression.js b/test/language/expressions/optional-chaining/optional-expression.js new file mode 100644 index 000000000..86d41432b --- /dev/null +++ b/test/language/expressions/optional-chaining/optional-expression.js @@ -0,0 +1,27 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chain on recursive optional expression +info: | + Left-Hand-Side Expressions + OptionalExpression: + OptionalExpression OptionalChain +features: [optional-chaining] +---*/ + +const obj = { + a: { + b: 22 + } +}; + +function fn () { + return {}; +} + +// OptionalExpression (MemberExpression OptionalChain) OptionalChain +assert.sameValue(22, obj?.a?.b); +// OptionalExpression (CallExpression OptionalChain) OptionalChain +assert.sameValue(undefined, fn()?.a?.b); diff --git a/test/language/expressions/optional-chaining/punctuator-decimal-lookahead.js b/test/language/expressions/optional-chaining/punctuator-decimal-lookahead.js new file mode 100644 index 000000000..8cb007e45 --- /dev/null +++ b/test/language/expressions/optional-chaining/punctuator-decimal-lookahead.js @@ -0,0 +1,15 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + ternary operation with decimal does not evaluate as optional chain +info: | + Punctuators + OptionalChainingPunctuator:: + ?.[lookahead ∉ DecimalDigit] +features: [optional-chaining] +---*/ + +const value = true ?.30 : false; +assert.sameValue(.30, value); diff --git a/test/language/expressions/optional-chaining/runtime-semantics-evaluation.js b/test/language/expressions/optional-chaining/runtime-semantics-evaluation.js new file mode 100644 index 000000000..9062e2c5a --- /dev/null +++ b/test/language/expressions/optional-chaining/runtime-semantics-evaluation.js @@ -0,0 +1,18 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + accessing optional value on undefined or null returns undefined. +info: | + If baseValue is undefined or null, then + Return undefined. +features: [optional-chaining] +---*/ + +const nul = null; +const undf = undefined; +assert.sameValue(undefined, nul?.a); +assert.sameValue(undefined, undf?.b); +assert.sameValue(undefined, null?.a); +assert.sameValue(undefined, undefined?.b); diff --git a/test/language/expressions/optional-chaining/short-circuiting.js b/test/language/expressions/optional-chaining/short-circuiting.js new file mode 100644 index 000000000..0b0a1e0d8 --- /dev/null +++ b/test/language/expressions/optional-chaining/short-circuiting.js @@ -0,0 +1,22 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + demonstrate syntax-based short-circuiting. +info: | + If the expression on the LHS of ?. evaluates to null/undefined, the RHS is + not evaluated +features: [optional-chaining] +---*/ + +const a = undefined; +let x = 1; + +a?.[++x] // short-circuiting. +a?.b.c(++x).d; // long short-circuiting. + +undefined?.[++x] // short-circuiting. +undefined?.b.c(++x).d; // long short-circuiting. + +assert.sameValue(1, x); diff --git a/test/language/expressions/optional-chaining/static-semantics-simple-assignment.js b/test/language/expressions/optional-chaining/static-semantics-simple-assignment.js new file mode 100644 index 000000000..7eb974455 --- /dev/null +++ b/test/language/expressions/optional-chaining/static-semantics-simple-assignment.js @@ -0,0 +1,23 @@ + +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + an optional expression cannot be target of assignment +info: | + Static Semantics: IsValidSimpleAssignmentTarget + LeftHandSideExpression: + OptionalExpression + Return false. +features: [optional-chaining] +negative: + type: SyntaxError + phase: parse +---*/ + +$DONOTEVALUATE(); + +const obj = {}; + +obj?.a = 33; diff --git a/test/language/expressions/optional-chaining/super-property-optional-call.js b/test/language/expressions/optional-chaining/super-property-optional-call.js new file mode 100644 index 000000000..cd572ade4 --- /dev/null +++ b/test/language/expressions/optional-chaining/super-property-optional-call.js @@ -0,0 +1,30 @@ +// Copyright 2019 Google, LLC. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional call invoked on super method should be equivalent to call +info: | + OptionalExpression + MemberExpression OptionalChain + SuperProperty OptionalChain +features: [optional-chaining] +---*/ + +let called = false; +let context; +class Base { + method() { + called = true; + context = this; + } +} +class Foo extends Base { + method() { + super.method?.(); + } +} +const foo = new Foo(); +foo.method(); +assert(foo === context); +assert.sameValue(called, true); diff --git a/test/language/expressions/optional-chaining/update-expression-postfix.js b/test/language/expressions/optional-chaining/update-expression-postfix.js new file mode 100644 index 000000000..b7a8e9e21 --- /dev/null +++ b/test/language/expressions/optional-chaining/update-expression-postfix.js @@ -0,0 +1,23 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chaining is forbidden in write contexts +info: | + UpdateExpression[Yield, Await]: + LeftHandSideExpression++ + LeftHandSideExpression-- + ++UnaryExpression + --UnaryExpression +features: [optional-chaining] +negative: + type: SyntaxError + phase: parse +---*/ + +$DONOTEVALUATE(); + +// LeftHandSideExpression ++ +const a = {}; +a?.b++; diff --git a/test/language/expressions/optional-chaining/update-expression-prefix.js b/test/language/expressions/optional-chaining/update-expression-prefix.js new file mode 100644 index 000000000..fb4b8ff8a --- /dev/null +++ b/test/language/expressions/optional-chaining/update-expression-prefix.js @@ -0,0 +1,23 @@ +// Copyright 2019 Google, Inc. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: prod-OptionalExpression +description: > + optional chaining is forbidden in write contexts +info: | + UpdateExpression[Yield, Await]: + LeftHandSideExpression++ + LeftHandSideExpression-- + ++UnaryExpression + --UnaryExpression +features: [optional-chaining] +negative: + type: SyntaxError + phase: parse +---*/ + +$DONOTEVALUATE(); + +// --UnaryExpression +const a = {}; +--a?.b; -- cgit v1.2.1