/* -*- 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/. */

/* JS reflection package. */

#include "mozilla/DebugOnly.h"

#include <stdlib.h>
#include <utility>

#include "jspubtd.h"

#include "builtin/Array.h"
#include "frontend/CompilationStencil.h"
#include "frontend/FrontendContext.h"  // AutoReportFrontendContext
#include "frontend/ModuleSharedContext.h"
#include "frontend/ParseNode.h"
#include "frontend/Parser.h"
#include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
#include "js/friend/StackLimits.h"    // js::AutoCheckRecursionLimit
#include "js/PropertyAndElement.h"    // JS_DefineFunction
#include "js/StableStringChars.h"
#include "vm/FunctionFlags.h"  // js::FunctionFlags
#include "vm/Interpreter.h"
#include "vm/JSAtom.h"
#include "vm/JSObject.h"
#include "vm/ModuleBuilder.h"  // js::ModuleBuilder
#include "vm/PlainObject.h"    // js::PlainObject
#include "vm/RegExpObject.h"

#include "vm/JSAtom-inl.h"
#include "vm/JSContext-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/ObjectOperations-inl.h"

using namespace js;
using namespace js::frontend;

using JS::AutoStableStringChars;
using JS::CompileOptions;
using JS::RootedValueArray;
using mozilla::DebugOnly;

enum ASTType {
  AST_ERROR = -1,
#define ASTDEF(ast, str) ast,
#include "jsast.tbl"
#undef ASTDEF
  AST_LIMIT
};

enum AssignmentOperator {
  AOP_ERR = -1,

  /* assign */
  AOP_ASSIGN = 0,
  /* operator-assign */
  AOP_PLUS,
  AOP_MINUS,
  AOP_STAR,
  AOP_DIV,
  AOP_MOD,
  AOP_POW,
  /* shift-assign */
  AOP_LSH,
  AOP_RSH,
  AOP_URSH,
  /* binary */
  AOP_BITOR,
  AOP_BITXOR,
  AOP_BITAND,
  /* short-circuit */
  AOP_COALESCE,
  AOP_OR,
  AOP_AND,

  AOP_LIMIT
};

enum BinaryOperator {
  BINOP_ERR = -1,

  /* eq */
  BINOP_EQ = 0,
  BINOP_NE,
  BINOP_STRICTEQ,
  BINOP_STRICTNE,
  /* rel */
  BINOP_LT,
  BINOP_LE,
  BINOP_GT,
  BINOP_GE,
  /* shift */
  BINOP_LSH,
  BINOP_RSH,
  BINOP_URSH,
  /* arithmetic */
  BINOP_ADD,
  BINOP_SUB,
  BINOP_STAR,
  BINOP_DIV,
  BINOP_MOD,
  BINOP_POW,
  /* binary */
  BINOP_BITOR,
  BINOP_BITXOR,
  BINOP_BITAND,
  /* misc */
  BINOP_IN,
  BINOP_INSTANCEOF,
  BINOP_COALESCE,

  BINOP_LIMIT
};

enum UnaryOperator {
  UNOP_ERR = -1,

  UNOP_DELETE = 0,
  UNOP_NEG,
  UNOP_POS,
  UNOP_NOT,
  UNOP_BITNOT,
  UNOP_TYPEOF,
  UNOP_VOID,
  UNOP_AWAIT,

  UNOP_LIMIT
};

enum VarDeclKind {
  VARDECL_ERR = -1,
  VARDECL_VAR = 0,
  VARDECL_CONST,
  VARDECL_LET,
  VARDECL_LIMIT
};

enum PropKind {
  PROP_ERR = -1,
  PROP_INIT = 0,
  PROP_GETTER,
  PROP_SETTER,
  PROP_MUTATEPROTO,
  PROP_LIMIT
};

static const char* const aopNames[] = {
    "=",     /* AOP_ASSIGN */
    "+=",    /* AOP_PLUS */
    "-=",    /* AOP_MINUS */
    "*=",    /* AOP_STAR */
    "/=",    /* AOP_DIV */
    "%=",    /* AOP_MOD */
    "**=",   /* AOP_POW */
    "<<=",   /* AOP_LSH */
    ">>=",   /* AOP_RSH */
    ">>>=",  /* AOP_URSH */
    "|=",    /* AOP_BITOR */
    "^=",    /* AOP_BITXOR */
    "&=",    /* AOP_BITAND */
    "\?\?=", /* AOP_COALESCE */
    "||=",   /* AOP_OR */
    "&&=",   /* AOP_AND */
};

static const char* const binopNames[] = {
    "==",         /* BINOP_EQ */
    "!=",         /* BINOP_NE */
    "===",        /* BINOP_STRICTEQ */
    "!==",        /* BINOP_STRICTNE */
    "<",          /* BINOP_LT */
    "<=",         /* BINOP_LE */
    ">",          /* BINOP_GT */
    ">=",         /* BINOP_GE */
    "<<",         /* BINOP_LSH */
    ">>",         /* BINOP_RSH */
    ">>>",        /* BINOP_URSH */
    "+",          /* BINOP_PLUS */
    "-",          /* BINOP_MINUS */
    "*",          /* BINOP_STAR */
    "/",          /* BINOP_DIV */
    "%",          /* BINOP_MOD */
    "**",         /* BINOP_POW */
    "|",          /* BINOP_BITOR */
    "^",          /* BINOP_BITXOR */
    "&",          /* BINOP_BITAND */
    "in",         /* BINOP_IN */
    "instanceof", /* BINOP_INSTANCEOF */
    "??",         /* BINOP_COALESCE */
};

static const char* const unopNames[] = {
    "delete", /* UNOP_DELETE */
    "-",      /* UNOP_NEG */
    "+",      /* UNOP_POS */
    "!",      /* UNOP_NOT */
    "~",      /* UNOP_BITNOT */
    "typeof", /* UNOP_TYPEOF */
    "void",   /* UNOP_VOID */
    "await",  /* UNOP_AWAIT */
};

static const char* const nodeTypeNames[] = {
#define ASTDEF(ast, str) str,
#include "jsast.tbl"
#undef ASTDEF
    nullptr};

enum YieldKind { Delegating, NotDelegating };

using NodeVector = RootedValueVector;

/*
 * ParseNode is a somewhat intricate data structure, and its invariants have
 * evolved, making it more likely that there could be a disconnect between the
 * parser and the AST serializer. We use these macros to check invariants on a
 * parse node and raise a dynamic error on failure.
 */
#define LOCAL_ASSERT(expr)                                    \
  JS_BEGIN_MACRO                                              \
    MOZ_ASSERT(expr);                                         \
    if (!(expr)) {                                            \
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, \
                                JSMSG_BAD_PARSE_NODE);        \
      return false;                                           \
    }                                                         \
  JS_END_MACRO

#define LOCAL_NOT_REACHED(expr)                             \
  JS_BEGIN_MACRO                                            \
    MOZ_ASSERT(false);                                      \
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, \
                              JSMSG_BAD_PARSE_NODE);        \
    return false;                                           \
  JS_END_MACRO

namespace {

/* Set 'result' to obj[id] if any such property exists, else defaultValue. */
static bool GetPropertyDefault(JSContext* cx, HandleObject obj, HandleId id,
                               HandleValue defaultValue,
                               MutableHandleValue result) {
  bool found;
  if (!HasProperty(cx, obj, id, &found)) {
    return false;
  }
  if (!found) {
    result.set(defaultValue);
    return true;
  }
  return GetProperty(cx, obj, obj, id, result);
}

enum class GeneratorStyle { None, ES6 };

/*
 * Builder class that constructs JavaScript AST node objects.
 */
class NodeBuilder {
  using CallbackArray = RootedValueArray<AST_LIMIT>;

  JSContext* cx;
  FrontendContext* fc;
  frontend::Parser<frontend::FullParseHandler, char16_t>* parser;
  bool saveLoc;       /* save source location information?     */
  char const* src;    /* UTF-8 encoded source filename or null */
  RootedValue srcval; /* source filename JS value or null      */

 public:
  NodeBuilder(JSContext* c, FrontendContext* f, bool l, char const* s)
      : cx(c), fc(f), parser(nullptr), saveLoc(l), src(s), srcval(c) {}

  [[nodiscard]] bool init() {
    if (src) {
      if (!atomValueUtf8(src, &srcval)) {
        return false;
      }
    } else {
      srcval.setNull();
    }

    return true;
  }

  void setParser(frontend::Parser<frontend::FullParseHandler, char16_t>* p) {
    parser = p;
  }

 private:
  [[nodiscard]] bool atomValue(const char* s, MutableHandleValue dst) {
    MOZ_ASSERT(JS::StringIsASCII(s));

    /*
     * Bug 575416: instead of Atomize, lookup constant atoms in tbl file
     */
    Rooted<JSAtom*> atom(cx, Atomize(cx, s, strlen(s)));
    if (!atom) {
      return false;
    }

    dst.setString(atom);
    return true;
  }

  [[nodiscard]] bool atomValueUtf8(const char* s, MutableHandleValue dst) {
    Rooted<JSAtom*> atom(cx, AtomizeUTF8Chars(cx, s, strlen(s)));
    if (!atom) {
      return false;
    }

    dst.setString(atom);
    return true;
  }

  [[nodiscard]] bool newObject(MutableHandleObject dst) {
    Rooted<PlainObject*> nobj(cx, NewPlainObject(cx));
    if (!nobj) {
      return false;
    }

    dst.set(nobj);
    return true;
  }

  [[nodiscard]] bool newArray(NodeVector& elts, MutableHandleValue dst);

  [[nodiscard]] bool createNode(ASTType type, TokenPos* pos,
                                MutableHandleObject dst);

  [[nodiscard]] bool newNodeHelper(HandleObject obj, MutableHandleValue dst) {
    // The end of the implementation of newNode().
    MOZ_ASSERT(obj);
    dst.setObject(*obj);
    return true;
  }

  template <typename... Arguments>
  [[nodiscard]] bool newNodeHelper(HandleObject obj, const char* name,
                                   HandleValue value, Arguments&&... rest) {
    // Recursive loop to define properties. Note that the newNodeHelper()
    // call below passes two fewer arguments than we received, as we omit
    // `name` and `value`. This eventually bottoms out in a call to the
    // non-template newNodeHelper() above.
    return defineProperty(obj, name, value) &&
           newNodeHelper(obj, std::forward<Arguments>(rest)...);
  }

  // Create a node object with "type" and "loc" properties, as well as zero
  // or more properties passed in as arguments. The signature is really more
  // like:
  //
  //     bool newNode(ASTType type, TokenPos* pos,
  //                  {const char *name0, HandleValue value0,}...
  //                  MutableHandleValue dst);
  template <typename... Arguments>
  [[nodiscard]] bool newNode(ASTType type, TokenPos* pos, Arguments&&... args) {
    RootedObject node(cx);
    return createNode(type, pos, &node) &&
           newNodeHelper(node, std::forward<Arguments>(args)...);
  }

  [[nodiscard]] bool listNode(ASTType type, const char* propName,
                              NodeVector& elts, TokenPos* pos,
                              MutableHandleValue dst) {
    RootedValue array(cx);
    if (!newArray(elts, &array)) {
      return false;
    }

    return newNode(type, pos, propName, array, dst);
  }

  [[nodiscard]] bool defineProperty(HandleObject obj, const char* name,
                                    HandleValue val) {
    MOZ_ASSERT_IF(val.isMagic(), val.whyMagic() == JS_SERIALIZE_NO_NODE);

    /*
     * Bug 575416: instead of Atomize, lookup constant atoms in tbl file
     */
    Rooted<JSAtom*> atom(cx, Atomize(cx, name, strlen(name)));
    if (!atom) {
      return false;
    }

    // Represent "no node" as null and ensure users are not exposed to magic
    // values.
    RootedValue optVal(cx,
                       val.isMagic(JS_SERIALIZE_NO_NODE) ? NullValue() : val);
    return DefineDataProperty(cx, obj, atom->asPropertyName(), optVal);
  }

