From 2678e7f06f0e13e617046bd270f25b8358be5f15 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Thu, 5 May 2022 16:49:07 -0500 Subject: [JS:Engine] Implement the Optional Chaining operator (?.) --- js/src/builtin/ReflectParse.cpp | 91 +- js/src/frontend/BytecodeEmitter.cpp | 1095 ++++++++++++++++++-- js/src/frontend/BytecodeEmitter.h | 54 +- js/src/frontend/FoldConstants.cpp | 33 +- js/src/frontend/FullParseHandler.h | 37 +- js/src/frontend/ParseNode.cpp | 5 + js/src/frontend/ParseNode.h | 108 +- js/src/frontend/Parser.cpp | 369 +++++-- js/src/frontend/Parser.h | 17 + js/src/frontend/SourceNotes.h | 2 +- js/src/frontend/SyntaxParseHandler.h | 21 +- js/src/frontend/TokenKind.h | 1 + js/src/frontend/TokenStream.cpp | 21 +- js/src/jit-test/tests/basic/bug1644839-2.js | 5 + js/src/jit-test/tests/basic/bug1644839.js | 5 + .../tests/optional-chain/call-ignore-rval.js | 34 + .../tests/optional-chain/fun-call-or-apply.js | 60 ++ js/src/jit/IonBuilder.cpp | 6 + js/src/js.msg | 2 + js/src/jsast.tbl | 4 + js/src/jsopcode.h | 24 + js/src/tests/non262/expressions/browser.js | 0 .../expressions/optional-chain-class-heritage.js | 10 + .../expressions/optional-chain-first-expression.js | 92 ++ .../expressions/optional-chain-super-elem.js | 12 + .../tests/non262/expressions/optional-chain-tdz.js | 28 + js/src/tests/non262/expressions/optional-chain.js | 223 ++++ js/src/tests/non262/expressions/shell.js | 0 js/src/tests/non262/shell.js | 0 ...lem-nested-memberexpr-optchain-prop-ref-init.js | 57 + ...-elem-put-obj-literal-optchain-prop-ref-init.js | 60 ++ ...lem-target-memberexpr-optchain-prop-ref-init.js | 57 + ...em-target-obj-literal-optchain-prop-ref-init.js | 60 ++ .../language/expressions/assignment/dstr/shell.js | 0 .../language/expressions/assignment/shell.js | 0 js/src/tests/test262/language/expressions/shell.js | 16 + .../call-expression-super-no-base.js | 24 + .../language/optional-chaining/call-expression.js | 77 ++ ...rs-tail-position-null-op-template-string-esi.js | 26 + ...errors-tail-position-null-op-template-string.js | 23 + ...l-position-null-optchain-template-string-esi.js | 26 + ...-tail-position-null-optchain-template-string.js | 23 + ...-errors-tail-position-op-template-string-esi.js | 28 + ...arly-errors-tail-position-op-template-string.js | 25 + ...s-tail-position-optchain-template-string-esi.js | 28 + ...rrors-tail-position-optchain-template-string.js | 25 + .../optional-chaining/eval-optional-call.js | 41 + .../optional-chaining/iteration-statement-do.js | 20 + .../iteration-statement-for-await-of.js | 36 + .../iteration-statement-for-in.js | 24 + .../iteration-statement-for-of-type-error.js | 30 + .../optional-chaining/iteration-statement-for.js | 45 + .../optional-chaining/iteration-statement-while.js | 20 + .../member-expression-async-identifier.js | 33 + .../member-expression-async-literal.js | 20 + .../member-expression-async-this.js | 21 + .../optional-chaining/member-expression.js | 106 ++ .../optional-chaining/new-target-optional-call.js | 32 + .../optional-call-preserves-this.js | 29 + ...l-chain-async-optional-chain-square-brackets.js | 29 + .../optional-chain-async-square-brackets.js | 25 + ...ptional-chain-expression-optional-expression.js | 22 + .../optional-chain-prod-arguments.js | 21 + .../optional-chain-prod-expression.js | 44 + .../optional-chain-prod-identifiername.js | 40 + .../language/optional-chaining/optional-chain.js | 52 + .../optional-chaining/optional-expression.js | 29 + .../punctuator-decimal-lookahead.js | 17 + .../runtime-semantics-evaluation.js | 20 + .../test262/language/optional-chaining/shell.js | 0 .../language/optional-chaining/short-circuiting.js | 24 + .../static-semantics-simple-assignment.js | 24 + .../super-property-optional-call.js | 32 + .../optional-chaining/update-expression-postfix.js | 24 + .../optional-chaining/update-expression-prefix.js | 24 + ...lem-nested-memberexpr-optchain-prop-ref-init.js | 66 ++ ...ray-elem-nested-memberexpr-optchain-prop-ref.js | 66 ++ ...-elem-put-obj-literal-optchain-prop-ref-init.js | 69 ++ ...array-elem-put-obj-literal-optchain-prop-ref.js | 69 ++ ...lem-target-memberexpr-optchain-prop-ref-init.js | 66 ++ ...rop-elem-target-memberexpr-optchain-prop-ref.js | 66 ++ ...em-target-obj-literal-optchain-prop-ref-init.js | 69 ++ ...op-elem-target-obj-literal-optchain-prop-ref.js | 69 ++ .../language/statements/for-in/dstr/shell.js | 0 .../test262/language/statements/for-in/shell.js | 0 ...lem-nested-memberexpr-optchain-prop-ref-init.js | 66 ++ ...ray-elem-nested-memberexpr-optchain-prop-ref.js | 66 ++ ...-elem-put-obj-literal-optchain-prop-ref-init.js | 69 ++ ...array-elem-put-obj-literal-optchain-prop-ref.js | 69 ++ ...lem-target-memberexpr-optchain-prop-ref-init.js | 66 ++ ...rop-elem-target-memberexpr-optchain-prop-ref.js | 66 ++ ...em-target-obj-literal-optchain-prop-ref-init.js | 69 ++ ...op-elem-target-obj-literal-optchain-prop-ref.js | 69 ++ .../language/statements/for-of/dstr/shell.js | 0 .../test262/language/statements/for-of/shell.js | 0 js/src/tests/test262/language/statements/shell.js | 0 96 files changed, 4654 insertions(+), 224 deletions(-) create mode 100644 js/src/jit-test/tests/basic/bug1644839-2.js create mode 100644 js/src/jit-test/tests/basic/bug1644839.js create mode 100644 js/src/jit-test/tests/optional-chain/call-ignore-rval.js create mode 100644 js/src/jit-test/tests/optional-chain/fun-call-or-apply.js create mode 100644 js/src/tests/non262/expressions/browser.js create mode 100644 js/src/tests/non262/expressions/optional-chain-class-heritage.js create mode 100644 js/src/tests/non262/expressions/optional-chain-first-expression.js create mode 100644 js/src/tests/non262/expressions/optional-chain-super-elem.js create mode 100644 js/src/tests/non262/expressions/optional-chain-tdz.js create mode 100644 js/src/tests/non262/expressions/optional-chain.js create mode 100644 js/src/tests/non262/expressions/shell.js create mode 100644 js/src/tests/non262/shell.js create mode 100644 js/src/tests/test262/language/expressions/assignment/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js create mode 100644 js/src/tests/test262/language/expressions/assignment/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js create mode 100644 js/src/tests/test262/language/expressions/assignment/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js create mode 100644 js/src/tests/test262/language/expressions/assignment/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js create mode 100644 js/src/tests/test262/language/expressions/assignment/dstr/shell.js create mode 100644 js/src/tests/test262/language/expressions/assignment/shell.js create mode 100644 js/src/tests/test262/language/expressions/shell.js create mode 100644 js/src/tests/test262/language/optional-chaining/call-expression-super-no-base.js create mode 100644 js/src/tests/test262/language/optional-chaining/call-expression.js create mode 100644 js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-op-template-string-esi.js create mode 100644 js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-op-template-string.js create mode 100644 js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-optchain-template-string-esi.js create mode 100644 js/src/tests/test262/language/optional-chaining/early-errors-tail-position-null-optchain-template-string.js create mode 100644 js/src/tests/test262/language/optional-chaining/early-errors-tail-position-op-template-string-esi.js create mode 100644 js/src/tests/test262/language/optional-chaining/early-errors-tail-position-op-template-string.js create mode 100644 js/src/tests/test262/language/optional-chaining/early-errors-tail-position-optchain-template-string-esi.js create mode 100644 js/src/tests/test262/language/optional-chaining/early-errors-tail-position-optchain-template-string.js create mode 100644 js/src/tests/test262/language/optional-chaining/eval-optional-call.js create mode 100644 js/src/tests/test262/language/optional-chaining/iteration-statement-do.js create mode 100644 js/src/tests/test262/language/optional-chaining/iteration-statement-for-await-of.js create mode 100644 js/src/tests/test262/language/optional-chaining/iteration-statement-for-in.js create mode 100644 js/src/tests/test262/language/optional-chaining/iteration-statement-for-of-type-error.js create mode 100644 js/src/tests/test262/language/optional-chaining/iteration-statement-for.js create mode 100644 js/src/tests/test262/language/optional-chaining/iteration-statement-while.js create mode 100644 js/src/tests/test262/language/optional-chaining/member-expression-async-identifier.js create mode 100644 js/src/tests/test262/language/optional-chaining/member-expression-async-literal.js create mode 100644 js/src/tests/test262/language/optional-chaining/member-expression-async-this.js create mode 100644 js/src/tests/test262/language/optional-chaining/member-expression.js create mode 100644 js/src/tests/test262/language/optional-chaining/new-target-optional-call.js create mode 100644 js/src/tests/test262/language/optional-chaining/optional-call-preserves-this.js create mode 100644 js/src/tests/test262/language/optional-chaining/optional-chain-async-optional-chain-square-brackets.js create mode 100644 js/src/tests/test262/language/optional-chaining/optional-chain-async-square-brackets.js create mode 100644 js/src/tests/test262/language/optional-chaining/optional-chain-expression-optional-expression.js create mode 100644 js/src/tests/test262/language/optional-chaining/optional-chain-prod-arguments.js create mode 100644 js/src/tests/test262/language/optional-chaining/optional-chain-prod-expression.js create mode 100644 js/src/tests/test262/language/optional-chaining/optional-chain-prod-identifiername.js create mode 100644 js/src/tests/test262/language/optional-chaining/optional-chain.js create mode 100644 js/src/tests/test262/language/optional-chaining/optional-expression.js create mode 100644 js/src/tests/test262/language/optional-chaining/punctuator-decimal-lookahead.js create mode 100644 js/src/tests/test262/language/optional-chaining/runtime-semantics-evaluation.js create mode 100644 js/src/tests/test262/language/optional-chaining/shell.js create mode 100644 js/src/tests/test262/language/optional-chaining/short-circuiting.js create mode 100644 js/src/tests/test262/language/optional-chaining/static-semantics-simple-assignment.js create mode 100644 js/src/tests/test262/language/optional-chaining/super-property-optional-call.js create mode 100644 js/src/tests/test262/language/optional-chaining/update-expression-postfix.js create mode 100644 js/src/tests/test262/language/optional-chaining/update-expression-prefix.js create mode 100644 js/src/tests/test262/language/statements/for-in/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js create mode 100644 js/src/tests/test262/language/statements/for-in/dstr/array-elem-nested-memberexpr-optchain-prop-ref.js create mode 100644 js/src/tests/test262/language/statements/for-in/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js create mode 100644 js/src/tests/test262/language/statements/for-in/dstr/array-elem-put-obj-literal-optchain-prop-ref.js create mode 100644 js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js create mode 100644 js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref.js create mode 100644 js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js create mode 100644 js/src/tests/test262/language/statements/for-in/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref.js create mode 100644 js/src/tests/test262/language/statements/for-in/dstr/shell.js create mode 100644 js/src/tests/test262/language/statements/for-in/shell.js create mode 100644 js/src/tests/test262/language/statements/for-of/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js create mode 100644 js/src/tests/test262/language/statements/for-of/dstr/array-elem-nested-memberexpr-optchain-prop-ref.js create mode 100644 js/src/tests/test262/language/statements/for-of/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js create mode 100644 js/src/tests/test262/language/statements/for-of/dstr/array-elem-put-obj-literal-optchain-prop-ref.js create mode 100644 js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js create mode 100644 js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref.js create mode 100644 js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref-init.js create mode 100644 js/src/tests/test262/language/statements/for-of/dstr/obj-prop-elem-target-obj-literal-optchain-prop-ref.js create mode 100644 js/src/tests/test262/language/statements/for-of/dstr/shell.js create mode 100644 js/src/tests/test262/language/statements/for-of/shell.js create mode 100644 js/src/tests/test262/language/statements/shell.js 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); } @@ -1231,6 +1244,22 @@ NodeBuilder::spreadExpression(HandleValue expr, TokenPos* pos, MutableHandleValu dst); } +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().isSuper()) { + bool isSuper = pn->is() && + pn->as().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().isSuper()) { + bool isSuper = pn->is() && + pn->as().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(); + 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(); + 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(), + !propExpr->as().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(), + !elemExpr->as().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(); + if (!emitOptionalDotExpression(prop, oe, calleeNode, isCall)) { + return false; + } + break; + } + case PNK_DOT: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PropertyAccess* prop = &calleeNode->as(); + if (!emitOptionalDotExpression(prop, oe, calleeNode, isCall)) { + return false; + } + break; + } + case PNK_OPTELEM: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + OptionalPropertyByValue* elem = &calleeNode->as(); + if (!emitOptionalElemExpression(elem, oe, calleeNode, isCall)) { + return false; + } + break; + } + case PNK_ELEM: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PropertyByValue* elem = &calleeNode->as(); + 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().isSuper()) { - if (!emitSuperPropOp(pn2, JSOP_GETPROP_SUPER, /* isCall = */ callop)) + if (calleeNode->as().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().isSuper()) { - if (!emitSuperElemOp(pn2, JSOP_GETELEM_SUPER, /* isCall = */ callop)) + if (calleeNode->as().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().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(); + if (!emitOptionalDotExpression(prop, oe, pn, false)) { + return false; + } + break; + } + case PNK_DOT: { + PropertyAccess* prop = &pn->as(); + if (!emitOptionalDotExpression(prop, oe, pn, false)) { + return false; + } + break; + } + case PNK_OPTELEM: { + OptionalPropertyByValue* elem = &pn->as(); + if (!emitOptionalElemExpression(elem, oe, pn, false)) { + return false; + } + break; + } + case PNK_ELEM: { + PropertyByValue* elem = &pn->as(); + 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() && + prop->as().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() && + elem->as().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& { 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& // 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& 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& 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& 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& 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& 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& 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& 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_(PNK_AWAIT, JSOP_AWAIT, pos, value); } + ParseNode* newOptionalChain(uint32_t begin, ParseNode* value) { + TokenPos pos(begin, value->pn_pos.end); + return new_(PNK_OPTCHAIN, JSOP_NOP, pos, value); + } + // Statements ParseNode* newStatementList(const TokenPos& pos) { @@ -678,6 +703,14 @@ class FullParseHandler return new_(lhs, index, lhs->pn_pos.begin, end); } + ParseNode* newOptionalPropertyAccess(ParseNode* pn, PropertyName* name, uint32_t end) { + return new_(pn, name, pn->pn_pos.begin, end); + } + + ParseNode* newOptionalPropertyByValue(ParseNode* lhs, ParseNode* index, uint32_t end) { + return new_(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() ? &pn->as().name() : nullptr; + return pn->is() ? + &pn->as().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,16 +1262,49 @@ 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)); @@ -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::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(); } @@ -8361,6 +8361,108 @@ Parser::unaryOpExpr(YieldHandling yieldHandling, ParseNodeKind kin return handler.newUnary(kind, op, begin, kid); } +template +typename ParseHandler::Node +Parser::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::Node Parser::unaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling, @@ -8412,7 +8514,7 @@ Parser::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::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::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::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::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::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::newName(PropertyName* name, TokenPos pos) return handler.newName(name, pos, context); } +template +typename ParseHandler::Node +Parser::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 +typename ParseHandler::Node +Parser::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 +typename ParseHandler::Node +Parser::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 bool Parser::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 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 @@ -699,6 +699,30 @@ IsEqualityOp(JSOp op) return op == JSOP_EQ || op == JSOP_NE || op == JSOP_STRICTEQ || op == JSOP_STRICTNE; } +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) { 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 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 diff --git a/js/src/tests/non262/shell.js b/js/src/tests/non262/shell.js new file mode 100644 index 000000000..e69de29bb 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 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 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 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 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 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 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 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 -- cgit v1.2.3