summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2022-05-05 16:49:07 -0500
committerMatt A. Tobin <email@mattatobin.com>2022-05-05 16:49:07 -0500
commit2678e7f06f0e13e617046bd270f25b8358be5f15 (patch)
tree0e5fa7827860e5d576e64e96b6103ac73f68543f
parent5033dbca32609b69c512e0edd0b4fc40d220de67 (diff)
downloadaura-central-2678e7f06f0e13e617046bd270f25b8358be5f15.tar.gz
[JS:Engine] Implement the Optional Chaining operator (?.)
-rw-r--r--js/src/builtin/ReflectParse.cpp91
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp1095
-rw-r--r--js/src/frontend/BytecodeEmitter.h54
-rw-r--r--js/src/frontend/FoldConstants.cpp33
-rw-r--r--js/src/frontend/FullParseHandler.h37
-rw-r--r--js/src/frontend/ParseNode.cpp5
-rw-r--r--js/src/frontend/ParseNode.h108
-rw-r--r--js/src/frontend/Parser.cpp369
-rw-r--r--js/src/frontend/Parser.h17
-rw-r--r--js/src/frontend/SourceNotes.h2
-rw-r--r--js/src/frontend/SyntaxParseHandler.h21
-rw-r--r--js/src/frontend/TokenKind.h1
-rw-r--r--js/src/frontend/TokenStream.cpp21
-rw-r--r--js/src/jit-test/tests/basic/bug1644839-2.js5
-rw-r--r--js/src/jit-test/tests/basic/bug1644839.js5
-rw-r--r--js/src/jit-test/tests/optional-chain/call-ignore-rval.js34
-rw-r--r--js/src/jit-test/tests/optional-chain/fun-call-or-apply.js60
-rw-r--r--js/src/jit/IonBuilder.cpp6
-rw-r--r--js/src/js.msg2
-rw-r--r--js/src/jsast.tbl4
-rw-r--r--js/src/jsopcode.h24
-rw-r--r--js/src/tests/non262/expressions/browser.js0
-rw-r--r--js/src/tests/non262/expressions/optional-chain-class-heritage.js10
-rw-r--r--js/src/tests/non262/expressions/optional-chain-first-expression.js92
-rw-r--r--js/src/tests/non262/expressions/optional-chain-super-elem.js12
-rw-r--r--js/src/tests/non262/expressions/optional-chain-tdz.js28
-rw-r--r--js/src/tests/non262/expressions/optional-chain.js223
-rw-r--r--js/src/tests/non262/expressions/shell.js0
-rw-r--r--js/src/tests/non262/shell.js0
-rw-r--r--js/src/tests/test262/language/expressions/assignment/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js57
-rw-r--r--js/src/tests/test262/language/expressions/assignment/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js60
-rw-r--r--js/src/tests/test262/language/expressions/assignment/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js57
-rw-r--r--js/src/tests/test262/language/expressions/assignment/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js60
-rw-r--r--js/src/tests/test262/language/expressions/assignment/dstr/shell.js0
-rw-r--r--js/src/tests/test262/language/expressions/assignment/shell.js0
-rw-r--r--js/src/tests/test262/language/expressions/shell.js16
-rw-r--r--js/src/tests/test262/language/optional-chaining/call-expression-super-no-base.js24
-rw-r--r--js/src/tests/test262/language/optional-chaining/call-expression.js77
-rw-r--r--js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-op-template-string-esi.js26
-rw-r--r--js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-op-template-string.js23
-rw-r--r--js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-optchain-template-string-esi.js26
-rw-r--r--js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-optchain-template-string.js23
-rw-r--r--js/src/tests/test262/language/optional-chaining/early-errors-tail-position-op-template-string-esi.js28
-rw-r--r--js/src/tests/test262/language/optional-chaining/early-errors-tail-position-op-template-string.js25
-rw-r--r--js/src/tests/test262/language/optional-chaining/early-errors-tail-position-optchain-template-string-esi.js28
-rw-r--r--js/src/tests/test262/language/optional-chaining/early-errors-tail-position-optchain-template-string.js25
-rw-r--r--js/src/tests/test262/language/optional-chaining/eval-optional-call.js41
-rw-r--r--js/src/tests/test262/language/optional-chaining/iteration-statement-do.js20
-rw-r--r--js/src/tests/test262/language/optional-chaining/iteration-statement-for-await-of.js36
-rw-r--r--js/src/tests/test262/language/optional-chaining/iteration-statement-for-in.js24
-rw-r--r--js/src/tests/test262/language/optional-chaining/iteration-statement-for-of-type-error.js30
-rw-r--r--js/src/tests/test262/language/optional-chaining/iteration-statement-for.js45
-rw-r--r--js/src/tests/test262/language/optional-chaining/iteration-statement-while.js20
-rw-r--r--js/src/tests/test262/language/optional-chaining/member-expression-async-identifier.js33
-rw-r--r--js/src/tests/test262/language/optional-chaining/member-expression-async-literal.js20
-rw-r--r--js/src/tests/test262/language/optional-chaining/member-expression-async-this.js21
-rw-r--r--js/src/tests/test262/language/optional-chaining/member-expression.js106
-rw-r--r--js/src/tests/test262/language/optional-chaining/new-target-optional-call.js32
-rw-r--r--js/src/tests/test262/language/optional-chaining/optional-call-preserves-this.js29
-rw-r--r--js/src/tests/test262/language/optional-chaining/optional-chain-async-optional-chain-square-brackets.js29
-rw-r--r--js/src/tests/test262/language/optional-chaining/optional-chain-async-square-brackets.js25
-rw-r--r--js/src/tests/test262/language/optional-chaining/optional-chain-expression-optional-expression.js22
-rw-r--r--js/src/tests/test262/language/optional-chaining/optional-chain-prod-arguments.js21
-rw-r--r--js/src/tests/test262/language/optional-chaining/optional-chain-prod-expression.js44
-rw-r--r--js/src/tests/test262/language/optional-chaining/optional-chain-prod-identifiername.js40
-rw-r--r--js/src/tests/test262/language/optional-chaining/optional-chain.js52
-rw-r--r--js/src/tests/test262/language/optional-chaining/optional-expression.js29
-rw-r--r--js/src/tests/test262/language/optional-chaining/punctuator-decimal-lookahead.js17
-rw-r--r--js/src/tests/test262/language/optional-chaining/runtime-semantics-evaluation.js20
-rw-r--r--js/src/tests/test262/language/optional-chaining/shell.js0
-rw-r--r--js/src/tests/test262/language/optional-chaining/short-circuiting.js24
-rw-r--r--js/src/tests/test262/language/optional-chaining/static-semantics-simple-assignment.js24
-rw-r--r--js/src/tests/test262/language/optional-chaining/super-property-optional-call.js32
-rw-r--r--js/src/tests/test262/language/optional-chaining/update-expression-postfix.js24
-rw-r--r--js/src/tests/test262/language/optional-chaining/update-expression-prefix.js24
-rw-r--r--js/src/tests/test262/language/statements/for-in/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js66
-rw-r--r--js/src/tests/test262/language/statements/for-in/dstr/array-elem-nested-memberexpr-optchain-prop-ref.js66
-rw-r--r--js/src/tests/test262/language/statements/for-in/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js69
-rw-r--r--js/src/tests/test262/language/statements/for-in/dstr/array-elem-put-obj-literal-optchain-prop-ref.js69
-rw-r--r--js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js66
-rw-r--r--js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref.js66
-rw-r--r--js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js69
-rw-r--r--js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref.js69
-rw-r--r--js/src/tests/test262/language/statements/for-in/dstr/shell.js0
-rw-r--r--js/src/tests/test262/language/statements/for-in/shell.js0
-rw-r--r--js/src/tests/test262/language/statements/for-of/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js66
-rw-r--r--js/src/tests/test262/language/statements/for-of/dstr/array-elem-nested-memberexpr-optchain-prop-ref.js66
-rw-r--r--js/src/tests/test262/language/statements/for-of/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js69
-rw-r--r--js/src/tests/test262/language/statements/for-of/dstr/array-elem-put-obj-literal-optchain-prop-ref.js69
-rw-r--r--js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js66
-rw-r--r--js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref.js66
-rw-r--r--js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js69
-rw-r--r--js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref.js69
-rw-r--r--js/src/tests/test262/language/statements/for-of/dstr/shell.js0
-rw-r--r--js/src/tests/test262/language/statements/for-of/shell.js0
-rw-r--r--js/src/tests/test262/language/statements/shell.js0
96 files changed, 4654 insertions, 224 deletions
diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp
index 65bd16b27..c4a5e8138 100644
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -576,10 +576,11 @@ class NodeBuilder
MutableHandleValue dst);
MOZ_MUST_USE bool callExpression(HandleValue callee, NodeVector& args, TokenPos* pos,
- MutableHandleValue dst);
+ MutableHandleValue dst, bool isOptional = false);
MOZ_MUST_USE bool memberExpression(bool computed, HandleValue expr, HandleValue member,
- TokenPos* pos, MutableHandleValue dst);
+ TokenPos* pos, MutableHandleValue dst,
+ bool isOptional = false);
MOZ_MUST_USE bool arrayExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst);
@@ -593,6 +594,11 @@ class NodeBuilder
MOZ_MUST_USE bool spreadExpression(HandleValue expr, TokenPos* pos, MutableHandleValue dst);
+ MOZ_MUST_USE bool optionalExpression(HandleValue expr, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool deleteOptionalExpression(HandleValue expr, TokenPos* pos,
+ MutableHandleValue dst);
+
MOZ_MUST_USE bool computedName(HandleValue name, TokenPos* pos, MutableHandleValue dst);
MOZ_MUST_USE bool objectExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst);
@@ -1121,7 +1127,7 @@ NodeBuilder::sequenceExpression(NodeVector& elts, TokenPos* pos, MutableHandleVa
bool
NodeBuilder::callExpression(HandleValue callee, NodeVector& args, TokenPos* pos,
- MutableHandleValue dst)
+ MutableHandleValue dst, bool isOptional)
{
RootedValue array(cx);
if (!newArray(args, &array))
@@ -1131,9 +1137,12 @@ NodeBuilder::callExpression(HandleValue callee, NodeVector& args, TokenPos* pos,
if (!cb.isNull())
return callback(cb, callee, array, pos, dst);
- return newNode(AST_CALL_EXPR, pos,
- "callee", callee,
- "arguments", array,
+ return newNode(isOptional ? AST_OPT_CALL_EXPR : AST_CALL_EXPR,
+ pos,
+ "callee",
+ callee,
+ "arguments",
+ array,
dst);
}
@@ -1157,7 +1166,7 @@ NodeBuilder::newExpression(HandleValue callee, NodeVector& args, TokenPos* pos,
bool
NodeBuilder::memberExpression(bool computed, HandleValue expr, HandleValue member, TokenPos* pos,
- MutableHandleValue dst)
+ MutableHandleValue dst, bool isOptional)
{
RootedValue computedVal(cx, BooleanValue(computed));
@@ -1165,10 +1174,14 @@ NodeBuilder::memberExpression(bool computed, HandleValue expr, HandleValue membe
if (!cb.isNull())
return callback(cb, computedVal, expr, member, pos, dst);
- return newNode(AST_MEMBER_EXPR, pos,
- "object", expr,
- "property", member,
- "computed", computedVal,
+ return newNode(isOptional ? AST_OPT_MEMBER_EXPR : AST_MEMBER_EXPR,
+ pos,
+ "object",
+ expr,
+ "property",
+ member,
+ "computed",
+ computedVal,
dst);
}
@@ -1232,6 +1245,22 @@ NodeBuilder::spreadExpression(HandleValue expr, TokenPos* pos, MutableHandleValu
}
bool
+NodeBuilder::optionalExpression(HandleValue expr, TokenPos* pos, MutableHandleValue dst)
+{
+ return newNode(AST_OPTIONAL_EXPR, pos,
+ "expression", expr,
+ dst);
+}
+
+bool
+NodeBuilder::deleteOptionalExpression(HandleValue expr, TokenPos* pos, MutableHandleValue dst)
+{
+ return newNode(AST_DELETE_OPTIONAL_EXPR, pos,
+ "expression", expr,
+ dst);
+}
+
+bool
NodeBuilder::propertyPattern(HandleValue key, HandleValue patt, bool isShorthand, TokenPos* pos,
MutableHandleValue dst)
{
@@ -2950,9 +2979,22 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
case PNK_GENEXP:
return generatorExpression(pn->generatorExpr(), dst);
+ case PNK_DELETEOPTCHAIN: {
+ RootedValue expr(cx);
+ return expression(pn->pn_kid, &expr) &&
+ builder.deleteOptionalExpression(expr, &pn->pn_pos, dst);
+ }
+
+ case PNK_OPTCHAIN: {
+ RootedValue expr(cx);
+ return expression(pn->pn_kid, &expr) &&
+ builder.optionalExpression(expr, &pn->pn_pos, dst);
+ }
+
case PNK_NEW:
case PNK_TAGGED_TEMPLATE:
case PNK_CALL:
+ case PNK_OPTCALL:
case PNK_SUPERCALL:
{
ParseNode* next = pn->pn_head;
@@ -2984,13 +3026,15 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
if (pn->getKind() == PNK_TAGGED_TEMPLATE)
return builder.taggedTemplate(callee, args, &pn->pn_pos, dst);
+ bool isOptional = pn->isKind(PNK_OPTCALL);
+
// SUPERCALL is Call(super, args)
return pn->isKind(PNK_NEW)
? builder.newExpression(callee, args, &pn->pn_pos, dst)
-
- : builder.callExpression(callee, args, &pn->pn_pos, dst);
+ : builder.callExpression(callee, args, &pn->pn_pos, dst, isOptional);
}
+ case PNK_OPTDOT:
case PNK_DOT:
{
MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_expr->pn_pos));
@@ -2999,7 +3043,10 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
RootedValue propname(cx);
RootedAtom pnAtom(cx, pn->pn_atom);
- if (pn->as<PropertyAccess>().isSuper()) {
+ bool isSuper = pn->is<PropertyAccess>() &&
+ pn->as<PropertyAccess>().isSuper();
+
+ if (isSuper) {
if (!builder.super(&pn->pn_expr->pn_pos, &expr))
return false;
} else {
@@ -3007,10 +3054,14 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
return false;
}
+ bool isOptional = pn->isKind(PNK_OPTDOT);
+
return identifier(pnAtom, nullptr, &propname) &&
- builder.memberExpression(false, expr, propname, &pn->pn_pos, dst);
+ builder.memberExpression(false, expr, propname, &pn->pn_pos,
+ dst, isOptional);
}
+ case PNK_OPTELEM:
case PNK_ELEM:
{
MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
@@ -3018,7 +3069,10 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
RootedValue left(cx), right(cx);
- if (pn->as<PropertyByValue>().isSuper()) {
+ bool isSuper = pn->is<PropertyByValue>() &&
+ pn->as<PropertyByValue>().isSuper();
+
+ if (isSuper) {
if (!builder.super(&pn->pn_left->pn_pos, &left))
return false;
} else {
@@ -3026,8 +3080,11 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
return false;
}
+ bool isOptional = pn->isKind(PNK_OPTELEM);
+
return expression(pn->pn_right, &right) &&
- builder.memberExpression(true, left, right, &pn->pn_pos, dst);
+ builder.memberExpression(true, left, right, &pn->pn_pos, dst,
+ isOptional);
}
case PNK_CALLSITEOBJ:
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
index fe5ef69f7..b5a96f32d 100644
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -59,6 +59,7 @@ class LabelControl;
class LoopControl;
class ForOfLoopControl;
class TryFinallyControl;
+class OptionalEmitter;
static bool
ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn)
@@ -1985,6 +1986,81 @@ class MOZ_STACK_CLASS IfThenElseEmitter
#endif
};
+// Class for emitting bytecode for optional expressions.
+class MOZ_RAII OptionalEmitter
+{
+ public:
+ OptionalEmitter(BytecodeEmitter* bce, int32_t initialDepth);
+
+ private:
+ BytecodeEmitter* bce_;
+
+ BytecodeEmitter::TDZCheckCache tdzCache_;
+
+ // Jump target for short circuiting code, which has null or undefined values.
+ JumpList jumpShortCircuit_;
+
+ // Jump target for code that does not short circuit.
+ JumpList jumpFinish_;
+
+ // Stack depth when the optional emitter was instantiated.
+ int32_t initialDepth_;
+
+ // The state of this emitter.
+ //
+ // +-------+ emitJumpShortCircuit +--------------+
+ // | Start |-+---------------------------->| ShortCircuit |-----------+
+ // +-------+ | +--------------+ |
+ // +----->| |
+ // | | emitJumpShortCircuitForCall +---------------------+ v
+ // | +---------------------------->| ShortCircuitForCall |--->+
+ // | +---------------------+ |
+ // | |
+ // ---------------------------------------------------------------+
+ // |
+ // |
+ // +------------------------------------------------------------------+
+ // |
+ // | emitOptionalJumpTarget +---------+
+ // +----------------------->| JumpEnd |
+ // +---------+
+ //
+#ifdef DEBUG
+ enum class State {
+ // The initial state.
+ Start,
+
+ // for shortcircuiting in most cases.
+ ShortCircuit,
+
+ // for shortcircuiting from references, which have two items on
+ // the stack. For example function calls.
+ ShortCircuitForCall,
+
+ // internally used, end of the jump code
+ JumpEnd
+ };
+
+ State state_ = State::Start;
+#endif
+
+ public:
+ enum class Kind {
+ // Requires two values on the stack
+ Reference,
+ // Requires one value on the stack
+ Other
+ };
+
+ MOZ_MUST_USE bool emitJumpShortCircuit();
+ MOZ_MUST_USE bool emitJumpShortCircuitForCall();
+
+ // JSOp is the op code to be emitted, Kind is if we are dealing with a
+ // reference (in which case we need two elements on the stack) or other value
+ // (which needs one element on the stack)
+ MOZ_MUST_USE bool emitOptionalJumpTarget(JSOp op, Kind kind = Kind::Other);
+};
+
class ForOfLoopControl : public LoopControl
{
using EmitterScope = BytecodeEmitter::EmitterScope;
@@ -3132,6 +3208,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
// Watch out for getters!
case PNK_DOT:
+ case PNK_OPTDOT:
MOZ_ASSERT(pn->isArity(PN_NAME));
*answer = true;
return true;
@@ -3214,6 +3291,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
case PNK_DELETENAME:
case PNK_DELETEPROP:
case PNK_DELETEELEM:
+ case PNK_DELETEOPTCHAIN:
MOZ_ASSERT(pn->isArity(PN_UNARY));
*answer = true;
return true;
@@ -3318,6 +3396,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
// More getters.
case PNK_ELEM:
+ case PNK_OPTELEM:
MOZ_ASSERT(pn->isArity(PN_BINARY));
*answer = true;
return true;
@@ -3375,12 +3454,21 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
// Function calls can invoke non-local code.
case PNK_NEW:
case PNK_CALL:
+ case PNK_OPTCALL:
case PNK_TAGGED_TEMPLATE:
case PNK_SUPERCALL:
MOZ_ASSERT(pn->isArity(PN_LIST));
*answer = true;
return true;
+ // Function arg lists can contain arbitrary expressions. Technically
+ // this only causes side-effects if one of the arguments does, but since
+ // the call being made will always trigger side-effects, it isn't needed.
+ case PNK_OPTCHAIN:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ *answer = true;
+ return true;
+
// Classes typically introduce names. Even if no name is introduced,
// the heritage and/or class body (through computed property names)
// usually have effects.
@@ -5274,6 +5362,55 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri
return true;
}
+bool BytecodeEmitter::emitPushNotUndefinedOrNull()
+{
+ // [stack] V
+ MOZ_ASSERT(stackDepth > 0);
+
+ if (!emit1(JSOP_DUP)) {
+ // [stack] V V
+ return false;
+ }
+ if (!emit1(JSOP_UNDEFINED)) {
+ // [stack] V V UNDEFINED
+ return false;
+ }
+ if (!emit1(JSOP_NE)) {
+ // [stack] V NEQ
+ return false;
+ }
+
+ JumpList undefinedOrNullJump;
+ if (!emitJump(JSOP_AND, &undefinedOrNullJump)) {
+ // [stack] V NEQ
+ return false;
+ }
+
+ if (!emit1(JSOP_POP)) {
+ // [stack] V
+ return false;
+ }
+ if (!emit1(JSOP_DUP)) {
+ // [stack] V V
+ return false;
+ }
+ if (!emit1(JSOP_NULL)) {
+ // [stack] V V NULL
+ return false;
+ }
+ if (!emit1(JSOP_NE)) {
+ // [stack] V NEQ
+ return false;
+ }
+
+ if (!emitJumpTargetAndPatch(undefinedOrNullJump)) {
+ // [stack] V NOT-UNDEF-OR-NULL
+ return false;
+ }
+
+ return true;
+}
+
bool
BytecodeEmitter::emitIteratorNext(ParseNode* pn, IteratorKind iterKind /* = IteratorKind::Sync */,
bool allowSelfHosted /* = false */)
@@ -9252,6 +9389,116 @@ BytecodeEmitter::emitDeleteExpression(ParseNode* node)
return emit1(JSOP_TRUE);
}
+bool
+BytecodeEmitter::emitDeleteOptionalChain(ParseNode* deleteNode)
+{
+ MOZ_ASSERT(deleteNode->isKind(PNK_DELETEOPTCHAIN));
+
+ OptionalEmitter oe(this, stackDepth);
+
+ ParseNode* kid = deleteNode->pn_kid;
+ switch (kid->getKind()) {
+ case PNK_ELEM:
+ case PNK_OPTELEM: {
+ PropertyByValueBase* elemExpr = &kid->as<PropertyByValueBase>();
+ if (!emitDeleteElementInOptChain(elemExpr, oe)) {
+ // [stack] # If shortcircuit
+ // [stack] UNDEFINED-OR-NULL
+ // [stack] # otherwise
+ // [stack] TRUE
+ return false;
+ }
+ break;
+ }
+ case PNK_DOT:
+ case PNK_OPTDOT: {
+ PropertyAccessBase* propExpr = &kid->as<PropertyAccessBase>();
+ if (!emitDeletePropertyInOptChain(propExpr, oe)) {
+ // [stack] # If shortcircuit
+ // [stack] UNDEFINED-OR-NULL
+ // [stack] # otherwise
+ // [stack] TRUE
+ return false;
+ }
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unrecognized optional delete ParseNodeKind");
+ }
+
+ if (!oe.emitOptionalJumpTarget(JSOP_TRUE)) {
+ // [stack] # If shortcircuit
+ // [stack] TRUE
+ // [stack] # otherwise
+ // [stack] SUCCEEDED
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitDeletePropertyInOptChain(
+ PropertyAccessBase* propExpr,
+ OptionalEmitter& oe)
+{
+ MOZ_ASSERT_IF(propExpr->is<PropertyAccess>(),
+ !propExpr->as<PropertyAccess>().isSuper());
+
+ if (!emitOptionalTree(&propExpr->expression(), oe)) {
+ // [stack] OBJ
+ return false;
+ }
+ if (propExpr->isKind(PNK_OPTDOT)) {
+ if (!oe.emitJumpShortCircuit()) {
+ // [stack] # if Jump
+ // [stack] UNDEFINED-OR-NULL
+ // [stack] # otherwise
+ // [stack] OBJ
+ return false;
+ }
+ }
+
+ JSOp delOp = sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP;
+ if (!emitAtomOp(propExpr, delOp)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitDeleteElementInOptChain(
+ PropertyByValueBase* elemExpr,
+ OptionalEmitter& oe)
+{
+ MOZ_ASSERT_IF(elemExpr->is<PropertyByValue>(),
+ !elemExpr->as<PropertyByValue>().isSuper());
+
+ if (!emitOptionalTree(elemExpr->pn_left, oe)) {
+ // [stack] OBJ
+ return false;
+ }
+
+ if (elemExpr->isKind(PNK_OPTELEM)) {
+ if (!oe.emitJumpShortCircuit()) {
+ // [stack] # if Jump
+ // [stack] UNDEFINED-OR-NULL
+ // [stack] # otherwise
+ // [stack] OBJ
+ return false;
+ }
+ }
+
+ if (!emitTree(elemExpr->pn_right)) {
+ // [stack] OBJ KEY
+ return false;
+ }
+
+ JSOp delOp = sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM;
+ return emitElemOpBase(delOp);
+}
+
static const char *
SelfHostedCallFunctionName(JSAtom* name, ExclusiveContext* cx)
{
@@ -9473,10 +9720,152 @@ BytecodeEmitter::emitOptimizeSpread(ParseNode* arg0, JumpList* jmp, bool* emitte
return true;
}
+/* A version of emitCalleeAndThis for the optional cases:
+ * * a?.()
+ * * a?.b()
+ * * a?.["b"]()
+ * * (a?.b)()
+ *
+ * See emitCallOrNew and emitOptionalCall for more context.
+ */
bool
-BytecodeEmitter::emitCallOrNew(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */)
+BytecodeEmitter::emitOptionalCalleeAndThis(
+ ParseNode* callNode,
+ ParseNode* calleeNode,
+ bool isCall,
+ OptionalEmitter& oe)
+{
+ JS_CHECK_RECURSION(cx, return false);
+
+ switch (calleeNode->getKind()) {
+ case PNK_NAME: {
+ if (!emitGetName(calleeNode, isCall)) {
+ return false;
+ }
+ break;
+ }
+ case PNK_OPTDOT: {
+ MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
+ OptionalPropertyAccess* prop = &calleeNode->as<OptionalPropertyAccess>();
+ if (!emitOptionalDotExpression(prop, oe, calleeNode, isCall)) {
+ return false;
+ }
+ break;
+ }
+ case PNK_DOT: {
+ MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
+ PropertyAccess* prop = &calleeNode->as<PropertyAccess>();
+ if (!emitOptionalDotExpression(prop, oe, calleeNode, isCall)) {
+ return false;
+ }
+ break;
+ }
+ case PNK_OPTELEM: {
+ MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
+ OptionalPropertyByValue* elem = &calleeNode->as<OptionalPropertyByValue>();
+ if (!emitOptionalElemExpression(elem, oe, calleeNode, isCall)) {
+ return false;
+ }
+ break;
+ }
+ case PNK_ELEM: {
+ MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
+ PropertyByValue* elem = &calleeNode->as<PropertyByValue>();
+ if (!emitOptionalElemExpression(elem, oe, calleeNode, isCall)) {
+ return false;
+ }
+ break;
+ }
+ case PNK_FUNCTION: {
+ /*
+ * Top level lambdas which are immediately invoked should be
+ * treated as only running once. Every time they execute we will
+ * create new types and scripts for their contents, to increase
+ * the quality of type information within them and enable more
+ * backend optimizations. Note that this does not depend on the
+ * lambda being invoked at most once (it may be named or be
+ * accessed via foo.caller indirection), as multiple executions
+ * will just cause the inner scripts to be repeatedly cloned.
+ */
+ MOZ_ASSERT(!emittingRunOnceLambda);
+ if (checkRunOnceContext()) {
+ emittingRunOnceLambda = true;
+ if (!emitOptionalTree(calleeNode, oe)) {
+ return false;
+ }
+ emittingRunOnceLambda = false;
+ } else {
+ if (!emitOptionalTree(calleeNode, oe)) {
+ return false;
+ }
+ }
+ isCall = false;
+ break;
+ }
+ case PNK_OPTCHAIN: {
+ return emitCalleeAndThisForOptionalChain(calleeNode, callNode, isCall);
+ }
+ default: {
+ MOZ_RELEASE_ASSERT(calleeNode->getKind() != PNK_SUPERBASE);
+ if (!emitOptionalTree(calleeNode, oe)) {
+ return false;
+ }
+ isCall = false; /* trigger JSOP_UNDEFINED after */
+ break;
+ }
+ }
+
+ if (!emitCallOrNewThis(callNode, isCall)) {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * A modified version of emitCallOrNew that handles optional calls.
+ *
+ * These include the following:
+ * a?.()
+ * a.b?.()
+ * a.["b"]?.()
+ * (a?.b)?.()
+ *
+ * See CallOrNewEmitter for more context.
+ */
+bool
+BytecodeEmitter::emitOptionalCall(
+ ParseNode* callNode,
+ OptionalEmitter& oe,
+ ValueUsage valueUsage)
+{
+ bool isCall = true;
+ ParseNode* calleeNode = callNode->pn_head;
+
+ if (!emitOptionalCalleeAndThis(callNode, calleeNode, isCall, oe)) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+
+ if (callNode->isKind(PNK_OPTCALL)) {
+ if (!oe.emitJumpShortCircuitForCall()) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+ }
+
+ if (!emitCallOrNewArgumentsAndEnd(callNode, calleeNode, isCall, valueUsage)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitCallOrNew(
+ ParseNode* callNode,
+ ValueUsage valueUsage /* = ValueUsage::WantValue */)
{
- bool callop = pn->isKind(PNK_CALL) || pn->isKind(PNK_TAGGED_TEMPLATE);
/*
* Emit callable invocation or operator new (constructor call) code.
* First, emit code for the left operand to evaluate the callable or
@@ -9492,65 +9881,82 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn, ValueUsage valueUsage /* = ValueUs
* value required for calls (which non-strict mode functions
* will box into the global object).
*/
- uint32_t argc = pn->pn_count - 1;
+ ParseNode* calleeNode = callNode->pn_head;
+ bool isCall = callNode->isKind(PNK_CALL) || callNode->isKind(PNK_TAGGED_TEMPLATE);
+
+ if (calleeNode->isKind(PNK_NAME) &&
+ emitterMode == BytecodeEmitter::SelfHosting &&
+ !IsSpreadOp(callNode->getOp())) {
+ // Calls to "forceInterpreter", "callFunction",
+ // "callContentFunction", or "resumeGenerator" in self-hosted
+ // code generate inline bytecode.
+ PropertyName* calleeName = calleeNode->name();
+ if (calleeName == cx->names().callFunction ||
+ calleeName == cx->names().callContentFunction ||
+ calleeName == cx->names().constructContentFunction)
+ {
+ return emitSelfHostedCallFunction(callNode);
+ }
+ if (calleeName == cx->names().resumeGenerator)
+ return emitSelfHostedResumeGenerator(callNode);
+ if (calleeName == cx->names().forceInterpreter)
+ return emitSelfHostedForceInterpreter(callNode);
+ if (calleeName == cx->names().allowContentIter)
+ return emitSelfHostedAllowContentIter(callNode);
+ // Fall through.
+ }
- if (argc >= ARGC_LIMIT) {
- parser->tokenStream.reportError(callop
- ? JSMSG_TOO_MANY_FUN_ARGS
- : JSMSG_TOO_MANY_CON_ARGS);
+ if (!emitCalleeAndThis(callNode, calleeNode, isCall)) {
return false;
}
- ParseNode* pn2 = pn->pn_head;
- bool spread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE;
- switch (pn2->getKind()) {
+ if (!emitCallOrNewArgumentsAndEnd(callNode, calleeNode, isCall, valueUsage)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitCalleeAndThis(
+ ParseNode* callNode,
+ ParseNode* calleeNode,
+ bool isCall)
+{
+ switch (calleeNode->getKind()) {
case PNK_NAME:
- if (emitterMode == BytecodeEmitter::SelfHosting && !spread) {
- // Calls to "forceInterpreter", "callFunction",
- // "callContentFunction", or "resumeGenerator" in self-hosted
- // code generate inline bytecode.
- if (pn2->name() == cx->names().callFunction ||
- pn2->name() == cx->names().callContentFunction ||
- pn2->name() == cx->names().constructContentFunction)
- {
- return emitSelfHostedCallFunction(pn);
- }
- if (pn2->name() == cx->names().resumeGenerator)
- return emitSelfHostedResumeGenerator(pn);
- if (pn2->name() == cx->names().forceInterpreter)
- return emitSelfHostedForceInterpreter(pn);
- if (pn2->name() == cx->names().allowContentIter)
- return emitSelfHostedAllowContentIter(pn);
- // Fall through.
- }
- if (!emitGetName(pn2, callop))
+ if (!emitGetName(calleeNode, isCall)) {
return false;
+ }
break;
case PNK_DOT:
MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
- if (pn2->as<PropertyAccess>().isSuper()) {
- if (!emitSuperPropOp(pn2, JSOP_GETPROP_SUPER, /* isCall = */ callop))
+ if (calleeNode->as<PropertyAccess>().isSuper()) {
+ if (!emitSuperPropOp(calleeNode, JSOP_GETPROP_SUPER, isCall)) {
return false;
+ }
} else {
- if (!emitPropOp(pn2, callop ? JSOP_CALLPROP : JSOP_GETPROP))
+ if (!emitPropOp(calleeNode, isCall ? JSOP_CALLPROP : JSOP_GETPROP)) {
return false;
+ }
}
-
break;
case PNK_ELEM:
MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
- if (pn2->as<PropertyByValue>().isSuper()) {
- if (!emitSuperElemOp(pn2, JSOP_GETELEM_SUPER, /* isCall = */ callop))
+ if (calleeNode->as<PropertyByValue>().isSuper()) {
+ if (!emitSuperElemOp(calleeNode, JSOP_GETELEM_SUPER, isCall)) {
return false;
+ }
} else {
- if (!emitElemOp(pn2, callop ? JSOP_CALLELEM : JSOP_GETELEM))
+ if (!emitElemOp(calleeNode, isCall ? JSOP_CALLELEM : JSOP_GETELEM)) {
return false;
- if (callop) {
- if (!emit1(JSOP_SWAP))
+ }
+ if (isCall) {
+ if (!emit1(JSOP_SWAP)) {
return false;
+ }
}
}
-
break;
case PNK_FUNCTION:
/*
@@ -9566,115 +9972,156 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn, ValueUsage valueUsage /* = ValueUs
MOZ_ASSERT(!emittingRunOnceLambda);
if (checkRunOnceContext()) {
emittingRunOnceLambda = true;
- if (!emitTree(pn2))
+ if (!emitTree(calleeNode)) {
return false;
+ }
emittingRunOnceLambda = false;
} else {
- if (!emitTree(pn2))
+ if (!emitTree(calleeNode)) {
return false;
+ }
}
- callop = false;
+ isCall = false;
break;
case PNK_SUPERBASE:
- MOZ_ASSERT(pn->isKind(PNK_SUPERCALL));
- MOZ_ASSERT(parser->handler.isSuperBase(pn2));
- if (!emit1(JSOP_SUPERFUN))
+ MOZ_ASSERT(callNode->isKind(PNK_SUPERCALL));
+ MOZ_ASSERT(parser->handler.isSuperBase(calleeNode));
+ if (!emit1(JSOP_SUPERFUN)) {
return false;
+ }
break;
+ case PNK_OPTCHAIN:
+ return emitCalleeAndThisForOptionalChain(calleeNode, callNode, isCall);
default:
- if (!emitTree(pn2))
+ if (!emitTree(calleeNode)) {
return false;
- callop = false; /* trigger JSOP_UNDEFINED after */
+ }
+ isCall = false; /* trigger JSOP_UNDEFINED after */
break;
}
- bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW ||
- pn->getOp() == JSOP_SUPERCALL || pn->getOp() == JSOP_SPREADSUPERCALL;
+ if (!emitCallOrNewThis(callNode, isCall)) {
+ return false;
+ }
+ return true;
+}
+// NOTE: Equivalent to CallOrNewEmitter::emitThis
+bool
+BytecodeEmitter::emitCallOrNewThis(
+ ParseNode* callNode,
+ bool isCall)
+{
// Emit room for |this|.
- if (!callop) {
- if (isNewOp) {
- if (!emit1(JSOP_IS_CONSTRUCTING))
- return false;
+ if (!isCall) {
+ JSOp opForEmit;
+ if (IsNewOp(callNode->getOp())) {
+ opForEmit = JSOP_IS_CONSTRUCTING;
} else {
- if (!emit1(JSOP_UNDEFINED))
- return false;
+ opForEmit = JSOP_UNDEFINED;
+ }
+ if (!emit1(opForEmit)) {
+ return false;
}
}
+ return true;
+}
+
+// NOTE: The following function is equivalent to CallOrNewEmitter::emitEnd
+// and BytecodeEmitter::emitArguments in a future refactor
+bool
+BytecodeEmitter::emitCallOrNewArgumentsAndEnd(
+ ParseNode* callNode,
+ ParseNode* calleeNode,
+ bool isCall,
+ ValueUsage valueUsage)
+{
+ uint32_t argumentCount = callNode->pn_count - 1;
+ if (argumentCount >= ARGC_LIMIT) {
+ parser->tokenStream.reportError(isCall
+ ? JSMSG_TOO_MANY_FUN_ARGS
+ : JSMSG_TOO_MANY_CON_ARGS);
+ return false;
+ }
+
+ JSOp op = callNode->getOp();
+ bool isSpread = IsSpreadOp(op);
+
/*
* Emit code for each argument in order, then emit the JSOP_*CALL or
* JSOP_NEW bytecode with a two-byte immediate telling how many args
* were pushed on the operand stack.
*/
- if (!spread) {
- for (ParseNode* pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) {
- if (!emitTree(pn3))
+ ParseNode* argumentNode = calleeNode->pn_next;
+ if (!isSpread) {
+ while (argumentNode) {
+ if (!emitTree(argumentNode)) {
return false;
- }
-
- if (isNewOp) {
- if (pn->isKind(PNK_SUPERCALL)) {
- if (!emit1(JSOP_NEWTARGET))
- return false;
- } else {
- // Repush the callee as new.target
- if (!emitDupAt(argc + 1))
- return false;
}
+ argumentNode = argumentNode->pn_next;
}
} else {
- ParseNode* args = pn2->pn_next;
JumpList jmp;
bool optCodeEmitted = false;
- if (argc == 1) {
- if (!emitOptimizeSpread(args->pn_kid, &jmp, &optCodeEmitted))
+ if (argumentCount == 1) {
+ if (!emitOptimizeSpread(argumentNode->pn_kid, &jmp,
+ &optCodeEmitted)) {
return false;
+ }
}
- if (!emitArray(args, argc, JSOP_SPREADCALLARRAY))
+ if (!emitArray(argumentNode, argumentCount, JSOP_SPREADCALLARRAY)) {
return false;
+ }
if (optCodeEmitted) {
- if (!emitJumpTargetAndPatch(jmp))
+ if (!emitJumpTargetAndPatch(jmp)) {
return false;
- }
-
- if (isNewOp) {
- if (pn->isKind(PNK_SUPERCALL)) {
- if (!emit1(JSOP_NEWTARGET))
- return false;
- } else {
- if (!emitDupAt(2))
- return false;
}
}
}
- if (!spread) {
- if (pn->getOp() == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) {
- if (!emitCall(JSOP_CALL_IGNORES_RV, argc, pn))
+ if (IsNewOp(op)) {
+ if (callNode->isKind(PNK_SUPERCALL)) {
+ if (!emit1(JSOP_NEWTARGET)) {
return false;
- checkTypeSet(JSOP_CALL_IGNORES_RV);
+ }
} else {
- if (!emitCall(pn->getOp(), argc, pn))
+ // Repush the callee as new.target
+ uint32_t finalArgumentCount = argumentCount + 1;
+ if (isSpread) {
+ finalArgumentCount = 2;
+ }
+ if (!emitDupAt(finalArgumentCount)) {
return false;
- checkTypeSet(pn->getOp());
+ }
+ }
+ }
+
+ if (!isSpread) {
+ JSOp callOp = op;
+ if (callOp == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) {
+ callOp = JSOP_CALL_IGNORES_RV;
+ }
+ if (!emitCall(callOp, argumentCount, callNode)) {
+ return false;
}
+ checkTypeSet(callOp);
} else {
- if (!emit1(pn->getOp()))
+ if (!emit1(op)) {
return false;
- checkTypeSet(pn->getOp());
+ }
+ checkTypeSet(op);
}
- if (pn->isOp(JSOP_EVAL) ||
- pn->isOp(JSOP_STRICTEVAL) ||
- pn->isOp(JSOP_SPREADEVAL) ||
- pn->isOp(JSOP_STRICTSPREADEVAL))
- {
- uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin);
- if (!emitUint32Operand(JSOP_LINENO, lineNum))
+
+ if (IsEvalOp(op)) {
+ uint32_t lineNum =
+ parser->tokenStream.srcCoords.lineNum(callNode->pn_pos.begin);
+ if (!emitUint32Operand(JSOP_LINENO, lineNum)) {
return false;
+ }
}
return true;
@@ -10992,6 +11439,18 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::
return false;
break;
+ case PNK_DELETEOPTCHAIN:
+ if (!emitDeleteOptionalChain(pn)) {
+ return false;
+ }
+ break;
+
+ case PNK_OPTCHAIN:
+ if (!emitOptionalChain(pn, valueUsage)) {
+ return false;
+ }
+ break;
+
case PNK_DOT:
if (pn->as<PropertyAccess>().isSuper()) {
if (!emitSuperPropOp(pn, JSOP_GETPROP_SUPER))
@@ -11184,6 +11643,283 @@ BytecodeEmitter::emitTreeInBranch(ParseNode* pn,
return emitTree(pn, valueUsage);
}
+/*
+ * Special `emitTree` for Optional Chaining case.
+ * Examples of this are `emitOptionalChain`, and `emitDeleteOptionalChain`.
+ */
+bool
+BytecodeEmitter::emitOptionalTree(
+ ParseNode* pn,
+ OptionalEmitter& oe,
+ ValueUsage valueUsage /* = ValueUsage::WantValue */)
+{
+ JS_CHECK_RECURSION(cx, return false);
+
+ ParseNodeKind kind = pn->getKind();
+ switch (kind) {
+ case PNK_OPTDOT: {
+ OptionalPropertyAccess* prop = &pn->as<OptionalPropertyAccess>();
+ if (!emitOptionalDotExpression(prop, oe, pn, false)) {
+ return false;
+ }
+ break;
+ }
+ case PNK_DOT: {
+ PropertyAccess* prop = &pn->as<PropertyAccess>();
+ if (!emitOptionalDotExpression(prop, oe, pn, false)) {
+ return false;
+ }
+ break;
+ }
+ case PNK_OPTELEM: {
+ OptionalPropertyByValue* elem = &pn->as<OptionalPropertyByValue>();
+ if (!emitOptionalElemExpression(elem, oe, pn, false)) {
+ return false;
+ }
+ break;
+ }
+ case PNK_ELEM: {
+ PropertyByValue* elem = &pn->as<PropertyByValue>();
+ if (!emitOptionalElemExpression(elem, oe, pn, false)) {
+ return false;
+ }
+ break;
+ }
+ case PNK_CALL:
+ case PNK_OPTCALL: {
+ if (!emitOptionalCall(pn, oe, valueUsage)) {
+ return false;
+ }
+ break;
+ }
+ // List of accepted ParseNodeKinds that might appear only at the beginning
+ // of an Optional Chain.
+ // For example, a taggedTemplateExpr node might occur if we have
+ // `test`?.b, with `test` as the taggedTemplateExpr ParseNode.
+ default: {
+#ifdef DEBUG
+ // https://tc39.es/ecma262/#sec-primary-expression
+ bool isPrimaryExpression =
+ kind == PNK_THIS ||
+ kind == PNK_NAME ||
+ kind == PNK_NULL ||
+ kind == PNK_TRUE ||
+ kind == PNK_FALSE ||
+ kind == PNK_NUMBER ||
+ kind == PNK_STRING ||
+ kind == PNK_ARRAY ||
+ kind == PNK_OBJECT ||
+ kind == PNK_FUNCTION ||
+ kind == PNK_CLASS ||
+ kind == PNK_REGEXP ||
+ kind == PNK_TEMPLATE_STRING ||
+ kind == PNK_RAW_UNDEFINED ||
+ pn->isInParens();
+
+ // https://tc39.es/ecma262/#sec-left-hand-side-expressions
+ bool isMemberExpression = isPrimaryExpression ||
+ kind == PNK_TAGGED_TEMPLATE ||
+ kind == PNK_NEW ||
+ kind == PNK_NEWTARGET;
+ //kind == ParseNodeKind::ImportMetaExpr;
+
+ bool isCallExpression = kind == PNK_SETTHIS;
+ //kind == ParseNodeKind::CallImportExpr;
+
+ MOZ_ASSERT(isMemberExpression || isCallExpression,
+ "Unknown ParseNodeKind for OptionalChain");
+#endif
+ return emitTree(pn);
+ }
+ }
+
+ return true;
+}
+
+// Handle the case of a call made on a OptionalChainParseNode.
+// For example `(a?.b)()` and `(a?.b)?.()`.
+bool
+BytecodeEmitter::emitCalleeAndThisForOptionalChain(
+ ParseNode* optionalChain,
+ ParseNode* callNode,
+ bool isCall)
+{
+ ParseNode* calleeNode = optionalChain->pn_kid;
+
+ // Create a new OptionalEmitter, in order to emit the right bytecode
+ // in isolation.
+ OptionalEmitter oe(this, stackDepth);
+
+ if (!emitOptionalCalleeAndThis(callNode, calleeNode, isCall, oe)) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+
+ // Complete the jump if necessary. This will set both the "this" value
+ // and the "callee" value to undefined, if the callee is undefined. It
+ // does not matter much what the this value is, the function call will
+ // fail if it is not optional, and be set to undefined otherwise.
+ if (!oe.emitOptionalJumpTarget(JSOP_UNDEFINED,
+ OptionalEmitter::Kind::Reference)) {
+ // [stack] # If shortcircuit
+ // [stack] UNDEFINED UNDEFINED
+ // [stack] # otherwise
+ // [stack] CALLEE THIS
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitOptionalChain(
+ ParseNode* optionalChain,
+ ValueUsage valueUsage)
+{
+ ParseNode* expression = optionalChain->pn_kid;
+
+ OptionalEmitter oe(this, stackDepth);
+
+ if (!emitOptionalTree(expression, oe, valueUsage)) {
+ // [stack] VAL
+ return false;
+ }
+
+ if (!oe.emitOptionalJumpTarget(JSOP_UNDEFINED)) {
+ // [stack] # If shortcircuit
+ // [stack] UNDEFINED
+ // [stack] # otherwise
+ // [stack] VAL
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitOptionalDotExpression(
+ PropertyAccessBase* prop,
+ OptionalEmitter& oe,
+ ParseNode* calleeNode,
+ bool isCall)
+{
+ bool isSuper = prop->is<PropertyAccess>() &&
+ prop->as<PropertyAccess>().isSuper();
+
+ ParseNode* base = &prop->expression();
+ if (isSuper) {
+ if (!emitGetThisForSuperBase(base)) {
+ // [stack] OBJ
+ return false;
+ }
+ } else {
+ if (!emitOptionalTree(base, oe)) {
+ // [stack] OBJ
+ return false;
+ }
+ }
+
+ if (prop->isKind(PNK_OPTDOT)) {
+ MOZ_ASSERT(!isSuper);
+ if (!oe.emitJumpShortCircuit()) {
+ // [stack] # if Jump
+ // [stack] UNDEFINED-OR-NULL
+ // [stack] # otherwise
+ // [stack] OBJ
+ return false;
+ }
+ }
+
+ // XXX emitSuperPropLHS, through emitSuperPropOp, contains a call to
+ // emitGetThisForSuperBase, which we've already called early in this
+ // function. However, modifying emitSuperPropLHS to prevent it from
+ // calling emitGetThisForSuperBase will involve too many changes to
+ // its callers. So, we duplicate their functionality here.
+ //
+ // emitPropOp, on the other hand, calls emitTree again, which is
+ // unnecessary for our case.
+ //
+ // This should be equivalent to PropOpEmitter::emitGet
+ if (isCall && !emit1(JSOP_DUP)) {
+ return false;
+ }
+ JSOp opForEmit = isCall ? JSOP_CALLPROP : JSOP_GETPROP;
+ if (isSuper) {
+ if (!emit1(JSOP_SUPERBASE)) {
+ return false;
+ }
+ opForEmit = JSOP_GETPROP_SUPER;
+ }
+ if (!emitAtomOp(calleeNode, opForEmit)) {
+ return false;
+ }
+ if (isCall && !emit1(JSOP_SWAP)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitOptionalElemExpression(
+ PropertyByValueBase* elem,
+ OptionalEmitter& oe,
+ ParseNode* calleeNode,
+ bool isCall)
+{
+ bool isSuper = elem->is<PropertyByValue>() &&
+ elem->as<PropertyByValue>().isSuper();
+
+ if (isSuper) {
+ if (!emitGetThisForSuperBase(calleeNode)) {
+ // [stack] OBJ
+ return false;
+ }
+ } else {
+ if (!emitOptionalTree(calleeNode->pn_left, oe)) {
+ // [stack] OBJ
+ return false;
+ }
+ }
+
+ if (elem->isKind(PNK_OPTELEM)) {
+ MOZ_ASSERT(!isSuper);
+ if (!oe.emitJumpShortCircuit()) {
+ // [stack] # if Jump
+ // [stack] UNDEFINED-OR-NULL
+ // [stack] # otherwise
+ // [stack] OBJ
+ return false;
+ }
+ }
+
+ // Note: the following conditional is more-or-less equivalent
+ // to ElemOpEmitter::prepareForKey in a future refactor
+ if (isCall) {
+ if (!emit1(JSOP_DUP)) {
+ return false;
+ }
+ }
+
+ if (!emitTree(calleeNode->pn_right)) {
+ // [stack] OBJ? OBJ KEY
+ return false;
+ }
+
+ // Note: the two (2) conditionals below are more-or-less
+ // equivalent to ElemOpEmitter::emitGet in a future refactor
+ if (!emitElemOpBase(isCall ? JSOP_CALLELEM : JSOP_GETELEM)) {
+ return false;
+ }
+ if (isCall) {
+ if (!emit1(JSOP_SWAP)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
static bool
AllocSrcNote(ExclusiveContext* cx, SrcNotesVector& notes, unsigned* index)
{
@@ -11407,6 +12143,171 @@ BytecodeEmitter::copySrcNotes(jssrcnote* destination, uint32_t nsrcnotes)
SN_MAKE_TERMINATOR(&destination[totalCount]);
}
+OptionalEmitter::OptionalEmitter(BytecodeEmitter* bce, int32_t initialDepth)
+ : bce_(bce),
+ tdzCache_(bce),
+ initialDepth_(initialDepth)
+{
+}
+
+bool
+OptionalEmitter::emitJumpShortCircuit() {
+ MOZ_ASSERT(state_ == State::Start ||
+ state_ == State::ShortCircuit ||
+ state_ == State::ShortCircuitForCall);
+ MOZ_ASSERT(initialDepth_ + 1 == bce_->stackDepth);
+
+ IfThenElseEmitter ifEmitter(bce_);
+ if (!bce_->emitPushNotUndefinedOrNull()) {
+ // [stack] OBJ NOT-UNDEFINED-OR-NULL
+ return false;
+ }
+
+ if (!bce_->emit1(JSOP_NOT)) {
+ // [stack] OBJ UNDEFINED-OR-NULL
+ return false;
+ }
+
+ if (!ifEmitter.emitIf() /* emitThen() */) {
+ return false;
+ }
+
+ if (!bce_->newSrcNote(SRC_OPTCHAIN)) {
+ return false;
+ }
+
+ if (!bce_->emitJump(JSOP_GOTO, &jumpShortCircuit_)) {
+ // [stack] UNDEFINED-OR-NULL
+ return false;
+ }
+
+ if (!ifEmitter.emitEnd()) {
+ return false;
+ }
+#ifdef DEBUG
+ state_ = State::ShortCircuit;
+#endif
+ return true;
+}
+
+bool
+OptionalEmitter::emitJumpShortCircuitForCall() {
+ MOZ_ASSERT(state_ == State::Start ||
+ state_ == State::ShortCircuit ||
+ state_ == State::ShortCircuitForCall);
+ int32_t depth = bce_->stackDepth;
+ MOZ_ASSERT(initialDepth_ + 2 == depth);
+ if (!bce_->emit1(JSOP_SWAP)) {
+ // [stack] THIS CALLEE
+ return false;
+ }
+
+ IfThenElseEmitter ifEmitter(bce_);
+ if (!bce_->emitPushNotUndefinedOrNull()) {
+ // [stack] THIS CALLEE NOT-UNDEFINED-OR-NULL
+ return false;
+ }
+
+ if (!bce_->emit1(JSOP_NOT)) {
+ // [stack] THIS CALLEE UNDEFINED-OR-NULL
+ return false;
+ }
+
+ if (!ifEmitter.emitIf() /* emitThen() */) {
+ return false;
+ }
+
+ if (!bce_->emit1(JSOP_POP)) {
+ // [stack] VAL
+ return false;
+ }
+
+ if (!bce_->newSrcNote(SRC_OPTCHAIN)) {
+ return false;
+ }
+
+ if (!bce_->emitJump(JSOP_GOTO, &jumpShortCircuit_)) {
+ // [stack] UNDEFINED-OR-NULL
+ return false;
+ }
+
+ if (!ifEmitter.emitEnd()) {
+ return false;
+ }
+
+ bce_->stackDepth = depth;
+
+ if (!bce_->emit1(JSOP_SWAP)) {
+ // [stack] THIS CALLEE
+ return false;
+ }
+#ifdef DEBUG
+ state_ = State::ShortCircuitForCall;
+#endif
+ return true;
+}
+
+bool
+OptionalEmitter::emitOptionalJumpTarget(JSOp op,
+ Kind kind /* = Kind::Other */) {
+#ifdef DEBUG
+ int32_t depth = bce_->stackDepth;
+#endif
+ MOZ_ASSERT(state_ == State::ShortCircuit ||
+ state_ == State::ShortCircuitForCall);
+
+ // if we get to this point, it means that the optional chain did not short
+ // circuit, so we should skip the short circuiting bytecode.
+ if (!bce_->emitJump(JSOP_GOTO, &jumpFinish_)) {
+ // [stack] # if call
+ // [stack] CALLEE THIS
+ // [stack] # otherwise, if defined
+ // [stack] VAL
+ // [stack] # otherwise
+ // [stack] UNDEFINED-OR-NULL
+ return false;
+ }
+
+ if (!bce_->emitJumpTargetAndPatch(jumpShortCircuit_)) {
+ // [stack] UNDEFINED-OR-NULL
+ return false;
+ }
+
+ // reset stack depth to the depth when we jumped
+ bce_->stackDepth = initialDepth_ + 1;
+
+ if (!bce_->emit1(JSOP_POP)) {
+ // [stack]
+ return false;
+ }
+
+ if (!bce_->emit1(op)) {
+ // [stack] JSOP
+ return false;
+ }
+
+ if (kind == Kind::Reference) {
+ if (!bce_->emit1(op)) {
+ // [stack] JSOP JSOP
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(depth == bce_->stackDepth);
+
+ if (!bce_->emitJumpTargetAndPatch(jumpFinish_)) {
+ // [stack] # if call
+ // [stack] CALLEE THIS
+ // [stack] # otherwise
+ // [stack] VAL
+ return false;
+ }
+#ifdef DEBUG
+ state_ = State::JumpEnd;
+#endif
+ return true;
+}
+
void
CGConstList::finish(ConstArray* array)
{
diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h
index 2c1adf827..bf1154e6e 100644
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -19,6 +19,8 @@
#include "frontend/SourceNotes.h"
#include "vm/Interpreter.h"
+class OptionalEmitter;
+
namespace js {
namespace frontend {
@@ -456,6 +458,11 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitTree(ParseNode* pn, ValueUsage valueUsage = ValueUsage::WantValue,
EmitLineNumberNote emitLineNote = EMIT_LINENOTE);
+ // Emit code for the optional tree rooted at pn.
+ MOZ_MUST_USE bool emitOptionalTree(ParseNode* pn,
+ OptionalEmitter& oe,
+ ValueUsage valueUsage = ValueUsage::WantValue);
+
// Emit code for the tree rooted at pn with its own TDZ cache.
MOZ_MUST_USE bool emitTreeInBranch(ParseNode* pn,
ValueUsage valueUsage = ValueUsage::WantValue);
@@ -731,6 +738,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitAsyncIterator();
+ // XXX currently used only by OptionalEmitter, research still required
+ // to identify when this was introduced in m-c.
+ MOZ_MUST_USE bool emitPushNotUndefinedOrNull();
+
// Pops iterator from the top of the stack. Pushes the result of |.next()|
// onto the stack.
MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, IteratorKind kind = IteratorKind::Sync,
@@ -776,6 +787,32 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitDeleteElement(ParseNode* pn);
MOZ_MUST_USE bool emitDeleteExpression(ParseNode* pn);
+ // Optional methods which emit Optional Jump Target
+ MOZ_MUST_USE bool emitOptionalChain(ParseNode* optionalChain,
+ ValueUsage valueUsage);
+ MOZ_MUST_USE bool emitCalleeAndThisForOptionalChain(ParseNode* optionalChain,
+ ParseNode* callNode,
+ bool isCall);
+ MOZ_MUST_USE bool emitDeleteOptionalChain(ParseNode* deleteNode);
+
+ // Optional methods which emit a shortCircuit jump. They need to be called by
+ // a method which emits an Optional Jump Target, see below.
+ MOZ_MUST_USE bool emitOptionalDotExpression(PropertyAccessBase* prop,
+ OptionalEmitter& oe,
+ ParseNode* calleeNode,
+ bool isCall);
+ MOZ_MUST_USE bool emitOptionalElemExpression(PropertyByValueBase* elem,
+ OptionalEmitter& oe,
+ ParseNode* calleeNode,
+ bool isCall);
+ MOZ_MUST_USE bool emitOptionalCall(ParseNode* callNode,
+ OptionalEmitter& oe,
+ ValueUsage valueUsage);
+ MOZ_MUST_USE bool emitDeletePropertyInOptChain(PropertyAccessBase* propExpr,
+ OptionalEmitter& oe);
+ MOZ_MUST_USE bool emitDeleteElementInOptChain(PropertyByValueBase* elemExpr,
+ OptionalEmitter& oe);
+
// |op| must be JSOP_TYPEOF or JSOP_TYPEOFEXPR.
MOZ_MUST_USE bool emitTypeof(ParseNode* node, JSOp op);
@@ -794,7 +831,22 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool isRestParameter(ParseNode* pn, bool* result);
MOZ_MUST_USE bool emitOptimizeSpread(ParseNode* arg0, JumpList* jmp, bool* emitted);
- MOZ_MUST_USE bool emitCallOrNew(ParseNode* pn, ValueUsage valueUsage = ValueUsage::WantValue);
+ MOZ_MUST_USE bool emitCallOrNew(ParseNode* pn,
+ ValueUsage valueUsage = ValueUsage::WantValue);
+ MOZ_MUST_USE bool emitCalleeAndThis(ParseNode* callNode,
+ ParseNode* calleeNode,
+ bool isCall);
+ MOZ_MUST_USE bool emitOptionalCalleeAndThis(ParseNode* callNode,
+ ParseNode* calleeNode,
+ bool isCall,
+ OptionalEmitter& oe);
+ MOZ_MUST_USE bool emitCallOrNewThis(ParseNode* callNode,
+ bool isCall);
+ MOZ_MUST_USE bool emitCallOrNewArgumentsAndEnd(ParseNode* callNode,
+ ParseNode* calleeNode,
+ bool isCall,
+ ValueUsage valueUsage);
+
MOZ_MUST_USE bool emitSelfHostedCallFunction(ParseNode* pn);
MOZ_MUST_USE bool emitSelfHostedResumeGenerator(ParseNode* pn);
MOZ_MUST_USE bool emitSelfHostedForceInterpreter(ParseNode* pn);
diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp
index c348056c4..979af29b4 100644
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -319,6 +319,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
case PNK_DELETEPROP:
case PNK_DELETEELEM:
case PNK_DELETEEXPR:
+ case PNK_DELETEOPTCHAIN:
case PNK_POS:
case PNK_NEG:
case PNK_PREINCREMENT:
@@ -368,6 +369,10 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
case PNK_DOT:
case PNK_ELEM:
case PNK_CALL:
+ case PNK_OPTCHAIN:
+ case PNK_OPTDOT:
+ case PNK_OPTELEM:
+ case PNK_OPTCALL:
case PNK_NAME:
case PNK_TEMPLATE_STRING:
case PNK_TEMPLATE_STRING_LIST:
@@ -1304,7 +1309,7 @@ FoldElement(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>&
{
ParseNode* node = *nodePtr;
- MOZ_ASSERT(node->isKind(PNK_ELEM));
+ MOZ_ASSERT(node->isKind(PNK_ELEM) || node->isKind(PNK_OPTELEM));
MOZ_ASSERT(node->isArity(PN_BINARY));
ParseNode*& expr = node->pn_left;
@@ -1348,9 +1353,15 @@ FoldElement(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>&
// Optimization 3: We have expr["foo"] where foo is not an index. Convert
// to a property access (like expr.foo) that optimizes better downstream.
- ParseNode* dottedAccess = parser.handler.newPropertyAccess(expr, name, node->pn_pos.end);
- if (!dottedAccess)
+ ParseNode* dottedAccess;
+ if (node->isKind(PNK_OPTELEM)) {
+ dottedAccess = parser.handler.newOptionalPropertyAccess(expr, name, node->pn_pos.end);
+ } else {
+ dottedAccess = parser.handler.newPropertyAccess(expr, name, node->pn_pos.end);
+ }
+ if (!dottedAccess) {
return false;
+ }
dottedAccess->setInParens(node->isInParens());
ReplaceNode(nodePtr, dottedAccess);
@@ -1522,7 +1533,9 @@ static bool
FoldCall(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
bool inGenexpLambda)
{
- MOZ_ASSERT(node->isKind(PNK_CALL) || node->isKind(PNK_SUPERCALL) ||
+ MOZ_ASSERT(node->isKind(PNK_CALL) ||
+ node->isKind(PNK_OPTCALL) ||
+ node->isKind(PNK_SUPERCALL) ||
node->isKind(PNK_TAGGED_TEMPLATE));
MOZ_ASSERT(node->isArity(PN_LIST));
@@ -1599,13 +1612,13 @@ static bool
FoldDottedProperty(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
bool inGenexpLambda)
{
- MOZ_ASSERT(node->isKind(PNK_DOT));
+ MOZ_ASSERT(node->isKind(PNK_DOT) || node->isKind(PNK_OPTDOT));
MOZ_ASSERT(node->isArity(PN_NAME));
// Iterate through a long chain of dotted property accesses to find the
// most-nested non-dotted property node, then fold that.
ParseNode** nested = &node->pn_expr;
- while ((*nested)->isKind(PNK_DOT)) {
+ while ((*nested)->isKind(PNK_DOT) || (*nested)->isKind(PNK_OPTDOT)) {
MOZ_ASSERT((*nested)->isArity(PN_NAME));
nested = &(*nested)->pn_expr;
}
@@ -1632,6 +1645,9 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
JS_CHECK_RECURSION(cx, return false);
ParseNode* pn = *pnp;
+#ifdef DEBUG
+ ParseNodeKind kind = pn->getKind();
+#endif
switch (pn->getKind()) {
case PNK_NOP:
@@ -1713,6 +1729,8 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
MOZ_ASSERT(pn->isArity(PN_BINARY));
return Fold(cx, &pn->pn_left, parser, inGenexpLambda);
+ case PNK_DELETEOPTCHAIN:
+ case PNK_OPTCHAIN:
case PNK_SEMI:
case PNK_THIS:
MOZ_ASSERT(pn->isArity(PN_UNARY));
@@ -1806,6 +1824,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
case PNK_CLASS:
return FoldClass(cx, pn, parser, inGenexpLambda);
+ case PNK_OPTELEM:
case PNK_ELEM:
return FoldElement(cx, pnp, parser, inGenexpLambda);
@@ -1813,6 +1832,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
return FoldAdd(cx, pnp, parser, inGenexpLambda);
case PNK_CALL:
+ case PNK_OPTCALL:
case PNK_SUPERCALL:
case PNK_TAGGED_TEMPLATE:
return FoldCall(cx, pn, parser, inGenexpLambda);
@@ -1896,6 +1916,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
MOZ_ASSERT(pn->isArity(PN_NAME));
return Fold(cx, &pn->pn_expr, parser, inGenexpLambda);
+ case PNK_OPTDOT:
case PNK_DOT:
return FoldDottedProperty(cx, pn, parser, inGenexpLambda);
diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h
index f34635125..eeb4432e4 100644
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -74,6 +74,10 @@ class FullParseHandler
return node->isKind(PNK_DOT) || node->isKind(PNK_ELEM);
}
+ bool isOptionalPropertyAccess(ParseNode* node) {
+ return node->isKind(PNK_OPTDOT) || node->isKind(PNK_OPTELEM);
+ }
+
bool isFunctionCall(ParseNode* node) {
// Note: super() is a special form, *not* a function call.
return node->isKind(PNK_CALL);
@@ -213,6 +217,18 @@ class FullParseHandler
if (expr->isKind(PNK_ELEM))
return newUnary(PNK_DELETEELEM, JSOP_NOP, begin, expr);
+ if (expr->isKind(PNK_OPTCHAIN)) {
+ ParseNode* kid = expr->pn_kid;
+ // Handle property deletion explicitly. OptionalCall is handled
+ // via DeleteExpr.
+ if (kid->isKind(PNK_DOT) ||
+ kid->isKind(PNK_OPTDOT) ||
+ kid->isKind(PNK_ELEM) ||
+ kid->isKind(PNK_OPTELEM)) {
+ return newUnary(PNK_DELETEOPTCHAIN, JSOP_NOP, begin, kid);
+ }
+ }
+
return newUnary(PNK_DELETEEXPR, JSOP_NOP, begin, expr);
}
@@ -319,6 +335,10 @@ class FullParseHandler
return newList(PNK_CALL, JSOP_CALL);
}
+ ParseNode* newOptionalCall() {
+ return newList(PNK_OPTCALL, JSOP_CALL);
+ }
+
ParseNode* newTaggedTemplate() {
return newList(PNK_TAGGED_TEMPLATE, JSOP_CALL);
}
@@ -459,6 +479,11 @@ class FullParseHandler
return new_<UnaryNode>(PNK_AWAIT, JSOP_AWAIT, pos, value);
}
+ ParseNode* newOptionalChain(uint32_t begin, ParseNode* value) {
+ TokenPos pos(begin, value->pn_pos.end);
+ return new_<UnaryNode>(PNK_OPTCHAIN, JSOP_NOP, pos, value);
+ }
+
// Statements
ParseNode* newStatementList(const TokenPos& pos) {
@@ -678,6 +703,14 @@ class FullParseHandler
return new_<PropertyByValue>(lhs, index, lhs->pn_pos.begin, end);
}
+ ParseNode* newOptionalPropertyAccess(ParseNode* pn, PropertyName* name, uint32_t end) {
+ return new_<OptionalPropertyAccess>(pn, name, pn->pn_pos.begin, end);
+ }
+
+ ParseNode* newOptionalPropertyByValue(ParseNode* lhs, ParseNode* index, uint32_t end) {
+ return new_<OptionalPropertyByValue>(lhs, index, lhs->pn_pos.begin, end);
+ }
+
inline MOZ_MUST_USE bool addCatchBlock(ParseNode* catchList, ParseNode* lexicalScope,
ParseNode* catchName, ParseNode* catchGuard,
ParseNode* catchBody);
@@ -920,7 +953,9 @@ class FullParseHandler
return pn->isKind(PNK_CALL);
}
PropertyName* maybeDottedProperty(ParseNode* pn) {
- return pn->is<PropertyAccess>() ? &pn->as<PropertyAccess>().name() : nullptr;
+ return pn->is<PropertyAccessBase>() ?
+ &pn->as<PropertyAccessBase>().name() :
+ nullptr;
}
JSAtom* isStringExprStatement(ParseNode* pn, TokenPos* pos) {
if (JSAtom* atom = pn->isStringExprStatement()) {
diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp
index 623346563..dfbd724e2 100644
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -228,6 +228,8 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
return PushUnaryNodeChild(pn, stack);
// Nodes with a single nullable child.
+ case PNK_OPTCHAIN:
+ case PNK_DELETEOPTCHAIN:
case PNK_THIS:
case PNK_SEMI: {
MOZ_ASSERT(pn->isArity(PN_UNARY));
@@ -253,6 +255,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
case PNK_MODASSIGN:
case PNK_POWASSIGN:
// ...and a few others.
+ case PNK_OPTELEM:
case PNK_ELEM:
case PNK_IMPORT_SPEC:
case PNK_EXPORT_SPEC:
@@ -449,6 +452,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
case PNK_POW:
case PNK_COMMA:
case PNK_NEW:
+ case PNK_OPTCALL:
case PNK_CALL:
case PNK_SUPERCALL:
case PNK_GENEXP:
@@ -483,6 +487,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
}
case PNK_LABEL:
+ case PNK_OPTDOT:
case PNK_DOT:
case PNK_NAME:
return PushNameNodeChildren(pn, stack);
diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h
index 8e5e74d3b..0ad0fe088 100644
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -34,6 +34,10 @@ class ObjectBox;
F(POSTDECREMENT) \
F(DOT) \
F(ELEM) \
+ F(OPTDOT) \
+ F(OPTCHAIN) \
+ F(OPTELEM) \
+ F(OPTCALL) \
F(ARRAY) \
F(ELISION) \
F(STATEMENTLIST) \
@@ -76,6 +80,7 @@ class ObjectBox;
F(DELETEPROP) \
F(DELETEELEM) \
F(DELETEEXPR) \
+ F(DELETEOPTCHAIN) \
F(TRY) \
F(CATCH) \
F(CATCHLIST) \
@@ -371,6 +376,29 @@ IsTypeofKind(ParseNodeKind kind)
* for a more-specific PNK_DELETE* unless constant
* folding (or a similar parse tree manipulation) has
* occurred
+ * PNK_DELETEOPTCHAIN unary pn_kid: MEMBER expr that's evaluated, then the
+ * overall delete evaluates to true; If constant
+ * folding occurs, PNK_ELEM may become PNK_DOT.
+ * PNK_OPTELEM does not get folded into PNK_OPTDOT.
+ * PNK_OPTCHAIN unary pn_kid: expression that is evaluated as a chain.
+ * Contains one or more optional nodes. Its first node
+ * is always an optional node, such as PNK_OPTELEM,
+ * PNK_OPTDOT, or PNK_OPTCALL. This will shortcircuit
+ * and return 'undefined' without evaluating the rest
+ * of the expression if any of the optional nodes it
+ * contains are nullish. An optional chain can also
+ * contain nodes such as PNK_DOT, PNK_ELEM, PNK_NAME,
+ * PNK_CALL, etc. These are evaluated normally.
+ * PNK_OPTDOT name pn_expr: MEMBER expr to left of .
+ * short circuits back to PNK_OPTCHAIN if nullish.
+ * pn_atom: name to right of .
+ * PNK_OPTELEM binary pn_left: MEMBER expr to left of [
+ * short circuits back to PNK_OPTCHAIN if nullish.
+ * pn_right: expr between [ and ]
+ * PNK_OPTCALL list pn_head: list of call, arg1, arg2, ... argN
+ * pn_count: 1 + N (where N is number of args)
+ * call is a MEMBER expr naming a callable object,
+ * short circuits back to PNK_OPTCHAIN if nullish.
* PNK_DOT name pn_expr: MEMBER expr to left of .
* pn_atom: name to right of .
* PNK_ELEM binary pn_left: MEMBER expr to left of [
@@ -1182,11 +1210,12 @@ class RegExpLiteral : public NullaryNode
}
};
-class PropertyAccess : public ParseNode
+class PropertyAccessBase : public ParseNode
{
public:
- PropertyAccess(ParseNode* lhs, PropertyName* name, uint32_t begin, uint32_t end)
- : ParseNode(PNK_DOT, JSOP_NOP, PN_NAME, TokenPos(begin, end))
+ PropertyAccessBase(ParseNodeKind kind, ParseNode* lhs, PropertyName* name,
+ uint32_t begin, uint32_t end)
+ : ParseNode(kind, JSOP_NOP, PN_NAME, TokenPos(begin, end))
{
MOZ_ASSERT(lhs != nullptr);
MOZ_ASSERT(name != nullptr);
@@ -1195,7 +1224,8 @@ class PropertyAccess : public ParseNode
}
static bool test(const ParseNode& node) {
- bool match = node.isKind(PNK_DOT);
+ bool match = node.isKind(PNK_DOT) ||
+ node.isKind(PNK_OPTDOT);
MOZ_ASSERT_IF(match, node.isArity(PN_NAME));
return match;
}
@@ -1207,6 +1237,24 @@ class PropertyAccess : public ParseNode
PropertyName& name() const {
return *pn_u.name.atom->asPropertyName();
}
+};
+
+class PropertyAccess : public PropertyAccessBase
+{
+ public:
+ PropertyAccess(ParseNode* lhs, PropertyName* name, uint32_t begin,
+ uint32_t end)
+ : PropertyAccessBase(PNK_DOT, lhs, name, begin, end)
+ {
+ MOZ_ASSERT(lhs != nullptr);
+ MOZ_ASSERT(name != nullptr);
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_DOT);
+ MOZ_ASSERT_IF(match, node.isArity(PN_NAME));
+ return match;
+ }
bool isSuper() const {
// PNK_SUPERBASE cannot result from any expression syntax.
@@ -1214,17 +1262,50 @@ class PropertyAccess : public ParseNode
}
};
-class PropertyByValue : public ParseNode
+class OptionalPropertyAccess : public PropertyAccessBase
+{
+ public:
+ OptionalPropertyAccess(ParseNode* lhs, PropertyName* name, uint32_t begin,
+ uint32_t end)
+ : PropertyAccessBase(PNK_OPTDOT, lhs, name, begin, end)
+ {
+ MOZ_ASSERT(lhs != nullptr);
+ MOZ_ASSERT(name != nullptr);
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_OPTDOT);
+ MOZ_ASSERT_IF(match, node.isArity(PN_NAME));
+ return match;
+ }
+};
+
+class PropertyByValueBase : public ParseNode
{
public:
- PropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin, uint32_t end)
- : ParseNode(PNK_ELEM, JSOP_NOP, PN_BINARY, TokenPos(begin, end))
+ PropertyByValueBase(ParseNodeKind kind, ParseNode* lhs, ParseNode* propExpr,
+ uint32_t begin, uint32_t end)
+ : ParseNode(kind, JSOP_NOP, PN_BINARY, TokenPos(begin, end))
{
pn_u.binary.left = lhs;
pn_u.binary.right = propExpr;
}
static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_ELEM) ||
+ node.isKind(PNK_OPTELEM);
+ MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
+ return match;
+ }
+};
+
+class PropertyByValue : public PropertyByValueBase {
+ public:
+ PropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin,
+ uint32_t end)
+ : PropertyByValueBase(PNK_ELEM, lhs, propExpr, begin, end) {}
+
+ static bool test(const ParseNode& node) {
bool match = node.isKind(PNK_ELEM);
MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
return match;
@@ -1235,6 +1316,19 @@ class PropertyByValue : public ParseNode
}
};
+class OptionalPropertyByValue : public PropertyByValueBase {
+ public:
+ OptionalPropertyByValue(ParseNode* lhs, ParseNode* propExpr,
+ uint32_t begin, uint32_t end)
+ : PropertyByValueBase(PNK_OPTELEM, lhs, propExpr, begin, end) {}
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_OPTELEM);
+ MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
+ return match;
+ }
+};
+
/*
* A CallSiteNode represents the implicit call site object argument in a TaggedTemplate.
*/
diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp
index 38bd77ead..5202b7154 100644
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -7154,7 +7154,7 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling,
if (hasHeritage) {
if (!tokenStream.getToken(&tt))
return null();
- classHeritage = memberExpr(yieldHandling, TripledotProhibited, tt);
+ classHeritage = optionalExpr(yieldHandling, TripledotProhibited, tt);
if (!classHeritage)
return null();
}
@@ -8363,6 +8363,108 @@ Parser<ParseHandler>::unaryOpExpr(YieldHandling yieldHandling, ParseNodeKind kin
template <typename ParseHandler>
typename ParseHandler::Node
+Parser<ParseHandler>::optionalExpr(
+ YieldHandling yieldHandling, TripledotHandling tripledotHandling,
+ TokenKind tt, bool allowCallSyntax /* = true */,
+ PossibleError* possibleError /* = nullptr */,
+ InvokedPrediction invoked /* = PredictUninvoked */)
+{
+ JS_CHECK_RECURSION(context, return null());
+
+ uint32_t begin = pos().begin;
+
+ Node lhs = memberExpr(yieldHandling, tripledotHandling, tt,
+ /* allowCallSyntax = */ true, possibleError, invoked);
+
+ if (!lhs) {
+ return null();
+ }
+
+ // Note: TokenStream::None is equivalent to TokenStream::SlashIsDiv
+ // in current Mozilla code, see bug 1539821.
+ if (!tokenStream.peekToken(&tt, TokenStream::None)) {
+ return null();
+ }
+
+ if (tt != TOK_OPTCHAIN) {
+ return lhs;
+ }
+
+ while (true) {
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ if (tt == TOK_EOF) {
+ break;
+ }
+
+ Node nextMember;
+ if (tt == TOK_OPTCHAIN) {
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+ if (TokenKindIsPossibleIdentifierName(tt)) {
+ nextMember = memberPropertyAccess(lhs, OptionalKind::Optional);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (tt == TOK_LB) {
+ nextMember = memberElemAccess(lhs, yieldHandling,
+ OptionalKind::Optional);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (tt == TOK_LP) {
+ nextMember = memberCall(tt, lhs, yieldHandling, possibleError,
+ OptionalKind::Optional);
+ if (!nextMember) {
+ return null();
+ }
+ } else {
+ error(JSMSG_NAME_AFTER_DOT);
+ return null();
+ }
+ } else if (tt == TOK_DOT) {
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+ if (TokenKindIsPossibleIdentifierName(tt)) {
+ nextMember = memberPropertyAccess(lhs);
+ if (!nextMember) {
+ return null();
+ }
+ } else {
+ error(JSMSG_NAME_AFTER_DOT);
+ return null();
+ }
+ } else if (tt == TOK_LB) {
+ nextMember = memberElemAccess(lhs, yieldHandling);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (allowCallSyntax && tt == TOK_LP) {
+ nextMember = memberCall(tt, lhs, yieldHandling, possibleError);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (tt == TOK_TEMPLATE_HEAD || tt == TOK_NO_SUBS_TEMPLATE) {
+ error(JSMSG_BAD_OPTIONAL_TEMPLATE);
+ return null();
+ } else {
+ tokenStream.ungetToken();
+ break;
+ }
+
+ MOZ_ASSERT(nextMember);
+ lhs = nextMember;
+ }
+
+ return handler.newOptionalChain(begin, lhs);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
PossibleError* possibleError /* = nullptr */,
InvokedPrediction invoked /* = PredictUninvoked */)
@@ -8412,7 +8514,7 @@ Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling t
return null();
uint32_t operandOffset = pos().begin;
- Node operand = memberExpr(yieldHandling, TripledotProhibited, tt2);
+ Node operand = optionalExpr(yieldHandling, TripledotProhibited, tt2);
if (!operand || !checkIncDecOperand(operand, operandOffset))
return null();
@@ -8454,8 +8556,9 @@ Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling t
MOZ_FALLTHROUGH;
default: {
- Node expr = memberExpr(yieldHandling, tripledotHandling, tt, /* allowCallSyntax = */ true,
- possibleError, invoked);
+ Node expr = optionalExpr(yieldHandling, tripledotHandling, tt,
+ /* allowCallSyntax = */ true,
+ possibleError, invoked);
if (!expr)
return null();
@@ -8904,6 +9007,18 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling
handler.addList(lhs, ctorExpr);
+ // If we have encountered an optional chain, in the form of `new
+ // ClassName?.()` then we need to throw, as this is disallowed
+ // by the spec.
+ bool optionalToken;
+ if (!tokenStream.matchToken(&optionalToken, TOK_OPTCHAIN)) {
+ return null();
+ }
+ if (optionalToken) {
+ errorAt(newBegin, JSMSG_BAD_NEW_OPTIONAL);
+ return null();
+ }
+
bool matched;
if (!tokenStream.matchToken(&matched, TOK_LP))
return null();
@@ -8941,12 +9056,7 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling
if (!tokenStream.getToken(&tt))
return null();
if (TokenKindIsPossibleIdentifierName(tt)) {
- PropertyName* field = tokenStream.currentName();
- if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
- error(JSMSG_BAD_SUPERPROP, "property");
- return null();
- }
- nextMember = handler.newPropertyAccess(lhs, field, pos().end);
+ nextMember = memberPropertyAccess(lhs);
if (!nextMember)
return null();
} else {
@@ -8954,17 +9064,7 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling
return null();
}
} else if (tt == TOK_LB) {
- Node propExpr = expr(InAllowed, yieldHandling, TripledotProhibited);
- if (!propExpr)
- return null();
-
- MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX);
-
- if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
- error(JSMSG_BAD_SUPERPROP, "member");
- return null();
- }
- nextMember = handler.newPropertyByValue(lhs, propExpr, pos().end);
+ nextMember = memberElemAccess(lhs, yieldHandling);
if (!nextMember)
return null();
} else if ((allowCallSyntax && tt == TOK_LP) ||
@@ -9000,82 +9100,17 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling
if (!thisName)
return null();
+ // XXX This was refactored out into memberSuperCall as part of
+ // bug 1566143, but was not used elsewhere aside from here, so
+ // I opted not to move this into a separate function. However,
+ // I'm unsure if this will be needed eventually in the future.
nextMember = handler.newSetThis(thisName, nextMember);
if (!nextMember)
return null();
} else {
- if (options().selfHostingMode && handler.isPropertyAccess(lhs)) {
- error(JSMSG_SELFHOSTED_METHOD_CALL);
- return null();
- }
-
- nextMember = tt == TOK_LP ? handler.newCall() : handler.newTaggedTemplate();
+ nextMember = memberCall(tt, lhs, yieldHandling, possibleError);
if (!nextMember)
return null();
-
- JSOp op = JSOP_CALL;
- bool maybeAsyncArrow = false;
- if (PropertyName* prop = handler.maybeDottedProperty(lhs)) {
- // Use the JSOP_FUN{APPLY,CALL} optimizations given the
- // right syntax.
- if (prop == context->names().apply) {
- op = JSOP_FUNAPPLY;
- if (pc->isFunctionBox()) {
- pc->functionBox()->usesApply = true;
- }
- } else if (prop == context->names().call) {
- op = JSOP_FUNCALL;
- }
- } else if (tt == TOK_LP) {
- if (handler.isAsyncKeyword(lhs, context)) {
- // |async (| can be the start of an async arrow
- // function, so we need to defer reporting possible
- // errors from destructuring syntax. To give better
- // error messages, we only allow the AsyncArrowHead
- // part of the CoverCallExpressionAndAsyncArrowHead
- // syntax when the initial name is "async".
- maybeAsyncArrow = true;
- } else if (handler.isEvalAnyParentheses(lhs, context)) {
- // Select the right EVAL op and flag pc as having a
- // direct eval.
- op = pc->sc()->strict() ? JSOP_STRICTEVAL : JSOP_EVAL;
- pc->sc()->setBindingsAccessedDynamically();
- pc->sc()->setHasDirectEval();
-
- // In non-strict mode code, direct calls to eval can
- // add variables to the call object.
- if (pc->isFunctionBox() && !pc->sc()->strict())
- pc->functionBox()->setHasExtensibleScope();
-
- // If we're in a method, mark the method as requiring
- // support for 'super', since direct eval code can use
- // it. (If we're not in a method, that's fine, so
- // ignore the return value.)
- checkAndMarkSuperScope();
- }
- }
-
- handler.setBeginPosition(nextMember, lhs);
- handler.addList(nextMember, lhs);
-
- if (tt == TOK_LP) {
- bool isSpread = false;
- PossibleError* asyncPossibleError = maybeAsyncArrow ? possibleError : nullptr;
- if (!argumentList(yieldHandling, nextMember, &isSpread, asyncPossibleError))
- return null();
- if (isSpread) {
- if (op == JSOP_EVAL)
- op = JSOP_SPREADEVAL;
- else if (op == JSOP_STRICTEVAL)
- op = JSOP_STRICTSPREADEVAL;
- else
- op = JSOP_SPREADCALL;
- }
- } else {
- if (!taggedTemplate(yieldHandling, nextMember, tt))
- return null();
- }
- handler.setOp(nextMember, op);
}
} else {
tokenStream.ungetToken();
@@ -9109,6 +9144,158 @@ Parser<ParseHandler>::newName(PropertyName* name, TokenPos pos)
return handler.newName(name, pos, context);
}
+template <class ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::memberPropertyAccess(
+ Node lhs, OptionalKind optionalKind /* = OptionalKind::NonOptional */)
+{
+ MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tokenStream.currentToken().type));
+ PropertyName* field = tokenStream.currentName();
+ if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
+ error(JSMSG_BAD_SUPERPROP, "property");
+ return null();
+ }
+ if (optionalKind == OptionalKind::Optional) {
+ MOZ_ASSERT(!handler.isSuperBase(lhs));
+ return handler.newOptionalPropertyAccess(lhs, field, pos().end);
+ }
+ return handler.newPropertyAccess(lhs, field, pos().end);
+}
+
+template <class ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::memberElemAccess(
+ Node lhs, YieldHandling yieldHandling,
+ OptionalKind optionalKind /* = OptionalKind::NonOptional */)
+{
+ MOZ_ASSERT(tokenStream.currentToken().type == TOK_LB);
+ Node propExpr = expr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!propExpr) {
+ return null();
+ }
+
+ MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX);
+
+ if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
+ error(JSMSG_BAD_SUPERPROP, "member");
+ return null();
+ }
+
+ if (optionalKind == OptionalKind::Optional) {
+ MOZ_ASSERT(!handler.isSuperBase(lhs));
+ return handler.newOptionalPropertyByValue(lhs, propExpr, pos().end);
+ }
+ return handler.newPropertyByValue(lhs, propExpr, pos().end);
+}
+
+template <class ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::memberCall(
+ TokenKind tt, Node lhs, YieldHandling yieldHandling,
+ PossibleError* possibleError /* = nullptr */,
+ OptionalKind optionalKind /* = OptionalKind::NonOptional */)
+{
+ if (options().selfHostingMode && (handler.isPropertyAccess(lhs) ||
+ handler.isOptionalPropertyAccess(lhs))) {
+ error(JSMSG_SELFHOSTED_METHOD_CALL);
+ return null();
+ }
+
+ MOZ_ASSERT(tt == TOK_LP ||
+ tt == TOK_TEMPLATE_HEAD ||
+ tt == TOK_NO_SUBS_TEMPLATE,
+ "Unexpected token kind for member call");
+
+ Node nextMember;
+ if (tt == TOK_LP) {
+ if (optionalKind == OptionalKind::Optional) {
+ nextMember = handler.newOptionalCall();
+ } else {
+ nextMember = handler.newCall();
+ }
+ } else {
+ nextMember = handler.newTaggedTemplate();
+ }
+
+ JSOp op = JSOP_CALL;
+ bool maybeAsyncArrow = false;
+ if (PropertyName* prop = handler.maybeDottedProperty(lhs)) {
+ // Use the JSOP_FUN{APPLY,CALL} optimizations given the
+ // right syntax.
+ if (prop == context->names().apply) {
+ op = JSOP_FUNAPPLY;
+ if (pc->isFunctionBox()) {
+ pc->functionBox()->usesApply = true;
+ }
+ } else if (prop == context->names().call) {
+ op = JSOP_FUNCALL;
+ }
+ } else if (tt == TOK_LP && optionalKind == OptionalKind::NonOptional) {
+ if (handler.isAsyncKeyword(lhs, context)) {
+ // |async (| can be the start of an async arrow
+ // function, so we need to defer reporting possible
+ // errors from destructuring syntax. To give better
+ // error messages, we only allow the AsyncArrowHead
+ // part of the CoverCallExpressionAndAsyncArrowHead
+ // syntax when the initial name is "async".
+ maybeAsyncArrow = true;
+ } else if (handler.isEvalAnyParentheses(lhs, context)) {
+ // Select the right EVAL op and flag pc as having a
+ // direct eval.
+ op = pc->sc()->strict() ? JSOP_STRICTEVAL : JSOP_EVAL;
+ pc->sc()->setBindingsAccessedDynamically();
+ pc->sc()->setHasDirectEval();
+
+ // In non-strict mode code, direct calls to eval can
+ // add variables to the call object.
+ if (pc->isFunctionBox() && !pc->sc()->strict()) {
+ pc->functionBox()->setHasExtensibleScope();
+ }
+
+ // If we're in a method, mark the method as requiring
+ // support for 'super', since direct eval code can use
+ // it. (If we're not in a method, that's fine, so
+ // ignore the return value.)
+ checkAndMarkSuperScope();
+ }
+ }
+
+ handler.setBeginPosition(nextMember, lhs);
+ handler.addList(nextMember, lhs);
+
+ if (tt == TOK_LP) {
+ bool isSpread = false;
+ PossibleError* asyncPossibleError = maybeAsyncArrow ?
+ possibleError :
+ nullptr;
+
+ if (!argumentList(yieldHandling, nextMember, &isSpread, asyncPossibleError)) {
+ return null();
+ }
+
+ if (isSpread) {
+ if (op == JSOP_EVAL) {
+ op = JSOP_SPREADEVAL;
+ } else if (op == JSOP_STRICTEVAL) {
+ op = JSOP_STRICTSPREADEVAL;
+ } else {
+ op = JSOP_SPREADCALL;
+ }
+ }
+ } else {
+ if (!taggedTemplate(yieldHandling, nextMember, tt)) {
+ return null();
+ }
+ if (optionalKind == OptionalKind::Optional) {
+ error(JSMSG_BAD_OPTIONAL_TEMPLATE);
+ return null();
+ }
+ }
+ handler.setOp(nextMember, op);
+
+ return nextMember;
+}
+
template <typename ParseHandler>
bool
Parser<ParseHandler>::checkLabelOrIdentifierReference(HandlePropertyName ident,
diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h
index 0e2f28fe8..97f6917bd 100644
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -1321,6 +1321,11 @@ class Parser final : public ParserBase, private JS::AutoGCRooter
Node unaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
PossibleError* possibleError = nullptr,
InvokedPrediction invoked = PredictUninvoked);
+ Node optionalExpr(YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling, TokenKind tt,
+ bool allowCallSyntax = true,
+ PossibleError* possibleError = nullptr,
+ InvokedPrediction invoked = PredictUninvoked);
Node memberExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
TokenKind tt, bool allowCallSyntax = true,
PossibleError* possibleError = nullptr,
@@ -1532,6 +1537,18 @@ class Parser final : public ParserBase, private JS::AutoGCRooter
JSAtom* prefixAccessorName(PropertyType propType, HandleAtom propAtom);
bool asmJS(Node list);
+
+ enum class OptionalKind {
+ NonOptional = 0,
+ Optional,
+ };
+ Node memberPropertyAccess(
+ Node lhs, OptionalKind optionalKind = OptionalKind::NonOptional);
+ Node memberElemAccess(Node lhs, YieldHandling yieldHandling,
+ OptionalKind optionalKind = OptionalKind::NonOptional);
+ Node memberCall(TokenKind tt, Node lhs, YieldHandling yieldHandling,
+ PossibleError* possibleError,
+ OptionalKind optionalKind = OptionalKind::NonOptional);
};
template <typename ParseHandler>
diff --git a/js/src/frontend/SourceNotes.h b/js/src/frontend/SourceNotes.h
index 3d2a1bc1f..3951f5564 100644
--- a/js/src/frontend/SourceNotes.h
+++ b/js/src/frontend/SourceNotes.h
@@ -49,6 +49,7 @@ namespace js {
M(SRC_BREAK, "break", 0) /* JSOP_GOTO is a break. */ \
M(SRC_BREAK2LABEL, "break2label", 0) /* JSOP_GOTO for 'break label'. */ \
M(SRC_SWITCHBREAK, "switchbreak", 0) /* JSOP_GOTO is a break in a switch. */ \
+ M(SRC_OPTCHAIN, "optchain", 0) /* JSOP_GOTO for optional chains. */ \
M(SRC_TABLESWITCH, "tableswitch", 1) /* JSOP_TABLESWITCH; offset points to end of switch. */ \
M(SRC_CONDSWITCH, "condswitch", 2) /* JSOP_CONDSWITCH; 1st offset points to end of switch, \
2nd points to first JSOP_CASE. */ \
@@ -63,7 +64,6 @@ namespace js {
M(SRC_COLSPAN, "colspan", 1) /* Number of columns this opcode spans. */ \
M(SRC_NEWLINE, "newline", 0) /* Bytecode follows a source newline. */ \
M(SRC_SETLINE, "setline", 1) /* A file-absolute source line number note. */ \
- M(SRC_UNUSED21, "unused21", 0) /* Unused. */ \
M(SRC_UNUSED22, "unused22", 0) /* Unused. */ \
M(SRC_UNUSED23, "unused23", 0) /* Unused. */ \
M(SRC_XDELTA, "xdelta", 0) /* 24-31 are for extended delta notes. */
diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h
index 4fef3584c..85c806116 100644
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -58,6 +58,7 @@ class SyntaxParseHandler
// in code not actually executed (or at least not executed enough to be
// noticed).
NodeFunctionCall,
+ NodeOptionalFunctionCall,
// Nodes representing *parenthesized* IsValidSimpleAssignmentTarget
// nodes. We can't simply treat all such parenthesized nodes
@@ -78,7 +79,9 @@ class SyntaxParseHandler
NodeParenthesizedName,
NodeDottedProperty,
+ NodeOptionalDottedProperty,
NodeElement,
+ NodeOptionalElement,
// Destructuring target patterns can't be parenthesized: |([a]) = [3];|
// must be a syntax error. (We can't use NodeGeneric instead of these
@@ -146,6 +149,10 @@ class SyntaxParseHandler
return node == NodeDottedProperty || node == NodeElement;
}
+ bool isOptionalPropertyAccess(Node node) {
+ return node == NodeOptionalDottedProperty || node == NodeOptionalElement;
+ }
+
bool isFunctionCall(Node node) {
// Note: super() is a special form, *not* a function call.
return node == NodeFunctionCall;
@@ -283,6 +290,7 @@ class SyntaxParseHandler
void addArrayElement(Node literal, Node element) { }
Node newCall() { return NodeFunctionCall; }
+ Node newOptionalCall() { return NodeOptionalFunctionCall; }
Node newTaggedTemplate() { return NodeGeneric; }
Node newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; }
@@ -303,6 +311,7 @@ class SyntaxParseHandler
Node newYieldExpression(uint32_t begin, Node value) { return NodeGeneric; }
Node newYieldStarExpression(uint32_t begin, Node value) { return NodeGeneric; }
Node newAwaitExpression(uint32_t begin, Node value) { return NodeGeneric; }
+ Node newOptionalChain(uint32_t begin, Node value) { return NodeGeneric; }
// Statements
@@ -353,8 +362,15 @@ class SyntaxParseHandler
return NodeDottedProperty;
}
+ Node newOptionalPropertyAccess(Node pn, PropertyName* name, uint32_t end) {
+ lastAtom = name;
+ return NodeOptionalDottedProperty;
+ }
+
Node newPropertyByValue(Node pn, Node kid, uint32_t end) { return NodeElement; }
+ Node newOptionalPropertyByValue(Node pn, Node kid, uint32_t end) { return NodeOptionalElement; }
+
MOZ_MUST_USE bool addCatchBlock(Node catchList, Node letBlock, Node catchName,
Node catchGuard, Node catchBody) { return true; }
@@ -471,7 +487,8 @@ class SyntaxParseHandler
list == NodeUnparenthesizedCommaExpr ||
list == NodeVarDeclaration ||
list == NodeLexicalDeclaration ||
- list == NodeFunctionCall);
+ list == NodeFunctionCall ||
+ list == NodeOptionalFunctionCall);
}
Node newAssignment(ParseNodeKind kind, Node lhs, Node rhs, JSOp op) {
@@ -590,7 +607,7 @@ class SyntaxParseHandler
// |this|. It's not really eligible for the funapply/funcall
// optimizations as they're currently implemented (assuming a single
// value is used for both retrieval and |this|).
- if (node != NodeDottedProperty)
+ if (node != NodeDottedProperty && node != NodeOptionalDottedProperty)
return nullptr;
return lastAtom->asPropertyName();
}
diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h
index f4e1afa32..27da8ecf3 100644
--- a/js/src/frontend/TokenKind.h
+++ b/js/src/frontend/TokenKind.h
@@ -63,6 +63,7 @@
macro(DEC, "'--'") /* decrement */ \
macro(DOT, "'.'") /* member operator */ \
macro(TRIPLEDOT, "'...'") /* rest arguments and spread operator */ \
+ macro(OPTCHAIN, "'?.'") \
macro(LB, "'['") \
macro(RB, "']'") \
macro(LC, "'{'") \
diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp
index 083bcd504..8f9e206d9 100644
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -1285,7 +1285,7 @@ static const uint8_t firstCharKinds[] = {
/* 30+ */ _______, _______, Space, _______, String, _______, Ident, _______, _______, String,
/* 40+ */ TOK_LP, TOK_RP, _______, _______, T_COMMA,_______, _______, _______,BasePrefix, Dec,
/* 50+ */ Dec, Dec, Dec, Dec, Dec, Dec, Dec, Dec, T_COLON,TOK_SEMI,
-/* 60+ */ _______, _______, _______,TOK_HOOK, _______, Ident, Ident, Ident, Ident, Ident,
+/* 60+ */ _______, _______, _______, _______, _______, Ident, Ident, Ident, Ident, Ident,
/* 70+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident,
/* 80+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident,
/* 90+ */ Ident, TOK_LB, _______, TOK_RB, _______, Ident, Templat, Ident, Ident, Ident,
@@ -1796,6 +1796,25 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
tp->type = matchChar('=') ? TOK_BITANDASSIGN : TOK_BITAND;
goto out;
+ case '?':
+ if (matchChar('.')) {
+ c = getCharIgnoreEOL();
+ if (JS7_ISDEC(c)) {
+ // if the code unit is followed by a number, for example it has
+ // the following form `<...> ?.5 <..> then it should be treated
+ // as a ternary rather than as an optional chain
+ tp->type = TOK_HOOK;
+ ungetCharIgnoreEOL(c);
+ ungetChar('.');
+ } else {
+ ungetCharIgnoreEOL(c);
+ tp->type = TOK_OPTCHAIN;
+ }
+ } else {
+ tp->type = TOK_HOOK;
+ }
+ goto out;
+
case '!':
if (matchChar('='))
tp->type = matchChar('=') ? TOK_STRICTNE : TOK_NE;
diff --git a/js/src/jit-test/tests/basic/bug1644839-2.js b/js/src/jit-test/tests/basic/bug1644839-2.js
new file mode 100644
index 000000000..62550670e
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1644839-2.js
@@ -0,0 +1,5 @@
+// |jit-test| skip-if: !('oomTest' in this)
+var code = `
+ (\`\${key}: \${(args[1]?.toString)?.()}\`)
+`;
+oomTest(function() { return parseModule(code); }); \ No newline at end of file
diff --git a/js/src/jit-test/tests/basic/bug1644839.js b/js/src/jit-test/tests/basic/bug1644839.js
new file mode 100644
index 000000000..1a9ec3dd8
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1644839.js
@@ -0,0 +1,5 @@
+// |jit-test| skip-if: !('oomTest' in this)
+var code = `
+ (\`\${key}: \${args[1]?.toString()}\`)
+`;
+oomTest(function() { return parseModule(code); }); \ No newline at end of file
diff --git a/js/src/jit-test/tests/optional-chain/call-ignore-rval.js b/js/src/jit-test/tests/optional-chain/call-ignore-rval.js
new file mode 100644
index 000000000..c5fd2fe97
--- /dev/null
+++ b/js/src/jit-test/tests/optional-chain/call-ignore-rval.js
@@ -0,0 +1,34 @@
+// Tests for JSOp::CallIgnoresRv in optional chains.
+
+// Note:: IgnoresReturnValueNative is supported for Array.prototype.splice.
+
+// Test for optional call.
+function testOptionalCall() {
+ for (var i = 0; i < 100; ++i) {
+ var x = [1, 2, 3];
+ x.splice?.(0);
+ }
+}
+
+for (var i = 0; i < 5; ++i) { testOptionalCall(); }
+
+// Test for optional prop directly followed by call.
+function testOptionalProp() {
+ for (var i = 0; i < 100; ++i) {
+ var x = [1, 2, 3];
+ x?.splice(0);
+ }
+}
+
+for (var i = 0; i < 5; ++i) { testOptionalProp(); }
+
+// Test for call in optional chain expression.
+function testOptionalChain() {
+ for (var i = 0; i < 100; ++i) {
+ var x = [1, 2, 3];
+ var o = {x};
+ o?.x.splice(0);
+ }
+}
+
+for (var i = 0; i < 5; ++i) { testOptionalChain(); }
diff --git a/js/src/jit-test/tests/optional-chain/fun-call-or-apply.js b/js/src/jit-test/tests/optional-chain/fun-call-or-apply.js
new file mode 100644
index 000000000..ed25de949
--- /dev/null
+++ b/js/src/jit-test/tests/optional-chain/fun-call-or-apply.js
@@ -0,0 +1,60 @@
+// Tests for JSOp::FunCall and JSOp::FunApply in optional calls.
+
+function f1() {
+ return 0;
+}
+function f2(a) {
+ return a * 2;
+}
+
+function funCall(fn) {
+ // Without arguments.
+ for (var i = 0; i < 100; ++i) {
+ assertEq(f1?.call(), 0);
+ }
+
+ // Only this-arg.
+ for (var i = 0; i < 100; ++i) {
+ assertEq(f1?.call(null), 0);
+ }
+
+ // With one arg.
+ for (var i = 0; i < 100; ++i) {
+ assertEq(f1?.call(null, 1), 0);
+ assertEq(f2?.call(null, 5), 10);
+ }
+
+ // With multiple args.
+ for (var i = 0; i < 100; ++i) {
+ assertEq(f1?.call(null, 1, 2, 3), 0);
+ assertEq(f2?.call(null, 4, 5, 6), 8);
+ }
+}
+
+for (var i = 0; i < 5; ++i) { funCall(); }
+
+function funApply(fn) {
+ // Without arguments.
+ for (var i = 0; i < 100; ++i) {
+ assertEq(f1?.apply(), 0);
+ }
+
+ // Only this-arg.
+ for (var i = 0; i < 100; ++i) {
+ assertEq(f1?.apply(null), 0);
+ }
+
+ // With one arg.
+ for (var i = 0; i < 100; ++i) {
+ assertEq(f1?.apply(null, [1]), 0);
+ assertEq(f2?.apply(null, [5]), 10);
+ }
+
+ // With multiple args.
+ for (var i = 0; i < 100; ++i) {
+ assertEq(f1?.apply(null, [1, 2, 3]), 0);
+ assertEq(f2?.apply(null, [4, 5, 6]), 8);
+ }
+}
+
+for (var i = 0; i < 5; ++i) { funApply(); } \ No newline at end of file
diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp
index dae86fd92..dbecec2a7 100644
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -1649,6 +1649,12 @@ IonBuilder::snoopControlFlow(JSOp op)
// while (cond) { }
return whileOrForInLoop(sn);
+ case SRC_OPTCHAIN:
+ // XXX Instead of aborting early, breaking at this point works.
+ // However, I'm not sure if we still need to further process
+ // optional chains under IonBuilder.
+ break;
+
default:
// Hard assert for now - make an error later.
MOZ_CRASH("unknown goto case");
diff --git a/js/src/js.msg b/js/src/js.msg
index b03da4153..566b55479 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -354,6 +354,8 @@ MSG_DEF(JSMSG_BAD_COLUMN_NUMBER, 0, JSEXN_RANGEERR, "column number out of
MSG_DEF(JSMSG_COMPUTED_NAME_IN_PATTERN,0, JSEXN_SYNTAXERR, "computed property names aren't supported in this destructuring declaration")
MSG_DEF(JSMSG_DEFAULT_IN_PATTERN, 0, JSEXN_SYNTAXERR, "destructuring defaults aren't supported in this destructuring declaration")
MSG_DEF(JSMSG_BAD_NEWTARGET, 0, JSEXN_SYNTAXERR, "new.target only allowed within functions")
+MSG_DEF(JSMSG_BAD_NEW_OPTIONAL, 0, JSEXN_SYNTAXERR, "new keyword cannot be used with an optional chain")
+MSG_DEF(JSMSG_BAD_OPTIONAL_TEMPLATE, 0, JSEXN_SYNTAXERR, "tagged template cannot be used with optional chain")
MSG_DEF(JSMSG_ESCAPED_KEYWORD, 0, JSEXN_SYNTAXERR, "keywords must be written literally, without embedded escapes")
// asm.js
diff --git a/js/src/jsast.tbl b/js/src/jsast.tbl
index a83ad74c6..a50e31d7b 100644
--- a/js/src/jsast.tbl
+++ b/js/src/jsast.tbl
@@ -27,10 +27,14 @@ ASTDEF(AST_LOGICAL_EXPR, "LogicalExpression", "logicalExpr
ASTDEF(AST_UPDATE_EXPR, "UpdateExpression", "updateExpression")
ASTDEF(AST_NEW_EXPR, "NewExpression", "newExpression")
ASTDEF(AST_CALL_EXPR, "CallExpression", "callExpression")
+ASTDEF(AST_OPT_CALL_EXPR, "OptionalCallExpression", "optionalCallExpression")
ASTDEF(AST_MEMBER_EXPR, "MemberExpression", "memberExpression")
+ASTDEF(AST_OPT_MEMBER_EXPR, "OptionalMemberExpression", "optionalMemberExpression")
ASTDEF(AST_FUNC_EXPR, "FunctionExpression", "functionExpression")
ASTDEF(AST_ARROW_EXPR, "ArrowFunctionExpression", "arrowFunctionExpression")
ASTDEF(AST_ARRAY_EXPR, "ArrayExpression", "arrayExpression")
+ASTDEF(AST_DELETE_OPTIONAL_EXPR, "DeleteOptionalExpression", "deleteOptionalExpression")
+ASTDEF(AST_OPTIONAL_EXPR, "OptionalExpression", "optionalExpression")
ASTDEF(AST_SPREAD_EXPR, "SpreadExpression", "spreadExpression")
ASTDEF(AST_OBJECT_EXPR, "ObjectExpression", "objectExpression")
ASTDEF(AST_THIS_EXPR, "ThisExpression", "thisExpression")
diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h
index 7d02fa946..11d042929 100644
--- a/js/src/jsopcode.h
+++ b/js/src/jsopcode.h
@@ -700,6 +700,30 @@ IsEqualityOp(JSOp op)
}
inline bool
+IsSpreadOp(JSOp op)
+{
+ return JOF_OPTYPE(op) == JOF_BYTE;
+}
+
+inline bool
+IsNewOp(JSOp op)
+{
+ return op == JSOP_NEW ||
+ op == JSOP_SPREADNEW ||
+ op == JSOP_SUPERCALL ||
+ op == JSOP_SPREADSUPERCALL;
+}
+
+inline bool
+IsEvalOp(JSOp op)
+{
+ return op == JSOP_EVAL ||
+ op == JSOP_STRICTEVAL ||
+ op == JSOP_SPREADEVAL ||
+ op == JSOP_STRICTSPREADEVAL;
+}
+
+inline bool
IsCheckStrictOp(JSOp op)
{
return CodeSpec[op].format & JOF_CHECKSTRICT;
diff --git a/js/src/tests/non262/expressions/browser.js b/js/src/tests/non262/expressions/browser.js
new file mode 100644
index 000000000..e69de29bb
--- /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 000000000..14a99c639
--- /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 000000000..89912aec4
--- /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 000000000..3b912a9ad
--- /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 000000000..e12d0fb86
--- /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 000000000..04e090935
--- /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 000000000..e69de29bb
--- /dev/null
+++ b/js/src/tests/non262/expressions/shell.js
diff --git a/js/src/tests/non262/shell.js b/js/src/tests/non262/shell.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/js/src/tests/non262/shell.js
diff --git a/js/src/tests/test262/language/expressions/assignment/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js b/js/src/tests/test262/language/expressions/assignment/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js
new file mode 100644
index 000000000..8bdeca68e
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/assignment/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js
@@ -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-init.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 Initializer) (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 = 42] = [23];
diff --git a/js/src/tests/test262/language/expressions/assignment/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js b/js/src/tests/test262/language/expressions/assignment/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js
new file mode 100644
index 000000000..57d80fa31
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/assignment/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js
@@ -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-init.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 = 42] = [23];
diff --git a/js/src/tests/test262/language/expressions/assignment/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js b/js/src/tests/test262/language/expressions/assignment/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js
new file mode 100644
index 000000000..b18643207
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/assignment/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js
@@ -0,0 +1,57 @@
+// |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
+/*---
+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 Initializer) (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 y = {};
+
+0, { x: y?.z = 42 } = { x: 23 };
diff --git a/js/src/tests/test262/language/expressions/assignment/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js b/js/src/tests/test262/language/expressions/assignment/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js
new file mode 100644
index 000000000..d0b80f742
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/assignment/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js
@@ -0,0 +1,60 @@
+// |reftest| error:SyntaxError
+// This file was procedurally generated from the following sources:
+// - src/dstr-assignment/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.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, { x: {
+ set y(val) {
+ throw new Test262Error('The property should not be accessed.');
+ }
+}?.y = 42} = {x: 42};
diff --git a/js/src/tests/test262/language/expressions/assignment/dstr/shell.js b/js/src/tests/test262/language/expressions/assignment/dstr/shell.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/assignment/dstr/shell.js
diff --git a/js/src/tests/test262/language/expressions/assignment/shell.js b/js/src/tests/test262/language/expressions/assignment/shell.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/assignment/shell.js
diff --git a/js/src/tests/test262/language/expressions/shell.js b/js/src/tests/test262/language/expressions/shell.js
new file mode 100644
index 000000000..9192cb7bf
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/shell.js
@@ -0,0 +1,16 @@
+// GENERATED, DO NOT EDIT
+// file: tcoHelper.js
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines the number of consecutive recursive function calls that must be
+ made in order to prove that stack frames are properly destroyed according to
+ ES2015 tail call optimization semantics.
+defines: [$MAX_ITERATIONS]
+---*/
+
+
+
+
+var $MAX_ITERATIONS = 100000; \ No newline at end of file
diff --git a/js/src/tests/test262/language/optional-chaining/call-expression-super-no-base.js b/js/src/tests/test262/language/optional-chaining/call-expression-super-no-base.js
new file mode 100644
index 000000000..da3a634bd
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/call-expression-super-no-base.js
@@ -0,0 +1,24 @@
+// |reftest| error:SyntaxError
+// 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:
+ phase: parse
+ type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+class C {
+ constructor () {
+ super()?.a;
+ }
+}
diff --git a/js/src/tests/test262/language/optional-chaining/call-expression.js b/js/src/tests/test262/language/optional-chaining/call-expression.js
new file mode 100644
index 000000000..aa3f4abeb
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/call-expression.js
@@ -0,0 +1,77 @@
+// 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-op-template-string-esi.js b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-op-template-string-esi.js
new file mode 100644
index 000000000..ae830b130
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-op-template-string-esi.js
@@ -0,0 +1,26 @@
+// |reftest| error:SyntaxError
+// 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:
+ phase: parse
+ type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+// This production exists in order to prevent automatic semicolon
+// insertion rules.
+null?.
+ `hello`
diff --git a/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-op-template-string.js b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-op-template-string.js
new file mode 100644
index 000000000..cb8361cd0
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-op-template-string.js
@@ -0,0 +1,23 @@
+// |reftest| error:SyntaxError
+// 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:
+ phase: parse
+ type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+null?.`hello`;
diff --git a/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-optchain-template-string-esi.js b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-optchain-template-string-esi.js
new file mode 100644
index 000000000..9c992b00d
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-optchain-template-string-esi.js
@@ -0,0 +1,26 @@
+// |reftest| error:SyntaxError
+// 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:
+ phase: parse
+ type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+// This production exists in order to prevent automatic semicolon
+// insertion rules.
+null?.fn
+ `hello`
diff --git a/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-optchain-template-string.js b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-optchain-template-string.js
new file mode 100644
index 000000000..a74ca1cf3
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-optchain-template-string.js
@@ -0,0 +1,23 @@
+// |reftest| error:SyntaxError
+// 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:
+ phase: parse
+ type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+null?.fn`hello`;
diff --git a/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-op-template-string-esi.js b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-op-template-string-esi.js
new file mode 100644
index 000000000..c1ec6d707
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-op-template-string-esi.js
@@ -0,0 +1,28 @@
+// |reftest| error:SyntaxError
+// 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:
+ phase: parse
+ type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+const a = function() {};
+
+// This production exists in order to prevent automatic semicolon
+// insertion rules.
+a?.
+ `hello`
diff --git a/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-op-template-string.js b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-op-template-string.js
new file mode 100644
index 000000000..043bfb3da
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-op-template-string.js
@@ -0,0 +1,25 @@
+// |reftest| error:SyntaxError
+// 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:
+ phase: parse
+ type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+const a = function() {};
+
+a?.`hello`;
diff --git a/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-optchain-template-string-esi.js b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-optchain-template-string-esi.js
new file mode 100644
index 000000000..1aefaaec2
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-optchain-template-string-esi.js
@@ -0,0 +1,28 @@
+// |reftest| error:SyntaxError
+// 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:
+ phase: parse
+ type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+const a = {fn() {}};
+
+// This production exists in order to prevent automatic semicolon
+// insertion rules.
+a?.fn
+ `hello`
diff --git a/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-optchain-template-string.js b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-optchain-template-string.js
new file mode 100644
index 000000000..277048e1a
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/early-errors-tail-position-optchain-template-string.js
@@ -0,0 +1,25 @@
+// |reftest| error:SyntaxError
+// 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:
+ phase: parse
+ type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+const a = {fn() {}};
+
+a?.fn`hello`;
diff --git a/js/src/tests/test262/language/optional-chaining/eval-optional-call.js b/js/src/tests/test262/language/optional-chaining/eval-optional-call.js
new file mode 100644
index 000000000..8ff480056
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/eval-optional-call.js
@@ -0,0 +1,41 @@
+// 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');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/iteration-statement-do.js b/js/src/tests/test262/language/optional-chaining/iteration-statement-do.js
new file mode 100644
index 000000000..470f067b2
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/iteration-statement-do.js
@@ -0,0 +1,20 @@
+// 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/iteration-statement-for-await-of.js b/js/src/tests/test262/language/optional-chaining/iteration-statement-for-await-of.js
new file mode 100644
index 000000000..2f668fd36
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/iteration-statement-for-await-of.js
@@ -0,0 +1,36 @@
+// |reftest| async
+// 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/js/src/tests/test262/language/optional-chaining/iteration-statement-for-in.js b/js/src/tests/test262/language/optional-chaining/iteration-statement-for-in.js
new file mode 100644
index 000000000..6774675f1
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/iteration-statement-for-in.js
@@ -0,0 +1,24 @@
+// 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/iteration-statement-for-of-type-error.js b/js/src/tests/test262/language/optional-chaining/iteration-statement-for-of-type-error.js
new file mode 100644
index 000000000..938e10d8d
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/iteration-statement-for-of-type-error.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 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);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/iteration-statement-for.js b/js/src/tests/test262/language/optional-chaining/iteration-statement-for.js
new file mode 100644
index 000000000..cb884d486
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/iteration-statement-for.js
@@ -0,0 +1,45 @@
+// 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/iteration-statement-while.js b/js/src/tests/test262/language/optional-chaining/iteration-statement-while.js
new file mode 100644
index 000000000..139031589
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/iteration-statement-while.js
@@ -0,0 +1,20 @@
+// 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/member-expression-async-identifier.js b/js/src/tests/test262/language/optional-chaining/member-expression-async-identifier.js
new file mode 100644
index 000000000..a0abb209d
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/member-expression-async-identifier.js
@@ -0,0 +1,33 @@
+// |reftest| async
+// 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/js/src/tests/test262/language/optional-chaining/member-expression-async-literal.js b/js/src/tests/test262/language/optional-chaining/member-expression-async-literal.js
new file mode 100644
index 000000000..4bdeb447d
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/member-expression-async-literal.js
@@ -0,0 +1,20 @@
+// |reftest| async
+// 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/js/src/tests/test262/language/optional-chaining/member-expression-async-this.js b/js/src/tests/test262/language/optional-chaining/member-expression-async-this.js
new file mode 100644
index 000000000..5de87fa9b
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/member-expression-async-this.js
@@ -0,0 +1,21 @@
+// |reftest| async
+// 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/js/src/tests/test262/language/optional-chaining/member-expression.js b/js/src/tests/test262/language/optional-chaining/member-expression.js
new file mode 100644
index 000000000..4854182c7
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/member-expression.js
@@ -0,0 +1,106 @@
+// 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));
+// AsyncGeneratorExpression
+assert.sameValue('a', (async function * a () {}?.name));
+// 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/new-target-optional-call.js b/js/src/tests/test262/language/optional-chaining/new-target-optional-call.js
new file mode 100644
index 000000000..df05a1150
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/new-target-optional-call.js
@@ -0,0 +1,32 @@
+// 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/optional-call-preserves-this.js b/js/src/tests/test262/language/optional-chaining/optional-call-preserves-this.js
new file mode 100644
index 000000000..dbaf92c3b
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/optional-call-preserves-this.js
@@ -0,0 +1,29 @@
+// 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/optional-chain-async-optional-chain-square-brackets.js b/js/src/tests/test262/language/optional-chaining/optional-chain-async-optional-chain-square-brackets.js
new file mode 100644
index 000000000..b47b2aeee
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/optional-chain-async-optional-chain-square-brackets.js
@@ -0,0 +1,29 @@
+// |reftest| async
+// 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/js/src/tests/test262/language/optional-chaining/optional-chain-async-square-brackets.js b/js/src/tests/test262/language/optional-chaining/optional-chain-async-square-brackets.js
new file mode 100644
index 000000000..38cdab73f
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/optional-chain-async-square-brackets.js
@@ -0,0 +1,25 @@
+// |reftest| async
+// 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/js/src/tests/test262/language/optional-chaining/optional-chain-expression-optional-expression.js b/js/src/tests/test262/language/optional-chaining/optional-chain-expression-optional-expression.js
new file mode 100644
index 000000000..be898b876
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/optional-chain-expression-optional-expression.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: >
+ 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]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/optional-chain-prod-arguments.js b/js/src/tests/test262/language/optional-chaining/optional-chain-prod-arguments.js
new file mode 100644
index 000000000..c9d987407
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/optional-chain-prod-arguments.js
@@ -0,0 +1,21 @@
+// 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');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/optional-chain-prod-expression.js b/js/src/tests/test262/language/optional-chaining/optional-chain-prod-expression.js
new file mode 100644
index 000000000..dfde4d26c
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/optional-chain-prod-expression.js
@@ -0,0 +1,44 @@
+// 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]');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/optional-chain-prod-identifiername.js b/js/src/tests/test262/language/optional-chaining/optional-chain-prod-identifiername.js
new file mode 100644
index 000000000..2636caf31
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/optional-chain-prod-identifiername.js
@@ -0,0 +1,40 @@
+// 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/optional-chain.js b/js/src/tests/test262/language/optional-chaining/optional-chain.js
new file mode 100644
index 000000000..b8dd18309
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/optional-chain.js
@@ -0,0 +1,52 @@
+// 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));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/optional-expression.js b/js/src/tests/test262/language/optional-chaining/optional-expression.js
new file mode 100644
index 000000000..38cba72d2
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/optional-expression.js
@@ -0,0 +1,29 @@
+// 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/punctuator-decimal-lookahead.js b/js/src/tests/test262/language/optional-chaining/punctuator-decimal-lookahead.js
new file mode 100644
index 000000000..ec64f9201
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/punctuator-decimal-lookahead.js
@@ -0,0 +1,17 @@
+// 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/runtime-semantics-evaluation.js b/js/src/tests/test262/language/optional-chaining/runtime-semantics-evaluation.js
new file mode 100644
index 000000000..a87af5785
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/runtime-semantics-evaluation.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: >
+ 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/shell.js b/js/src/tests/test262/language/optional-chaining/shell.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/shell.js
diff --git a/js/src/tests/test262/language/optional-chaining/short-circuiting.js b/js/src/tests/test262/language/optional-chaining/short-circuiting.js
new file mode 100644
index 000000000..74295cb1e
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/short-circuiting.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: >
+ 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/static-semantics-simple-assignment.js b/js/src/tests/test262/language/optional-chaining/static-semantics-simple-assignment.js
new file mode 100644
index 000000000..cbbcedba5
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/static-semantics-simple-assignment.js
@@ -0,0 +1,24 @@
+// |reftest| error:SyntaxError
+
+// 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:
+ phase: parse
+ type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+const obj = {};
+
+obj?.a = 33;
diff --git a/js/src/tests/test262/language/optional-chaining/super-property-optional-call.js b/js/src/tests/test262/language/optional-chaining/super-property-optional-call.js
new file mode 100644
index 000000000..21d1635ec
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/super-property-optional-call.js
@@ -0,0 +1,32 @@
+// 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);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/optional-chaining/update-expression-postfix.js b/js/src/tests/test262/language/optional-chaining/update-expression-postfix.js
new file mode 100644
index 000000000..8b8fc68f9
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/update-expression-postfix.js
@@ -0,0 +1,24 @@
+// |reftest| error:SyntaxError
+// 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:
+ phase: parse
+ type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+// LeftHandSideExpression ++
+const a = {};
+a?.b++;
diff --git a/js/src/tests/test262/language/optional-chaining/update-expression-prefix.js b/js/src/tests/test262/language/optional-chaining/update-expression-prefix.js
new file mode 100644
index 000000000..ba65aadc0
--- /dev/null
+++ b/js/src/tests/test262/language/optional-chaining/update-expression-prefix.js
@@ -0,0 +1,24 @@
+// |reftest| error:SyntaxError
+// 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:
+ phase: parse
+ type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+// --UnaryExpression
+const a = {};
+--a?.b;
diff --git a/js/src/tests/test262/language/statements/for-in/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js b/js/src/tests/test262/language/statements/for-in/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js
new file mode 100644
index 000000000..f07643b8f
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-in/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js
@@ -0,0 +1,66 @@
+// |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/for-in.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 Initializer) (For..in statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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 = {};
+
+for ([x?.y = 42] in [[23]]) ;
diff --git a/js/src/tests/test262/language/statements/for-in/dstr/array-elem-nested-memberexpr-optchain-prop-ref.js b/js/src/tests/test262/language/statements/for-in/dstr/array-elem-nested-memberexpr-optchain-prop-ref.js
new file mode 100644
index 000000000..179c4f199
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-in/dstr/array-elem-nested-memberexpr-optchain-prop-ref.js
@@ -0,0 +1,66 @@
+// |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/for-in.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) (For..in statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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 = {};
+
+for ([x?.y] in [[23]]) ;
diff --git a/js/src/tests/test262/language/statements/for-in/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js b/js/src/tests/test262/language/statements/for-in/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js
new file mode 100644
index 000000000..1b05d6fd9
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-in/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js
@@ -0,0 +1,69 @@
+// |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/for-in.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 (For..in statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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();
+
+for ([{
+ set y(val) {
+ throw new Test262Error('The property should not be accessed.');
+ }
+}?.y = 42] in [[23]]) ;
diff --git a/js/src/tests/test262/language/statements/for-in/dstr/array-elem-put-obj-literal-optchain-prop-ref.js b/js/src/tests/test262/language/statements/for-in/dstr/array-elem-put-obj-literal-optchain-prop-ref.js
new file mode 100644
index 000000000..1f7780136
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-in/dstr/array-elem-put-obj-literal-optchain-prop-ref.js
@@ -0,0 +1,69 @@
+// |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/for-in.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 (For..in statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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();
+
+for ([{
+ set y(val) {
+ throw new Test262Error('The property should not be accessed.');
+ }
+}?.y] in [[23]]) ;
diff --git a/js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js b/js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js
new file mode 100644
index 000000000..91c8913af
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js
@@ -0,0 +1,66 @@
+// |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/for-in.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 Initializer) (For..in statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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 y = {};
+
+for ({ x: y?.z = 42 } in [{ x: 23 }]) ;
diff --git a/js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref.js b/js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref.js
new file mode 100644
index 000000000..be0b5b7f3
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref.js
@@ -0,0 +1,66 @@
+// |reftest| error:SyntaxError
+// This file was procedurally generated from the following sources:
+// - src/dstr-assignment/obj-prop-elem-target-memberexpr-optchain-prop-ref.case
+// - src/dstr-assignment/syntax/for-in.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) (For..in statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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 y = {};
+
+for ({ x: y?.z } in [{ x: 23 }]) ;
diff --git a/js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js b/js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js
new file mode 100644
index 000000000..ba16f696e
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js
@@ -0,0 +1,69 @@
+// |reftest| error:SyntaxError
+// This file was procedurally generated from the following sources:
+// - src/dstr-assignment/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.case
+// - src/dstr-assignment/syntax/for-in.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 (For..in statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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();
+
+for ({ x: {
+ set y(val) {
+ throw new Test262Error('The property should not be accessed.');
+ }
+}?.y = 42} in [{x: 42}]) ;
diff --git a/js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref.js b/js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref.js
new file mode 100644
index 000000000..c4e02dacb
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref.js
@@ -0,0 +1,69 @@
+// |reftest| error:SyntaxError
+// This file was procedurally generated from the following sources:
+// - src/dstr-assignment/obj-prop-elem-target-obj-literal-optchain-prop-ref.case
+// - src/dstr-assignment/syntax/for-in.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 (For..in statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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();
+
+for ({ x: {
+ set y(val) {
+ throw new Test262Error('The property should not be accessed.');
+ }
+}?.y} in [{x: 42}]) ;
diff --git a/js/src/tests/test262/language/statements/for-in/dstr/shell.js b/js/src/tests/test262/language/statements/for-in/dstr/shell.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-in/dstr/shell.js
diff --git a/js/src/tests/test262/language/statements/for-in/shell.js b/js/src/tests/test262/language/statements/for-in/shell.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-in/shell.js
diff --git a/js/src/tests/test262/language/statements/for-of/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js b/js/src/tests/test262/language/statements/for-of/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js
new file mode 100644
index 000000000..7c69c77bb
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-of/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js
@@ -0,0 +1,66 @@
+// |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/for-of.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 Initializer) (For..of statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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 = {};
+
+for ([x?.y = 42] of [[23]]) ;
diff --git a/js/src/tests/test262/language/statements/for-of/dstr/array-elem-nested-memberexpr-optchain-prop-ref.js b/js/src/tests/test262/language/statements/for-of/dstr/array-elem-nested-memberexpr-optchain-prop-ref.js
new file mode 100644
index 000000000..6b9072f88
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-of/dstr/array-elem-nested-memberexpr-optchain-prop-ref.js
@@ -0,0 +1,66 @@
+// |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/for-of.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) (For..of statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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 = {};
+
+for ([x?.y] of [[23]]) ;
diff --git a/js/src/tests/test262/language/statements/for-of/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js b/js/src/tests/test262/language/statements/for-of/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js
new file mode 100644
index 000000000..b83831dde
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-of/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js
@@ -0,0 +1,69 @@
+// |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/for-of.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 (For..of statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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();
+
+for ([{
+ set y(val) {
+ throw new Test262Error('The property should not be accessed.');
+ }
+}?.y = 42] of [[23]]) ;
diff --git a/js/src/tests/test262/language/statements/for-of/dstr/array-elem-put-obj-literal-optchain-prop-ref.js b/js/src/tests/test262/language/statements/for-of/dstr/array-elem-put-obj-literal-optchain-prop-ref.js
new file mode 100644
index 000000000..f15ea9f5e
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-of/dstr/array-elem-put-obj-literal-optchain-prop-ref.js
@@ -0,0 +1,69 @@
+// |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/for-of.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 (For..of statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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();
+
+for ([{
+ set y(val) {
+ throw new Test262Error('The property should not be accessed.');
+ }
+}?.y] of [[23]]) ;
diff --git a/js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js b/js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js
new file mode 100644
index 000000000..92718cc18
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js
@@ -0,0 +1,66 @@
+// |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/for-of.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 Initializer) (For..of statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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 y = {};
+
+for ({ x: y?.z = 42 } of [{ x: 23 }]) ;
diff --git a/js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref.js b/js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref.js
new file mode 100644
index 000000000..211d287a3
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref.js
@@ -0,0 +1,66 @@
+// |reftest| error:SyntaxError
+// This file was procedurally generated from the following sources:
+// - src/dstr-assignment/obj-prop-elem-target-memberexpr-optchain-prop-ref.case
+// - src/dstr-assignment/syntax/for-of.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) (For..of statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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 y = {};
+
+for ({ x: y?.z } of [{ x: 23 }]) ;
diff --git a/js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js b/js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js
new file mode 100644
index 000000000..630f73e9b
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js
@@ -0,0 +1,69 @@
+// |reftest| error:SyntaxError
+// This file was procedurally generated from the following sources:
+// - src/dstr-assignment/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.case
+// - src/dstr-assignment/syntax/for-of.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 (For..of statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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();
+
+for ({ x: {
+ set y(val) {
+ throw new Test262Error('The property should not be accessed.');
+ }
+}?.y = 42} of [{x: 42}]) ;
diff --git a/js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref.js b/js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref.js
new file mode 100644
index 000000000..b0c4d7177
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref.js
@@ -0,0 +1,69 @@
+// |reftest| error:SyntaxError
+// This file was procedurally generated from the following sources:
+// - src/dstr-assignment/obj-prop-elem-target-obj-literal-optchain-prop-ref.case
+// - src/dstr-assignment/syntax/for-of.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 (For..of statement)
+esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation
+features: [optional-chaining, destructuring-binding]
+flags: [generated]
+negative:
+ phase: parse
+ type: SyntaxError
+info: |
+ IterationStatement :
+ for ( LeftHandSideExpression of AssignmentExpression ) Statement
+
+ 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« »,
+ AssignmentExpression, iterate).
+ 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement,
+ keyResult, assignment, labelSet).
+
+ 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation
+
+ [...]
+ 4. If destructuring is true and if lhsKind is assignment, then
+ a. Assert: lhs is a LeftHandSideExpression.
+ b. Let assignmentPattern be the parse of the source text corresponding to
+ lhs using AssignmentPattern as the goal symbol.
+ [...]
+
+ 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();
+
+for ({ x: {
+ set y(val) {
+ throw new Test262Error('The property should not be accessed.');
+ }
+}?.y} of [{x: 42}]) ;
diff --git a/js/src/tests/test262/language/statements/for-of/dstr/shell.js b/js/src/tests/test262/language/statements/for-of/dstr/shell.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-of/dstr/shell.js
diff --git a/js/src/tests/test262/language/statements/for-of/shell.js b/js/src/tests/test262/language/statements/for-of/shell.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/js/src/tests/test262/language/statements/for-of/shell.js
diff --git a/js/src/tests/test262/language/statements/shell.js b/js/src/tests/test262/language/statements/shell.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/js/src/tests/test262/language/statements/shell.js