  [[nodiscard]] bool newNodeLoc(TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool setNodeLoc(HandleObject node, TokenPos* pos);

 public:
  /*
   * All of the public builder methods take as their last two
   * arguments a nullable token position and a non-nullable, rooted
   * outparam.
   *
   * Any Value arguments representing optional subnodes may be a
   * JS_SERIALIZE_NO_NODE magic value.
   */

  /*
   * misc nodes
   */

  [[nodiscard]] bool program(NodeVector& elts, TokenPos* pos,
                             MutableHandleValue dst);

  [[nodiscard]] bool literal(HandleValue val, TokenPos* pos,
                             MutableHandleValue dst);

  [[nodiscard]] bool identifier(HandleValue name, TokenPos* pos,
                                MutableHandleValue dst);

  [[nodiscard]] bool function(ASTType type, TokenPos* pos, HandleValue id,
                              NodeVector& args, NodeVector& defaults,
                              HandleValue body, HandleValue rest,
                              GeneratorStyle generatorStyle, bool isAsync,
                              bool isExpression, MutableHandleValue dst);

  [[nodiscard]] bool variableDeclarator(HandleValue id, HandleValue init,
                                        TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool switchCase(HandleValue expr, NodeVector& elts,
                                TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool catchClause(HandleValue var, HandleValue body,
                                 TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool prototypeMutation(HandleValue val, TokenPos* pos,
                                       MutableHandleValue dst);
  [[nodiscard]] bool propertyInitializer(HandleValue key, HandleValue val,
                                         PropKind kind, bool isShorthand,
                                         bool isMethod, TokenPos* pos,
                                         MutableHandleValue dst);

  /*
   * statements
   */

  [[nodiscard]] bool blockStatement(NodeVector& elts, TokenPos* pos,
                                    MutableHandleValue dst);

  [[nodiscard]] bool expressionStatement(HandleValue expr, TokenPos* pos,
                                         MutableHandleValue dst);

  [[nodiscard]] bool emptyStatement(TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool ifStatement(HandleValue test, HandleValue cons,
                                 HandleValue alt, TokenPos* pos,
                                 MutableHandleValue dst);

  [[nodiscard]] bool breakStatement(HandleValue label, TokenPos* pos,
                                    MutableHandleValue dst);

  [[nodiscard]] bool continueStatement(HandleValue label, TokenPos* pos,
                                       MutableHandleValue dst);

  [[nodiscard]] bool labeledStatement(HandleValue label, HandleValue stmt,
                                      TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool throwStatement(HandleValue arg, TokenPos* pos,
                                    MutableHandleValue dst);

  [[nodiscard]] bool returnStatement(HandleValue arg, TokenPos* pos,
                                     MutableHandleValue dst);

  [[nodiscard]] bool forStatement(HandleValue init, HandleValue test,
                                  HandleValue update, HandleValue stmt,
                                  TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool forInStatement(HandleValue var, HandleValue expr,
                                    HandleValue stmt, TokenPos* pos,
                                    MutableHandleValue dst);

  [[nodiscard]] bool forOfStatement(HandleValue var, HandleValue expr,
                                    HandleValue stmt, TokenPos* pos,
                                    MutableHandleValue dst);

  [[nodiscard]] bool withStatement(HandleValue expr, HandleValue stmt,
                                   TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool whileStatement(HandleValue test, HandleValue stmt,
                                    TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool doWhileStatement(HandleValue stmt, HandleValue test,
                                      TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool switchStatement(HandleValue disc, NodeVector& elts,
                                     bool lexical, TokenPos* pos,
                                     MutableHandleValue dst);

  [[nodiscard]] bool tryStatement(HandleValue body, HandleValue handler,
                                  HandleValue finally, TokenPos* pos,
                                  MutableHandleValue dst);

  [[nodiscard]] bool debuggerStatement(TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool moduleRequest(HandleValue moduleSpec,
                                   NodeVector& assertions, TokenPos* pos,
                                   MutableHandleValue dst);

  [[nodiscard]] bool importAssertion(HandleValue key, HandleValue value,
                                     TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool importDeclaration(NodeVector& elts, HandleValue moduleSpec,
                                       TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool importSpecifier(HandleValue importName,
                                     HandleValue bindingName, TokenPos* pos,
                                     MutableHandleValue dst);

  [[nodiscard]] bool importNamespaceSpecifier(HandleValue bindingName,
                                              TokenPos* pos,
                                              MutableHandleValue dst);

  [[nodiscard]] bool exportDeclaration(HandleValue decl, NodeVector& elts,
                                       HandleValue moduleSpec,
                                       HandleValue isDefault, TokenPos* pos,
                                       MutableHandleValue dst);

  [[nodiscard]] bool exportSpecifier(HandleValue bindingName,
                                     HandleValue exportName, TokenPos* pos,
                                     MutableHandleValue dst);

  [[nodiscard]] bool exportNamespaceSpecifier(HandleValue exportName,
                                              TokenPos* pos,
                                              MutableHandleValue dst);

  [[nodiscard]] bool exportBatchSpecifier(TokenPos* pos,
                                          MutableHandleValue dst);

  [[nodiscard]] bool classDefinition(bool expr, HandleValue name,
                                     HandleValue heritage, HandleValue block,
                                     TokenPos* pos, MutableHandleValue dst);
  [[nodiscard]] bool classMembers(NodeVector& members, MutableHandleValue dst);
  [[nodiscard]] bool classMethod(HandleValue name, HandleValue body,
                                 PropKind kind, bool isStatic, TokenPos* pos,
                                 MutableHandleValue dst);
  [[nodiscard]] bool classField(HandleValue name, HandleValue initializer,
                                TokenPos* pos, MutableHandleValue dst);
  [[nodiscard]] bool staticClassBlock(HandleValue body, TokenPos* pos,
                                      MutableHandleValue dst);

  /*
   * expressions
   */

  [[nodiscard]] bool binaryExpression(BinaryOperator op, HandleValue left,
                                      HandleValue right, TokenPos* pos,
                                      MutableHandleValue dst);

  [[nodiscard]] bool unaryExpression(UnaryOperator op, HandleValue expr,
                                     TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool assignmentExpression(AssignmentOperator op,
                                          HandleValue lhs, HandleValue rhs,
                                          TokenPos* pos,
                                          MutableHandleValue dst);

  [[nodiscard]] bool updateExpression(HandleValue expr, bool incr, bool prefix,
                                      TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool logicalExpression(ParseNodeKind pnk, HandleValue left,
                                       HandleValue right, TokenPos* pos,
                                       MutableHandleValue dst);

  [[nodiscard]] bool conditionalExpression(HandleValue test, HandleValue cons,
                                           HandleValue alt, TokenPos* pos,
                                           MutableHandleValue dst);

  [[nodiscard]] bool sequenceExpression(NodeVector& elts, TokenPos* pos,
                                        MutableHandleValue dst);

  [[nodiscard]] bool newExpression(HandleValue callee, NodeVector& args,
                                   TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool callExpression(HandleValue callee, NodeVector& args,
                                    TokenPos* pos, MutableHandleValue dst,
                                    bool isOptional = false);

  [[nodiscard]] bool memberExpression(bool computed, HandleValue expr,
                                      HandleValue member, TokenPos* pos,
                                      MutableHandleValue dst,
                                      bool isOptional = false);

  [[nodiscard]] bool arrayExpression(NodeVector& elts, TokenPos* pos,
                                     MutableHandleValue dst);

  [[nodiscard]] bool templateLiteral(NodeVector& elts, TokenPos* pos,
                                     MutableHandleValue dst);

  [[nodiscard]] bool taggedTemplate(HandleValue callee, NodeVector& args,
                                    TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool callSiteObj(NodeVector& raw, NodeVector& cooked,
                                 TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool spreadExpression(HandleValue expr, TokenPos* pos,
                                      MutableHandleValue dst);

  [[nodiscard]] bool optionalExpression(HandleValue expr, TokenPos* pos,
                                        MutableHandleValue dst);

  [[nodiscard]] bool deleteOptionalExpression(HandleValue expr, TokenPos* pos,
                                              MutableHandleValue dst);

  [[nodiscard]] bool computedName(HandleValue name, TokenPos* pos,
                                  MutableHandleValue dst);

  [[nodiscard]] bool objectExpression(NodeVector& elts, TokenPos* pos,
                                      MutableHandleValue dst);

  [[nodiscard]] bool thisExpression(TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool yieldExpression(HandleValue arg, YieldKind kind,
                                     TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool metaProperty(HandleValue meta, HandleValue property,
                                  TokenPos* pos, MutableHandleValue dst);

  [[nodiscard]] bool callImportExpression(HandleValue ident, NodeVector& args,
                                          TokenPos* pos,
                                          MutableHandleValue dst);

  [[nodiscard]] bool super(TokenPos* pos, MutableHandleValue dst);

#ifdef ENABLE_RECORD_TUPLE
  [[nodiscard]] bool recordExpression(NodeVector& elts, TokenPos* pos,
                                      MutableHandleValue dst);

  [[nodiscard]] bool tupleExpression(NodeVector& elts, TokenPos* pos,
                                     MutableHandleValue dst);
#endif

  /*
   * declarations
   */

  [[nodiscard]] bool variableDeclaration(NodeVector& elts, VarDeclKind kind,
                                         TokenPos* pos, MutableHandleValue dst);

  /*
   * patterns
   */

  [[nodiscard]] bool arrayPattern(NodeVector& elts, TokenPos* pos,
                                  MutableHandleValue dst);

  [[nodiscard]] bool objectPattern(NodeVector& elts, TokenPos* pos,
                                   MutableHandleValue dst);

  [[nodiscard]] bool propertyPattern(HandleValue key, HandleValue patt,
                                     bool isShorthand, TokenPos* pos,
                                     MutableHandleValue dst);
};

} /* anonymous namespace */

bool NodeBuilder::createNode(ASTType type, TokenPos* pos,
                             MutableHandleObject dst) {
  MOZ_ASSERT(type > AST_ERROR && type < AST_LIMIT);

  RootedValue tv(cx);
  Rooted<PlainObject*> node(cx, NewPlainObject(cx));
  if (!node || !setNodeLoc(node, pos) || !atomValue(nodeTypeNames[type], &tv) ||
      !defineProperty(node, "type", tv)) {
    return false;
  }

  dst.set(node);
  return true;
}

bool NodeBuilder::newArray(NodeVector& elts, MutableHandleValue dst) {
  const size_t len = elts.length();
  if (len > UINT32_MAX) {
    ReportAllocationOverflow(fc);
    return false;
  }
  RootedObject array(cx, NewDenseFullyAllocatedArray(cx, uint32_t(len)));
  if (!array) {
    return false;
  }

  for (size_t i = 0; i < len; i++) {
    RootedValue val(cx, elts[i]);

    MOZ_ASSERT_IF(val.isMagic(), val.whyMagic() == JS_SERIALIZE_NO_NODE);

    /* Represent "no node" as an array hole by not adding the value. */
    if (val.isMagic(JS_SERIALIZE_NO_NODE)) {
      continue;
    }

    if (!DefineDataElement(cx, array, i, val)) {
      return false;
    }
  }

  dst.setObject(*array);
  return true;
}

bool NodeBuilder::newNodeLoc(TokenPos* pos, MutableHandleValue dst) {
  if (!pos) {
    dst.setNull();
    return true;
  }

  RootedObject loc(cx);
  RootedObject to(cx);
  RootedValue val(cx);

  if (!newObject(&loc)) {
    return false;
  }

  dst.setObject(*loc);

  uint32_t startLineNum, startColumnIndex;
  uint32_t endLineNum, endColumnIndex;
  parser->tokenStream.computeLineAndColumn(pos->begin, &startLineNum,
                                           &startColumnIndex);
  parser->tokenStream.computeLineAndColumn(pos->end, &endLineNum,
                                           &endColumnIndex);

  if (!newObject(&to)) {
    return false;
  }
  val.setObject(*to);
  if (!defineProperty(loc, "start", val)) {
    return false;
  }
  val.setNumber(startLineNum);
  if (!defineProperty(to, "line", val)) {
    return false;
  }
  val.setNumber(startColumnIndex);
  if (!defineProperty(to, "column", val)) {
    return false;
  }

  if (!newObject(&to)) {
    return false;
  }
  val.setObject(*to);
  if (!defineProperty(loc, "end", val)) {
    return false;
  }
  val.setNumber(endLineNum);
  if (!defineProperty(to, "line", val)) {
    return false;
  }
  val.setNumber(endColumnIndex);
  if (!defineProperty(to, "column", val)) {
    return false;
  }

  if (!defineProperty(loc, "source", srcval)) {
    return false;
  }

  return true;
}

bool NodeBuilder::setNodeLoc(HandleObject node, TokenPos* pos) {
  if (!saveLoc) {
    return true;
  }

  RootedValue loc(cx);
  return newNodeLoc(pos, &loc) && defineProperty(node, "loc", loc);
}

bool NodeBuilder::program(NodeVector& elts, TokenPos* pos,
                          MutableHandleValue dst) {
  return listNode(AST_PROGRAM, "body", elts, pos, dst);
}

bool NodeBuilder::blockStatement(NodeVector& elts, TokenPos* pos,
                                 MutableHandleValue dst) {
  return listNode(AST_BLOCK_STMT, "body", elts, pos, dst);
}

bool NodeBuilder::expressionStatement(HandleValue expr, TokenPos* pos,
                                      MutableHandleValue dst) {
  return newNode(AST_EXPR_STMT, pos, "expression", expr, dst);
}

bool NodeBuilder::emptyStatement(TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_EMPTY_STMT, pos, dst);
}

bool NodeBuilder::ifStatement(HandleValue test, HandleValue cons,
                              HandleValue alt, TokenPos* pos,
                              MutableHandleValue dst) {
  return newNode(AST_IF_STMT, pos, "test", test, "consequent", cons,
                 "alternate", alt, dst);
}

bool NodeBuilder::breakStatement(HandleValue label, TokenPos* pos,
                                 MutableHandleValue dst) {
  return newNode(AST_BREAK_STMT, pos, "label", label, dst);
}

bool NodeBuilder::continueStatement(HandleValue label, TokenPos* pos,
                                    MutableHandleValue dst) {
  return newNode(AST_CONTINUE_STMT, pos, "label", label, dst);
}

bool NodeBuilder::labeledStatement(HandleValue label, HandleValue stmt,
                                   TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_LAB_STMT, pos, "label", label, "body", stmt, dst);
}

bool NodeBuilder::throwStatement(HandleValue arg, TokenPos* pos,
                                 MutableHandleValue dst) {
  return newNode(AST_THROW_STMT, pos, "argument", arg, dst);
}

bool NodeBuilder::returnStatement(HandleValue arg, TokenPos* pos,
                                  MutableHandleValue dst) {
  return newNode(AST_RETURN_STMT, pos, "argument", arg, dst);
}

bool NodeBuilder::forStatement(HandleValue init, HandleValue test,
                               HandleValue update, HandleValue stmt,
                               TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_FOR_STMT, pos, "init", init, "test", test, "update",
                 update, "body", stmt, dst);
}

bool NodeBuilder::forInStatement(HandleValue var, HandleValue expr,
                                 HandleValue stmt, TokenPos* pos,
                                 MutableHandleValue dst) {
  return newNode(AST_FOR_IN_STMT, pos, "left", var, "right", expr, "body", stmt,
                 dst);
}

bool NodeBuilder::forOfStatement(HandleValue var, HandleValue expr,
                                 HandleValue stmt, TokenPos* pos,
                                 MutableHandleValue dst) {
  return newNode(AST_FOR_OF_STMT, pos, "left", var, "right", expr, "body", stmt,
                 dst);
}

bool NodeBuilder::withStatement(HandleValue expr, HandleValue stmt,
                                TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_WITH_STMT, pos, "object", expr, "body", stmt, dst);
}

bool NodeBuilder::whileStatement(HandleValue test, HandleValue stmt,
                                 TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_WHILE_STMT, pos, "test", test, "body", stmt, dst);
}

bool NodeBuilder::doWhileStatement(HandleValue stmt, HandleValue test,
                                   TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_DO_STMT, pos, "body", stmt, "test", test, dst);
}

bool NodeBuilder::switchStatement(HandleValue disc, NodeVector& elts,
                                  bool lexical, TokenPos* pos,
                                  MutableHandleValue dst) {
  RootedValue array(cx);
  if (!newArray(elts, &array)) {
    return false;
  }

  RootedValue lexicalVal(cx, BooleanValue(lexical));
  return newNode(AST_SWITCH_STMT, pos, "discriminant", disc, "cases", array,
                 "lexical", lexicalVal, dst);
}

bool NodeBuilder::tryStatement(HandleValue body, HandleValue handler,
                               HandleValue finally, TokenPos* pos,
                               MutableHandleValue dst) {
  return newNode(AST_TRY_STMT, pos, "block", body, "handler", handler,
                 "finalizer", finally, dst);
}

bool NodeBuilder::debuggerStatement(TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_DEBUGGER_STMT, pos, dst);
}

bool NodeBuilder::binaryExpression(BinaryOperator op, HandleValue left,
                                   HandleValue right, TokenPos* pos,
                                   MutableHandleValue dst) {
  MOZ_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT);

  RootedValue opName(cx);
  if (!atomValue(binopNames[op], &opName)) {
    return false;
  }

  return newNode(AST_BINARY_EXPR, pos, "operator", opName, "left", left,
                 "right", right, dst);
}

bool NodeBuilder::unaryExpression(UnaryOperator unop, HandleValue expr,
                                  TokenPos* pos, MutableHandleValue dst) {
  MOZ_ASSERT(unop > UNOP_ERR && unop < UNOP_LIMIT);

  RootedValue opName(cx);
  if (!atomValue(unopNames[unop], &opName)) {
    return false;
  }

  RootedValue trueVal(cx, BooleanValue(true));
  return newNode(AST_UNARY_EXPR, pos, "operator", opName, "argument", expr,
                 "prefix", trueVal, dst);
}

bool NodeBuilder::assignmentExpression(AssignmentOperator aop, HandleValue lhs,
                                       HandleValue rhs, TokenPos* pos,
                                       MutableHandleValue dst) {
  MOZ_ASSERT(aop > AOP_ERR && aop < AOP_LIMIT);

  RootedValue opName(cx);
  if (!atomValue(aopNames[aop], &opName)) {
    return false;
  }

  return newNode(AST_ASSIGN_EXPR, pos, "operator", opName, "left", lhs, "right",
                 rhs, dst);
}

bool NodeBuilder::updateExpression(HandleValue expr, bool incr, bool prefix,
                                   TokenPos* pos, MutableHandleValue dst) {
  RootedValue opName(cx);
  if (!atomValue(incr ? "++" : "--", &opName)) {
    return false;
  }

  RootedValue prefixVal(cx, BooleanValue(prefix));

  return newNode(AST_UPDATE_EXPR, pos, "operator", opName, "argument", expr,
                 "prefix", prefixVal, dst);
}

bool NodeBuilder::logicalExpression(ParseNodeKind pnk, HandleValue left,
                                    HandleValue right, TokenPos* pos,
                                    MutableHandleValue dst) {
  RootedValue opName(cx);
  switch (pnk) {
    case ParseNodeKind::OrExpr:
      if (!atomValue("||", &opName)) {
        return false;
      }
      break;
    case ParseNodeKind::CoalesceExpr:
      if (!atomValue("??", &opName)) {
        return false;
      }
      break;
    case ParseNodeKind::AndExpr:
      if (!atomValue("&&", &opName)) {
        return false;
      }
      break;
    default:
      MOZ_CRASH("Unexpected ParseNodeKind: Must be `Or`, `And`, or `Coalesce`");
  }

  return newNode(AST_LOGICAL_EXPR, pos, "operator", opName, "left", left,
                 "right", right, dst);
}

bool NodeBuilder::conditionalExpression(HandleValue test, HandleValue cons,
                                        HandleValue alt, TokenPos* pos,
                                        MutableHandleValue dst) {
  return newNode(AST_COND_EXPR, pos, "test", test, "consequent", cons,
                 "alternate", alt, dst);
}

bool NodeBuilder::sequenceExpression(NodeVector& elts, TokenPos* pos,
                                     MutableHandleValue dst) {
  return listNode(AST_LIST_EXPR, "expressions", elts, pos, dst);
}

bool NodeBuilder::callExpression(HandleValue callee, NodeVector& args,
                                 TokenPos* pos, MutableHandleValue dst,
                                 bool isOptional) {
  RootedValue array(cx);
  if (!newArray(args, &array)) {
    return false;
  }

  return newNode(isOptional ? AST_OPT_CALL_EXPR : AST_CALL_EXPR, pos, "callee",
                 callee, "arguments", array, dst);
}

bool NodeBuilder::newExpression(HandleValue callee, NodeVector& args,
                                TokenPos* pos, MutableHandleValue dst) {
  RootedValue array(cx);
  if (!newArray(args, &array)) {
    return false;
  }

  return newNode(AST_NEW_EXPR, pos, "callee", callee, "arguments", array, dst);
}

bool NodeBuilder::memberExpression(bool computed, HandleValue expr,
                                   HandleValue member, TokenPos* pos,
                                   MutableHandleValue dst,
                                   bool isOptional /* = false */) {
  RootedValue computedVal(cx, BooleanValue(computed));

  return newNode(isOptional ? AST_OPT_MEMBER_EXPR : AST_MEMBER_EXPR, pos,
                 "object", expr, "property", member, "computed", computedVal,
                 dst);
}

bool NodeBuilder::arrayExpression(NodeVector& elts, TokenPos* pos,
                                  MutableHandleValue dst) {
  return listNode(AST_ARRAY_EXPR, "elements", elts, pos, dst);
}

bool NodeBuilder::callSiteObj(NodeVector& raw, NodeVector& cooked,
                              TokenPos* pos, MutableHandleValue dst) {
  RootedValue rawVal(cx);
  if (!newArray(raw, &rawVal)) {
    return false;
  }

  RootedValue cookedVal(cx);
  if (!newArray(cooked, &cookedVal)) {
    return false;
  }

  return newNode(AST_CALL_SITE_OBJ, pos, "raw", rawVal, "cooked", cookedVal,
                 dst);
}

bool NodeBuilder::taggedTemplate(HandleValue callee, NodeVector& args,
                                 TokenPos* pos, MutableHandleValue dst) {
  RootedValue array(cx);
  if (!newArray(args, &array)) {
    return false;
  }

  return newNode(AST_TAGGED_TEMPLATE, pos, "callee", callee, "arguments", array,
                 dst);
}

bool NodeBuilder::templateLiteral(NodeVector& elts, TokenPos* pos,
                                  MutableHandleValue dst) {
  return listNode(AST_TEMPLATE_LITERAL, "elements", elts, pos, dst);
}

bool NodeBuilder::computedName(HandleValue name, TokenPos* pos,
                               MutableHandleValue dst) {
  return newNode(AST_COMPUTED_NAME, pos, "name", name, dst);
}

bool NodeBuilder::spreadExpression(HandleValue expr, TokenPos* pos,
                                   MutableHandleValue dst) {
  return newNode(AST_SPREAD_EXPR, pos, "expression", expr, 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) {
  RootedValue kindName(cx);
  if (!atomValue("init", &kindName)) {
    return false;
  }

  RootedValue isShorthandVal(cx, BooleanValue(isShorthand));

  return newNode(AST_PROP_PATT, pos, "key", key, "value", patt, "kind",
                 kindName, "shorthand", isShorthandVal, dst);
}

bool NodeBuilder::prototypeMutation(HandleValue val, TokenPos* pos,
                                    MutableHandleValue dst) {
  return newNode(AST_PROTOTYPEMUTATION, pos, "value", val, dst);
}

bool NodeBuilder::propertyInitializer(HandleValue key, HandleValue val,
                                      PropKind kind, bool isShorthand,
                                      bool isMethod, TokenPos* pos,
                                      MutableHandleValue dst) {
  RootedValue kindName(cx);
  if (!atomValue(kind == PROP_INIT     ? "init"
                 : kind == PROP_GETTER ? "get"
                                       : "set",
                 &kindName)) {
    return false;
  }

  RootedValue isShorthandVal(cx, BooleanValue(isShorthand));
  RootedValue isMethodVal(cx, BooleanValue(isMethod));

  return newNode(AST_PROPERTY, pos, "key", key, "value", val, "kind", kindName,
                 "method", isMethodVal, "shorthand", isShorthandVal, dst);
}

bool NodeBuilder::objectExpression(NodeVector& elts, TokenPos* pos,
                                   MutableHandleValue dst) {
  return listNode(AST_OBJECT_EXPR, "properties", elts, pos, dst);
}

#ifdef ENABLE_RECORD_TUPLE
bool NodeBuilder::recordExpression(NodeVector& elts, TokenPos* pos,
                                   MutableHandleValue dst) {
  return listNode(AST_RECORD_EXPR, "properties", elts, pos, dst);
}

bool NodeBuilder::tupleExpression(NodeVector& elts, TokenPos* pos,
                                  MutableHandleValue dst) {
  return listNode(AST_TUPLE_EXPR, "elements", elts, pos, dst);
}
#endif

bool NodeBuilder::thisExpression(TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_THIS_EXPR, pos, dst);
}

bool NodeBuilder::yieldExpression(HandleValue arg, YieldKind kind,
                                  TokenPos* pos, MutableHandleValue dst) {
  RootedValue delegateVal(cx);
  switch (kind) {
    case Delegating:
      delegateVal = BooleanValue(true);
      break;
    case NotDelegating:
      delegateVal = BooleanValue(false);
      break;
  }
  return newNode(AST_YIELD_EXPR, pos, "argument", arg, "delegate", delegateVal,
                 dst);
}

bool NodeBuilder::moduleRequest(HandleValue moduleSpec, NodeVector& assertions,
                                TokenPos* pos, MutableHandleValue dst) {
  RootedValue array(cx);
  if (!newArray(assertions, &array)) {
    return false;
  }

  return newNode(AST_MODULE_REQUEST, pos, "source", moduleSpec, "assertions",
                 array, dst);
}

bool NodeBuilder::importAssertion(HandleValue key, HandleValue value,
                                  TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_IMPORT_ASSERTION, pos, "key", key, "value", value, dst);
}

bool NodeBuilder::importDeclaration(NodeVector& elts, HandleValue moduleRequest,
                                    TokenPos* pos, MutableHandleValue dst) {
  RootedValue array(cx);
  if (!newArray(elts, &array)) {
    return false;
  }

  return newNode(AST_IMPORT_DECL, pos, "specifiers", array, "moduleRequest",
                 moduleRequest, dst);
}

bool NodeBuilder::importSpecifier(HandleValue importName,
                                  HandleValue bindingName, TokenPos* pos,
                                  MutableHandleValue dst) {
  return newNode(AST_IMPORT_SPEC, pos, "id", importName, "name", bindingName,
                 dst);
}

bool NodeBuilder::importNamespaceSpecifier(HandleValue bindingName,
                                           TokenPos* pos,
                                           MutableHandleValue dst) {
  return newNode(AST_IMPORT_NAMESPACE_SPEC, pos, "name", bindingName, dst);
}

bool NodeBuilder::exportDeclaration(HandleValue decl, NodeVector& elts,
                                    HandleValue moduleRequest,
                                    HandleValue isDefault, TokenPos* pos,
                                    MutableHandleValue dst) {
  RootedValue array(cx, NullValue());
  if (decl.isNull() && !newArray(elts, &array)) {
    return false;
  }

  return newNode(AST_EXPORT_DECL, pos, "declaration", decl, "specifiers", array,
                 "moduleRequest", moduleRequest, "isDefault", isDefault, dst);
}

bool NodeBuilder::exportSpecifier(HandleValue bindingName,
                                  HandleValue exportName, TokenPos* pos,
                                  MutableHandleValue dst) {
  return newNode(AST_EXPORT_SPEC, pos, "id", bindingName, "name", exportName,
                 dst);
}

bool NodeBuilder::exportNamespaceSpecifier(HandleValue exportName,
                                           TokenPos* pos,
                                           MutableHandleValue dst) {
  return newNode(AST_EXPORT_NAMESPACE_SPEC, pos, "name", exportName, dst);
}

bool NodeBuilder::exportBatchSpecifier(TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_EXPORT_BATCH_SPEC, pos, dst);
}

bool NodeBuilder::variableDeclaration(NodeVector& elts, VarDeclKind kind,
                                      TokenPos* pos, MutableHandleValue dst) {
  MOZ_ASSERT(kind > VARDECL_ERR && kind < VARDECL_LIMIT);

  RootedValue array(cx), kindName(cx);
  if (!newArray(elts, &array) || !atomValue(kind == VARDECL_CONST ? "const"
                                            : kind == VARDECL_LET ? "let"
                                                                  : "var",
                                            &kindName)) {
    return false;
  }

  return newNode(AST_VAR_DECL, pos, "kind", kindName, "declarations", array,
                 dst);
}

bool NodeBuilder::variableDeclarator(HandleValue id, HandleValue init,
                                     TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_VAR_DTOR, pos, "id", id, "init", init, dst);
}

bool NodeBuilder::switchCase(HandleValue expr, NodeVector& elts, TokenPos* pos,
                             MutableHandleValue dst) {
  RootedValue array(cx);
  if (!newArray(elts, &array)) {
    return false;
  }

  return newNode(AST_CASE, pos, "test", expr, "consequent", array, dst);
}

bool NodeBuilder::catchClause(HandleValue var, HandleValue body, TokenPos* pos,
                              MutableHandleValue dst) {
  return newNode(AST_CATCH, pos, "param", var, "body", body, dst);
}

bool NodeBuilder::literal(HandleValue val, TokenPos* pos,
                          MutableHandleValue dst) {
  return newNode(AST_LITERAL, pos, "value", val, dst);
}

bool NodeBuilder::identifier(HandleValue name, TokenPos* pos,
                             MutableHandleValue dst) {
  return newNode(AST_IDENTIFIER, pos, "name", name, dst);
}

bool NodeBuilder::objectPattern(NodeVector& elts, TokenPos* pos,
                                MutableHandleValue dst) {
  return listNode(AST_OBJECT_PATT, "properties", elts, pos, dst);
}

bool NodeBuilder::arrayPattern(NodeVector& elts, TokenPos* pos,
                               MutableHandleValue dst) {
  return listNode(AST_ARRAY_PATT, "elements", elts, pos, dst);
}

bool NodeBuilder::function(ASTType type, TokenPos* pos, HandleValue id,
                           NodeVector& args, NodeVector& defaults,
                           HandleValue body, HandleValue rest,
                           GeneratorStyle generatorStyle, bool isAsync,
                           bool isExpression, MutableHandleValue dst) {
  RootedValue array(cx), defarray(cx);
  if (!newArray(args, &array)) {
    return false;
  }
  if (!newArray(defaults, &defarray)) {
    return false;
  }

  bool isGenerator = generatorStyle != GeneratorStyle::None;
  RootedValue isGeneratorVal(cx, BooleanValue(isGenerator));
  RootedValue isAsyncVal(cx, BooleanValue(isAsync));
  RootedValue isExpressionVal(cx, BooleanValue(isExpression));

  if (isGenerator) {
    MOZ_ASSERT(generatorStyle == GeneratorStyle::ES6);
    JSAtom* styleStr = Atomize(cx, "es6", 3);
    if (!styleStr) {
      return false;
    }
    RootedValue styleVal(cx, StringValue(styleStr));
    return newNode(type, pos, "id", id, "params", array, "defaults", defarray,
                   "body", body, "rest", rest, "generator", isGeneratorVal,
                   "async", isAsyncVal, "style", styleVal, "expression",
                   isExpressionVal, dst);
  }

  return newNode(type, pos, "id", id, "params", array, "defaults", defarray,
                 "body", body, "rest", rest, "generator", isGeneratorVal,
                 "async", isAsyncVal, "expression", isExpressionVal, dst);
}

bool NodeBuilder::classMethod(HandleValue name, HandleValue body, PropKind kind,
                              bool isStatic, TokenPos* pos,
                              MutableHandleValue dst) {
  RootedValue kindName(cx);
  if (!atomValue(kind == PROP_INIT     ? "method"
                 : kind == PROP_GETTER ? "get"
                                       : "set",
                 &kindName)) {
    return false;
  }

  RootedValue isStaticVal(cx, BooleanValue(isStatic));
  return newNode(AST_CLASS_METHOD, pos, "name", name, "body", body, "kind",
                 kindName, "static", isStaticVal, dst);
}

bool NodeBuilder::classField(HandleValue name, HandleValue initializer,
                             TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_CLASS_FIELD, pos, "name", name, "init", initializer, dst);
}

bool NodeBuilder::staticClassBlock(HandleValue body, TokenPos* pos,
                                   MutableHandleValue dst) {
  return newNode(AST_STATIC_CLASS_BLOCK, pos, "body", body, dst);
}

bool NodeBuilder::classMembers(NodeVector& members, MutableHandleValue dst) {
  return newArray(members, dst);
}

bool NodeBuilder::classDefinition(bool expr, HandleValue name,
                                  HandleValue heritage, HandleValue block,
                                  TokenPos* pos, MutableHandleValue dst) {
  ASTType type = expr ? AST_CLASS_EXPR : AST_CLASS_STMT;
  return newNode(type, pos, "id", name, "superClass", heritage, "body", block,
                 dst);
}

bool NodeBuilder::metaProperty(HandleValue meta, HandleValue property,
                               TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_METAPROPERTY, pos, "meta", meta, "property", property,
                 dst);
}

bool NodeBuilder::callImportExpression(HandleValue ident, NodeVector& args,
                                       TokenPos* pos, MutableHandleValue dst) {
  RootedValue array(cx);
  if (!newArray(args, &array)) {
    return false;
  }

  return newNode(AST_CALL_IMPORT, pos, "ident", ident, "arguments", array, dst);
}

bool NodeBuilder::super(TokenPos* pos, MutableHandleValue dst) {
  return newNode(AST_SUPER, pos, dst);
}

namespace {

/*
 * Serialization of parse nodes to JavaScript objects.
 *
 * All serialization methods take a non-nullable ParseNode pointer.
 */
class ASTSerializer {
  JSContext* cx;
  FrontendContext* fc;
  Parser<FullParseHandler, char16_t>* parser;
  NodeBuilder builder;
  DebugOnly<uint32_t> lineno;

  Value unrootedAtomContents(JSAtom* atom) {
    return StringValue(atom ? atom : cx->names().empty);
  }

  BinaryOperator binop(ParseNodeKind kind);
  UnaryOperator unop(ParseNodeKind kind);
  AssignmentOperator aop(ParseNodeKind kind);

  bool statements(ListNode* stmtList, NodeVector& elts);
  bool expressions(ListNode* exprList, NodeVector& elts);
  bool leftAssociate(ListNode* node, MutableHandleValue dst);
  bool rightAssociate(ListNode* node, MutableHandleValue dst);
  bool functionArgs(ParamsBodyNode* pn, NodeVector& args, NodeVector& defaults,
                    MutableHandleValue rest);

  bool sourceElement(ParseNode* pn, MutableHandleValue dst);

  bool declaration(ParseNode* pn, MutableHandleValue dst);
  bool variableDeclaration(ListNode* declList, bool lexical,
                           MutableHandleValue dst);
  bool variableDeclarator(ParseNode* pn, MutableHandleValue dst);
  bool importDeclaration(BinaryNode* importNode, MutableHandleValue dst);
  bool importSpecifier(BinaryNode* importSpec, MutableHandleValue dst);
  bool importNamespaceSpecifier(UnaryNode* importSpec, MutableHandleValue dst);
  bool exportDeclaration(ParseNode* exportNode, MutableHandleValue dst);
  bool exportSpecifier(BinaryNode* exportSpec, MutableHandleValue dst);
  bool exportNamespaceSpecifier(UnaryNode* exportSpec, MutableHandleValue dst);
  bool classDefinition(ClassNode* pn, bool expr, MutableHandleValue dst);
  bool importAssertions(ListNode* assertionList, NodeVector& assertions);

  bool optStatement(ParseNode* pn, MutableHandleValue dst) {
    if (!pn) {
      dst.setMagic(JS_SERIALIZE_NO_NODE);
      return true;
    }
    return statement(pn, dst);
  }

  bool forInit(ParseNode* pn, MutableHandleValue dst);
  bool forIn(ForNode* loop, ParseNode* iterExpr, HandleValue var,
             HandleValue stmt, MutableHandleValue dst);
  bool forOf(ForNode* loop, ParseNode* iterExpr, HandleValue var,
             HandleValue stmt, MutableHandleValue dst);
  bool statement(ParseNode* pn, MutableHandleValue dst);
  bool blockStatement(ListNode* node, MutableHandleValue dst);
  bool switchStatement(SwitchStatement* switchStmt, MutableHandleValue dst);
  bool switchCase(CaseClause* caseClause, MutableHandleValue dst);
  bool tryStatement(TryNode* tryNode, MutableHandleValue dst);
  bool catchClause(BinaryNode* catchClause, MutableHandleValue dst);

  bool optExpression(ParseNode* pn, MutableHandleValue dst) {
    if (!pn) {
      dst.setMagic(JS_SERIALIZE_NO_NODE);
      return true;
    }
    return expression(pn, dst);
  }

  bool expression(ParseNode* pn, MutableHandleValue dst);

  bool propertyName(ParseNode* key, MutableHandleValue dst);
  bool property(ParseNode* pn, MutableHandleValue dst);

  bool classMethod(ClassMethod* classMethod, MutableHandleValue dst);
  bool classField(ClassField* classField, MutableHandleValue dst);
  bool staticClassBlock(StaticClassBlock* staticClassBlock,
                        MutableHandleValue dst);

  bool optIdentifier(Handle<JSAtom*> atom, TokenPos* pos,
                     MutableHandleValue dst) {
    if (!atom) {
      dst.setMagic(JS_SERIALIZE_NO_NODE);
      return true;
    }
    return identifier(atom, pos, dst);
  }

  bool identifier(Handle<JSAtom*> atom, TokenPos* pos, MutableHandleValue dst);
  bool identifier(NameNode* id, MutableHandleValue dst);
  bool identifierOrLiteral(ParseNode* id, MutableHandleValue dst);
  bool literal(ParseNode* pn, MutableHandleValue dst);

  bool optPattern(ParseNode* pn, MutableHandleValue dst) {
    if (!pn) {
      dst.setMagic(JS_SERIALIZE_NO_NODE);
      return true;
    }
    return pattern(pn, dst);
  }

  bool pattern(ParseNode* pn, MutableHandleValue dst);
  bool arrayPattern(ListNode* array, MutableHandleValue dst);
  bool objectPattern(ListNode* obj, MutableHandleValue dst);

  bool function(FunctionNode* funNode, ASTType type, MutableHandleValue dst);
  bool functionArgsAndBody(ParamsBodyNode* pn, NodeVector& args,
                           NodeVector& defaults, bool isAsync,
                           bool isExpression, MutableHandleValue body,
                           MutableHandleValue rest);
  bool functionBody(ParseNode* pn, TokenPos* pos, MutableHandleValue dst);

 public:
  ASTSerializer(JSContext* c, FrontendContext* f, bool l, char const* src,
                uint32_t ln)
      : cx(c),
        fc(f),
        parser(nullptr),
        builder(c, f, l, src)
#ifdef DEBUG
        ,
        lineno(ln)
#endif
  {
  }

  bool init() { return builder.init(); }

  void setParser(frontend::Parser<frontend::FullParseHandler, char16_t>* p) {
    parser = p;
    builder.setParser(p);
  }

  bool program(ListNode* node, MutableHandleValue dst);
};

} /* anonymous namespace */

AssignmentOperator ASTSerializer::aop(ParseNodeKind kind) {
  switch (kind) {
    case ParseNodeKind::AssignExpr:
      return AOP_ASSIGN;
    case ParseNodeKind::AddAssignExpr:
      return AOP_PLUS;
    case ParseNodeKind::SubAssignExpr:
      return AOP_MINUS;
    case ParseNodeKind::MulAssignExpr:
      return AOP_STAR;
    case ParseNodeKind::DivAssignExpr:
      return AOP_DIV;
    case ParseNodeKind::ModAssignExpr:
      return AOP_MOD;
    case ParseNodeKind::PowAssignExpr:
      return AOP_POW;
    case ParseNodeKind::LshAssignExpr:
      return AOP_LSH;
    case ParseNodeKind::RshAssignExpr:
      return AOP_RSH;
    case ParseNodeKind::UrshAssignExpr:
      return AOP_URSH;
    case ParseNodeKind::BitOrAssignExpr:
      return AOP_BITOR;
    case ParseNodeKind::BitXorAssignExpr:
      return AOP_BITXOR;
    case ParseNodeKind::BitAndAssignExpr:
      return AOP_BITAND;
    case ParseNodeKind::CoalesceAssignExpr:
      return AOP_COALESCE;
    case ParseNodeKind::OrAssignExpr:
      return AOP_OR;
    case ParseNodeKind::AndAssignExpr:
      return AOP_AND;
    default:
      return AOP_ERR;
  }
}

UnaryOperator ASTSerializer::unop(ParseNodeKind kind) {
  if (IsDeleteKind(kind)) {
    return UNOP_DELETE;
  }

  if (IsTypeofKind(kind)) {
    return UNOP_TYPEOF;
  }

  switch (kind) {
    case ParseNodeKind::AwaitExpr:
      return UNOP_AWAIT;
    case ParseNodeKind::NegExpr:
      return UNOP_NEG;
    case ParseNodeKind::PosExpr:
      return UNOP_POS;
    case ParseNodeKind::NotExpr:
      return UNOP_NOT;
    case ParseNodeKind::BitNotExpr:
      return UNOP_BITNOT;
    case ParseNodeKind::VoidExpr:
      return UNOP_VOID;
    default:
      return UNOP_ERR;
  }
}

BinaryOperator ASTSerializer::binop(ParseNodeKind kind) {
  switch (kind) {
    case ParseNodeKind::LshExpr:
      return BINOP_LSH;
    case ParseNodeKind::RshExpr:
      return BINOP_RSH;
    case ParseNodeKind::UrshExpr:
      return BINOP_URSH;
    case ParseNodeKind::LtExpr:
      return BINOP_LT;
    case ParseNodeKind::LeExpr:
      return BINOP_LE;
    case ParseNodeKind::GtExpr:
      return BINOP_GT;
    case ParseNodeKind::GeExpr:
      return BINOP_GE;
    case ParseNodeKind::EqExpr:
      return BINOP_EQ;
    case ParseNodeKind::NeExpr:
      return BINOP_NE;
    case ParseNodeKind::StrictEqExpr:
      return BINOP_STRICTEQ;
    case ParseNodeKind::StrictNeExpr:
      return BINOP_STRICTNE;
    case ParseNodeKind::AddExpr:
      return BINOP_ADD;
    case ParseNodeKind::SubExpr:
      return BINOP_SUB;
    case ParseNodeKind::MulExpr:
      return BINOP_STAR;
    case ParseNodeKind::DivExpr:
      return BINOP_DIV;
    case ParseNodeKind::ModExpr:
      return BINOP_MOD;
    case ParseNodeKind::PowExpr:
      return BINOP_POW;
    case ParseNodeKind::BitOrExpr:
      return BINOP_BITOR;
    case ParseNodeKind::BitXorExpr:
      return BINOP_BITXOR;
    case ParseNodeKind::BitAndExpr:
      return BINOP_BITAND;
    case ParseNodeKind::InExpr:
    case ParseNodeKind::PrivateInExpr:
      return BINOP_IN;
    case ParseNodeKind::InstanceOfExpr:
      return BINOP_INSTANCEOF;
    case ParseNodeKind::CoalesceExpr:
      return BINOP_COALESCE;
    default:
      return BINOP_ERR;
  }
}

bool ASTSerializer::statements(ListNode* stmtList, NodeVector& elts) {
  MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList));

  if (!elts.reserve(stmtList->count())) {
    return false;
  }

  for (ParseNode* stmt : stmtList->contents()) {
    MOZ_ASSERT(stmtList->pn_pos.encloses(stmt->pn_pos));

    RootedValue elt(cx);
    if (!sourceElement(stmt, &elt)) {
      return false;
    }
    elts.infallibleAppend(elt);
  }

  return true;
}

bool ASTSerializer::expressions(ListNode* exprList, NodeVector& elts) {
  if (!elts.reserve(exprList->count())) {
    return false;
  }

  for (ParseNode* expr : exprList->contents()) {
    MOZ_ASSERT(exprList->pn_pos.encloses(expr->pn_pos));

    RootedValue elt(cx);
    if (!expression(expr, &elt)) {
      return false;
    }
    elts.infallibleAppend(elt);
  }

  return true;
}

bool ASTSerializer::blockStatement(ListNode* node, MutableHandleValue dst) {
  MOZ_ASSERT(node->isKind(ParseNodeKind::StatementList));

  NodeVector stmts(cx);
  return statements(node, stmts) &&
         builder.blockStatement(stmts, &node->pn_pos, dst);
}

bool ASTSerializer::program(ListNode* node, MutableHandleValue dst) {
#ifdef DEBUG
  {
    const TokenStreamAnyChars& anyChars = parser->anyChars;
    auto lineToken = anyChars.lineToken(node->pn_pos.begin);
    MOZ_ASSERT(anyChars.lineNumber(lineToken) == lineno);
  }
#endif

  NodeVector stmts(cx);
  return statements(node, stmts) && builder.program(stmts, &node->pn_pos, dst);
}

bool ASTSerializer::sourceElement(ParseNode* pn, MutableHandleValue dst) {
  /* SpiderMonkey allows declarations even in pure statement contexts. */
  return statement(pn, dst);
}

bool ASTSerializer::declaration(ParseNode* pn, MutableHandleValue dst) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::Function) ||
             pn->isKind(ParseNodeKind::VarStmt) ||
             pn->isKind(ParseNodeKind::LetDecl) ||
             pn->isKind(ParseNodeKind::ConstDecl));

  switch (pn->getKind()) {
    case ParseNodeKind::Function:
      return function(&pn->as<FunctionNode>(), AST_FUNC_DECL, dst);

    case ParseNodeKind::VarStmt:
      return variableDeclaration(&pn->as<ListNode>(), false, dst);

    default:
      MOZ_ASSERT(pn->isKind(ParseNodeKind::LetDecl) ||
                 pn->isKind(ParseNodeKind::ConstDecl));
      return variableDeclaration(&pn->as<ListNode>(), true, dst);
  }
}

bool ASTSerializer::variableDeclaration(ListNode* declList, bool lexical,
                                        MutableHandleValue dst) {
  MOZ_ASSERT_IF(lexical, declList->isKind(ParseNodeKind::LetDecl) ||
                             declList->isKind(ParseNodeKind::ConstDecl));
  MOZ_ASSERT_IF(!lexical, declList->isKind(ParseNodeKind::VarStmt));

  VarDeclKind kind = VARDECL_ERR;
  // Treat both the toplevel const binding (secretly var-like) and the lexical
  // const the same way
  if (lexical) {
    kind =
        declList->isKind(ParseNodeKind::LetDecl) ? VARDECL_LET : VARDECL_CONST;
  } else {
    kind =
        declList->isKind(ParseNodeKind::VarStmt) ? VARDECL_VAR : VARDECL_CONST;
  }

  NodeVector dtors(cx);
  if (!dtors.reserve(declList->count())) {
    return false;
  }
  for (ParseNode* decl : declList->contents()) {
    RootedValue child(cx);
    if (!variableDeclarator(decl, &child)) {
      return false;
    }
    dtors.infallibleAppend(child);
  }
  return builder.variableDeclaration(dtors, kind, &declList->pn_pos, dst);
}

bool ASTSerializer::variableDeclarator(ParseNode* pn, MutableHandleValue dst) {
  ParseNode* patternNode;
  ParseNode* initNode;

  if (pn->isKind(ParseNodeKind::Name)) {
    patternNode = pn;
    initNode = nullptr;
  } else if (pn->isKind(ParseNodeKind::AssignExpr)) {
    AssignmentNode* assignNode = &pn->as<AssignmentNode>();
    patternNode = assignNode->left();
    initNode = assignNode->right();
    MOZ_ASSERT(pn->pn_pos.encloses(patternNode->pn_pos));
    MOZ_ASSERT(pn->pn_pos.encloses(initNode->pn_pos));
  } else {
    /* This happens for a destructuring declarator in a for-in/of loop. */
    patternNode = pn;
    initNode = nullptr;
  }

  RootedValue patternVal(cx), init(cx);
  return pattern(patternNode, &patternVal) && optExpression(initNode, &init) &&
         builder.variableDeclarator(patternVal, init, &pn->pn_pos, dst);
}

bool ASTSerializer::importDeclaration(BinaryNode* importNode,
                                      MutableHandleValue dst) {
  MOZ_ASSERT(importNode->isKind(ParseNodeKind::ImportDecl));

  ListNode* specList = &importNode->left()->as<ListNode>();
  MOZ_ASSERT(specList->isKind(ParseNodeKind::ImportSpecList));

  auto* moduleRequest = &importNode->right()->as<BinaryNode>();
  MOZ_ASSERT(moduleRequest->isKind(ParseNodeKind::ImportModuleRequest));

  ParseNode* moduleSpecNode = moduleRequest->left();
  MOZ_ASSERT(moduleSpecNode->isKind(ParseNodeKind::StringExpr));

  auto* assertionList = &moduleRequest->right()->as<ListNode>();
  MOZ_ASSERT(assertionList->isKind(ParseNodeKind::ImportAssertionList));

  NodeVector elts(cx);
  if (!elts.reserve(specList->count())) {
    return false;
  }

  for (ParseNode* item : specList->contents()) {
    RootedValue elt(cx);
    if (item->is<UnaryNode>()) {
      auto* spec = &item->as<UnaryNode>();
      if (!importNamespaceSpecifier(spec, &elt)) {
        return false;
      }
    } else {
      auto* spec = &item->as<BinaryNode>();
      if (!importSpecifier(spec, &elt)) {
        return false;
      }
    }
    elts.infallibleAppend(elt);
  }

  RootedValue moduleSpec(cx);
  if (!literal(moduleSpecNode, &moduleSpec)) {
    return false;
  }

  NodeVector assertions(cx);
  if (!importAssertions(assertionList, assertions)) {
    return false;
  }

  RootedValue moduleRequestValue(cx);
  if (!builder.moduleRequest(moduleSpec, assertions, &importNode->pn_pos,
                             &moduleRequestValue)) {
    return false;
  }

  return builder.importDeclaration(elts, moduleRequestValue,
                                   &importNode->pn_pos, dst);
}

bool ASTSerializer::importSpecifier(BinaryNode* importSpec,
                                    MutableHandleValue dst) {
  MOZ_ASSERT(importSpec->isKind(ParseNodeKind::ImportSpec));
  NameNode* importNameNode = &importSpec->left()->as<NameNode>();
  NameNode* bindingNameNode = &importSpec->right()->as<NameNode>();

  RootedValue importName(cx);
  RootedValue bindingName(cx);
  return identifierOrLiteral(importNameNode, &importName) &&
         identifier(bindingNameNode, &bindingName) &&
         builder.importSpecifier(importName, bindingName, &importSpec->pn_pos,
                                 dst);
}

bool ASTSerializer::importNamespaceSpecifier(UnaryNode* importSpec,
                                             MutableHandleValue dst) {
  MOZ_ASSERT(importSpec->isKind(ParseNodeKind::ImportNamespaceSpec));
  NameNode* bindingNameNode = &importSpec->kid()->as<NameNode>();

  RootedValue bindingName(cx);
  return identifier(bindingNameNode, &bindingName) &&
         builder.importNamespaceSpecifier(bindingName, &importSpec->pn_pos,
                                          dst);
}

bool ASTSerializer::exportDeclaration(ParseNode* exportNode,
                                      MutableHandleValue dst) {
  MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportStmt) ||
             exportNode->isKind(ParseNodeKind::ExportFromStmt) ||
             exportNode->isKind(ParseNodeKind::ExportDefaultStmt));
  MOZ_ASSERT_IF(exportNode->isKind(ParseNodeKind::ExportStmt),
                exportNode->is<UnaryNode>());
  MOZ_ASSERT_IF(exportNode->isKind(ParseNodeKind::ExportFromStmt),
                exportNode->as<BinaryNode>().right()->isKind(
                    ParseNodeKind::ImportModuleRequest));

