Blob Blame History Raw
#
#  Manual backport of optchain support, preliminary part:
#
#    bug 1566143 part 1
#    bug 1566143 part 2
#

diff -Nrup mozilla-OLD/js/src/frontend/BytecodeEmitter.cpp mozilla/js/src/frontend/BytecodeEmitter.cpp
--- mozilla-OLD/js/src/frontend/BytecodeEmitter.cpp	2022-01-10 07:20:08.134341896 +0300
+++ mozilla/js/src/frontend/BytecodeEmitter.cpp	2022-01-10 07:21:07.212924429 +0300
@@ -9604,6 +9604,42 @@ BytecodeEmitter::emitPipeline(ParseNode*
     return true;
 }
 
+ParseNode* BytecodeEmitter::getCoordNode(ParseNode* pn,
+                                         ParseNode* calleeNode,
+                                         ParseNode* argsList) {
+    ParseNode* coordNode = pn;
+    if (pn->isOp(JSOP_CALL) || pn->isOp(JSOP_SPREADCALL) || pn->isOp(JSOP_FUNCALL) ||
+        pn->isOp(JSOP_FUNAPPLY)) {
+        // Default to using the location of the `(` itself.
+        // obj[expr]() // expression
+        //          ^  // column coord
+        coordNode = argsList;
+
+        switch (calleeNode->getKind()) {
+          case ParseNodeKind::Dot:
+            // Use the position of a property access identifier.
+            //
+            // obj().aprop() // expression
+            //       ^       // column coord
+            //
+            // Note: Because of the constant folding logic in FoldElement,
+            // this case also applies for constant string properties.
+            //
+            // obj()['aprop']() // expression
+            //       ^          // column coord
+            coordNode = calleeNode->pn_right;
+            break;
+          case ParseNodeKind::Name:
+            // Use the start of callee names.
+            coordNode = calleeNode;
+            break;
+          default:
+            break;
+        }
+    }
+    return coordNode;
+}
+
 bool
 BytecodeEmitter::emitArguments(ParseNode* pn, bool callop, bool spread)
 {
@@ -9745,36 +9781,7 @@ BytecodeEmitter::emitCallOrNew(ParseNode
         }
     }
 
