/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef frontend_FullParseHandler_h
#define frontend_FullParseHandler_h

#include "mozilla/Maybe.h"  // mozilla::Maybe

#include <cstddef>  // std::nullptr_t
#include <string.h>

#include "jstypes.h"

#include "frontend/CompilationStencil.h"  // CompilationState
#include "frontend/FunctionSyntaxKind.h"  // FunctionSyntaxKind
#include "frontend/NameAnalysisTypes.h"   // PrivateNameKind
#include "frontend/ParseNode.h"
#include "frontend/ParserAtom.h"  // TaggedParserAtomIndex
#include "frontend/SharedContext.h"
#include "frontend/Stencil.h"

namespace js {
namespace frontend {

class TokenStreamAnyChars;

// Parse handler used when generating a full parse tree for all code which the
// parser encounters.
class FullParseHandler {
  ParseNodeAllocator allocator;

  ParseNode* allocParseNode(size_t size) {
    return static_cast<ParseNode*>(allocator.allocNode(size));
  }

  // If this is a full parse to construct the bytecode for a function that
  // was previously lazily parsed, we still don't want to full parse the
  // inner functions. These members are used for this functionality:
  //
  // - reuseGCThings if ture it means that the following fields are valid.
  // - gcThingsData holds an incomplete stencil-like copy of inner functions as
  //   well as atoms.
  // - scriptData and scriptExtra_ hold information necessary to locate inner
  //   functions to skip over each.
  // - lazyInnerFunctionIndex is used as we skip over inner functions
  //   (see skipLazyInnerFunction),
  // - lazyClosedOverBindingIndex is used to synchronize binding computation
  //   with the scope traversal.
  //   (see propagateFreeNamesAndMarkClosedOverBindings),
  const CompilationSyntaxParseCache& previousParseCache_;

  size_t lazyInnerFunctionIndex;
  size_t lazyClosedOverBindingIndex;

  bool reuseGCThings;

 public:
  /* new_ methods for creating parse nodes. These report OOM on context. */
  JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline)

  // FIXME: Use ListNode instead of ListNodeType as an alias (bug 1489008).
  using Node = ParseNode*;

#define DECLARE_TYPE(typeName, longTypeName, asMethodName) \
  using longTypeName = typeName*;
  FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE)
#undef DECLARE_TYPE

  using NullNode = std::nullptr_t;

  bool isPropertyOrPrivateMemberAccess(Node node) {
    return node->isKind(ParseNodeKind::DotExpr) ||
           node->isKind(ParseNodeKind::ElemExpr) ||
           node->isKind(ParseNodeKind::PrivateMemberExpr);
  }

  bool isOptionalPropertyOrPrivateMemberAccess(Node node) {
    return node->isKind(ParseNodeKind::OptionalDotExpr) ||
           node->isKind(ParseNodeKind::OptionalElemExpr) ||
           node->isKind(ParseNodeKind::PrivateMemberExpr);
  }

  bool isFunctionCall(Node node) {
    // Note: super() is a special form, *not* a function call.
    return node->isKind(ParseNodeKind::CallExpr);
  }

  static bool isUnparenthesizedDestructuringPattern(Node node) {
    return !node->isInParens() && (node->isKind(ParseNodeKind::ObjectExpr) ||
                                   node->isKind(ParseNodeKind::ArrayExpr));
  }

  static bool isParenthesizedDestructuringPattern(Node node) {
    // Technically this isn't a destructuring pattern at all -- the grammar
    // doesn't treat it as such.  But we need to know when this happens to
    // consider it a SyntaxError rather than an invalid-left-hand-side
    // ReferenceError.
    return node->isInParens() && (node->isKind(ParseNodeKind::ObjectExpr) ||
                                  node->isKind(ParseNodeKind::ArrayExpr));
  }

  FullParseHandler(FrontendContext* fc, CompilationState& compilationState)
      : allocator(fc, compilationState.parserAllocScope.alloc()),
        previousParseCache_(compilationState.previousParseCache),
        lazyInnerFunctionIndex(0),
        lazyClosedOverBindingIndex(0),
        reuseGCThings(compilationState.input.isDelazifying()) {}

  static NullNode null() { return NullNode(); }

#define DECLARE_AS(typeName, longTypeName, asMethodName) \
  static longTypeName asMethodName(Node node) { return &node->as<typeName>(); }
  FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
#undef DECLARE_AS

  NameNodeType newName(TaggedParserAtomIndex name, const TokenPos& pos) {
    return new_<NameNode>(ParseNodeKind::Name, name, pos);
  }

  UnaryNodeType newComputedName(Node expr, uint32_t begin, uint32_t end) {
    TokenPos pos(begin, end);
    return new_<UnaryNode>(ParseNodeKind::ComputedName, pos, expr);
  }

  UnaryNodeType newSyntheticComputedName(Node expr, uint32_t begin,
                                         uint32_t end) {
    TokenPos pos(begin, end);
    UnaryNode* node = new_<UnaryNode>(ParseNodeKind::ComputedName, pos, expr);
    if (!node) {
      return nullptr;
    }
    node->setSyntheticComputedName();
    return node;
  }

  NameNodeType newObjectLiteralPropertyName(TaggedParserAtomIndex atom,
                                            const TokenPos& pos) {
    return new_<NameNode>(ParseNodeKind::ObjectPropertyName, atom, pos);
  }

  NameNodeType newPrivateName(TaggedParserAtomIndex atom, const TokenPos& pos) {
    return new_<NameNode>(ParseNodeKind::PrivateName, atom, pos);
  }

  NumericLiteralType newNumber(double value, DecimalPoint decimalPoint,
                               const TokenPos& pos) {
    return new_<NumericLiteral>(value, decimalPoint, pos);
  }

  BigIntLiteralType newBigInt(BigIntIndex index, bool isZero,
                              const TokenPos& pos) {
    return new_<BigIntLiteral>(index, isZero, pos);
  }

  BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) {
    return new_<BooleanLiteral>(cond, pos);
  }

  NameNodeType newStringLiteral(TaggedParserAtomIndex atom,
                                const TokenPos& pos) {
    return new_<NameNode>(ParseNodeKind::StringExpr, atom, pos);
  }

  NameNodeType newTemplateStringLiteral(TaggedParserAtomIndex atom,
                                        const TokenPos& pos) {
    return new_<NameNode>(ParseNodeKind::TemplateStringExpr, atom, pos);
  }

  CallSiteNodeType newCallSiteObject(uint32_t begin) {
    CallSiteNode* callSiteObj = new_<CallSiteNode>(begin);
    if (!callSiteObj) {
      return null();
    }

    ListNode* rawNodes = newArrayLiteral(callSiteObj->pn_pos.begin);
    if (!rawNodes) {
      return null();
    }

    addArrayElement(callSiteObj, rawNodes);

    return callSiteObj;
  }

  void addToCallSiteObject(CallSiteNodeType callSiteObj, Node rawNode,
                           Node cookedNode) {
    MOZ_ASSERT(callSiteObj->isKind(ParseNodeKind::CallSiteObj));
    MOZ_ASSERT(rawNode->isKind(ParseNodeKind::TemplateStringExpr));
    MOZ_ASSERT(cookedNode->isKind(ParseNodeKind::TemplateStringExpr) ||
               cookedNode->isKind(ParseNodeKind::RawUndefinedExpr));

    addArrayElement(callSiteObj, cookedNode);
    addArrayElement(callSiteObj->rawNodes(), rawNode);

    /*
     * We don't know when the last noSubstTemplate will come in, and we
     * don't want to deal with this outside this method
     */
    setEndPosition(callSiteObj, callSiteObj->rawNodes());
  }

  ThisLiteralType newThisLiteral(const TokenPos& pos, Node thisName) {
    return new_<ThisLiteral>(pos, thisName);
  }

  NullLiteralType newNullLiteral(const TokenPos& pos) {
    return new_<NullLiteral>(pos);
  }

  RawUndefinedLiteralType newRawUndefinedLiteral(const TokenPos& pos) {
    return new_<RawUndefinedLiteral>(pos);
  }

  RegExpLiteralType newRegExp(RegExpIndex index, const TokenPos& pos) {
    return new_<RegExpLiteral>(index, pos);
  }

  ConditionalExpressionType newConditional(Node cond, Node thenExpr,
                                           Node elseExpr) {
    return new_<ConditionalExpression>(cond, thenExpr, elseExpr);
  }

  UnaryNodeType newDelete(uint32_t begin, Node expr) {
    if (expr->isKind(ParseNodeKind::Name)) {
      return newUnary(ParseNodeKind::DeleteNameExpr, begin, expr);
    }

    if (expr->isKind(ParseNodeKind::DotExpr)) {
      return newUnary(ParseNodeKind::DeletePropExpr, begin, expr);
    }

    if (expr->isKind(ParseNodeKind::ElemExpr)) {
      return newUnary(ParseNodeKind::DeleteElemExpr, begin, expr);
    }

    if (expr->isKind(ParseNodeKind::OptionalChain)) {
      Node kid = expr->as<UnaryNode>().kid();
      // Handle property deletion explicitly. OptionalCall is handled
      // via DeleteExpr.
      if (kid->isKind(ParseNodeKind::DotExpr) ||
          kid->isKind(ParseNodeKind::OptionalDotExpr) ||
          kid->isKind(ParseNodeKind::ElemExpr) ||
          kid->isKind(ParseNodeKind::OptionalElemExpr)) {
        return newUnary(ParseNodeKind::DeleteOptionalChainExpr, begin, kid);
      }
    }

    return newUnary(ParseNodeKind::DeleteExpr, begin, expr);
  }

  UnaryNodeType newTypeof(uint32_t begin, Node kid) {
    ParseNodeKind pnk = kid->isKind(ParseNodeKind::Name)
                            ? ParseNodeKind::TypeOfNameExpr
                            : ParseNodeKind::TypeOfExpr;
    return newUnary(pnk, begin, kid);
  }

  UnaryNodeType newUnary(ParseNodeKind kind, uint32_t begin, Node kid) {
    TokenPos pos(begin, kid->pn_pos.end);
    return new_<UnaryNode>(kind, pos, kid);
  }

  UnaryNodeType newUpdate(ParseNodeKind kind, uint32_t begin, Node kid) {
    TokenPos pos(begin, kid->pn_pos.end);
    return new_<UnaryNode>(kind, pos, kid);
  }

  UnaryNodeType newSpread(uint32_t begin, Node kid) {
    TokenPos pos(begin, kid->pn_pos.end);
    return new_<UnaryNode>(ParseNodeKind::Spread, pos, kid);
  }

 private:
  BinaryNodeType newBinary(ParseNodeKind kind, Node left, Node right) {
    TokenPos pos(left->pn_pos.begin, right->pn_pos.end);
    return new_<BinaryNode>(kind, pos, left, right);
  }

 public:
  Node appendOrCreateList(ParseNodeKind kind, Node left, Node right,
                          ParseContext* pc) {
    return ParseNode::appendOrCreateList(kind, left, right, this, pc);
  }

  // Expressions

  ListNodeType newArrayLiteral(uint32_t begin) {
    return new_<ListNode>(ParseNodeKind::ArrayExpr, TokenPos(begin, begin + 1));
  }

  [[nodiscard]] bool addElision(ListNodeType literal, const TokenPos& pos) {
    MOZ_ASSERT(literal->isKind(ParseNodeKind::ArrayExpr));

    NullaryNode* elision = new_<NullaryNode>(ParseNodeKind::Elision, pos);
    if (!elision) {
      return false;
    }
    addList(/* list = */ literal, /* kid = */ elision);
    literal->setHasNonConstInitializer();
    return true;
  }

  [[nodiscard]] bool addSpreadElement(ListNodeType literal, uint32_t begin,
                                      Node inner) {
    MOZ_ASSERT(
        literal->isKind(ParseNodeKind::ArrayExpr) ||
        IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::TupleExpr), false));

    UnaryNodeType spread = newSpread(begin, inner);
    if (!spread) {
      return false;
    }
    addList(/* list = */ literal, /* kid = */ spread);
    literal->setHasNonConstInitializer();
    return true;
  }

  void addArrayElement(ListNodeType literal, Node element) {
    MOZ_ASSERT(
        literal->isKind(ParseNodeKind::ArrayExpr) ||
        literal->isKind(ParseNodeKind::CallSiteObj) ||
        IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::TupleExpr), false));
    if (!element->isConstant()) {
      literal->setHasNonConstInitializer();
    }
    addList(/* list = */ literal, /* kid = */ element);
  }

  CallNodeType newCall(Node callee, ListNodeType args, JSOp callOp) {
    return new_<CallNode>(ParseNodeKind::CallExpr, callOp, callee, args);
  }

  CallNodeType newOptionalCall(Node callee, ListNodeType args, JSOp callOp) {
    return new_<CallNode>(ParseNodeKind::OptionalCallExpr, callOp, callee,
                          args);
  }

  ListNodeType newArguments(const TokenPos& pos) {
    return new_<ListNode>(ParseNodeKind::Arguments, pos);
  }

  CallNodeType newSuperCall(Node callee, ListNodeType args, bool isSpread) {
    return new_<CallNode>(ParseNodeKind::SuperCallExpr,
                          isSpread ? JSOp::SpreadSuperCall : JSOp::SuperCall,
                          callee, args);
  }

  CallNodeType newTaggedTemplate(Node tag, ListNodeType args, JSOp callOp) {
    return new_<CallNode>(ParseNodeKind::TaggedTemplateExpr, callOp, tag, args);
  }

  ListNodeType newObjectLiteral(uint32_t begin) {
    return new_<ListNode>(ParseNodeKind::ObjectExpr,
                          TokenPos(begin, begin + 1));
  }