  RootedValue decl(cx, NullValue());
  NodeVector elts(cx);

  ParseNode* kid = exportNode->isKind(ParseNodeKind::ExportStmt)
                       ? exportNode->as<UnaryNode>().kid()
                       : exportNode->as<BinaryNode>().left();
  switch (ParseNodeKind kind = kid->getKind()) {
    case ParseNodeKind::ExportSpecList: {
      ListNode* specList = &kid->as<ListNode>();
      if (!elts.reserve(specList->count())) {
        return false;
      }

      for (ParseNode* spec : specList->contents()) {
        RootedValue elt(cx);
        if (spec->isKind(ParseNodeKind::ExportSpec)) {
          if (!exportSpecifier(&spec->as<BinaryNode>(), &elt)) {
            return false;
          }
        } else if (spec->isKind(ParseNodeKind::ExportNamespaceSpec)) {
          if (!exportNamespaceSpecifier(&spec->as<UnaryNode>(), &elt)) {
            return false;
          }
        } else {
          MOZ_ASSERT(spec->isKind(ParseNodeKind::ExportBatchSpecStmt));
          if (!builder.exportBatchSpecifier(&exportNode->pn_pos, &elt)) {
            return false;
          }
        }
        elts.infallibleAppend(elt);
      }
      break;
    }

    case ParseNodeKind::Function:
      if (!function(&kid->as<FunctionNode>(), AST_FUNC_DECL, &decl)) {
        return false;
      }
      break;

    case ParseNodeKind::ClassDecl:
      if (!classDefinition(&kid->as<ClassNode>(), false, &decl)) {
        return false;
      }
      break;

    case ParseNodeKind::VarStmt:
    case ParseNodeKind::ConstDecl:
    case ParseNodeKind::LetDecl:
      if (!variableDeclaration(&kid->as<ListNode>(),
                               kind != ParseNodeKind::VarStmt, &decl)) {
        return false;
      }
      break;

    default:
      if (!expression(kid, &decl)) {
        return false;
      }
      break;
  }