-    ParseNode* coordNode = pn;
-    if (pn->isOp(JSOP_CALL) || pn->isOp(JSOP_SPREADCALL) || pn->isOp(JSOP_FUNCALL) ||
-        pn->isOp(JSOP_FUNAPPLY)) {
-        // Default to using the location of the `(` itself.
-        // obj[expr]() // expression
-        //          ^  // column coord
-        coordNode = pn_args;
-
-        switch (pn_callee->getKind()) {
-          case ParseNodeKind::Dot:
-            // Use the position of a property access identifier.
-            //
-            // obj().aprop() // expression
-            //       ^       // column coord
-            //
-            // Note: Because of the constant folding logic in FoldElement,
-            // this case also applies for constant string properties.
-            //
-            // obj()['aprop']() // expression
-            //       ^          // column coord
-            coordNode = pn_callee->pn_right;
-            break;
-          case ParseNodeKind::Name:
-            // Use the start of callee names.
-            coordNode = pn_callee;
-            break;
-          default:
-            break;
-        }
-    }
+    ParseNode* coordNode = getCoordNode(pn, pn_callee, pn_args);
 
     if (!spread) {
         if (pn->getOp() == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) {
diff -Nrup mozilla-OLD/js/src/frontend/BytecodeEmitter.h mozilla/js/src/frontend/BytecodeEmitter.h
--- mozilla-OLD/js/src/frontend/BytecodeEmitter.h	2022-01-10 07:20:08.135341889 +0300
+++ mozilla/js/src/frontend/BytecodeEmitter.h	2022-01-10 07:21:07.213924422 +0300
@@ -818,6 +818,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter
 
     bool isRestParameter(ParseNode* pn);
 
+    MOZ_MUST_USE ParseNode* getCoordNode(ParseNode* pn,
+                                         ParseNode* calleeNode,
+                                         ParseNode* argsList);
     MOZ_MUST_USE bool emitArguments(ParseNode* pn, bool callop, bool spread);
     MOZ_MUST_USE bool emitCallOrNew(ParseNode* pn, ValueUsage valueUsage = ValueUsage::WantValue);
     MOZ_MUST_USE bool emitSelfHostedCallFunction(ParseNode* pn);
diff -Nrup mozilla-OLD/js/src/frontend/FoldConstants.cpp mozilla/js/src/frontend/FoldConstants.cpp
--- mozilla-OLD/js/src/frontend/FoldConstants.cpp	2022-01-10 07:07:24.305742743 +0300
+++ mozilla/js/src/frontend/FoldConstants.cpp	2022-01-10 07:21:07.214924415 +0300
@@ -315,6 +315,7 @@ ContainsHoistedDeclaration(JSContext* cx
       case ParseNodeKind::DeleteName:
       case ParseNodeKind::DeleteProp:
       case ParseNodeKind::DeleteElem:
+      case ParseNodeKind::DeleteOptionalChain:
       case ParseNodeKind::DeleteExpr:
       case ParseNodeKind::Pos:
       case ParseNodeKind::Neg:
diff -Nrup mozilla-OLD/js/src/frontend/FullParseHandler.h mozilla/js/src/frontend/FullParseHandler.h
--- mozilla-OLD/js/src/frontend/FullParseHandler.h	2022-01-10 07:07:24.306742735 +0300
+++ mozilla/js/src/frontend/FullParseHandler.h	2022-01-10 07:21:07.214924415 +0300
@@ -179,6 +179,18 @@ class FullParseHandler
         if (expr->isKind(ParseNodeKind::Elem))
             return newUnary(ParseNodeKind::DeleteElem, begin, expr);
 
+        if (expr->isKind(ParseNodeKind::OptionalChain)) {
+          ParseNode* kid = expr->pn_kid;
+          // Handle property deletion explictly. OptionalCall is handled
+          // via DeleteExpr.
+          if (kid->isKind(ParseNodeKind::Dot) ||
+              kid->isKind(ParseNodeKind::OptionalDot) ||
+              kid->isKind(ParseNodeKind::Elem) ||
+              kid->isKind(ParseNodeKind::OptionalElem)) {
+            return newUnary(ParseNodeKind::DeleteOptionalChain, begin, kid);
+          }
+        }
+
         return newUnary(ParseNodeKind::DeleteExpr, begin, expr);
     }
 
@@ -289,6 +301,10 @@ class FullParseHandler
         return new_<BinaryNode>(ParseNodeKind::Call, JSOP_CALL, callee, args);
     }
 
+    ParseNode* newOptionalCall(ParseNode* callee, ParseNode* args) {
+        return new_<BinaryNode>(ParseNodeKind::OptionalCall, JSOP_CALL, callee, args);
+    }
+
     ParseNode* newArguments(const TokenPos& pos) {
         return new_<ListNode>(ParseNodeKind::Arguments, JSOP_NOP, pos);
     }
@@ -438,6 +454,11 @@ class FullParseHandler
         return new_<UnaryNode>(ParseNodeKind::Await, pos, value);
     }
 
+    ParseNode* newOptionalChain(uint32_t begin, Node value) {
+        TokenPos pos(begin, value->pn_pos.end);
+        return new_<UnaryNode>(ParseNodeKind::OptionalChain, pos, value);
+    }
+
     // Statements
 
     ParseNode* newStatementList(const TokenPos& pos) {
@@ -685,6 +706,14 @@ class FullParseHandler
         return new_<PropertyByValue>(lhs, index, lhs->pn_pos.begin, end);
     }
 