#ifdef ENABLE_RECORD_TUPLE
  ListNodeType newRecordLiteral(uint32_t begin) {
    return new_<ListNode>(ParseNodeKind::RecordExpr,
                          TokenPos(begin, begin + 1));
  }

  ListNodeType newTupleLiteral(uint32_t begin) {
    return new_<ListNode>(ParseNodeKind::TupleExpr, TokenPos(begin, begin + 1));
  }
#endif

  ClassNodeType newClass(Node name, Node heritage,
                         LexicalScopeNodeType memberBlock,
#ifdef ENABLE_DECORATORS
                         ListNodeType decorators,
#endif
                         const TokenPos& pos) {
    return new_<ClassNode>(name, heritage, memberBlock,
#ifdef ENABLE_DECORATORS
                           decorators,
#endif
                           pos);
  }
  ListNodeType newClassMemberList(uint32_t begin) {
    return new_<ListNode>(ParseNodeKind::ClassMemberList,
                          TokenPos(begin, begin + 1));
  }
  ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) {
    return new_<ClassNames>(outer, inner, pos);
  }
  NewTargetNodeType newNewTarget(NullaryNodeType newHolder,
                                 NullaryNodeType targetHolder,
                                 NameNodeType newTargetName) {
    return new_<NewTargetNode>(newHolder, targetHolder, newTargetName);
  }
  NullaryNodeType newPosHolder(const TokenPos& pos) {
    return new_<NullaryNode>(ParseNodeKind::PosHolder, pos);
  }
  UnaryNodeType newSuperBase(Node thisName, const TokenPos& pos) {
    return new_<UnaryNode>(ParseNodeKind::SuperBase, pos, thisName);
  }
  [[nodiscard]] bool addPrototypeMutation(ListNodeType literal, uint32_t begin,
                                          Node expr) {
    MOZ_ASSERT(literal->isKind(ParseNodeKind::ObjectExpr));

    // Object literals with mutated [[Prototype]] are non-constant so that
    // singleton objects will have Object.prototype as their [[Prototype]].
    literal->setHasNonConstInitializer();

    UnaryNode* mutation = newUnary(ParseNodeKind::MutateProto, begin, expr);
    if (!mutation) {
      return false;
    }
    addList(/* list = */ literal, /* kid = */ mutation);
    return true;
  }

  BinaryNodeType newPropertyDefinition(Node key, Node val) {
    MOZ_ASSERT(isUsableAsObjectPropertyName(key));
    checkAndSetIsDirectRHSAnonFunction(val);
    return new_<PropertyDefinition>(key, val, AccessorType::None);
  }

  void addPropertyDefinition(ListNodeType literal, BinaryNodeType propdef) {
    MOZ_ASSERT(
        literal->isKind(ParseNodeKind::ObjectExpr) ||
        IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::RecordExpr), false));
    MOZ_ASSERT(propdef->isKind(ParseNodeKind::PropertyDefinition));

    if (!propdef->right()->isConstant()) {
      literal->setHasNonConstInitializer();
    }

    addList(/* list = */ literal, /* kid = */ propdef);
  }

  [[nodiscard]] bool addPropertyDefinition(ListNodeType literal, Node key,
                                           Node val) {
    BinaryNode* propdef = newPropertyDefinition(key, val);
    if (!propdef) {
      return false;
    }
    addPropertyDefinition(literal, propdef);
    return true;
  }

  [[nodiscard]] bool addShorthand(ListNodeType literal, NameNodeType name,
                                  NameNodeType expr) {
    MOZ_ASSERT(
        literal->isKind(ParseNodeKind::ObjectExpr) ||
        IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::RecordExpr), false));
    MOZ_ASSERT(name->isKind(ParseNodeKind::ObjectPropertyName));
    MOZ_ASSERT(expr->isKind(ParseNodeKind::Name));
    MOZ_ASSERT(name->atom() == expr->atom());

    literal->setHasNonConstInitializer();
    BinaryNode* propdef = newBinary(ParseNodeKind::Shorthand, name, expr);
    if (!propdef) {
      return false;
    }
    addList(/* list = */ literal, /* kid = */ propdef);
    return true;
  }

  [[nodiscard]] bool addSpreadProperty(ListNodeType literal, uint32_t begin,
                                       Node inner) {
    MOZ_ASSERT(
        literal->isKind(ParseNodeKind::ObjectExpr) ||
        IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::RecordExpr), false));

    literal->setHasNonConstInitializer();
    ParseNode* spread = newSpread(begin, inner);
    if (!spread) {
      return false;
    }
    addList(/* list = */ literal, /* kid = */ spread);
    return true;
  }

  [[nodiscard]] bool addObjectMethodDefinition(ListNodeType literal, Node key,
                                               FunctionNodeType funNode,
                                               AccessorType atype) {
    literal->setHasNonConstInitializer();

    checkAndSetIsDirectRHSAnonFunction(funNode);

    ParseNode* propdef =
        newObjectMethodOrPropertyDefinition(key, funNode, atype);
    if (!propdef) {
      return false;
    }

    addList(/* list = */ literal, /* kid = */ propdef);
    return true;
  }

  [[nodiscard]] ClassMethod* newDefaultClassConstructor(
      Node key, FunctionNodeType funNode) {
    MOZ_ASSERT(isUsableAsObjectPropertyName(key));

    checkAndSetIsDirectRHSAnonFunction(funNode);

    return new_<ClassMethod>(
        ParseNodeKind::DefaultConstructor, key, funNode, AccessorType::None,
        /* isStatic = */ false, /* initializeIfPrivate = */ nullptr
#ifdef ENABLE_DECORATORS
        ,
        /* decorators = */ nullptr
#endif
    );
  }

  [[nodiscard]] ClassMethod* newClassMethodDefinition(
      Node key, FunctionNodeType funNode, AccessorType atype, bool isStatic,
      mozilla::Maybe<FunctionNodeType> initializerIfPrivate
#ifdef ENABLE_DECORATORS
      ,
      ListNodeType decorators
#endif
  ) {
    MOZ_ASSERT(isUsableAsObjectPropertyName(key));

    checkAndSetIsDirectRHSAnonFunction(funNode);

    if (initializerIfPrivate.isSome()) {
      return new_<ClassMethod>(ParseNodeKind::ClassMethod, key, funNode, atype,
                               isStatic, initializerIfPrivate.value()
#ifdef ENABLE_DECORATORS
                                             ,
                               decorators
#endif
      );
    }
    return new_<ClassMethod>(ParseNodeKind::ClassMethod, key, funNode, atype,
                             isStatic, /* initializeIfPrivate = */ nullptr
#ifdef ENABLE_DECORATORS
                             ,
                             decorators
#endif
    );
  }

  [[nodiscard]] ClassField* newClassFieldDefinition(
      Node name, FunctionNodeType initializer, bool isStatic
#ifdef ENABLE_DECORATORS
      ,
      ListNodeType decorators, ClassMethodType accessorGetterNode,
      ClassMethodType accessorSetterNode
#endif
  ) {
    MOZ_ASSERT(isUsableAsObjectPropertyName(name));

    return new_<ClassField>(name, initializer, isStatic
#if ENABLE_DECORATORS
                            ,
                            decorators, accessorGetterNode, accessorSetterNode
#endif
    );
  }

  [[nodiscard]] StaticClassBlock* newStaticClassBlock(FunctionNodeType block) {
    return new_<StaticClassBlock>(block);
  }

  [[nodiscard]] bool addClassMemberDefinition(ListNodeType memberList,
                                              Node member) {
    MOZ_ASSERT(memberList->isKind(ParseNodeKind::ClassMemberList));
    // Constructors can be surrounded by LexicalScopes.
    MOZ_ASSERT(member->isKind(ParseNodeKind::DefaultConstructor) ||
               member->isKind(ParseNodeKind::ClassMethod) ||
               member->isKind(ParseNodeKind::ClassField) ||
               member->isKind(ParseNodeKind::StaticClassBlock) ||
               (member->isKind(ParseNodeKind::LexicalScope) &&
                member->as<LexicalScopeNode>().scopeBody()->is<ClassMethod>()));

    addList(/* list = */ memberList, /* kid = */ member);
    return true;
  }

  UnaryNodeType newInitialYieldExpression(uint32_t begin, Node gen) {
    TokenPos pos(begin, begin + 1);
    return new_<UnaryNode>(ParseNodeKind::InitialYield, pos, gen);
  }

  UnaryNodeType newYieldExpression(uint32_t begin, Node value) {
    TokenPos pos(begin, value ? value->pn_pos.end : begin + 1);
    return new_<UnaryNode>(ParseNodeKind::YieldExpr, pos, value);
  }

  UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) {
    TokenPos pos(begin, value->pn_pos.end);
    return new_<UnaryNode>(ParseNodeKind::YieldStarExpr, pos, value);
  }

  UnaryNodeType newAwaitExpression(uint32_t begin, Node value) {
    TokenPos pos(begin, value ? value->pn_pos.end : begin + 1);
    return new_<UnaryNode>(ParseNodeKind::AwaitExpr, pos, value);
  }

  UnaryNodeType newOptionalChain(uint32_t begin, Node value) {
    TokenPos pos(begin, value->pn_pos.end);
    return new_<UnaryNode>(ParseNodeKind::OptionalChain, pos, value);
  }

  // Statements

  ListNodeType newStatementList(const TokenPos& pos) {
    return new_<ListNode>(ParseNodeKind::StatementList, pos);
  }

  [[nodiscard]] bool isFunctionStmt(Node stmt) {
    while (stmt->isKind(ParseNodeKind::LabelStmt)) {
      stmt = stmt->as<LabeledStatement>().statement();
    }
    return stmt->is<FunctionNode>();
  }

  void addStatementToList(ListNodeType list, Node stmt) {
    MOZ_ASSERT(list->isKind(ParseNodeKind::StatementList));

    addList(/* list = */ list, /* kid = */ stmt);

    if (isFunctionStmt(stmt)) {
      // Notify the emitter that the block contains body-level function
      // definitions that should be processed before the rest of nodes.
      list->setHasTopLevelFunctionDeclarations();
    }
  }

  void setListEndPosition(ListNodeType list, const TokenPos& pos) {
    MOZ_ASSERT(list->isKind(ParseNodeKind::StatementList));
    list->pn_pos.end = pos.end;
  }

  void addCaseStatementToList(ListNodeType list, CaseClauseType caseClause) {
    MOZ_ASSERT(list->isKind(ParseNodeKind::StatementList));

    addList(/* list = */ list, /* kid = */ caseClause);

    if (caseClause->statementList()->hasTopLevelFunctionDeclarations()) {
      list->setHasTopLevelFunctionDeclarations();
    }
  }

  [[nodiscard]] bool prependInitialYield(ListNodeType stmtList, Node genName) {
    MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList));

    TokenPos yieldPos(stmtList->pn_pos.begin, stmtList->pn_pos.begin + 1);
    NullaryNode* makeGen =
        new_<NullaryNode>(ParseNodeKind::Generator, yieldPos);
    if (!makeGen) {
      return false;
    }

    ParseNode* genInit =
        newAssignment(ParseNodeKind::AssignExpr, /* lhs = */ genName,
                      /* rhs = */ makeGen);
    if (!genInit) {
      return false;
    }

    UnaryNode* initialYield =
        newInitialYieldExpression(yieldPos.begin, genInit);
    if (!initialYield) {
      return false;
    }

    stmtList->prepend(initialYield);
    return true;
  }

  BinaryNodeType newSetThis(Node thisName, Node value) {
    return newBinary(ParseNodeKind::SetThis, thisName, value);
  }

  NullaryNodeType newEmptyStatement(const TokenPos& pos) {
    return new_<NullaryNode>(ParseNodeKind::EmptyStmt, pos);
  }

  BinaryNodeType newImportAssertion(Node keyNode, Node valueNode) {
    return newBinary(ParseNodeKind::ImportAssertion, keyNode, valueNode);
  }

  BinaryNodeType newModuleRequest(Node moduleSpec, Node importAssertionList,
                                  const TokenPos& pos) {
    return new_<BinaryNode>(ParseNodeKind::ImportModuleRequest, pos, moduleSpec,
                            importAssertionList);
  }

  BinaryNodeType newImportDeclaration(Node importSpecSet, Node moduleRequest,
                                      const TokenPos& pos) {
    return new_<BinaryNode>(ParseNodeKind::ImportDecl, pos, importSpecSet,
                            moduleRequest);
  }

  BinaryNodeType newImportSpec(Node importNameNode, Node bindingName) {
    return newBinary(ParseNodeKind::ImportSpec, importNameNode, bindingName);
  }

  UnaryNodeType newImportNamespaceSpec(uint32_t begin, Node bindingName) {
    return newUnary(ParseNodeKind::ImportNamespaceSpec, begin, bindingName);
  }

  UnaryNodeType newExportDeclaration(Node kid, const TokenPos& pos) {
    return new_<UnaryNode>(ParseNodeKind::ExportStmt, pos, kid);
  }

  BinaryNodeType newExportFromDeclaration(uint32_t begin, Node exportSpecSet,
                                          Node moduleRequest) {
    BinaryNode* decl = new_<BinaryNode>(ParseNodeKind::ExportFromStmt,
                                        exportSpecSet, moduleRequest);
    if (!decl) {
      return nullptr;
    }
    decl->pn_pos.begin = begin;
    return decl;
  }

  BinaryNodeType newExportDefaultDeclaration(Node kid, Node maybeBinding,
                                             const TokenPos& pos) {
    if (maybeBinding) {
      MOZ_ASSERT(maybeBinding->isKind(ParseNodeKind::Name));
      MOZ_ASSERT(!maybeBinding->isInParens());

      checkAndSetIsDirectRHSAnonFunction(kid);
    }

    return new_<BinaryNode>(ParseNodeKind::ExportDefaultStmt, pos, kid,
                            maybeBinding);
  }

  BinaryNodeType newExportSpec(Node bindingName, Node exportName) {
    return newBinary(ParseNodeKind::ExportSpec, bindingName, exportName);
  }

  UnaryNodeType newExportNamespaceSpec(uint32_t begin, Node exportName) {
    return newUnary(ParseNodeKind::ExportNamespaceSpec, begin, exportName);
  }

  NullaryNodeType newExportBatchSpec(const TokenPos& pos) {
    return new_<NullaryNode>(ParseNodeKind::ExportBatchSpecStmt, pos);
  }

  BinaryNodeType newImportMeta(NullaryNodeType importHolder,
                               NullaryNodeType metaHolder) {
    return new_<BinaryNode>(ParseNodeKind::ImportMetaExpr, importHolder,
                            metaHolder);
  }

  BinaryNodeType newCallImport(NullaryNodeType importHolder, Node singleArg) {
    return new_<BinaryNode>(ParseNodeKind::CallImportExpr, importHolder,
                            singleArg);
  }

  BinaryNodeType newCallImportSpec(Node specifierArg, Node optionalArg) {
    return new_<BinaryNode>(ParseNodeKind::CallImportSpec, specifierArg,
                            optionalArg);
  }

  UnaryNodeType newExprStatement(Node expr, uint32_t end) {
    MOZ_ASSERT(expr->pn_pos.end <= end);
    return new_<UnaryNode>(ParseNodeKind::ExpressionStmt,
                           TokenPos(expr->pn_pos.begin, end), expr);
  }

  TernaryNodeType newIfStatement(uint32_t begin, Node cond, Node thenBranch,
                                 Node elseBranch) {
    TernaryNode* node =
        new_<TernaryNode>(ParseNodeKind::IfStmt, cond, thenBranch, elseBranch);
    if (!node) {
      return nullptr;
    }
    node->pn_pos.begin = begin;
    return node;
  }

  BinaryNodeType newDoWhileStatement(Node body, Node cond,
                                     const TokenPos& pos) {
    return new_<BinaryNode>(ParseNodeKind::DoWhileStmt, pos, body, cond);
  }

  BinaryNodeType newWhileStatement(uint32_t begin, Node cond, Node body) {
    TokenPos pos(begin, body->pn_pos.end);
    return new_<BinaryNode>(ParseNodeKind::WhileStmt, pos, cond, body);
  }

  ForNodeType newForStatement(uint32_t begin, TernaryNodeType forHead,
                              Node body, unsigned iflags) {
    return new_<ForNode>(TokenPos(begin, body->pn_pos.end), forHead, body,
                         iflags);
  }

  TernaryNodeType newForHead(Node init, Node test, Node update,
                             const TokenPos& pos) {
    return new_<TernaryNode>(ParseNodeKind::ForHead, init, test, update, pos);
  }

  TernaryNodeType newForInOrOfHead(ParseNodeKind kind, Node target,
                                   Node iteratedExpr, const TokenPos& pos) {
    MOZ_ASSERT(kind == ParseNodeKind::ForIn || kind == ParseNodeKind::ForOf);
    return new_<TernaryNode>(kind, target, nullptr, iteratedExpr, pos);
  }

  SwitchStatementType newSwitchStatement(
      uint32_t begin, Node discriminant,
      LexicalScopeNodeType lexicalForCaseList, bool hasDefault) {
    return new_<SwitchStatement>(begin, discriminant, lexicalForCaseList,
                                 hasDefault);
  }

  CaseClauseType newCaseOrDefault(uint32_t begin, Node expr, Node body) {
    return new_<CaseClause>(expr, body, begin);
  }

  ContinueStatementType newContinueStatement(TaggedParserAtomIndex label,
                                             const TokenPos& pos) {
    return new_<ContinueStatement>(label, pos);
  }

  BreakStatementType newBreakStatement(TaggedParserAtomIndex label,
                                       const TokenPos& pos) {
    return new_<BreakStatement>(label, pos);
  }

  UnaryNodeType newReturnStatement(Node expr, const TokenPos& pos) {
    MOZ_ASSERT_IF(expr, pos.encloses(expr->pn_pos));
    return new_<UnaryNode>(ParseNodeKind::ReturnStmt, pos, expr);
  }

  UnaryNodeType newExpressionBody(Node expr) {
    return new_<UnaryNode>(ParseNodeKind::ReturnStmt, expr->pn_pos, expr);
  }

  BinaryNodeType newWithStatement(uint32_t begin, Node expr, Node body) {
    return new_<BinaryNode>(ParseNodeKind::WithStmt,
                            TokenPos(begin, body->pn_pos.end), expr, body);
  }

  LabeledStatementType newLabeledStatement(TaggedParserAtomIndex label,
                                           Node stmt, uint32_t begin) {
    return new_<LabeledStatement>(label, stmt, begin);
  }

  UnaryNodeType newThrowStatement(Node expr, const TokenPos& pos) {
    MOZ_ASSERT(pos.encloses(expr->pn_pos));
    return new_<UnaryNode>(ParseNodeKind::ThrowStmt, pos, expr);
  }

  TernaryNodeType newTryStatement(uint32_t begin, Node body,
                                  LexicalScopeNodeType catchScope,
                                  Node finallyBlock) {
    return new_<TryNode>(begin, body, catchScope, finallyBlock);
  }

  DebuggerStatementType newDebuggerStatement(const TokenPos& pos) {
    return new_<DebuggerStatement>(pos);
  }

  NameNodeType newPropertyName(TaggedParserAtomIndex name,
                               const TokenPos& pos) {
    return new_<NameNode>(ParseNodeKind::PropertyNameExpr, name, pos);
  }

  PropertyAccessType newPropertyAccess(Node expr, NameNodeType key) {
    return new_<PropertyAccess>(expr, key, expr->pn_pos.begin, key->pn_pos.end);
  }

  PropertyByValueType newPropertyByValue(Node lhs, Node index, uint32_t end) {
    return new_<PropertyByValue>(lhs, index, lhs->pn_pos.begin, end);
  }

  OptionalPropertyAccessType newOptionalPropertyAccess(Node expr,
                                                       NameNodeType key) {
    return new_<OptionalPropertyAccess>(expr, key, expr->pn_pos.begin,
                                        key->pn_pos.end);
  }

  OptionalPropertyByValueType newOptionalPropertyByValue(Node lhs, Node index,
                                                         uint32_t end) {
    return new_<OptionalPropertyByValue>(lhs, index, lhs->pn_pos.begin, end);
  }

  PrivateMemberAccessType newPrivateMemberAccess(Node lhs,
                                                 NameNodeType privateName,
                                                 uint32_t end) {
    return new_<PrivateMemberAccess>(lhs, privateName, lhs->pn_pos.begin, end);
  }

  OptionalPrivateMemberAccessType newOptionalPrivateMemberAccess(
      Node lhs, NameNodeType privateName, uint32_t end) {
    return new_<OptionalPrivateMemberAccess>(lhs, privateName,
                                             lhs->pn_pos.begin, end);
  }

  bool setupCatchScope(LexicalScopeNodeType lexicalScope, Node catchName,
                       Node catchBody) {
    BinaryNode* catchClause;
    if (catchName) {
      catchClause =
          new_<BinaryNode>(ParseNodeKind::Catch, catchName, catchBody);
    } else {
      catchClause = new_<BinaryNode>(ParseNodeKind::Catch, catchBody->pn_pos,
                                     catchName, catchBody);
    }
    if (!catchClause) {
      return false;
    }
    lexicalScope->setScopeBody(catchClause);
    return true;
  }

  [[nodiscard]] inline bool setLastFunctionFormalParameterDefault(
      FunctionNodeType funNode, Node defaultValue);

  void checkAndSetIsDirectRHSAnonFunction(Node pn) {
    if (IsAnonymousFunctionDefinition(pn)) {
      pn->setDirectRHSAnonFunction(true);
    }
  }

  ParamsBodyNodeType newParamsBody(const TokenPos& pos) {
    return new_<ParamsBodyNode>(pos);
  }

  FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind,
                               const TokenPos& pos) {
    return new_<FunctionNode>(syntaxKind, pos);
  }

  BinaryNodeType newObjectMethodOrPropertyDefinition(Node key, Node value,
                                                     AccessorType atype) {
    MOZ_ASSERT(isUsableAsObjectPropertyName(key));

    return new_<PropertyDefinition>(key, value, atype);
  }

  void setFunctionFormalParametersAndBody(FunctionNodeType funNode,
                                          ParamsBodyNodeType paramsBody) {
    funNode->setBody(paramsBody);
  }
  void setFunctionBox(FunctionNodeType funNode, FunctionBox* funbox) {
    funNode->setFunbox(funbox);
    funbox->functionNode = funNode;
  }
  void addFunctionFormalParameter(FunctionNodeType funNode, Node argpn) {
    addList(/* list = */ funNode->body(), /* kid = */ argpn);
  }
  void setFunctionBody(FunctionNodeType funNode, LexicalScopeNodeType body) {
    addList(/* list = */ funNode->body(), /* kid = */ body);
  }

  ModuleNodeType newModule(const TokenPos& pos) {
    return new_<ModuleNode>(pos);
  }

  LexicalScopeNodeType newLexicalScope(LexicalScope::ParserData* bindings,
                                       Node body,
                                       ScopeKind kind = ScopeKind::Lexical) {
    return new_<LexicalScopeNode>(bindings, body, kind);
  }

  ClassBodyScopeNodeType newClassBodyScope(ClassBodyScope::ParserData* bindings,
                                           ListNodeType body) {
    return new_<ClassBodyScopeNode>(bindings, body);
  }

  CallNodeType newNewExpression(uint32_t begin, Node ctor, ListNodeType args,
                                bool isSpread) {
    return new_<CallNode>(ParseNodeKind::NewExpr,
                          isSpread ? JSOp::SpreadNew : JSOp::New,
                          TokenPos(begin, args->pn_pos.end), ctor, args);
  }

  AssignmentNodeType newAssignment(ParseNodeKind kind, Node lhs, Node rhs) {
    if ((kind == ParseNodeKind::AssignExpr ||
         kind == ParseNodeKind::CoalesceAssignExpr ||
         kind == ParseNodeKind::OrAssignExpr ||
         kind == ParseNodeKind::AndAssignExpr) &&
        lhs->isKind(ParseNodeKind::Name) && !lhs->isInParens()) {
      checkAndSetIsDirectRHSAnonFunction(rhs);
    }

    return new_<AssignmentNode>(kind, lhs, rhs);
  }

  BinaryNodeType newInitExpr(Node lhs, Node rhs) {
    TokenPos pos(lhs->pn_pos.begin, rhs->pn_pos.end);
    return new_<BinaryNode>(ParseNodeKind::InitExpr, pos, lhs, rhs);
  }

  bool isUnparenthesizedAssignment(Node node) {
    if ((node->isKind(ParseNodeKind::AssignExpr)) && !node->isInParens()) {
      return true;
    }

    return false;
  }

  bool isUnparenthesizedUnaryExpression(Node node) {
    if (!node->isInParens()) {
      ParseNodeKind kind = node->getKind();
      return kind == ParseNodeKind::VoidExpr ||
             kind == ParseNodeKind::NotExpr ||
             kind == ParseNodeKind::BitNotExpr ||
             kind == ParseNodeKind::PosExpr || kind == ParseNodeKind::NegExpr ||
             kind == ParseNodeKind::AwaitExpr || IsTypeofKind(kind) ||
             IsDeleteKind(kind);
    }
    return false;
  }

  bool isReturnStatement(Node node) {
    return node->isKind(ParseNodeKind::ReturnStmt);
  }

  bool isStatementPermittedAfterReturnStatement(Node node) {
    ParseNodeKind kind = node->getKind();
    return kind == ParseNodeKind::Function || kind == ParseNodeKind::VarStmt ||
           kind == ParseNodeKind::BreakStmt ||
           kind == ParseNodeKind::ThrowStmt || kind == ParseNodeKind::EmptyStmt;
  }

  bool isSuperBase(Node node) { return node->isKind(ParseNodeKind::SuperBase); }

  bool isUsableAsObjectPropertyName(Node node) {
    return node->isKind(ParseNodeKind::NumberExpr) ||
           node->isKind(ParseNodeKind::BigIntExpr) ||
           node->isKind(ParseNodeKind::ObjectPropertyName) ||
           node->isKind(ParseNodeKind::StringExpr) ||
           node->isKind(ParseNodeKind::ComputedName) ||
           node->isKind(ParseNodeKind::PrivateName);
  }

  AssignmentNodeType finishInitializerAssignment(NameNodeType nameNode,
                                                 Node init) {
    MOZ_ASSERT(nameNode->isKind(ParseNodeKind::Name));
    MOZ_ASSERT(!nameNode->isInParens());

    checkAndSetIsDirectRHSAnonFunction(init);

    return newAssignment(ParseNodeKind::AssignExpr, nameNode, init);
  }

  void setBeginPosition(Node pn, Node oth) {
    setBeginPosition(pn, oth->pn_pos.begin);
  }
  void setBeginPosition(Node pn, uint32_t begin) {
    pn->pn_pos.begin = begin;
    MOZ_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end);
  }

  void setEndPosition(Node pn, Node oth) {
    setEndPosition(pn, oth->pn_pos.end);
  }
  void setEndPosition(Node pn, uint32_t end) {
    pn->pn_pos.end = end;
    MOZ_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end);
  }

  uint32_t getFunctionNameOffset(Node func, TokenStreamAnyChars& ts) {
    return func->pn_pos.begin;
  }

  ListNodeType newList(ParseNodeKind kind, const TokenPos& pos) {
    auto* list = new_<ListNode>(kind, pos);
    MOZ_ASSERT_IF(list, !list->is<DeclarationListNode>());
    MOZ_ASSERT_IF(list, !list->is<ParamsBodyNode>());
    return list;
  }

  ListNodeType newList(ParseNodeKind kind, Node kid) {
    auto* list = new_<ListNode>(kind, kid);
    MOZ_ASSERT_IF(list, !list->is<DeclarationListNode>());
    MOZ_ASSERT_IF(list, !list->is<ParamsBodyNode>());
    return list;
  }

  DeclarationListNodeType newDeclarationList(ParseNodeKind kind,
                                             const TokenPos& pos) {
    return new_<DeclarationListNode>(kind, pos);
  }

  ListNodeType newCommaExpressionList(Node kid) {
    return new_<ListNode>(ParseNodeKind::CommaExpr, kid);
  }

  void addList(ListNodeType list, Node kid) { list->append(kid); }

  void setListHasNonConstInitializer(ListNodeType literal) {
    literal->setHasNonConstInitializer();
  }
  template <typename NodeType>
  [[nodiscard]] NodeType parenthesize(NodeType node) {
    node->setInParens(true);
    return node;
  }
  template <typename NodeType>
  [[nodiscard]] NodeType setLikelyIIFE(NodeType node) {
    return parenthesize(node);
  }

  bool isName(Node node) { return node->isKind(ParseNodeKind::Name); }

  bool isArgumentsName(Node node) {
    return node->isKind(ParseNodeKind::Name) &&
           node->as<NameNode>().atom() ==
               TaggedParserAtomIndex::WellKnown::arguments();
  }

  bool isEvalName(Node node) {
    return node->isKind(ParseNodeKind::Name) &&
           node->as<NameNode>().atom() ==
               TaggedParserAtomIndex::WellKnown::eval();
  }

  bool isAsyncKeyword(Node node) {
    return node->isKind(ParseNodeKind::Name) &&
           node->pn_pos.begin + strlen("async") == node->pn_pos.end &&
           node->as<NameNode>().atom() ==
               TaggedParserAtomIndex::WellKnown::async();
  }

  bool isPrivateName(Node node) {
    return node->isKind(ParseNodeKind::PrivateName);
  }

  bool isPrivateMemberAccess(Node node) {
    if (node->isKind(ParseNodeKind::OptionalChain)) {
      return isPrivateMemberAccess(node->as<UnaryNode>().kid());
    }
    return node->is<PrivateMemberAccessBase>();
  }

  TaggedParserAtomIndex maybeDottedProperty(Node pn) {
    return pn->is<PropertyAccessBase>() ? pn->as<PropertyAccessBase>().name()
                                        : TaggedParserAtomIndex::null();
  }
  TaggedParserAtomIndex isStringExprStatement(Node pn, TokenPos* pos) {
    if (pn->is<UnaryNode>()) {
      UnaryNode* unary = &pn->as<UnaryNode>();
      if (auto atom = unary->isStringExprStatement()) {
        *pos = unary->kid()->pn_pos;
        return atom;
      }
    }
    return TaggedParserAtomIndex::null();
  }

  bool reuseLazyInnerFunctions() { return reuseGCThings; }
  bool reuseClosedOverBindings() { return reuseGCThings; }
  bool reuseRegexpSyntaxParse() { return reuseGCThings; }
  void nextLazyInnerFunction() { lazyInnerFunctionIndex++; }
  TaggedParserAtomIndex nextLazyClosedOverBinding() {
    // Trailing nullptrs were elided in PerHandlerParser::finishFunction().
    auto closedOverBindings = previousParseCache_.closedOverBindings();
    if (lazyClosedOverBindingIndex >= closedOverBindings.Length()) {
      return TaggedParserAtomIndex::null();
    }

    return closedOverBindings[lazyClosedOverBindingIndex++];
  }
  const ScriptStencil& cachedScriptData() const {
    // lazyInnerFunctionIndex is incremented with nextLazyInnferFunction before
    // reading the content, thus we need -1 to access the element that we just
    // skipped.
    return previousParseCache_.scriptData(lazyInnerFunctionIndex - 1);
  }
  const ScriptStencilExtra& cachedScriptExtra() const {
    // lazyInnerFunctionIndex is incremented with nextLazyInnferFunction before
    // reading the content, thus we need -1 to access the element that we just
    // skipped.
    return previousParseCache_.scriptExtra(lazyInnerFunctionIndex - 1);
  }

  void setPrivateNameKind(Node node, PrivateNameKind kind) {
    MOZ_ASSERT(node->is<NameNode>());
    node->as<NameNode>().setPrivateNameKind(kind);
  }
};

inline bool FullParseHandler::setLastFunctionFormalParameterDefault(
    FunctionNodeType funNode, Node defaultValue) {
  ParamsBodyNode* body = funNode->body();
  ParseNode* arg = body->last();
  ParseNode* pn = newAssignment(ParseNodeKind::AssignExpr, arg, defaultValue);
  if (!pn) {
    return false;
  }

  body->replaceLast(pn);
  return true;
}

}  // namespace frontend
}  // namespace js

#endif /* frontend_FullParseHandler_h */