  RootedValue moduleSpec(cx, NullValue());
  RootedValue moduleRequestValue(cx, NullValue());
  if (exportNode->isKind(ParseNodeKind::ExportFromStmt)) {
    ParseNode* moduleRequest = exportNode->as<BinaryNode>().right();
    if (!literal(moduleRequest->as<BinaryNode>().left(), &moduleSpec)) {
      return false;
    }

    auto* assertionList =
        &moduleRequest->as<BinaryNode>().right()->as<ListNode>();
    MOZ_ASSERT(assertionList->isKind(ParseNodeKind::ImportAssertionList));

    NodeVector assertions(cx);
    if (!importAssertions(assertionList, assertions)) {
      return false;
    }

    if (!builder.moduleRequest(moduleSpec, assertions, &exportNode->pn_pos,
                               &moduleRequestValue)) {
      return false;
    }
  }

  RootedValue isDefault(cx, BooleanValue(false));
  if (exportNode->isKind(ParseNodeKind::ExportDefaultStmt)) {
    isDefault.setBoolean(true);
  }

  return builder.exportDeclaration(decl, elts, moduleRequestValue, isDefault,
                                   &exportNode->pn_pos, dst);
}

bool ASTSerializer::exportSpecifier(BinaryNode* exportSpec,
                                    MutableHandleValue dst) {
  MOZ_ASSERT(exportSpec->isKind(ParseNodeKind::ExportSpec));
  NameNode* bindingNameNode = &exportSpec->left()->as<NameNode>();
  NameNode* exportNameNode = &exportSpec->right()->as<NameNode>();

  RootedValue bindingName(cx);
  RootedValue exportName(cx);
  return identifierOrLiteral(bindingNameNode, &bindingName) &&
         identifierOrLiteral(exportNameNode, &exportName) &&
         builder.exportSpecifier(bindingName, exportName, &exportSpec->pn_pos,
                                 dst);
}