+    ParseNode* newOptionalPropertyAccess(ParseNode* expr, ParseNode* key) {
+        return new_<OptionalPropertyAccess>(expr, key, expr->pn_pos.begin, key->pn_pos.end);
+    }
+  
+    ParseNode* newOptionalPropertyByValue(ParseNode* lhs, ParseNode* index, uint32_t end) {
+        return new_<OptionalPropertyByValue>(lhs, index, lhs->pn_pos.begin, end);
+    }
+
     bool setupCatchScope(ParseNode* lexicalScope, ParseNode* catchName, ParseNode* catchBody) {
         ParseNode* catchpn;
         if (catchName) {
diff -Nrup mozilla-OLD/js/src/frontend/ParseNode.h mozilla/js/src/frontend/ParseNode.h
--- mozilla-OLD/js/src/frontend/ParseNode.h	2022-01-10 07:07:24.306742735 +0300
+++ mozilla/js/src/frontend/ParseNode.h	2022-01-10 07:21:07.215924408 +0300
@@ -37,6 +37,10 @@ class ObjectBox;
     F(PropertyName) \
     F(Dot) \
     F(Elem) \
+    F(OptionalDot) \
+    F(OptionalChain) \
+    F(OptionalElem) \
+    F(OptionalCall) \
     F(Array) \
     F(Elision) \
     F(StatementList) \
@@ -79,6 +83,7 @@ class ObjectBox;
     F(DeleteName) \
     F(DeleteProp) \
     F(DeleteElem) \
+    F(DeleteOptionalChain) \
     F(DeleteExpr) \
     F(Try) \
     F(Catch) \
@@ -1197,31 +1202,32 @@ class RegExpLiteral : public NullaryNode
     }
 };
 
-class PropertyAccess : public BinaryNode
+class PropertyAccessBase : public BinaryNode
 {
   public:
     /*
      * PropertyAccess nodes can have any expression/'super' as left-hand
      * side, but the name must be a ParseNodeKind::PropertyName node.
      */
-    PropertyAccess(ParseNode* lhs, ParseNode* name, uint32_t begin, uint32_t end)
-      : BinaryNode(ParseNodeKind::Dot, JSOP_NOP, TokenPos(begin, end), lhs, name)
+    PropertyAccessBase(ParseNodeKind kind, ParseNode* lhs, ParseNode* name, uint32_t begin, uint32_t end)
+      : BinaryNode(kind, JSOP_NOP, TokenPos(begin, end), lhs, name)
     {
         MOZ_ASSERT(lhs != nullptr);
         MOZ_ASSERT(name != nullptr);
     }
 
+    ParseNode& expression() const {
+        return *pn_u.binary.left;
+    }
+
     static bool test(const ParseNode& node) {
-        bool match = node.isKind(ParseNodeKind::Dot);
+        bool match = node.isKind(ParseNodeKind::Dot) ||
+                     node.isKind(ParseNodeKind::OptionalDot);
         MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
         MOZ_ASSERT_IF(match, node.pn_right->isKind(ParseNodeKind::PropertyName));
         return match;
     }
 
-    ParseNode& expression() const {
-        return *pn_u.binary.left;
-    }
-
     PropertyName& name() const {
         return *pn_u.binary.right->pn_atom->asPropertyName();
     }
@@ -1232,18 +1238,51 @@ class PropertyAccess : public BinaryNode
     }
 };
 
-class PropertyByValue : public ParseNode
+class PropertyAccess : public PropertyAccessBase
 {
   public:
-    PropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin, uint32_t end)
-      : ParseNode(ParseNodeKind::Elem, JSOP_NOP, PN_BINARY, TokenPos(begin, end))
+    PropertyAccess(ParseNode* lhs, ParseNode* name, uint32_t begin, uint32_t end)
+        : PropertyAccessBase(ParseNodeKind::Dot, lhs, name, begin, end) {
+      MOZ_ASSERT(lhs);
+      MOZ_ASSERT(name);
+    }
+
+    static bool test(const ParseNode& node) {
+      bool match = node.isKind(ParseNodeKind::Dot);
+      MOZ_ASSERT_IF(match, node.is<PropertyAccessBase>());
+      return match;
+    }
+};
+  
+class OptionalPropertyAccess : public PropertyAccessBase {
+  public:
+    OptionalPropertyAccess(ParseNode* lhs, ParseNode* name, uint32_t begin, uint32_t end)
+        : PropertyAccessBase(ParseNodeKind::OptionalDot, lhs, name, begin, end) {
+      MOZ_ASSERT(lhs);
+      MOZ_ASSERT(name);
+    }
+  
+    static bool test(const ParseNode& node) {
+      bool match = node.isKind(ParseNodeKind::OptionalDot);
+      MOZ_ASSERT_IF(match, node.is<PropertyAccessBase>());
+      return match;
+    }
+};
+
+class PropertyByValueBase : public ParseNode
+{
+  public:
+    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(ParseNodeKind::Elem);
+        bool match = node.isKind(ParseNodeKind::Elem) ||
+                     node.isKind(ParseNodeKind::OptionalElem);
         MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
         return match;
     }