bool ASTSerializer::exportNamespaceSpecifier(UnaryNode* exportSpec,
                                             MutableHandleValue dst) {
  MOZ_ASSERT(exportSpec->isKind(ParseNodeKind::ExportNamespaceSpec));
  NameNode* exportNameNode = &exportSpec->kid()->as<NameNode>();

  RootedValue exportName(cx);
  return identifierOrLiteral(exportNameNode, &exportName) &&
         builder.exportNamespaceSpecifier(exportName, &exportSpec->pn_pos, dst);
}

bool ASTSerializer::importAssertions(ListNode* assertionList,
                                     NodeVector& assertions) {
  for (ParseNode* assertionItem : assertionList->contents()) {
    BinaryNode* assertionNode = &assertionItem->as<BinaryNode>();
    MOZ_ASSERT(assertionNode->isKind(ParseNodeKind::ImportAssertion));

    NameNode* keyNameNode = &assertionNode->left()->as<NameNode>();
    NameNode* valueNameNode = &assertionNode->right()->as<NameNode>();

    RootedValue key(cx);
    if (!identifierOrLiteral(keyNameNode, &key)) {
      return false;
    }

    RootedValue value(cx);
    if (!literal(valueNameNode, &value)) {
      return false;
    }

    RootedValue assertion(cx);
    if (!builder.importAssertion(key, value, &assertionNode->pn_pos,
                                 &assertion)) {
      return false;
    }

    if (!assertions.append(assertion)) {
      return false;
    }
  }

  return true;
}

bool ASTSerializer::switchCase(CaseClause* caseClause, MutableHandleValue dst) {
  MOZ_ASSERT_IF(
      caseClause->caseExpression(),
      caseClause->pn_pos.encloses(caseClause->caseExpression()->pn_pos));
  MOZ_ASSERT(caseClause->pn_pos.encloses(caseClause->statementList()->pn_pos));

  NodeVector stmts(cx);
  RootedValue expr(cx);
  return optExpression(caseClause->caseExpression(), &expr) &&
         statements(caseClause->statementList(), stmts) &&
         builder.switchCase(expr, stmts, &caseClause->pn_pos, dst);
}

bool ASTSerializer::switchStatement(SwitchStatement* switchStmt,
                                    MutableHandleValue dst) {
  MOZ_ASSERT(switchStmt->pn_pos.encloses(switchStmt->discriminant().pn_pos));
  MOZ_ASSERT(
      switchStmt->pn_pos.encloses(switchStmt->lexicalForCaseList().pn_pos));

  RootedValue disc(cx);
  if (!expression(&switchStmt->discriminant(), &disc)) {
    return false;
  }

  ListNode* caseList =
      &switchStmt->lexicalForCaseList().scopeBody()->as<ListNode>();

  NodeVector cases(cx);
  if (!cases.reserve(caseList->count())) {
    return false;
  }

  for (ParseNode* item : caseList->contents()) {
    CaseClause* caseClause = &item->as<CaseClause>();
    RootedValue child(cx);
    if (!switchCase(caseClause, &child)) {
      return false;
    }
    cases.infallibleAppend(child);
  }

  // `lexical` field is always true.
  return builder.switchStatement(disc, cases, true, &switchStmt->pn_pos, dst);
}

bool ASTSerializer::catchClause(BinaryNode* catchClause,
                                MutableHandleValue dst) {
  MOZ_ASSERT(catchClause->isKind(ParseNodeKind::Catch));

  ParseNode* varNode = catchClause->left();
  MOZ_ASSERT_IF(varNode, catchClause->pn_pos.encloses(varNode->pn_pos));

  ParseNode* bodyNode = catchClause->right();
  MOZ_ASSERT(catchClause->pn_pos.encloses(bodyNode->pn_pos));

  RootedValue var(cx), body(cx);
  if (!optPattern(varNode, &var)) {
    return false;
  }

  return statement(bodyNode, &body) &&
         builder.catchClause(var, body, &catchClause->pn_pos, dst);
}

bool ASTSerializer::tryStatement(TryNode* tryNode, MutableHandleValue dst) {
  ParseNode* bodyNode = tryNode->body();
  MOZ_ASSERT(tryNode->pn_pos.encloses(bodyNode->pn_pos));

  LexicalScopeNode* catchNode = tryNode->catchScope();
  MOZ_ASSERT_IF(catchNode, tryNode->pn_pos.encloses(catchNode->pn_pos));

  ParseNode* finallyNode = tryNode->finallyBlock();
  MOZ_ASSERT_IF(finallyNode, tryNode->pn_pos.encloses(finallyNode->pn_pos));

  RootedValue body(cx);
  if (!statement(bodyNode, &body)) {
    return false;
  }

  RootedValue handler(cx, NullValue());
  if (catchNode) {
    LexicalScopeNode* catchScope = &catchNode->as<LexicalScopeNode>();
    if (!catchClause(&catchScope->scopeBody()->as<BinaryNode>(), &handler)) {
      return false;
    }
  }

  RootedValue finally(cx);
  return optStatement(finallyNode, &finally) &&
         builder.tryStatement(body, handler, finally, &tryNode->pn_pos, dst);
}

bool ASTSerializer::forInit(ParseNode* pn, MutableHandleValue dst) {
  if (!pn) {
    dst.setMagic(JS_SERIALIZE_NO_NODE);
    return true;
  }

  bool lexical = pn->isKind(ParseNodeKind::LetDecl) ||
                 pn->isKind(ParseNodeKind::ConstDecl);
  return (lexical || pn->isKind(ParseNodeKind::VarStmt))
             ? variableDeclaration(&pn->as<ListNode>(), lexical, dst)
             : expression(pn, dst);
}

bool ASTSerializer::forOf(ForNode* loop, ParseNode* iterExpr, HandleValue var,
                          HandleValue stmt, MutableHandleValue dst) {
  RootedValue expr(cx);

  return expression(iterExpr, &expr) &&
         builder.forOfStatement(var, expr, stmt, &loop->pn_pos, dst);
}

bool ASTSerializer::forIn(ForNode* loop, ParseNode* iterExpr, HandleValue var,
                          HandleValue stmt, MutableHandleValue dst) {
  RootedValue expr(cx);

  return expression(iterExpr, &expr) &&
         builder.forInStatement(var, expr, stmt, &loop->pn_pos, dst);
}

bool ASTSerializer::classDefinition(ClassNode* pn, bool expr,
                                    MutableHandleValue dst) {
  RootedValue className(cx, MagicValue(JS_SERIALIZE_NO_NODE));
  RootedValue heritage(cx);
  RootedValue classBody(cx);

  if (ClassNames* names = pn->names()) {
    if (!identifier(names->innerBinding(), &className)) {
      return false;
    }
  }

  return optExpression(pn->heritage(), &heritage) &&
         statement(pn->memberList(), &classBody) &&
         builder.classDefinition(expr, className, heritage, classBody,
                                 &pn->pn_pos, dst);
}

bool ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) {
  AutoCheckRecursionLimit recursion(cx);
  if (!recursion.check(cx)) {
    return false;
  }

  switch (pn->getKind()) {
    case ParseNodeKind::Function:
    case ParseNodeKind::VarStmt:
      return declaration(pn, dst);

    case ParseNodeKind::LetDecl:
    case ParseNodeKind::ConstDecl:
      return declaration(pn, dst);

    case ParseNodeKind::ImportDecl:
      return importDeclaration(&pn->as<BinaryNode>(), dst);

    case ParseNodeKind::ExportStmt:
    case ParseNodeKind::ExportDefaultStmt:
    case ParseNodeKind::ExportFromStmt:
      return exportDeclaration(pn, dst);

    case ParseNodeKind::EmptyStmt:
      return builder.emptyStatement(&pn->pn_pos, dst);

    case ParseNodeKind::ExpressionStmt: {
      RootedValue expr(cx);
      return expression(pn->as<UnaryNode>().kid(), &expr) &&
             builder.expressionStatement(expr, &pn->pn_pos, dst);
    }

    case ParseNodeKind::LexicalScope:
      pn = pn->as<LexicalScopeNode>().scopeBody();
      if (!pn->isKind(ParseNodeKind::StatementList)) {
        return statement(pn, dst);
      }
      [[fallthrough]];

    case ParseNodeKind::StatementList:
      return blockStatement(&pn->as<ListNode>(), dst);

    case ParseNodeKind::IfStmt: {
      TernaryNode* ifNode = &pn->as<TernaryNode>();

      ParseNode* testNode = ifNode->kid1();
      MOZ_ASSERT(ifNode->pn_pos.encloses(testNode->pn_pos));

      ParseNode* consNode = ifNode->kid2();
      MOZ_ASSERT(ifNode->pn_pos.encloses(consNode->pn_pos));

      ParseNode* altNode = ifNode->kid3();
      MOZ_ASSERT_IF(altNode, ifNode->pn_pos.encloses(altNode->pn_pos));

      RootedValue test(cx), cons(cx), alt(cx);

      return expression(testNode, &test) && statement(consNode, &cons) &&
             optStatement(altNode, &alt) &&
             builder.ifStatement(test, cons, alt, &ifNode->pn_pos, dst);
    }

    case ParseNodeKind::SwitchStmt:
      return switchStatement(&pn->as<SwitchStatement>(), dst);

    case ParseNodeKind::TryStmt:
      return tryStatement(&pn->as<TryNode>(), dst);

    case ParseNodeKind::WithStmt:
    case ParseNodeKind::WhileStmt: {
      BinaryNode* node = &pn->as<BinaryNode>();

      ParseNode* exprNode = node->left();
      MOZ_ASSERT(node->pn_pos.encloses(exprNode->pn_pos));

      ParseNode* stmtNode = node->right();
      MOZ_ASSERT(node->pn_pos.encloses(stmtNode->pn_pos));

      RootedValue expr(cx), stmt(cx);

      return expression(exprNode, &expr) && statement(stmtNode, &stmt) &&
             (node->isKind(ParseNodeKind::WithStmt)
                  ? builder.withStatement(expr, stmt, &node->pn_pos, dst)
                  : builder.whileStatement(expr, stmt, &node->pn_pos, dst));
    }

    case ParseNodeKind::DoWhileStmt: {
      BinaryNode* node = &pn->as<BinaryNode>();

      ParseNode* stmtNode = node->left();
      MOZ_ASSERT(node->pn_pos.encloses(stmtNode->pn_pos));

      ParseNode* testNode = node->right();
      MOZ_ASSERT(node->pn_pos.encloses(testNode->pn_pos));

      RootedValue stmt(cx), test(cx);

      return statement(stmtNode, &stmt) && expression(testNode, &test) &&
             builder.doWhileStatement(stmt, test, &node->pn_pos, dst);
    }

    case ParseNodeKind::ForStmt: {
      ForNode* forNode = &pn->as<ForNode>();

      TernaryNode* head = forNode->head();
      MOZ_ASSERT(forNode->pn_pos.encloses(head->pn_pos));

      ParseNode* stmtNode = forNode->right();
      MOZ_ASSERT(forNode->pn_pos.encloses(stmtNode->pn_pos));

      ParseNode* initNode = head->kid1();
      MOZ_ASSERT_IF(initNode, head->pn_pos.encloses(initNode->pn_pos));

      ParseNode* maybeTest = head->kid2();
      MOZ_ASSERT_IF(maybeTest, head->pn_pos.encloses(maybeTest->pn_pos));

      ParseNode* updateOrIter = head->kid3();
      MOZ_ASSERT_IF(updateOrIter, head->pn_pos.encloses(updateOrIter->pn_pos));

      RootedValue stmt(cx);
      if (!statement(stmtNode, &stmt)) {
        return false;
      }

      if (head->isKind(ParseNodeKind::ForIn) ||
          head->isKind(ParseNodeKind::ForOf)) {
        RootedValue var(cx);
        if (initNode->is<LexicalScopeNode>()) {
          LexicalScopeNode* scopeNode = &initNode->as<LexicalScopeNode>();
          if (!variableDeclaration(&scopeNode->scopeBody()->as<ListNode>(),
                                   true, &var)) {
            return false;
          }
        } else if (!initNode->isKind(ParseNodeKind::VarStmt) &&
                   !initNode->isKind(ParseNodeKind::LetDecl) &&
                   !initNode->isKind(ParseNodeKind::ConstDecl)) {
          if (!pattern(initNode, &var)) {
            return false;
          }
        } else {
          if (!variableDeclaration(
                  &initNode->as<ListNode>(),
                  initNode->isKind(ParseNodeKind::LetDecl) ||
                      initNode->isKind(ParseNodeKind::ConstDecl),
                  &var)) {
            return false;
          }
        }
        if (head->isKind(ParseNodeKind::ForIn)) {
          return forIn(forNode, updateOrIter, var, stmt, dst);
        }
        return forOf(forNode, updateOrIter, var, stmt, dst);
      }

      RootedValue init(cx), test(cx), update(cx);

      return forInit(initNode, &init) && optExpression(maybeTest, &test) &&
             optExpression(updateOrIter, &update) &&
             builder.forStatement(init, test, update, stmt, &forNode->pn_pos,
                                  dst);
    }

    case ParseNodeKind::BreakStmt:
    case ParseNodeKind::ContinueStmt: {
      LoopControlStatement* node = &pn->as<LoopControlStatement>();
      RootedValue label(cx);
      Rooted<JSAtom*> pnAtom(cx);
      if (node->label()) {
        pnAtom.set(parser->liftParserAtomToJSAtom(node->label()));
        if (!pnAtom) {
          return false;
        }
      }
      return optIdentifier(pnAtom, nullptr, &label) &&
             (node->isKind(ParseNodeKind::BreakStmt)
                  ? builder.breakStatement(label, &node->pn_pos, dst)
                  : builder.continueStatement(label, &node->pn_pos, dst));
    }

    case ParseNodeKind::LabelStmt: {
      LabeledStatement* labelNode = &pn->as<LabeledStatement>();
      ParseNode* stmtNode = labelNode->statement();
      MOZ_ASSERT(labelNode->pn_pos.encloses(stmtNode->pn_pos));

      RootedValue label(cx), stmt(cx);
      Rooted<JSAtom*> pnAtom(
          cx, parser->liftParserAtomToJSAtom(labelNode->label()));
      if (!pnAtom.get()) {
        return false;
      }
      return identifier(pnAtom, nullptr, &label) &&
             statement(stmtNode, &stmt) &&
             builder.labeledStatement(label, stmt, &labelNode->pn_pos, dst);
    }

    case ParseNodeKind::ThrowStmt: {
      UnaryNode* throwNode = &pn->as<UnaryNode>();
      ParseNode* operand = throwNode->kid();
      MOZ_ASSERT(throwNode->pn_pos.encloses(operand->pn_pos));

      RootedValue arg(cx);

      return expression(operand, &arg) &&
             builder.throwStatement(arg, &throwNode->pn_pos, dst);
    }

    case ParseNodeKind::ReturnStmt: {
      UnaryNode* returnNode = &pn->as<UnaryNode>();
      ParseNode* operand = returnNode->kid();
      MOZ_ASSERT_IF(operand, returnNode->pn_pos.encloses(operand->pn_pos));

      RootedValue arg(cx);

      return optExpression(operand, &arg) &&
             builder.returnStatement(arg, &returnNode->pn_pos, dst);
    }

    case ParseNodeKind::DebuggerStmt:
      return builder.debuggerStatement(&pn->pn_pos, dst);

    case ParseNodeKind::ClassDecl:
      return classDefinition(&pn->as<ClassNode>(), false, dst);

    case ParseNodeKind::ClassMemberList: {
      ListNode* memberList = &pn->as<ListNode>();
      NodeVector members(cx);
      if (!members.reserve(memberList->count())) {
        return false;
      }

      for (ParseNode* item : memberList->contents()) {
        if (item->is<LexicalScopeNode>()) {
          item = item->as<LexicalScopeNode>().scopeBody();
        }
        if (item->is<ClassField>()) {
          ClassField* field = &item->as<ClassField>();
          MOZ_ASSERT(memberList->pn_pos.encloses(field->pn_pos));

          RootedValue prop(cx);
          if (!classField(field, &prop)) {
            return false;
          }
          members.infallibleAppend(prop);
        } else if (item->is<StaticClassBlock>()) {
          // StaticClassBlock* block = &item->as<StaticClassBlock>();
          StaticClassBlock* scb = &item->as<StaticClassBlock>();
          MOZ_ASSERT(memberList->pn_pos.encloses(scb->pn_pos));
          RootedValue prop(cx);
          if (!staticClassBlock(scb, &prop)) {
            return false;
          }
          members.infallibleAppend(prop);
        } else if (!item->isKind(ParseNodeKind::DefaultConstructor)) {
          ClassMethod* method = &item->as<ClassMethod>();
          MOZ_ASSERT(memberList->pn_pos.encloses(method->pn_pos));

          RootedValue prop(cx);
          if (!classMethod(method, &prop)) {
            return false;
          }
          members.infallibleAppend(prop);
        }
      }

      return builder.classMembers(members, dst);
    }

    default:
      LOCAL_NOT_REACHED("unexpected statement type");
  }
}

bool ASTSerializer::classMethod(ClassMethod* classMethod,
                                MutableHandleValue dst) {
  PropKind kind;
  switch (classMethod->accessorType()) {
    case AccessorType::None:
      kind = PROP_INIT;
      break;

    case AccessorType::Getter:
      kind = PROP_GETTER;
      break;

    case AccessorType::Setter:
      kind = PROP_SETTER;
      break;

    default:
      LOCAL_NOT_REACHED("unexpected object-literal property");
  }

  RootedValue key(cx), val(cx);
  bool isStatic = classMethod->isStatic();
  return propertyName(&classMethod->name(), &key) &&
         expression(&classMethod->method(), &val) &&
         builder.classMethod(key, val, kind, isStatic, &classMethod->pn_pos,
                             dst);
}

bool ASTSerializer::classField(ClassField* classField, MutableHandleValue dst) {
  RootedValue key(cx), val(cx);
  // Dig through the lambda and get to the actual expression
  ParseNode* value = classField->initializer()
                         ->body()
                         ->body()
                         ->scopeBody()
                         ->as<ListNode>()
                         .head()
                         ->as<UnaryNode>()
                         .kid()
                         ->as<BinaryNode>()
                         .right();
  // RawUndefinedExpr is the node we use for "there is no initializer". If one
  // writes, literally, `x = undefined;`, it will not be a RawUndefinedExpr
  // node, but rather a variable reference.
  // Behavior for "there is no initializer" should be { ..., "init": null }
  if (value->getKind() != ParseNodeKind::RawUndefinedExpr) {
    if (!expression(value, &val)) {
      return false;
    }
  } else {
    val.setNull();
  }
  return propertyName(&classField->name(), &key) &&
         builder.classField(key, val, &classField->pn_pos, dst);
}

bool ASTSerializer::staticClassBlock(StaticClassBlock* staticClassBlock,
                                     MutableHandleValue dst) {
  FunctionNode* fun = staticClassBlock->function();

  NodeVector args(cx);
  NodeVector defaults(cx);

  RootedValue body(cx), rest(cx);
  rest.setNull();
  return functionArgsAndBody(fun->body(), args, defaults, false, false, &body,
                             &rest) &&
         builder.staticClassBlock(body, &staticClassBlock->pn_pos, dst);
}

bool ASTSerializer::leftAssociate(ListNode* node, MutableHandleValue dst) {
  MOZ_ASSERT(!node->empty());

  ParseNodeKind pnk = node->getKind();
  bool lor = pnk == ParseNodeKind::OrExpr;
  bool coalesce = pnk == ParseNodeKind::CoalesceExpr;
  bool logop = lor || coalesce || pnk == ParseNodeKind::AndExpr;

  ParseNode* head = node->head();
  RootedValue left(cx);
  if (!expression(head, &left)) {
    return false;
  }
  for (ParseNode* next : node->contentsFrom(head->pn_next)) {
    RootedValue right(cx);
    if (!expression(next, &right)) {
      return false;
    }

    TokenPos subpos(node->pn_pos.begin, next->pn_pos.end);

    if (logop) {
      if (!builder.logicalExpression(pnk, left, right, &subpos, &left)) {
        return false;
      }
    } else {
      BinaryOperator op = binop(node->getKind());
      LOCAL_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT);

      if (!builder.binaryExpression(op, left, right, &subpos, &left)) {
        return false;
      }
    }
  }

  dst.set(left);
  return true;
}

bool ASTSerializer::rightAssociate(ListNode* node, MutableHandleValue dst) {
  MOZ_ASSERT(!node->empty());

  // First, we need to reverse the list, so that we can traverse it in the right
  // order. It's OK to destructively reverse the list, because there are no
  // other consumers.

  ParseNode* head = node->head();
  ParseNode* prev = nullptr;
  ParseNode* current = head;
  ParseNode* next;
  while (current != nullptr) {
    next = current->pn_next;
    current->pn_next = prev;
    prev = current;
    current = next;
  }

  head = prev;

  RootedValue right(cx);
  if (!expression(head, &right)) {
    return false;
  }
  for (ParseNode* next = head->pn_next; next; next = next->pn_next) {
    RootedValue left(cx);
    if (!expression(next, &left)) {
      return false;
    }

    TokenPos subpos(node->pn_pos.begin, next->pn_pos.end);

    BinaryOperator op = binop(node->getKind());
    LOCAL_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT);

    if (!builder.binaryExpression(op, left, right, &subpos, &right)) {
      return false;
    }
  }

  dst.set(right);
  return true;
}