@@ -1253,6 +1292,30 @@ class PropertyByValue : public ParseNode
     }
 };
 
+class PropertyByValue : public PropertyByValueBase {
+  public:
+    PropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin, uint32_t end)
+        : PropertyByValueBase(ParseNodeKind::Elem, lhs, propExpr, begin, end) {}
+  
+    static bool test(const ParseNode& node) {
+      bool match = node.isKind(ParseNodeKind::Elem);
+      MOZ_ASSERT_IF(match, node.is<PropertyByValueBase>());
+      return match;
+    }
+};
+
+class OptionalPropertyByValue : public PropertyByValueBase {
+  public:
+    OptionalPropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin, uint32_t end)
+        : PropertyByValueBase(ParseNodeKind::OptionalElem, lhs, propExpr, begin, end) {}
+  
+    static bool test(const ParseNode& node) {
+      bool match = node.isKind(ParseNodeKind::OptionalElem);
+      MOZ_ASSERT_IF(match, node.is<PropertyByValueBase>());
+      return match;
+    }
+};
+
 /*
  * A CallSiteNode represents the implicit call site object argument in a TaggedTemplate.
  */
diff -Nrup mozilla-OLD/js/src/frontend/Parser.cpp mozilla/js/src/frontend/Parser.cpp
--- mozilla-OLD/js/src/frontend/Parser.cpp	2022-01-10 07:07:24.309742714 +0300
+++ mozilla/js/src/frontend/Parser.cpp	2022-01-10 07:21:07.218924387 +0300
@@ -8413,6 +8413,107 @@ Parser<ParseHandler, CharT>::unaryOpExpr
 
 template <class ParseHandler, typename CharT>
 typename ParseHandler::Node
+Parser<ParseHandler, CharT>::optionalExpr(YieldHandling yieldHandling,
+                                          TripledotHandling tripledotHandling,
+                                          TokenKind tt, bool allowCallSyntax /* = true */,
+                                          PossibleError* possibleError /* = nullptr */,
+                                          InvokedPrediction invoked /* = PredictUninvoked */)
+{
+    if (!CheckRecursionLimit(context)) {
+        return null();
+    }
+  
+    uint32_t begin = pos().begin;
+  
+    Node lhs = memberExpr(yieldHandling, tripledotHandling, tt,
+                          /* allowCallSyntax = */ true, possibleError, invoked);
+    if (!lhs) {
+        return null();
+    }
+  
+    if (!tokenStream.peekToken(&tt, TokenStream::None)) {
+        return null();
+    }
+  
+    if (tt == TOK_EOF || 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 {
+            anyChars.ungetToken();
+            break;
+        }
+    
+        if (nextMember) {
+            lhs = nextMember;
+        }
+    }
+  
+    return handler.newOptionalChain(begin, lhs);
+}
+
+template <class ParseHandler, typename CharT>
+typename ParseHandler::Node
 Parser<ParseHandler, CharT>::unaryExpr(YieldHandling yieldHandling,
                                        TripledotHandling tripledotHandling,
                                        PossibleError* possibleError /* = nullptr */,
@@ -8508,8 +8609,8 @@ Parser<ParseHandler, CharT>::unaryExpr(Y
         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();
 
@@ -8955,6 +9056,18 @@ Parser<ParseHandler, CharT>::memberExpr(
             if (!ctorExpr)
                 return null();
 
+            // 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();
@@ -9003,17 +9116,7 @@ Parser<ParseHandler, CharT>::memberExpr(
             if (!tokenStream.getToken(&tt))
                 return null();
             if (TokenKindIsPossibleIdentifierName(tt)) {
-                PropertyName* field = anyChars.currentName();
-                if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
-                    error(JSMSG_BAD_SUPERPROP, "property");
-                    return null();
-                }
-
-                Node name = handler.newPropertyName(field, pos());
-                if (!name)
-                    return null();
-
-                nextMember = handler.newPropertyAccess(lhs, name);
+                nextMember = memberPropertyAccess(lhs);
                 if (!nextMember)
                     return null();
             } else {
@@ -9021,17 +9124,7 @@ Parser<ParseHandler, CharT>::memberExpr(
                 return null();
             }
         } else if (tt == TOK_LB) {
-            Node propExpr = expr(InAllowed, yieldHandling, TripledotProhibited);
-            if (!propExpr)
-                return null();
-
-            MUST_MATCH_TOKEN_MOD(TOK_RB, TokenStream::Operand, 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) ||
@@ -9049,106 +9142,14 @@ Parser<ParseHandler, CharT>::memberExpr(
                     return null();
                 }
 
-                // Despite the fact that it's impossible to have |super()| in a
-                // generator, we still inherit the yieldHandling of the
-                // memberExpression, per spec. Curious.
-                bool isSpread = false;
-                Node args = argumentList(yieldHandling, &isSpread);
-                if (!args)
-                    return null();
-
-                nextMember = handler.newSuperCall(lhs, args);
-                if (!nextMember)
-                    return null();
-
-                if (isSpread)
-                    handler.setOp(nextMember, JSOP_SPREADSUPERCALL);
-
-                Node thisName = newThisName();
-                if (!thisName)
-                    return null();
-
-                nextMember = handler.newSetThis(thisName, nextMember);
+                nextMember = memberSuperCall(lhs, yieldHandling);
                 if (!nextMember)
                     return null();
             } else {
-                if (options().selfHostingMode && handler.isPropertyAccess(lhs)) {
-                    error(JSMSG_SELFHOSTED_METHOD_CALL);
+                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.isEvalName(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();
-                    }
-                }
-
-                if (tt == TOK_LP) {
-                    bool isSpread = false;
-                    PossibleError* asyncPossibleError = maybeAsyncArrow ? possibleError : nullptr;
-                    Node args = argumentList(yieldHandling, &isSpread, asyncPossibleError);
-                    if (!args)
-                        return null();
-                    if (isSpread) {
-                        if (op == JSOP_EVAL)
-                            op = JSOP_SPREADEVAL;
-                        else if (op == JSOP_STRICTEVAL)
-                            op = JSOP_STRICTSPREADEVAL;
-                        else
-                            op = JSOP_SPREADCALL;
-                    }
-
-                    nextMember = handler.newCall(lhs, args);
-                    if (!nextMember)
-                        return null();
-                } else {
-                    Node args = handler.newArguments(pos());
-                    if (!args)
-                        return null();
-
-                    if (!taggedTemplate(yieldHandling, args, tt))
-                        return null();
-
-                    nextMember = handler.newTaggedTemplate(lhs, args);
-                    if (!nextMember)
-                        return null();
-                }
-                handler.setOp(nextMember, op);
             }
         } else {
             anyChars.ungetToken();
@@ -9183,6 +9184,193 @@ Parser<ParseHandler, CharT>::newName(Pro
 }
 
 template <class ParseHandler, typename CharT>
+typename ParseHandler::Node
+Parser<ParseHandler, CharT>::memberPropertyAccess(Node lhs,
+                                                  OptionalKind optionalKind /* = OptionalKind::NonOptional */)
+{
+    MOZ_ASSERT(TokenKindIsPossibleIdentifierName(anyChars.currentToken().type));
+    PropertyName* field = anyChars.currentName();
+    if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
+        error(JSMSG_BAD_SUPERPROP, "property");
+        return null();
+    }
+
+    Node name = handler.newPropertyName(field, pos());
+    if (!name) {
+        return null();
+    }
+
+    if (optionalKind == OptionalKind::Optional) {
+        return handler.newOptionalPropertyAccess(lhs, name);
+    }
+    return handler.newPropertyAccess(lhs, name);
+}
+
+template <class ParseHandler, typename CharT>
+typename ParseHandler::Node
+Parser<ParseHandler, CharT>::memberElemAccess(Node lhs, YieldHandling yieldHandling,
+                                                     OptionalKind optionalKind /* = OptionalKind::NonOptional */)
+{
+    MOZ_ASSERT(anyChars.currentToken().type == TokenKind::LeftBracket);
+    Node propExpr = expr(InAllowed, yieldHandling, TripledotProhibited);
+    if (!propExpr) {
+        return null();
+    }
+
+    MUST_MATCH_TOKEN_MOD(TOK_RB, TokenStream::Operand, JSMSG_BRACKET_IN_INDEX);
+
+    if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
+        error(JSMSG_BAD_SUPERPROP, "member");
+        return null();
+    }
+    if (optionalKind == OptionalKind::Optional) {
+        return handler.newOptionalPropertyByValue(lhs, propExpr, pos().end);
+    }
+    return handler.newPropertyByValue(lhs, propExpr, pos().end);
+}
+
+template <class ParseHandler, typename CharT>
+typename ParseHandler::Node
+Parser<ParseHandler, CharT>::memberSuperCall(Node lhs, YieldHandling yieldHandling)
+{
+    MOZ_ASSERT(anyChars.currentToken().type == TOK_LP);
+    // Despite the fact that it's impossible to have |super()| in a
+    // generator, we still inherit the yieldHandling of the
+    // memberExpression, per spec. Curious.
+    bool isSpread = false;
+    Node args = argumentList(yieldHandling, &isSpread);
+    if (!args) {
+        return null();
+    }
+
+    Node nextMember = handler.newSuperCall(lhs, args);
+    if (!nextMember){
+        return null();
+    }
+
+    if (isSpread) {
+        handler.setOp(nextMember, JSOP_SPREADSUPERCALL);
+    }
+
+    Node thisName = newThisName();
+    if (!thisName) {
+        return null();
+    }
+
+    return handler.newSetThis(thisName, nextMember);
+}
+
+template <class ParseHandler, typename CharT>
+typename ParseHandler::Node
+Parser<ParseHandler, CharT>::memberCall(TokenKind tt, Node lhs, YieldHandling yieldHandling,
+                                               PossibleError* possibleError /* = nullptr */,
+                                               OptionalKind optionalKind /* = OptionalKind::NonOptional */)
+{
+    if (options().selfHostingMode && handler.isPropertyAccess(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");
+
+    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.isEvalName(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();
+        }
+    }
+
+    if (tt == TOK_LP) {
+        bool isSpread = false;
+        PossibleError* asyncPossibleError = maybeAsyncArrow ? possibleError : nullptr;
+        Node args = argumentList(yieldHandling, &isSpread, asyncPossibleError);
+        if (!args) {
+            return null();
+        }
+        if (isSpread) {
+            if (op == JSOP_EVAL) {
+                op = JSOP_SPREADEVAL;
+            } else if (op == JSOP_STRICTEVAL) {
+                op = JSOP_STRICTSPREADEVAL;
+            } else {
+                op = JSOP_SPREADCALL;
+            }
+        }
+  
+        Node nextMember;
+        if (optionalKind == OptionalKind::Optional)
+            nextMember = handler.newOptionalCall(lhs, args);
+        else
+            nextMember = handler.newCall(lhs, args);
+
+        if (!nextMember)
+            return null();
+
+        handler.setOp(nextMember, op);
+        return nextMember;
+    }
+
+    Node args = handler.newArguments(pos());
+    if (!args) {
+        return null();
+    }
+
+    if (!taggedTemplate(yieldHandling, args, tt)) {
+        return null();
+    }
+
+    if (optionalKind == OptionalKind::Optional) {
+        error(JSMSG_BAD_OPTIONAL_TEMPLATE);
+        return null();
+    }
+
+    Node nextMember = handler.newTaggedTemplate(lhs, args);
+    if (!nextMember) {
+        return null();
+    }
+
+    handler.setOp(nextMember, op);
+    return nextMember;
+}
+
+template <class ParseHandler, typename CharT>
 bool
 Parser<ParseHandler, CharT>::checkLabelOrIdentifierReference(PropertyName* ident,
                                                              uint32_t offset,
diff -Nrup mozilla-OLD/js/src/frontend/Parser.h mozilla/js/src/frontend/Parser.h
--- mozilla-OLD/js/src/frontend/Parser.h	2022-01-10 07:07:24.310742706 +0300
+++ mozilla/js/src/frontend/Parser.h	2022-01-10 07:21:07.219924380 +0300
@@ -753,6 +753,10 @@ class Parser final
     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,
@@ -948,6 +952,18 @@ class Parser final
         return handler.newNumber(tok.number(), tok.decimalPoint(), tok.pos);
     }
 
+    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 memberSuperCall(Node lhs, YieldHandling yieldHandling);
+    Node memberCall(TokenKind tt, Node lhs, YieldHandling yieldHandling,
+                    PossibleError* possibleError,
+                    OptionalKind optionalKind = OptionalKind::NonOptional);
+
     static Node null() { return ParseHandler::null(); }
 
     JSAtom* prefixAccessorName(PropertyType propType, HandleAtom propAtom);
diff -Nrup mozilla-OLD/js/src/frontend/SyntaxParseHandler.h mozilla/js/src/frontend/SyntaxParseHandler.h
--- mozilla-OLD/js/src/frontend/SyntaxParseHandler.h	2022-01-10 07:07:24.310742706 +0300
+++ mozilla/js/src/frontend/SyntaxParseHandler.h	2022-01-10 07:21:07.219924380 +0300
@@ -59,6 +59,8 @@ class SyntaxParseHandler
         // noticed).
         NodeFunctionCall,
 
+        NodeOptionalFunctionCall,
+
         // Node representing normal names which don't require any special
         // casing.
         NodeName,
@@ -72,7 +74,9 @@ class SyntaxParseHandler
         NodePotentialAsyncKeyword,
 
         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
@@ -243,6 +247,7 @@ class SyntaxParseHandler
 
     Node newArguments(const TokenPos& pos) { return NodeGeneric; }
     Node newCall(Node callee, Node args) { return NodeFunctionCall; }
+    Node newOptionalCall(Node callee, Node args) { return NodeOptionalFunctionCall; }
 
     Node newSuperCall(Node callee, Node args) { return NodeGeneric; }
     Node newTaggedTemplate(Node callee, Node args) { return NodeGeneric; }
@@ -265,6 +270,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
 
@@ -327,8 +333,16 @@ class SyntaxParseHandler
         return NodeDottedProperty;
     }
 
+    Node newOptionalPropertyAccess(Node expr, Node key) {
+        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 setupCatchScope(Node letBlock, Node catchName, Node catchBody) {
         return true;
     }
diff -Nrup mozilla-OLD/js/src/frontend/TokenKind.h mozilla/js/src/frontend/TokenKind.h
--- mozilla-OLD/js/src/frontend/TokenKind.h	2022-01-10 07:07:24.311742699 +0300
+++ mozilla/js/src/frontend/TokenKind.h	2022-01-10 07:21:07.220924373 +0300
@@ -64,6 +64,7 @@
     macro(DEC,          "'--'")   /* decrement */ \
     macro(DOT,          "'.'")    /* member operator */ \
     macro(TRIPLEDOT,    "'...'")  /* rest arguments and spread operator */ \
+    macro(OPTCHAIN,     "'?.'") \
     macro(LB,           "'['") \
     macro(RB,           "']'") \
     macro(LC,           "'{'") \
diff -Nrup mozilla-OLD/js/src/frontend/TokenStream.cpp mozilla/js/src/frontend/TokenStream.cpp
--- mozilla-OLD/js/src/frontend/TokenStream.cpp	2022-01-10 07:07:24.312742692 +0300
+++ mozilla/js/src/frontend/TokenStream.cpp	2022-01-10 07:21:07.220924373 +0300
@@ -1853,7 +1853,23 @@ TokenStreamSpecific<CharT, AnyCharsAcces
         goto out;
 
       case '?':
-        tp->type = matchChar('?') ? TOK_COALESCE : TOK_HOOK;
+        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);
+                ungetCharIgnoreEOL('.');
+            } else {
+                ungetCharIgnoreEOL(c);
+                tp->type = TOK_OPTCHAIN;
+            }
+        } else {
+            tp->type = matchChar('?') ? TOK_COALESCE : TOK_HOOK;
+        }
+
         goto out;
 
       case '!':
diff -Nrup mozilla-OLD/js/src/js.msg mozilla/js/src/js.msg
--- mozilla-OLD/js/src/js.msg	2022-01-10 07:07:24.312742692 +0300
+++ mozilla/js/src/js.msg	2022-01-10 07:21:07.221924366 +0300
@@ -358,6 +358,8 @@ MSG_DEF(JSMSG_BAD_COLUMN_NUMBER,       0
 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