bool ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) {
  AutoCheckRecursionLimit recursion(cx);
  if (!recursion.check(cx)) {
    return false;
  }

  switch (pn->getKind()) {
    case ParseNodeKind::Function: {
      FunctionNode* funNode = &pn->as<FunctionNode>();
      ASTType type =
          funNode->funbox()->isArrow() ? AST_ARROW_EXPR : AST_FUNC_EXPR;
      return function(funNode, type, dst);
    }

    case ParseNodeKind::CommaExpr: {
      NodeVector exprs(cx);
      return expressions(&pn->as<ListNode>(), exprs) &&
             builder.sequenceExpression(exprs, &pn->pn_pos, dst);
    }

    case ParseNodeKind::ConditionalExpr: {
      ConditionalExpression* condNode = &pn->as<ConditionalExpression>();
      ParseNode* testNode = condNode->kid1();
      ParseNode* consNode = condNode->kid2();
      ParseNode* altNode = condNode->kid3();
      MOZ_ASSERT(condNode->pn_pos.encloses(testNode->pn_pos));
      MOZ_ASSERT(condNode->pn_pos.encloses(consNode->pn_pos));
      MOZ_ASSERT(condNode->pn_pos.encloses(altNode->pn_pos));

      RootedValue test(cx), cons(cx), alt(cx);

      return expression(testNode, &test) && expression(consNode, &cons) &&
             expression(altNode, &alt) &&
             builder.conditionalExpression(test, cons, alt, &condNode->pn_pos,
                                           dst);
    }

    case ParseNodeKind::CoalesceExpr:
    case ParseNodeKind::OrExpr:
    case ParseNodeKind::AndExpr:
      return leftAssociate(&pn->as<ListNode>(), dst);

    case ParseNodeKind::PreIncrementExpr:
    case ParseNodeKind::PreDecrementExpr: {
      UnaryNode* incDec = &pn->as<UnaryNode>();
      ParseNode* operand = incDec->kid();
      MOZ_ASSERT(incDec->pn_pos.encloses(operand->pn_pos));

      bool inc = incDec->isKind(ParseNodeKind::PreIncrementExpr);
      RootedValue expr(cx);
      return expression(operand, &expr) &&
             builder.updateExpression(expr, inc, true, &incDec->pn_pos, dst);
    }

    case ParseNodeKind::PostIncrementExpr:
    case ParseNodeKind::PostDecrementExpr: {
      UnaryNode* incDec = &pn->as<UnaryNode>();
      ParseNode* operand = incDec->kid();
      MOZ_ASSERT(incDec->pn_pos.encloses(operand->pn_pos));

      bool inc = incDec->isKind(ParseNodeKind::PostIncrementExpr);
      RootedValue expr(cx);
      return expression(operand, &expr) &&
             builder.updateExpression(expr, inc, false, &incDec->pn_pos, dst);
    }

    case ParseNodeKind::AssignExpr:
    case ParseNodeKind::AddAssignExpr:
    case ParseNodeKind::SubAssignExpr:
    case ParseNodeKind::CoalesceAssignExpr:
    case ParseNodeKind::OrAssignExpr:
    case ParseNodeKind::AndAssignExpr:
    case ParseNodeKind::BitOrAssignExpr:
    case ParseNodeKind::BitXorAssignExpr:
    case ParseNodeKind::BitAndAssignExpr:
    case ParseNodeKind::LshAssignExpr:
    case ParseNodeKind::RshAssignExpr:
    case ParseNodeKind::UrshAssignExpr:
    case ParseNodeKind::MulAssignExpr:
    case ParseNodeKind::DivAssignExpr:
    case ParseNodeKind::ModAssignExpr:
    case ParseNodeKind::PowAssignExpr: {
      AssignmentNode* assignNode = &pn->as<AssignmentNode>();
      ParseNode* lhsNode = assignNode->left();
      ParseNode* rhsNode = assignNode->right();
      MOZ_ASSERT(assignNode->pn_pos.encloses(lhsNode->pn_pos));
      MOZ_ASSERT(assignNode->pn_pos.encloses(rhsNode->pn_pos));

      AssignmentOperator op = aop(assignNode->getKind());
      LOCAL_ASSERT(op > AOP_ERR && op < AOP_LIMIT);

      RootedValue lhs(cx), rhs(cx);
      return pattern(lhsNode, &lhs) && expression(rhsNode, &rhs) &&
             builder.assignmentExpression(op, lhs, rhs, &assignNode->pn_pos,
                                          dst);
    }

    case ParseNodeKind::AddExpr:
    case ParseNodeKind::SubExpr:
    case ParseNodeKind::StrictEqExpr:
    case ParseNodeKind::EqExpr:
    case ParseNodeKind::StrictNeExpr:
    case ParseNodeKind::NeExpr:
    case ParseNodeKind::LtExpr:
    case ParseNodeKind::LeExpr:
    case ParseNodeKind::GtExpr:
    case ParseNodeKind::GeExpr:
    case ParseNodeKind::LshExpr:
    case ParseNodeKind::RshExpr:
    case ParseNodeKind::UrshExpr:
    case ParseNodeKind::MulExpr:
    case ParseNodeKind::DivExpr:
    case ParseNodeKind::ModExpr:
    case ParseNodeKind::BitOrExpr:
    case ParseNodeKind::BitXorExpr:
    case ParseNodeKind::BitAndExpr:
    case ParseNodeKind::InExpr:
    case ParseNodeKind::PrivateInExpr:
    case ParseNodeKind::InstanceOfExpr:
      return leftAssociate(&pn->as<ListNode>(), dst);

    case ParseNodeKind::PowExpr:
      return rightAssociate(&pn->as<ListNode>(), dst);

    case ParseNodeKind::DeleteNameExpr:
    case ParseNodeKind::DeletePropExpr:
    case ParseNodeKind::DeleteElemExpr:
    case ParseNodeKind::DeleteExpr:
    case ParseNodeKind::TypeOfNameExpr:
    case ParseNodeKind::TypeOfExpr:
    case ParseNodeKind::VoidExpr:
    case ParseNodeKind::NotExpr:
    case ParseNodeKind::BitNotExpr:
    case ParseNodeKind::PosExpr:
    case ParseNodeKind::AwaitExpr:
    case ParseNodeKind::NegExpr: {
      UnaryNode* unaryNode = &pn->as<UnaryNode>();
      ParseNode* operand = unaryNode->kid();
      MOZ_ASSERT(unaryNode->pn_pos.encloses(operand->pn_pos));

      UnaryOperator op = unop(unaryNode->getKind());
      LOCAL_ASSERT(op > UNOP_ERR && op < UNOP_LIMIT);

      RootedValue expr(cx);
      return expression(operand, &expr) &&
             builder.unaryExpression(op, expr, &unaryNode->pn_pos, dst);
    }

    case ParseNodeKind::DeleteOptionalChainExpr: {
      RootedValue expr(cx);
      return expression(pn->as<UnaryNode>().kid(), &expr) &&
             builder.deleteOptionalExpression(expr, &pn->pn_pos, dst);
    }

    case ParseNodeKind::OptionalChain: {
      RootedValue expr(cx);
      return expression(pn->as<UnaryNode>().kid(), &expr) &&
             builder.optionalExpression(expr, &pn->pn_pos, dst);
    }

    case ParseNodeKind::NewExpr:
    case ParseNodeKind::TaggedTemplateExpr:
    case ParseNodeKind::CallExpr:
    case ParseNodeKind::OptionalCallExpr:
    case ParseNodeKind::SuperCallExpr: {
      BinaryNode* node = &pn->as<BinaryNode>();
      ParseNode* calleeNode = node->left();
      ListNode* argsList = &node->right()->as<ListNode>();
      MOZ_ASSERT(node->pn_pos.encloses(calleeNode->pn_pos));

      RootedValue callee(cx);
      if (node->isKind(ParseNodeKind::SuperCallExpr)) {
        MOZ_ASSERT(calleeNode->isKind(ParseNodeKind::SuperBase));
        if (!builder.super(&calleeNode->pn_pos, &callee)) {
          return false;
        }
      } else {
        if (!expression(calleeNode, &callee)) {
          return false;
        }
      }

      NodeVector args(cx);
      if (!args.reserve(argsList->count())) {
        return false;
      }

      for (ParseNode* argNode : argsList->contents()) {
        MOZ_ASSERT(node->pn_pos.encloses(argNode->pn_pos));

        RootedValue arg(cx);
        if (!expression(argNode, &arg)) {
          return false;
        }
        args.infallibleAppend(arg);
      }

      if (node->getKind() == ParseNodeKind::TaggedTemplateExpr) {
        return builder.taggedTemplate(callee, args, &node->pn_pos, dst);
      }

      bool isOptional = node->isKind(ParseNodeKind::OptionalCallExpr);

      // SUPERCALL is Call(super, args)
      return node->isKind(ParseNodeKind::NewExpr)
                 ? builder.newExpression(callee, args, &node->pn_pos, dst)
                 : builder.callExpression(callee, args, &node->pn_pos, dst,
                                          isOptional);
    }

    case ParseNodeKind::DotExpr:
    case ParseNodeKind::OptionalDotExpr: {
      PropertyAccessBase* prop = &pn->as<PropertyAccessBase>();
      MOZ_ASSERT(prop->pn_pos.encloses(prop->expression().pn_pos));

      bool isSuper =
          prop->is<PropertyAccess>() && prop->as<PropertyAccess>().isSuper();

      RootedValue expr(cx);
      RootedValue propname(cx);
      Rooted<JSAtom*> pnAtom(
          cx, parser->liftParserAtomToJSAtom(prop->key().atom()));
      if (!pnAtom.get()) {
        return false;
      }

      if (isSuper) {
        if (!builder.super(&prop->expression().pn_pos, &expr)) {
          return false;
        }
      } else {
        if (!expression(&prop->expression(), &expr)) {
          return false;
        }
      }

      bool isOptional = prop->isKind(ParseNodeKind::OptionalDotExpr);

      return identifier(pnAtom, nullptr, &propname) &&
             builder.memberExpression(false, expr, propname, &prop->pn_pos, dst,
                                      isOptional);
    }

    case ParseNodeKind::ElemExpr:
    case ParseNodeKind::OptionalElemExpr: {
      PropertyByValueBase* elem = &pn->as<PropertyByValueBase>();
      MOZ_ASSERT(elem->pn_pos.encloses(elem->expression().pn_pos));
      MOZ_ASSERT(elem->pn_pos.encloses(elem->key().pn_pos));

      bool isSuper =
          elem->is<PropertyByValue>() && elem->as<PropertyByValue>().isSuper();

      RootedValue expr(cx), key(cx);

      if (isSuper) {
        if (!builder.super(&elem->expression().pn_pos, &expr)) {
          return false;
        }
      } else {
        if (!expression(&elem->expression(), &expr)) {
          return false;
        }
      }

      bool isOptional = elem->isKind(ParseNodeKind::OptionalElemExpr);

      return expression(&elem->key(), &key) &&
             builder.memberExpression(true, expr, key, &elem->pn_pos, dst,
                                      isOptional);
    }

    case ParseNodeKind::PrivateMemberExpr:
    case ParseNodeKind::OptionalPrivateMemberExpr: {
      PrivateMemberAccessBase* privateExpr = &pn->as<PrivateMemberAccessBase>();
      MOZ_ASSERT(
          privateExpr->pn_pos.encloses(privateExpr->expression().pn_pos));
      MOZ_ASSERT(
          privateExpr->pn_pos.encloses(privateExpr->privateName().pn_pos));

      RootedValue expr(cx), key(cx);

      if (!expression(&privateExpr->expression(), &expr)) {
        return false;
      }

      bool isOptional =
          privateExpr->isKind(ParseNodeKind::OptionalPrivateMemberExpr);

      return expression(&privateExpr->privateName(), &key) &&
             builder.memberExpression(true, expr, key, &privateExpr->pn_pos,
                                      dst, isOptional);
    }

    case ParseNodeKind::CallSiteObj: {
      CallSiteNode* callSiteObj = &pn->as<CallSiteNode>();
      ListNode* rawNodes = callSiteObj->rawNodes();
      NodeVector raw(cx);
      if (!raw.reserve(rawNodes->count())) {
        return false;
      }
      for (ParseNode* item : rawNodes->contents()) {
        NameNode* rawItem = &item->as<NameNode>();
        MOZ_ASSERT(callSiteObj->pn_pos.encloses(rawItem->pn_pos));

        JSAtom* exprAtom = parser->liftParserAtomToJSAtom(rawItem->atom());
        if (!exprAtom) {
          return false;
        }
        RootedValue expr(cx, StringValue(exprAtom));
        raw.infallibleAppend(expr);
      }

      NodeVector cooked(cx);
      if (!cooked.reserve(callSiteObj->count() - 1)) {
        return false;
      }

      for (ParseNode* cookedItem :
           callSiteObj->contentsFrom(rawNodes->pn_next)) {
        MOZ_ASSERT(callSiteObj->pn_pos.encloses(cookedItem->pn_pos));

        RootedValue expr(cx);
        if (cookedItem->isKind(ParseNodeKind::RawUndefinedExpr)) {
          expr.setUndefined();
        } else {
          MOZ_ASSERT(cookedItem->isKind(ParseNodeKind::TemplateStringExpr));
          JSAtom* exprAtom =
              parser->liftParserAtomToJSAtom(cookedItem->as<NameNode>().atom());
          if (!exprAtom) {
            return false;
          }
          expr.setString(exprAtom);
        }
        cooked.infallibleAppend(expr);
      }

      return builder.callSiteObj(raw, cooked, &callSiteObj->pn_pos, dst);
    }

    case ParseNodeKind::ArrayExpr: {
      ListNode* array = &pn->as<ListNode>();
      NodeVector elts(cx);
      if (!elts.reserve(array->count())) {
        return false;
      }

      for (ParseNode* item : array->contents()) {
        MOZ_ASSERT(array->pn_pos.encloses(item->pn_pos));

        if (item->isKind(ParseNodeKind::Elision)) {
          elts.infallibleAppend(NullValue());
        } else {
          RootedValue expr(cx);
          if (!expression(item, &expr)) {
            return false;
          }
          elts.infallibleAppend(expr);
        }
      }

      return builder.arrayExpression(elts, &array->pn_pos, dst);
    }

    case ParseNodeKind::Spread: {
      RootedValue expr(cx);
      return expression(pn->as<UnaryNode>().kid(), &expr) &&
             builder.spreadExpression(expr, &pn->pn_pos, dst);
    }

    case ParseNodeKind::ComputedName: {
      if (pn->as<UnaryNode>().isSyntheticComputedName()) {
        return literal(pn->as<UnaryNode>().kid(), dst);
      }
      RootedValue name(cx);
      return expression(pn->as<UnaryNode>().kid(), &name) &&
             builder.computedName(name, &pn->pn_pos, dst);
    }

    case ParseNodeKind::ObjectExpr: {
      ListNode* obj = &pn->as<ListNode>();
      NodeVector elts(cx);
      if (!elts.reserve(obj->count())) {
        return false;
      }

      for (ParseNode* item : obj->contents()) {
        MOZ_ASSERT(obj->pn_pos.encloses(item->pn_pos));

        RootedValue prop(cx);
        if (!property(item, &prop)) {
          return false;
        }
        elts.infallibleAppend(prop);
      }

      return builder.objectExpression(elts, &obj->pn_pos, dst);
    }

#ifdef ENABLE_RECORD_TUPLE
    case ParseNodeKind::RecordExpr: {
      ListNode* record = &pn->as<ListNode>();
      NodeVector elts(cx);
      if (!elts.reserve(record->count())) {
        return false;
      }

      for (ParseNode* item : record->contents()) {
        MOZ_ASSERT(record->pn_pos.encloses(item->pn_pos));

        RootedValue prop(cx);
        if (!property(item, &prop)) {
          return false;
        }
        elts.infallibleAppend(prop);
      }

      return builder.recordExpression(elts, &record->pn_pos, dst);
    }

    case ParseNodeKind::TupleExpr: {
      ListNode* tuple = &pn->as<ListNode>();
      NodeVector elts(cx);
      if (!elts.reserve(tuple->count())) {
        return false;
      }

      for (ParseNode* item : tuple->contents()) {
        MOZ_ASSERT(tuple->pn_pos.encloses(item->pn_pos));

        RootedValue expr(cx);
        if (!expression(item, &expr)) {
          return false;
        }
        elts.infallibleAppend(expr);
      }

      return builder.tupleExpression(elts, &tuple->pn_pos, dst);
    }
#endif

    case ParseNodeKind::PrivateName:
    case ParseNodeKind::Name:
      return identifier(&pn->as<NameNode>(), dst);

    case ParseNodeKind::ThisExpr:
      return builder.thisExpression(&pn->pn_pos, dst);

    case ParseNodeKind::TemplateStringListExpr: {
      ListNode* list = &pn->as<ListNode>();
      NodeVector elts(cx);
      if (!elts.reserve(list->count())) {
        return false;
      }

      for (ParseNode* item : list->contents()) {
        MOZ_ASSERT(list->pn_pos.encloses(item->pn_pos));

        RootedValue expr(cx);
        if (!expression(item, &expr)) {
          return false;
        }
        elts.infallibleAppend(expr);
      }

      return builder.templateLiteral(elts, &list->pn_pos, dst);
    }

    case ParseNodeKind::TemplateStringExpr:
    case ParseNodeKind::StringExpr:
    case ParseNodeKind::RegExpExpr:
    case ParseNodeKind::NumberExpr:
    case ParseNodeKind::BigIntExpr:
    case ParseNodeKind::TrueExpr:
    case ParseNodeKind::FalseExpr:
    case ParseNodeKind::NullExpr:
    case ParseNodeKind::RawUndefinedExpr:
      return literal(pn, dst);

    case ParseNodeKind::YieldStarExpr: {
      UnaryNode* yieldNode = &pn->as<UnaryNode>();
      ParseNode* operand = yieldNode->kid();
      MOZ_ASSERT(yieldNode->pn_pos.encloses(operand->pn_pos));

      RootedValue arg(cx);
      return expression(operand, &arg) &&
             builder.yieldExpression(arg, Delegating, &yieldNode->pn_pos, dst);
    }

    case ParseNodeKind::YieldExpr: {
      UnaryNode* yieldNode = &pn->as<UnaryNode>();
      ParseNode* operand = yieldNode->kid();
      MOZ_ASSERT_IF(operand, yieldNode->pn_pos.encloses(operand->pn_pos));

      RootedValue arg(cx);
      return optExpression(operand, &arg) &&
             builder.yieldExpression(arg, NotDelegating, &yieldNode->pn_pos,
                                     dst);
    }

    case ParseNodeKind::ClassDecl:
      return classDefinition(&pn->as<ClassNode>(), true, dst);

    case ParseNodeKind::NewTargetExpr: {
      auto* node = &pn->as<NewTargetNode>();
      ParseNode* firstNode = node->newHolder();
      MOZ_ASSERT(firstNode->isKind(ParseNodeKind::PosHolder));
      MOZ_ASSERT(node->pn_pos.encloses(firstNode->pn_pos));

      ParseNode* secondNode = node->targetHolder();
      MOZ_ASSERT(secondNode->isKind(ParseNodeKind::PosHolder));
      MOZ_ASSERT(node->pn_pos.encloses(secondNode->pn_pos));

      RootedValue firstIdent(cx);
      RootedValue secondIdent(cx);

      Rooted<JSAtom*> firstStr(cx, cx->names().new_);
      Rooted<JSAtom*> secondStr(cx, cx->names().target);

      return identifier(firstStr, &firstNode->pn_pos, &firstIdent) &&
             identifier(secondStr, &secondNode->pn_pos, &secondIdent) &&
             builder.metaProperty(firstIdent, secondIdent, &node->pn_pos, dst);
    }

    case ParseNodeKind::ImportMetaExpr: {
      BinaryNode* node = &pn->as<BinaryNode>();
      ParseNode* firstNode = node->left();
      MOZ_ASSERT(firstNode->isKind(ParseNodeKind::PosHolder));
      MOZ_ASSERT(node->pn_pos.encloses(firstNode->pn_pos));

      ParseNode* secondNode = node->right();
      MOZ_ASSERT(secondNode->isKind(ParseNodeKind::PosHolder));
      MOZ_ASSERT(node->pn_pos.encloses(secondNode->pn_pos));

      RootedValue firstIdent(cx);
      RootedValue secondIdent(cx);

      Rooted<JSAtom*> firstStr(cx, cx->names().import);
      Rooted<JSAtom*> secondStr(cx, cx->names().meta);

      return identifier(firstStr, &firstNode->pn_pos, &firstIdent) &&
             identifier(secondStr, &secondNode->pn_pos, &secondIdent) &&
             builder.metaProperty(firstIdent, secondIdent, &node->pn_pos, dst);
    }

    case ParseNodeKind::CallImportExpr: {
      BinaryNode* node = &pn->as<BinaryNode>();
      ParseNode* identNode = node->left();
      MOZ_ASSERT(identNode->isKind(ParseNodeKind::PosHolder));
      MOZ_ASSERT(identNode->pn_pos.encloses(identNode->pn_pos));

      ParseNode* specNode = node->right();
      MOZ_ASSERT(specNode->is<BinaryNode>());
      MOZ_ASSERT(specNode->isKind(ParseNodeKind::CallImportSpec));

      ParseNode* argNode = specNode->as<BinaryNode>().left();
      MOZ_ASSERT(node->pn_pos.encloses(argNode->pn_pos));

      ParseNode* optionsArgNode = specNode->as<BinaryNode>().right();
      MOZ_ASSERT(node->pn_pos.encloses(optionsArgNode->pn_pos));

      RootedValue ident(cx);
      Handle<PropertyName*> name = cx->names().import;
      if (!identifier(name, &identNode->pn_pos, &ident)) {
        return false;
      }

      NodeVector args(cx);

      RootedValue arg(cx);
      if (!expression(argNode, &arg)) {
        return false;
      }
      if (!args.append(arg)) {
        return false;
      }

      if (!optionsArgNode->isKind(ParseNodeKind::PosHolder)) {
        RootedValue optionsArg(cx);
        if (!expression(optionsArgNode, &optionsArg)) {
          return false;
        }
        if (!args.append(optionsArg)) {
          return false;
        }
      }

      return builder.callImportExpression(ident, args, &pn->pn_pos, dst);
    }

    case ParseNodeKind::SetThis: {
      // SETTHIS is used to assign the result of a super() call to |this|.
      // It's not part of the original AST, so just forward to the call.
      BinaryNode* node = &pn->as<BinaryNode>();
      MOZ_ASSERT(node->left()->isKind(ParseNodeKind::Name));
      return expression(node->right(), dst);
    }

    default:
      LOCAL_NOT_REACHED("unexpected expression type");
  }
}

bool ASTSerializer::propertyName(ParseNode* key, MutableHandleValue dst) {
  if (key->isKind(ParseNodeKind::ComputedName)) {
    return expression(key, dst);
  }
  if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
      key->isKind(ParseNodeKind::PrivateName)) {
    return identifier(&key->as<NameNode>(), dst);
  }

  LOCAL_ASSERT(key->isKind(ParseNodeKind::StringExpr) ||
               key->isKind(ParseNodeKind::NumberExpr) ||
               key->isKind(ParseNodeKind::BigIntExpr));

  return literal(key, dst);
}

bool ASTSerializer::property(ParseNode* pn, MutableHandleValue dst) {
  if (pn->isKind(ParseNodeKind::MutateProto)) {
    RootedValue val(cx);
    return expression(pn->as<UnaryNode>().kid(), &val) &&
           builder.prototypeMutation(val, &pn->pn_pos, dst);
  }
  if (pn->isKind(ParseNodeKind::Spread)) {
    return expression(pn, dst);
  }

  PropKind kind;
  if (pn->is<PropertyDefinition>()) {
    switch (pn->as<PropertyDefinition>().accessorType()) {
      case AccessorType::None:
        kind = PROP_INIT;
        break;

      case AccessorType::Getter:
        kind = PROP_GETTER;
        break;

      case AccessorType::Setter:
        kind = PROP_SETTER;
        break;

      default:
        LOCAL_NOT_REACHED("unexpected object-literal property");
    }
  } else {
    MOZ_ASSERT(pn->isKind(ParseNodeKind::Shorthand));
    kind = PROP_INIT;
  }

  BinaryNode* node = &pn->as<BinaryNode>();
  ParseNode* keyNode = node->left();
  ParseNode* valNode = node->right();

  bool isShorthand = node->isKind(ParseNodeKind::Shorthand);
  bool isMethod =
      valNode->is<FunctionNode>() &&
      valNode->as<FunctionNode>().funbox()->kind() == FunctionFlags::Method;
  RootedValue key(cx), val(cx);
  return propertyName(keyNode, &key) && expression(valNode, &val) &&
         builder.propertyInitializer(key, val, kind, isShorthand, isMethod,
                                     &node->pn_pos, dst);
}

bool ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst) {
  RootedValue val(cx);
  switch (pn->getKind()) {
    case ParseNodeKind::TemplateStringExpr:
    case ParseNodeKind::StringExpr: {
      JSAtom* exprAtom =
          parser->liftParserAtomToJSAtom(pn->as<NameNode>().atom());
      if (!exprAtom) {
        return false;
      }
      val.setString(exprAtom);
      break;
    }

    case ParseNodeKind::RegExpExpr: {
      RegExpObject* re = pn->as<RegExpLiteral>().create(
          cx, fc, parser->parserAtoms(),
          parser->getCompilationState().input.atomCache,
          parser->getCompilationState());
      if (!re) {
        return false;
      }

      val.setObject(*re);
      break;
    }

    case ParseNodeKind::NumberExpr:
      val.setNumber(pn->as<NumericLiteral>().value());
      break;

    case ParseNodeKind::BigIntExpr: {
      auto index = pn->as<BigIntLiteral>().index();
      BigInt* x = parser->compilationState_.bigIntData[index].createBigInt(cx);
      if (!x) {
        return false;
      }
      cx->check(x);
      val.setBigInt(x);
      break;
    }

    case ParseNodeKind::NullExpr:
      val.setNull();
      break;

    case ParseNodeKind::RawUndefinedExpr:
      val.setUndefined();
      break;

    case ParseNodeKind::TrueExpr:
      val.setBoolean(true);
      break;

    case ParseNodeKind::FalseExpr:
      val.setBoolean(false);
      break;

    default:
      LOCAL_NOT_REACHED("unexpected literal type");
  }

  return builder.literal(val, &pn->pn_pos, dst);
}

bool ASTSerializer::arrayPattern(ListNode* array, MutableHandleValue dst) {
  MOZ_ASSERT(array->isKind(ParseNodeKind::ArrayExpr));

  NodeVector elts(cx);
  if (!elts.reserve(array->count())) {
    return false;
  }

  for (ParseNode* item : array->contents()) {
    if (item->isKind(ParseNodeKind::Elision)) {
      elts.infallibleAppend(NullValue());
    } else if (item->isKind(ParseNodeKind::Spread)) {
      RootedValue target(cx);
      RootedValue spread(cx);
      if (!pattern(item->as<UnaryNode>().kid(), &target)) {
        return false;
      }
      if (!builder.spreadExpression(target, &item->pn_pos, &spread))
        return false;
      elts.infallibleAppend(spread);
    } else {
      RootedValue patt(cx);
      if (!pattern(item, &patt)) {
        return false;
      }
      elts.infallibleAppend(patt);
    }
  }

  return builder.arrayPattern(elts, &array->pn_pos, dst);
}

bool ASTSerializer::objectPattern(ListNode* obj, MutableHandleValue dst) {
  MOZ_ASSERT(obj->isKind(ParseNodeKind::ObjectExpr));

  NodeVector elts(cx);
  if (!elts.reserve(obj->count())) {
    return false;
  }

  for (ParseNode* propdef : obj->contents()) {
    if (propdef->isKind(ParseNodeKind::Spread)) {
      RootedValue target(cx);
      RootedValue spread(cx);
      if (!pattern(propdef->as<UnaryNode>().kid(), &target)) {
        return false;
      }
      if (!builder.spreadExpression(target, &propdef->pn_pos, &spread))
        return false;
      elts.infallibleAppend(spread);
      continue;
    }
    // Patterns can't have getters/setters.
    LOCAL_ASSERT(!propdef->is<PropertyDefinition>() ||
                 propdef->as<PropertyDefinition>().accessorType() ==
                     AccessorType::None);

    RootedValue key(cx);
    ParseNode* target;
    if (propdef->isKind(ParseNodeKind::MutateProto)) {
      RootedValue pname(cx, StringValue(cx->names().proto));
      if (!builder.literal(pname, &propdef->pn_pos, &key)) {
        return false;
      }
      target = propdef->as<UnaryNode>().kid();
    } else {
      BinaryNode* prop = &propdef->as<BinaryNode>();
      if (!propertyName(prop->left(), &key)) {
        return false;
      }
      target = prop->right();
    }

    RootedValue patt(cx), prop(cx);
    if (!pattern(target, &patt) ||
        !builder.propertyPattern(key, patt,
                                 propdef->isKind(ParseNodeKind::Shorthand),
                                 &propdef->pn_pos, &prop)) {
      return false;
    }

    elts.infallibleAppend(prop);
  }

  return builder.objectPattern(elts, &obj->pn_pos, dst);
}

bool ASTSerializer::pattern(ParseNode* pn, MutableHandleValue dst) {
  AutoCheckRecursionLimit recursion(cx);
  if (!recursion.check(cx)) {
    return false;
  }

  switch (pn->getKind()) {
    case ParseNodeKind::ObjectExpr:
      return objectPattern(&pn->as<ListNode>(), dst);

    case ParseNodeKind::ArrayExpr:
      return arrayPattern(&pn->as<ListNode>(), dst);

    default:
      return expression(pn, dst);
  }
}

bool ASTSerializer::identifier(Handle<JSAtom*> atom, TokenPos* pos,
                               MutableHandleValue dst) {
  RootedValue atomContentsVal(cx, unrootedAtomContents(atom));
  return builder.identifier(atomContentsVal, pos, dst);
}

bool ASTSerializer::identifier(NameNode* id, MutableHandleValue dst) {
  LOCAL_ASSERT(id->atom());

  Rooted<JSAtom*> pnAtom(cx, parser->liftParserAtomToJSAtom(id->atom()));
  if (!pnAtom.get()) {
    return false;
  }
  return identifier(pnAtom, &id->pn_pos, dst);
}

bool ASTSerializer::identifierOrLiteral(ParseNode* id, MutableHandleValue dst) {
  if (id->getKind() == ParseNodeKind::Name) {
    return identifier(&id->as<NameNode>(), dst);
  }
  return literal(id, dst);
}

bool ASTSerializer::function(FunctionNode* funNode, ASTType type,
                             MutableHandleValue dst) {
  FunctionBox* funbox = funNode->funbox();

  GeneratorStyle generatorStyle =
      funbox->isGenerator() ? GeneratorStyle::ES6 : GeneratorStyle::None;

  bool isAsync = funbox->isAsync();
  bool isExpression = funbox->hasExprBody();

  RootedValue id(cx);
  Rooted<JSAtom*> funcAtom(cx);
  if (funbox->explicitName()) {
    funcAtom.set(parser->liftParserAtomToJSAtom(funbox->explicitName()));
    if (!funcAtom) {
      return false;
    }
  }
  if (!optIdentifier(funcAtom, nullptr, &id)) {
    return false;
  }

  NodeVector args(cx);
  NodeVector defaults(cx);

  RootedValue body(cx), rest(cx);
  if (funbox->hasRest()) {
    rest.setUndefined();
  } else {
    rest.setNull();
  }
  return functionArgsAndBody(funNode->body(), args, defaults, isAsync,
                             isExpression, &body, &rest) &&
         builder.function(type, &funNode->pn_pos, id, args, defaults, body,
                          rest, generatorStyle, isAsync, isExpression, dst);
}

bool ASTSerializer::functionArgsAndBody(ParamsBodyNode* pn, NodeVector& args,
                                        NodeVector& defaults, bool isAsync,
                                        bool isExpression,
                                        MutableHandleValue body,
                                        MutableHandleValue rest) {
  // Serialize the arguments.
  if (!functionArgs(pn, args, defaults, rest)) {
    return false;
  }

  // Skip the enclosing lexical scope.
  ParseNode* bodyNode = pn->body()->scopeBody();

  // Serialize the body.
  switch (bodyNode->getKind()) {
    // Arrow function with expression body.
    case ParseNodeKind::ReturnStmt:
      MOZ_ASSERT(isExpression);
      return expression(bodyNode->as<UnaryNode>().kid(), body);

    // Function with statement body.
    case ParseNodeKind::StatementList: {
      ParseNode* firstNode = bodyNode->as<ListNode>().head();

      // Skip over initial yield in generator.
      if (firstNode && firstNode->isKind(ParseNodeKind::InitialYield)) {
        firstNode = firstNode->pn_next;
      }

      // Async arrow with expression body is converted into STATEMENTLIST
      // to insert initial yield.
      if (isAsync && isExpression) {
        MOZ_ASSERT(firstNode->getKind() == ParseNodeKind::ReturnStmt);
        return expression(firstNode->as<UnaryNode>().kid(), body);
      }

      return functionBody(firstNode, &bodyNode->pn_pos, body);
    }

    default:
      LOCAL_NOT_REACHED("unexpected function contents");
  }
}

bool ASTSerializer::functionArgs(ParamsBodyNode* pn, NodeVector& args,
                                 NodeVector& defaults,
                                 MutableHandleValue rest) {
  RootedValue node(cx);
  bool defaultsNull = true;
  MOZ_ASSERT(defaults.empty(),
             "must be initially empty for it to be proper to clear this "
             "when there are no defaults");

  MOZ_ASSERT(rest.isNullOrUndefined(),
             "rest is set to |undefined| when a rest argument is present, "
             "otherwise rest is set to |null|");

  for (ParseNode* arg : pn->parameters()) {
    ParseNode* pat;
    ParseNode* defNode;
    if (arg->isKind(ParseNodeKind::Name) ||
        arg->isKind(ParseNodeKind::ArrayExpr) ||
        arg->isKind(ParseNodeKind::ObjectExpr)) {
      pat = arg;
      defNode = nullptr;
    } else {
      MOZ_ASSERT(arg->isKind(ParseNodeKind::AssignExpr));
      AssignmentNode* assignNode = &arg->as<AssignmentNode>();
      pat = assignNode->left();
      defNode = assignNode->right();
    }

    // Process the name or pattern.
    MOZ_ASSERT(pat->isKind(ParseNodeKind::Name) ||
               pat->isKind(ParseNodeKind::ArrayExpr) ||
               pat->isKind(ParseNodeKind::ObjectExpr));
    if (!pattern(pat, &node)) {
      return false;
    }
    if (rest.isUndefined() && arg->pn_next == *std::end(pn->parameters())) {
      rest.setObject(node.toObject());
    } else {
      if (!args.append(node)) {
        return false;
      }
    }

    // Process its default (or lack thereof).
    if (defNode) {
      defaultsNull = false;
      RootedValue def(cx);
      if (!expression(defNode, &def) || !defaults.append(def)) {
        return false;
      }
    } else {
      if (!defaults.append(NullValue())) {
        return false;
      }
    }
  }
  MOZ_ASSERT(!rest.isUndefined(),
             "if a rest argument was present (signified by "
             "|rest.isUndefined()| initially), the rest node was properly "
             "recorded");

  if (defaultsNull) {
    defaults.clear();
  }

  return true;
}

bool ASTSerializer::functionBody(ParseNode* pn, TokenPos* pos,
                                 MutableHandleValue dst) {
  NodeVector elts(cx);

  // We aren't sure how many elements there are up front, so we'll check each
  // append.
  for (ParseNode* next = pn; next; next = next->pn_next) {
    RootedValue child(cx);
    if (!sourceElement(next, &child) || !elts.append(child)) {
      return false;
    }
  }

  return builder.blockStatement(elts, pos, dst);
}

static bool reflect_parse(JSContext* cx, uint32_t argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!args.requireAtLeast(cx, "Reflect.parse", 1)) {
    return false;
  }

  RootedString src(cx, ToString<CanGC>(cx, args[0]));
  if (!src) {
    return false;
  }

  UniqueChars filename;
  uint32_t lineno = 1;
  bool loc = true;
  ParseGoal target = ParseGoal::Script;

  RootedValue arg(cx, args.get(1));

  if (!arg.isNullOrUndefined()) {
    if (!arg.isObject()) {
      ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, arg,
                       nullptr, "not an object");
      return false;
    }

    RootedObject config(cx, &arg.toObject());

    RootedValue prop(cx);

    /* config.loc */
    RootedId locId(cx, NameToId(cx->names().loc));
    RootedValue trueVal(cx, BooleanValue(true));
    if (!GetPropertyDefault(cx, config, locId, trueVal, &prop)) {
      return false;
    }

    loc = ToBoolean(prop);

    if (loc) {
      /* config.source */
      RootedId sourceId(cx, NameToId(cx->names().source));
      RootedValue nullVal(cx, NullValue());
      if (!GetPropertyDefault(cx, config, sourceId, nullVal, &prop)) {
        return false;
      }

      if (!prop.isNullOrUndefined()) {
        RootedString str(cx, ToString<CanGC>(cx, prop));
        if (!str) {
          return false;
        }

        filename = StringToNewUTF8CharsZ(cx, *str);
        if (!filename) {
          return false;
        }
      }

      /* config.line */
      RootedId lineId(cx, NameToId(cx->names().line));
      RootedValue oneValue(cx, Int32Value(1));
      if (!GetPropertyDefault(cx, config, lineId, oneValue, &prop) ||
          !ToUint32(cx, prop, &lineno)) {
        return false;
      }
    }

    /* config.target */
    RootedId targetId(cx, NameToId(cx->names().target));
    RootedValue scriptVal(cx, StringValue(cx->names().script));
    if (!GetPropertyDefault(cx, config, targetId, scriptVal, &prop)) {
      return false;
    }

    if (!prop.isString()) {
      ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, prop,
                       nullptr, "not 'script' or 'module'");
      return false;
    }

    RootedString stringProp(cx, prop.toString());
    bool isScript = false;
    bool isModule = false;
    if (!EqualStrings(cx, stringProp, cx->names().script, &isScript)) {
      return false;
    }

    if (!EqualStrings(cx, stringProp, cx->names().module, &isModule)) {
      return false;
    }

    if (isScript) {
      target = ParseGoal::Script;
    } else if (isModule) {
      target = ParseGoal::Module;
    } else {
      JS_ReportErrorASCII(cx,
                          "Bad target value, expected 'script' or 'module'");
      return false;
    }
  }

  AutoReportFrontendContext fc(cx);
  ASTSerializer serialize(cx, &fc, loc, filename.get(), lineno);
  if (!serialize.init()) {
    return false;
  }

  JSLinearString* linear = src->ensureLinear(cx);
  if (!linear) {
    return false;
  }

  AutoStableStringChars linearChars(cx);
  if (!linearChars.initTwoByte(cx, linear)) {
    return false;
  }

  CompileOptions options(cx);
  options.setFileAndLine(filename.get(), lineno);
  options.setForceFullParse();
  options.allowHTMLComments = target == ParseGoal::Script;
  mozilla::Range<const char16_t> chars = linearChars.twoByteRange();

  Rooted<CompilationInput> input(cx, CompilationInput(options));
  if (target == ParseGoal::Script) {
    if (!input.get().initForGlobal(&fc)) {
      return false;
    }
  } else {
    if (!input.get().initForModule(&fc)) {
      return false;
    }
  }

  LifoAllocScope allocScope(&cx->tempLifoAlloc());
  frontend::NoScopeBindingCache scopeCache;
  frontend::CompilationState compilationState(&fc, allocScope, input.get());
  if (!compilationState.init(&fc, &scopeCache)) {
    return false;
  }

  Parser<FullParseHandler, char16_t> parser(
      &fc, options, chars.begin().get(), chars.length(),
      /* foldConstants = */ false, compilationState,
      /* syntaxParser = */ nullptr);
  if (!parser.checkOptions()) {
    return false;
  }

  serialize.setParser(&parser);

  ParseNode* pn;
  if (target == ParseGoal::Script) {
    pn = parser.parse();
    if (!pn) {
      return false;
    }
  } else {
    ModuleBuilder builder(&fc, &parser);

    uint32_t len = chars.length();
    SourceExtent extent =
        SourceExtent::makeGlobalExtent(len, options.lineno, options.column);
    ModuleSharedContext modulesc(&fc, options, builder, extent);
    pn = parser.moduleBody(&modulesc);
    if (!pn) {
      return false;
    }

    pn = pn->as<ModuleNode>().body();
  }

  RootedValue val(cx);
  if (!serialize.program(&pn->as<ListNode>(), &val)) {
    args.rval().setNull();
    return false;
  }

  args.rval().set(val);
  return true;
}

JS_PUBLIC_API bool JS_InitReflectParse(JSContext* cx, HandleObject global) {
  RootedValue reflectVal(cx);
  if (!GetProperty(cx, global, global, cx->names().Reflect, &reflectVal)) {
    return false;
  }
  if (!reflectVal.isObject()) {
    JS_ReportErrorASCII(
        cx, "JS_InitReflectParse must be called during global initialization");
    return false;
  }

  RootedObject reflectObj(cx, &reflectVal.toObject());
  return JS_DefineFunction(cx, reflectObj, "parse", reflect_parse, 1, 0);
}
