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

#ifndef frontend_ParseNode_h
#define frontend_ParseNode_h

#include "mozilla/Attributes.h"

#include "frontend/TokenStream.h"
#include "vm/BytecodeUtil.h"
#include "vm/Printer.h"
#include "vm/Scope.h"

// A few notes on lifetime of ParseNode trees:
//
// - All the `ParseNode` instances MUST BE explicitly allocated in the context's `LifoAlloc`.
//   This is typically implemented by the `FullParseHandler` or it can be reimplemented with
//   a custom `new_`.
//
// - The tree is bulk-deallocated when the parser is deallocated. Consequently, references
//   to a subtree MUST NOT exist once the parser has been deallocated.
//
// - This bulk-deallocation DOES NOT run destructors.
//
// - Instances of `LexicalScope::Data` MUST BE allocated as instances of `ParseNode`, in the same
//   `LifoAlloc`. They are bulk-deallocated alongside the rest of the tree.
//
// - Instances of `JSAtom` used throughout the tree (including instances of `PropertyName`) MUST
//   be kept alive by the parser. This is done through an instance of `AutoKeepAtoms` held by
//   the parser.
//
// - Once the parser is deallocated, the `JSAtom` instances MAY be garbage-collected.


namespace js {
namespace frontend {

class ParseContext;
class FullParseHandler;
class FunctionBox;
class ObjectBox;

#define FOR_EACH_PARSE_NODE_KIND(F) \
    F(EmptyStatement) \
    F(ExpressionStatement) \
    F(Comma) \
    F(Conditional) \
    F(Colon) \
    F(Shorthand) \
    F(Pos) \
    F(Neg) \
    F(PreIncrement) \
    F(PostIncrement) \
    F(PreDecrement) \
    F(PostDecrement) \
    F(PropertyName) \
    F(Dot) \
    F(Elem) \
    F(Array) \
    F(Elision) \
    F(StatementList) \
    F(Label) \
    F(Object) \
    F(Call) \
    F(Arguments) \
    F(Name) \
    F(ObjectPropertyName) \
    F(ComputedName) \
    F(Number) \
    F(String) \
    F(TemplateStringList) \
    F(TemplateString) \
    F(TaggedTemplate) \
    F(CallSiteObj) \
    F(RegExp) \
    F(True) \
    F(False) \
    F(Null) \
    F(RawUndefined) \
    F(This) \
    F(Function) \
    F(Module) \
    F(If) \
    F(Switch) \
    F(Case) \
    F(While) \
    F(DoWhile) \
    F(For) \
    F(Break) \
    F(Continue) \
    F(Var) \
    F(Const) \
    F(With) \
    F(Return) \
    F(New) \
    /* Delete operations.  These must be sequential. */ \
    F(DeleteName) \
    F(DeleteProp) \
    F(DeleteElem) \
    F(DeleteExpr) \
    F(Try) \
    F(Catch) \
    F(Throw) \
    F(Debugger) \
    F(Generator) \
    F(InitialYield) \
    F(Yield) \
    F(YieldStar) \
    F(LexicalScope) \
    F(Let) \
    F(Import) \
    F(ImportSpecList) \
    F(ImportSpec) \
    F(Export) \
    F(ExportFrom) \
    F(ExportDefault) \
    F(ExportSpecList) \
    F(ExportSpec) \
    F(ExportBatchSpec) \
    F(ForIn) \
    F(ForOf) \
    F(ForHead) \
    F(ParamsBody) \
    F(Spread) \
    F(MutateProto) \
    F(Class) \
    F(ClassMethod) \
    F(ClassMethodList) \
    F(ClassNames) \
    F(NewTarget) \
    F(PosHolder) \
    F(SuperBase) \
    F(SuperCall) \
    F(SetThis) \
    F(ImportMeta) \
    F(CallImport) \
    \
    /* Unary operators. */ \
    F(TypeOfName) \
    F(TypeOfExpr) \
    F(Void) \
    F(Not) \
    F(BitNot) \
    F(Await) \
    \
    /* \
     * Binary operators. \
     * These must be in the same order as TOK_OR and friends in TokenStream.h. \
     */ \
    F(Pipeline) \
    F(Or) \
    F(And) \
    F(BitOr) \
    F(BitXor) \
    F(BitAnd) \
    F(StrictEq) \
    F(Eq) \
    F(StrictNe) \
    F(Ne) \
    F(Lt) \
    F(Le) \
    F(Gt) \
    F(Ge) \
    F(InstanceOf) \
    F(In) \
    F(Lsh) \
    F(Rsh) \
    F(Ursh) \
    F(Add) \
    F(Sub) \
    F(Star) \
    F(Div) \
    F(Mod) \
    F(Pow) \
    \
    /* Assignment operators (= += -= etc.). */ \
    /* ParseNode::isAssignment assumes all these are consecutive. */ \
    F(Assign) \
    F(AddAssign) \
    F(SubAssign) \
    F(BitOrAssign) \
    F(BitXorAssign) \
    F(BitAndAssign) \
    F(LshAssign) \
    F(RshAssign) \
    F(UrshAssign) \
    F(MulAssign) \
    F(DivAssign) \
    F(ModAssign) \
    F(PowAssign)

/*
 * Parsing builds a tree of nodes that directs code generation.  This tree is
 * not a concrete syntax tree in all respects (for example, || and && are left
 * associative, but (A && B && C) translates into the right-associated tree
 * <A && <B && C>> so that code generation can emit a left-associative branch
 * around <B && C> when A is false).  Nodes are labeled by kind, with a
 * secondary JSOp label when needed.
 *
 * The long comment after this enum block describes the kinds in detail.
 */
enum class ParseNodeKind : uint16_t
{
#define EMIT_ENUM(name) name,
    FOR_EACH_PARSE_NODE_KIND(EMIT_ENUM)
#undef EMIT_ENUM
    Limit, /* domain size */
    BinOpFirst = ParseNodeKind::Pipeline,
    BinOpLast = ParseNodeKind::Pow,
    AssignmentStart = ParseNodeKind::Assign,
    AssignmentLast = ParseNodeKind::PowAssign
};

inline bool
IsDeleteKind(ParseNodeKind kind)
{
    return ParseNodeKind::DeleteName <= kind && kind <= ParseNodeKind::DeleteExpr;
}

inline bool
IsTypeofKind(ParseNodeKind kind)
{
    return ParseNodeKind::TypeOfName <= kind && kind <= ParseNodeKind::TypeOfExpr;
}

/*
 * <Definitions>
 * Function (CodeNode)
 *   funbox: ptr to js::FunctionBox holding function object containing arg and
 *           var properties.  We create the function object at parse (not emit)
 *           time to specialize arg and var bytecodes early.
 *   body: ParamsBody or null for lazily-parsed function, ordinarily;
 *         ParseNodeKind::LexicalScope for implicit function in genexpr
 * ParamsBody (ListNode)
 *   head: list of formal parameters with
 *           * Name node with non-empty name for SingleNameBinding without
 *             Initializer
 *           * Assign node for SingleNameBinding with Initializer
 *           * Name node with empty name for destructuring
 *               expr: Array or Object for BindingPattern without
 *                     Initializer, Assign for BindingPattern with
 *                     Initializer
 *         followed by either:
 *           * StatementList node for function body statements
 *           * Return for expression closure
 *   count: number of formal parameters + 1
 * Spread (UnaryNode)
 *   kid: expression being spread
 * Class (ClassNode)
 *   kid1: ClassNames for class name. can be null for anonymous class.
 *   kid2: expression after `extends`. null if no expression
 *   kid3: either of
 *           * ClassMethodList, if anonymous class
 *           * LexicalScopeNode which contains ClassMethodList as scopeBody,
 *             if named class
 * ClassNames (ClassNames)
 *   left: Name node for outer binding, or null if the class is an expression
 *         that doesn't create an outer binding
 *   right: Name node for inner binding
 * ClassMethodList (ListNode)
 *   head: list of N ClassMethod nodes
 *   count: N >= 0
 * ClassMethod (ClassMethod)
 *   name: propertyName
 *   method: methodDefinition
 * Module (CodeNode)
 *   funbox: ?
 *   body: ?
 *
 * <Statements>
 * StatementList (ListNode)
 *   head: list of N statements
 *   count: N >= 0
 * If (TernaryNode)
 *   kid1: cond
 *   kid2: then
 *   kid3: else or null
 * Switch (SwitchStatement)
 *   left: discriminant
 *   right: LexicalScope node that contains the list of Case nodes, with at
 *          most one default node.
 *   hasDefault: true if there's a default case
 * Case (CaseClause)
 *   left: case-expression if CaseClause, or null if DefaultClause
 *   right: StatementList node for this case's statements
 * While (BinaryNode)
 *   left: cond
 *   right: body
 * DoWhile (BinaryNode)
 *   left: body
 *   right: cond
 * For (ForNode)
 *   left: one of
 *           * ForIn: for (x in y) ...
 *           * ForOf: for (x of x) ...
 *           * ForHead: for (;;) ...
 *   right: body
 * ForIn (TernaryNode)
 *   kid1: declaration or expression to left of 'in'
 *   kid2: null
 *   kid3: object expr to right of 'in'
 * ForOf (TernaryNode)
 *   kid1: declaration or expression to left of 'of'
 *   kid2: null
 *   kid3: expr to right of 'of'
 * ForHead (TernaryNode)
 *   kid1:  init expr before first ';' or nullptr
 *   kid2:  cond expr before second ';' or nullptr
 *   kid3:  update expr after second ';' or nullptr
 * Throw (UnaryNode)
 *   kid: thrown exception
 * Try (TernaryNode)
 *   kid1: try block
 *   kid2: null or LexicalScope for catch-block with scopeBody pointing to a
 *         Catch node
 *   kid3: null or finally block
 * Catch (BinaryNode)
 *   left: Name, Array, or Object catch var node
 *         (Array or Object if destructuring),
 *         or null if optional catch binding
 *   right: catch block statements
 * Break (BreakStatement)
 *   label: label or null
 * Continue (ContinueStatement)
 *   label: label or null
 * With (BinaryNode)
 *   left: head expr
 *   right: body
 * Var, Let, Const (ListNode)
 *   head: list of N Name or Assign nodes
 *         each name node has either
 *           atom: variable name
 *           expr: initializer or null
 *         or
 *           atom: variable name
 *         each assignment node has
 *           left: pattern
 *           right: initializer
 *   count: N > 0
 * Return (UnaryNode)
 *   kid: returned expression, or null if none
 * ExpressionStatement (UnaryNode)
 *   kid: expr
 *   prologue: true if Directive Prologue member in original source, not
 *             introduced via constant folding or other tree rewriting
 * EmptyStatement (NullaryNode)
 *   (no fields)
 * Label (LabeledStatement)
 *   atom: label
 *   expr: labeled statement
 * Import (BinaryNode)
 *   left: ImportSpecList import specifiers
 *   right: String module specifier
 * ImportSpecList (ListNode)
 *   head: list of N ImportSpec nodes
 *   count: N >= 0 (N = 0 for `import {} from ...`)
 * ImportSpec (BinaryNode)
 *   left: import name
 *   right: local binding name
 * Export (UnaryNode)
 *   kid: declaration expression
 * ExportFrom (BinaryNode)
 *   left: ExportSpecList export specifiers
 *   right: String module specifier
 * ExportSpecList (ListNode)
 *   head: list of N ExportSpec nodes
 *   count: N >= 0 (N = 0 for `export {}`)
 * ExportSpec (BinaryNode)
 *   left: local binding name
 *   right: export name
 * ExportDefault (BinaryNode)
 *   left: export default declaration or expression
 *   right: Name node for assignment
 *
 * <Expressions>
 * All left-associated binary trees of the same type are optimized into lists
 * to avoid recursion when processing expression chains.
 * Comma (ListNode)
 *   head: list of N comma-separated exprs
 *   count: N >= 2
 * Assign (BinaryNode)
 *   left: target of assignment
 *   right: value to assign
 * AddAssign, SubAssign, BitOrAssign, BitXorAssign, BitAndAssign,
 * LshAssign, RshAssign, UrshAssign, MulAssign, DivAssign, ModAssign,
 * PowAssign (AssignmentNode)
 *   left: target of assignment
 *   right: value to assign
 *   pn_op: JSOP_ADD for +=, etc
 * Conditional (ConditionalExpression)
 *   (cond ? thenExpr : elseExpr)
 *   kid1: cond
 *   kid2: thenExpr
 *   kid3: elseExpr
 * Pipeline, Or, And, BitOr, BitXor, BitAnd, StrictEq, Eq, StrictNe, Ne,
 * Lt, Le, Gt, Ge, InstanceOf, In, Lsh, Rsh, Ursh, Add, Sub, Star, Div, Mod,
 * Pow (ListNode)
 *   head: list of N subexpressions
 *         All of these operators are left-associative except Pow which is
 *         right-associative, but still forms a list (see comments in
 *         ParseNode::appendOrCreateList).
 *   count: N >= 2
 * Pos, Neg, Void, Not, BitNot, TypeOfName, TypeOfExpr (UnaryNode)
 *   kid: unary expr
 * PreIncrement PostIncrement, PreDecrement, PostDecrement (UnaryNode)
 *   kid: member expr
 * New (BinaryNode)
 *   left: ctor expression on the left of the '('
 *   right: Arguments
 * DeleteName, DeleteProp, DeleteElem, DeleteExpr (UnaryNode)
 *   kid: expression that's evaluated, then the overall delete evaluates to
 *        true; can't be a kind for a more-specific ParseNodeKind::Delete*
 *        unless constant folding (or a similar parse tree manipulation) has
 *        occurred
 *          * DeleteName: Name expr
 *          * DeleteProp: Dot expr
 *          * DeleteElem: Elem expr
 *          * DeleteExpr: Member expr
 * PropertyName (NameNode)
 *   atom: property name being accessed
 * Dot (PropertyAccess)
 *   left: MEMBER expr to left of '.'
 *   right: PropertyName to right of '.'
 * Elem (PropertyByValue)
 *   left: MEMBER expr to left of '['
 *   right: expr between '[' and ']'
 * Call (BinaryNode)
 *   left: callee expression on the left of the '('
 *   right: Arguments
 * Arguments (ListNode)
 *   head: list of arg1, arg2, ... argN
 *   count: N >= 0
 * Array (ListNode)
 *   head: list of N array element expressions
 *         holes ([,,]) are represented by Elision nodes,
 *         spread elements ([...X]) are represented by Spread nodes
 *   count: N >= 0
 * Object (ListNode)
 *   head: list of N nodes, each item is one of:
 *           * MutateProto
 *           * Colon
 *           * Shorthand
 *           * Spread
 *   count: N >= 0
 * Colon (BinaryNode)
 *   key-value pair in object initializer or destructuring lhs
 *   left: property id
 *   right: value
 * Shorthand (BinaryNode)
 *   Same fields as Colon. This is used for object literal properties using
 *   shorthand ({x}).
 * ComputedName (UnaryNode)
 *   ES6 ComputedPropertyName.
 *   kid: the AssignmentExpression inside the square brackets
 * Name (NameNode)
 *   atom: name, or object atom
 *   pn_op: JSOP_GETNAME, JSOP_STRING, or JSOP_OBJECT
 *          If JSOP_GETNAME, pn_op may be JSOP_*ARG or JSOP_*VAR telling
 *          const-ness and static analysis results
 * String (NameNode)
 *   atom: string
 * TemplateStringList (ListNode)
 *   head: list of alternating expr and template strings
 *           TemplateString [, expression, TemplateString]+
 *         there's at least one expression.  If the template literal contains
 *         no ${}-delimited expression, it's parsed as a single TemplateString
 * TemplateString (NameNode)
 *   atom: template string atom
 * TaggedTemplate (BinaryNode)
 *   left: tag expression
 *   right: Arguments, with the first being the call site object, then
 *          arg1, arg2, ... argN
 * CallSiteObj (CallSiteNode)
 *   head:  an Array of raw TemplateString, then corresponding cooked
 *          TemplateString nodes
 *            Array [, cooked TemplateString]+
 *          where the Array is
 *            [raw TemplateString]+
 * RegExp (RegExpLiteral)
 *   regexp: RegExp model object
 * Number (NumericLiteral)
 *   value: double value of numeric literal
 * True, False (BooleanLiteral)
 *   pn_op: JSOp bytecode
 * Null (NullLiteral)
 *   pn_op: JSOp bytecode
 * RawUndefined (RawUndefinedLiteral)
 *   pn_op: JSOp bytecode
 *
 * This (UnaryNode)
 *   kid: '.this' Name if function `this`, else nullptr
 * SuperBase (UnaryNode)
 *   kid: '.this' Name
 * SuperCall (BinaryNode)
 *   left: SuperBase
 *   right: Arguments
 * SetThis (BinaryNode)
 *   left: '.this' Name
 *   right: SuperCall
 *
 * LexicalScope (LexicalScopeNode)
 *   scopeBindings: scope bindings
 *   scopeBody: scope body
 * Generator (NullaryNode)
 * InitialYield (UnaryNode)
 *   kid: generator object
 * Yield, YieldStar, Await (UnaryNode)
 *   kid: expr or null
 * Nop (NullaryNode)
 */
enum ParseNodeArity
{
    PN_NULLARY,                         /* 0 kids */
    PN_UNARY,                           /* one kid, plus a couple of scalars */
    PN_BINARY,                          /* two kids, plus a couple of scalars */
    PN_TERNARY,                         /* three kids */
    PN_CODE,                            /* module or function definition node */
    PN_LIST,                            /* generic singly linked list */
    PN_NAME,                            /* name, label, string */
    PN_NUMBER,                          /* numeric literal */
    PN_REGEXP,                          /* regexp literal */
    PN_LOOP,                            /* loop control (break/continue) */
    PN_SCOPE                            /* lexical scope */
};

// FIXME: Remove `*Type` (bug 1489008)
#define FOR_EACH_PARSENODE_SUBCLASS(macro) \
    macro(BinaryNode, BinaryNodeType, asBinary) \
    macro(AssignmentNode, AssignmentNodeType, asAssignment) \
    macro(CaseClause, CaseClauseType, asCaseClause) \
    macro(ClassMethod, ClassMethodType, asClassMethod) \
    macro(ClassNames, ClassNamesType, asClassNames) \
    macro(ForNode, ForNodeType, asFor) \
    macro(PropertyAccess, PropertyAccessType, asPropertyAccess) \
    macro(PropertyByValue, PropertyByValueType, asPropertyByValue) \
    macro(SwitchStatement, SwitchStatementType, asSwitchStatement) \
    \
    macro(CodeNode, CodeNodeType, asCode) \
    \
    macro(LexicalScopeNode, LexicalScopeNodeType, asLexicalScope) \
    \
    macro(ListNode, ListNodeType, asList) \
    macro(CallSiteNode, CallSiteNodeType, asCallSite) \
    \
    macro(LoopControlStatement, LoopControlStatementType, asLoopControlStatement) \
    macro(BreakStatement, BreakStatementType, asBreakStatement) \
    macro(ContinueStatement, ContinueStatementType, asContinueStatement) \
    \
    macro(NameNode, NameNodeType, asName) \
    macro(LabeledStatement, LabeledStatementType, asLabeledStatement) \
    \
    macro(NullaryNode, NullaryNodeType, asNullary) \
    macro(BooleanLiteral, BooleanLiteralType, asBooleanLiteral) \
    macro(DebuggerStatement, DebuggerStatementType, asDebuggerStatement) \
    macro(NullLiteral, NullLiteralType, asNullLiteral) \
    macro(RawUndefinedLiteral, RawUndefinedLiteralType, asRawUndefinedLiteral) \
    \
    macro(NumericLiteral, NumericLiteralType, asNumericLiteral) \
    \
    macro(RegExpLiteral, RegExpLiteralType, asRegExpLiteral) \
    \
    macro(TernaryNode, TernaryNodeType, asTernary) \
    macro(ClassNode, ClassNodeType, asClass) \
    macro(ConditionalExpression, ConditionalExpressionType, asConditionalExpression) \
    macro(TryNode, TryNodeType, asTry) \
    \
    macro(UnaryNode, UnaryNodeType, asUnary) \
    macro(ThisLiteral, ThisLiteralType, asThisLiteral)

#define DECLARE_CLASS(typeName, longTypeName, asMethodName) \
class typeName;
FOR_EACH_PARSENODE_SUBCLASS(DECLARE_CLASS)
#undef DECLARE_CLASS

class ParseNode
{
    ParseNodeKind pn_type;   /* ParseNodeKind::PNK_* type */
    // pn_op and pn_arity are not declared as the correct enum types
    // due to difficulties with MS bitfield layout rules and a GCC
    // bug.  See https://bugzilla.mozilla.org/show_bug.cgi?id=1383157#c4 for
    // details.
    uint8_t pn_op;      /* see JSOp enum and jsopcode.tbl */
    uint8_t pn_arity:4; /* see ParseNodeArity enum */
    bool pn_parens:1;   /* this expr was enclosed in parens */
    bool pn_rhs_anon_fun:1;  /* this expr is anonymous function or class that
                              * is a direct RHS of ParseNodeKind::Assign or ParseNodeKind::Colon of
                              * property, that needs SetFunctionName. */

    ParseNode(const ParseNode& other) = delete;
    void operator=(const ParseNode& other) = delete;

  public:
    ParseNode(ParseNodeKind kind, JSOp op, ParseNodeArity arity)
      : pn_type(kind),
        pn_op(op),
        pn_arity(arity),
        pn_parens(false),
        pn_rhs_anon_fun(false),
        pn_pos(0, 0),
        pn_next(nullptr)
    {
        MOZ_ASSERT(kind < ParseNodeKind::Limit);
        memset(&pn_u, 0, sizeof pn_u);
    }

    ParseNode(ParseNodeKind kind, JSOp op, ParseNodeArity arity, const TokenPos& pos)
      : pn_type(kind),
        pn_op(op),
        pn_arity(arity),
        pn_parens(false),
        pn_rhs_anon_fun(false),
        pn_pos(pos),
        pn_next(nullptr)
    {
        MOZ_ASSERT(kind < ParseNodeKind::Limit);
        memset(&pn_u, 0, sizeof pn_u);
    }

    JSOp getOp() const                     { return JSOp(pn_op); }
    void setOp(JSOp op)                    { pn_op = op; }
    bool isOp(JSOp op) const               { return getOp() == op; }

    ParseNodeKind getKind() const {
        MOZ_ASSERT(pn_type < ParseNodeKind::Limit);
        return pn_type;
    }
    void setKind(ParseNodeKind kind) {
        MOZ_ASSERT(kind < ParseNodeKind::Limit);
        pn_type = kind;
    }
    bool isKind(ParseNodeKind kind) const  { return getKind() == kind; }

    ParseNodeArity getArity() const        { return ParseNodeArity(pn_arity); }
    bool isArity(ParseNodeArity a) const   { return getArity() == a; }
    void setArity(ParseNodeArity a)        { pn_arity = a; }

    bool isBinaryOperation() const {
        ParseNodeKind kind = getKind();
        return ParseNodeKind::BinOpFirst <= kind && kind <= ParseNodeKind::BinOpLast;
    }
    inline bool isName(PropertyName* name) const;

    /* Boolean attributes. */
    bool isInParens() const                { return pn_parens; }
    bool isLikelyIIFE() const              { return isInParens(); }
    void setInParens(bool enabled)         { pn_parens = enabled; }

    bool isDirectRHSAnonFunction() const {
        return pn_rhs_anon_fun;
    }
    void setDirectRHSAnonFunction(bool enabled) {
        pn_rhs_anon_fun = enabled;
    }

    TokenPos            pn_pos;         /* two 16-bit pairs here, for 64 bits */
    ParseNode*          pn_next;        /* intrinsic link in parent PN_LIST */

    union {
        struct {                        /* list of next-linked nodes */
          private:
            friend class ListNode;
            ParseNode*  head;           /* first node in list */
            ParseNode** tail;           /* ptr to last node's pn_next in list */
            uint32_t    count;          /* number of nodes in list */
            uint32_t    xflags;         /* see ListNode class */
        } list;
        struct {                        /* ternary: if, for(;;), ?: */
          private:
            friend class TernaryNode;
            ParseNode*  kid1;           /* condition, discriminant, etc. */
            ParseNode*  kid2;           /* then-part, case list, etc. */
            ParseNode*  kid3;           /* else-part, default case, etc. */
        } ternary;
        struct {                        /* two kids if binary */
          private:
            friend class BinaryNode;
            friend class ForNode;
            friend class ClassMethod;
            friend class PropertyAccess;
            friend class SwitchStatement;
            ParseNode*  left;
            ParseNode*  right;
            union {
                unsigned iflags;        /* JSITER_* flags for ParseNodeKind::For node */
                bool isStatic;          /* only for ParseNodeKind::ClassMethod */
                bool hasDefault;        /* only for ParseNodeKind::Switch */
            };
        } binary;
        struct {                        /* one kid if unary */
          private:
            friend class UnaryNode;
            ParseNode*  kid;
            bool        prologue;       /* directive prologue member */
        } unary;
        struct {                        /* name, labeled statement, etc. */
          private:
            friend class NameNode;
            JSAtom*      atom;          /* lexical name or label atom */
            ParseNode*  initOrStmt;     /* var initializer, argument default,
                                         * or label statement target */
        } name;
        struct {
          private:
            friend class RegExpLiteral;
            ObjectBox* objbox;
        } regexp;
        struct {
          private:
            friend class CodeNode;
            FunctionBox* funbox;        /* function object */
            ParseNode*  body;           /* module or function body */
        } code;
        struct {
          private:
            friend class LexicalScopeNode;
            LexicalScope::Data* bindings;
            ParseNode*          body;
        } scope;
        struct {
          private:
            friend class NumericLiteral;
            double       value;         /* aligned numeric literal value */
            DecimalPoint decimalPoint;  /* Whether the number has a decimal point */
        } number;
        class {
          private:
            friend class LoopControlStatement;
            PropertyName*    label;    /* target of break/continue statement */
        } loopControl;
    } pn_u;

  public:
    /*
     * If |left| is a list of the given kind/left-associative op, append
     * |right| to it and return |left|.  Otherwise return a [left, right] list.
     */
    static ParseNode*
    appendOrCreateList(ParseNodeKind kind, ParseNode* left, ParseNode* right,
                       FullParseHandler* handler, ParseContext* pc);

    /* True if pn is a parsenode representing a literal constant. */
    bool isLiteral() const {
        return isKind(ParseNodeKind::Number) ||
               isKind(ParseNodeKind::String) ||
               isKind(ParseNodeKind::True) ||
               isKind(ParseNodeKind::False) ||
               isKind(ParseNodeKind::Null) ||
               isKind(ParseNodeKind::RawUndefined);
    }

    // True iff this is a for-in/of loop variable declaration (var/let/const).
    inline bool isForLoopDeclaration() const;

    enum AllowConstantObjects {
        DontAllowObjects = 0,
        AllowObjects,
        ForCopyOnWriteArray
    };

    MOZ_MUST_USE bool getConstantValue(JSContext* cx, AllowConstantObjects allowObjects,
                                       MutableHandleValue vp, Value* compare = nullptr,
                                       size_t ncompare = 0, NewObjectKind newKind = TenuredObject);
    inline bool isConstant();

    template <class NodeType>
    inline bool is() const {
        return NodeType::test(*this);
    }

    /* Casting operations. */
    template <class NodeType>
    inline NodeType& as() {
        MOZ_ASSERT(NodeType::test(*this));
        return *static_cast<NodeType*>(this);
    }

    template <class NodeType>
    inline const NodeType& as() const {
        MOZ_ASSERT(NodeType::test(*this));
        return *static_cast<const NodeType*>(this);
    }

#ifdef DEBUG
    // Debugger-friendly stderr printer.
    void dump();
    void dump(GenericPrinter& out);
    void dump(GenericPrinter& out, int indent);
#endif
};

class NullaryNode : public ParseNode
{
  public:
    NullaryNode(ParseNodeKind kind, const TokenPos& pos)
      : ParseNode(kind, JSOP_NOP, PN_NULLARY, pos) {}

    NullaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos)
      : ParseNode(kind, op, PN_NULLARY, pos) {}

    static bool test(const ParseNode& node) {
        return node.isArity(PN_NULLARY);
    }

#ifdef DEBUG
    void dump(GenericPrinter& out);
#endif
};

class NameNode : public ParseNode
{
  protected:
    NameNode(ParseNodeKind kind, JSOp op, JSAtom* atom, ParseNode* initOrStmt, const TokenPos& pos)
      : ParseNode(kind, op, PN_NAME, pos)
    {
        pn_u.name.atom = atom;
        pn_u.name.initOrStmt = initOrStmt;
    }

  public:
    NameNode(ParseNodeKind kind, JSOp op, JSAtom* atom, const TokenPos& pos)
      : ParseNode(kind, op, PN_NAME, pos)
    {
        pn_u.name.atom = atom;
        pn_u.name.initOrStmt = nullptr;
    }

    static bool test(const ParseNode& node) {
        return node.isArity(PN_NAME);
    }

#ifdef DEBUG
    void dump(GenericPrinter& out, int indent);
#endif

    JSAtom* atom() const {
        return pn_u.name.atom;
    }

    PropertyName* name() const {
        MOZ_ASSERT(isKind(ParseNodeKind::Name));
        return atom()->asPropertyName();
    }

    ParseNode* initializer() const {
        return pn_u.name.initOrStmt;
    }

    void setAtom(JSAtom* atom) {
        pn_u.name.atom = atom;
    }

    void setInitializer(ParseNode* init) {
        pn_u.name.initOrStmt = init;
    }

    // Methods used by FoldConstants.cpp.
    ParseNode** unsafeInitializerReference() {
        return &pn_u.name.initOrStmt;
    }
};

inline bool
ParseNode::isName(PropertyName* name) const
{
    return getKind() == ParseNodeKind::Name && as<NameNode>().name() == name;
}

class UnaryNode : public ParseNode
{
  public:
    UnaryNode(ParseNodeKind kind, const TokenPos& pos, ParseNode* kid)
      : ParseNode(kind, JSOP_NOP, PN_UNARY, pos)
    {
        pn_u.unary.kid = kid;
    }

    static bool test(const ParseNode& node) {
        return node.isArity(PN_UNARY);
    }

#ifdef DEBUG
    void dump(GenericPrinter& out, int indent);
#endif

    ParseNode* kid() const {
        return pn_u.unary.kid;
    }

    /* Return true if this node appears in a Directive Prologue. */
    bool isDirectivePrologueMember() const {
        return pn_u.unary.prologue;
    }

    void setIsDirectivePrologueMember() {
        pn_u.unary.prologue = true;
    }

    /*
     * Non-null if this is a statement node which could be a member of a
     * Directive Prologue: an expression statement consisting of a single
     * string literal.
     *
     * This considers only the node and its children, not its context. After
     * parsing, check the node's prologue flag to see if it is indeed part of
     * a directive prologue.
     *
     * Note that a Directive Prologue can contain statements that cannot
     * themselves be directives (string literals that include escape sequences
     * or escaped newlines, say). This member function returns true for such
     * nodes; we use it to determine the extent of the prologue.
     */
    JSAtom* isStringExprStatement() const {
        if (isKind(ParseNodeKind::ExpressionStatement)) {
            if (kid()->isKind(ParseNodeKind::String) && !kid()->isInParens()) {
                return kid()->as<NameNode>().atom();
            }
        }
        return nullptr;
    }

    // Methods used by FoldConstants.cpp.
    ParseNode** unsafeKidReference() {
        return &pn_u.unary.kid;
    }
};

class BinaryNode : public ParseNode
{
  public:
    BinaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos, ParseNode* left, ParseNode* right)
      : ParseNode(kind, op, PN_BINARY, pos)
    {
        pn_u.binary.left = left;
        pn_u.binary.right = right;
    }

    BinaryNode(ParseNodeKind kind, JSOp op, ParseNode* left, ParseNode* right)
      : ParseNode(kind, op, PN_BINARY, TokenPos::box(left->pn_pos, right->pn_pos))
    {
        pn_u.binary.left = left;
        pn_u.binary.right = right;
    }

    static bool test(const ParseNode& node) {
        return node.isArity(PN_BINARY);
    }

#ifdef DEBUG
    void dump(GenericPrinter& out, int indent);
#endif

    ParseNode* left() const {
        return pn_u.binary.left;
    }

    ParseNode* right() const {
        return pn_u.binary.right;
    }

    // Methods used by FoldConstants.cpp.
    // caller are responsible for keeping the list consistent.
    ParseNode** unsafeLeftReference() {
        return &pn_u.binary.left;
    }

    ParseNode** unsafeRightReference() {
        return &pn_u.binary.right;
    }
};

class AssignmentNode : public BinaryNode
{
  public:
    AssignmentNode(ParseNodeKind kind, JSOp op, ParseNode* left, ParseNode* right)
      : BinaryNode(kind, op, TokenPos(left->pn_pos.begin, right->pn_pos.end), left, right)
    {}

    static bool test(const ParseNode& node) {
        ParseNodeKind kind = node.getKind();
        bool match = ParseNodeKind::AssignmentStart <= kind &&
                     kind <= ParseNodeKind::AssignmentLast;
        MOZ_ASSERT_IF(match, node.is<BinaryNode>());
        return match;
    }
};

class ForNode : public BinaryNode
{
  public:
    ForNode(const TokenPos& pos, ParseNode* forHead, ParseNode* body, unsigned iflags)
      : BinaryNode(ParseNodeKind::For,
                   forHead->isKind(ParseNodeKind::ForIn) ? JSOP_ITER : JSOP_NOP,
                   pos, forHead, body)
    {
        MOZ_ASSERT(forHead->isKind(ParseNodeKind::ForIn) ||
                   forHead->isKind(ParseNodeKind::ForOf) ||
                   forHead->isKind(ParseNodeKind::ForHead));
        pn_u.binary.iflags = iflags;
    }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::For);
        MOZ_ASSERT_IF(match, node.is<BinaryNode>());
        return match;
    }

    TernaryNode* head() const {
        return &left()->as<TernaryNode>();
    }

    ParseNode* body() const {
        return right();
    }

    unsigned iflags() const {
        return pn_u.binary.iflags;
    }
};

class TernaryNode : public ParseNode
{
  public:
    TernaryNode(ParseNodeKind kind, ParseNode* kid1, ParseNode* kid2, ParseNode* kid3)
      : TernaryNode(kind, kid1, kid2, kid3,
                    TokenPos((kid1 ? kid1 : kid2 ? kid2 : kid3)->pn_pos.begin,
                             (kid3 ? kid3 : kid2 ? kid2 : kid1)->pn_pos.end))
    {}

    TernaryNode(ParseNodeKind kind, ParseNode* kid1, ParseNode* kid2, ParseNode* kid3,
                const TokenPos& pos)
      : ParseNode(kind, JSOP_NOP, PN_TERNARY, pos)
    {
        pn_u.ternary.kid1 = kid1;
        pn_u.ternary.kid2 = kid2;
        pn_u.ternary.kid3 = kid3;
    }

    static bool test(const ParseNode& node) {
        return node.isArity(PN_TERNARY);
    }

#ifdef DEBUG
    void dump(GenericPrinter& out, int indent);
#endif

    ParseNode* kid1() const {
        return pn_u.ternary.kid1;
    }

    ParseNode* kid2() const {
        return pn_u.ternary.kid2;
    }

    ParseNode* kid3() const {
        return pn_u.ternary.kid3;
    }

    // Methods used by FoldConstants.cpp.
    ParseNode** unsafeKid1Reference() {
        return &pn_u.ternary.kid1;
    }

    ParseNode** unsafeKid2Reference() {
        return &pn_u.ternary.kid2;
    }

    ParseNode** unsafeKid3Reference() {
        return &pn_u.ternary.kid3;
    }
};

class ListNode : public ParseNode
{
  private:
    // xflags bits.

    // Statement list has top-level function statements.
    static constexpr uint32_t hasTopLevelFunctionDeclarationsBit = 0x01;

    // One or more of
    //   * array has holes
    //   * array has spread node
    static constexpr uint32_t hasArrayHoleOrSpreadBit = 0x02;

    // Array/Object/Class initializer has non-constants.
    //   * array has holes
    //   * array has spread node
    //   * array has element which is known not to be constant
    //   * array has no element
    //   * object/class has __proto__
    //   * object/class has property which is known not to be constant
    //   * object/class shorthand property
    //   * object/class spread property
    //   * object/class has method
    //   * object/class has computed property
    static constexpr uint32_t hasNonConstInitializerBit = 0x04;

    void checkConsistency() const
#ifndef DEBUG
    {}
#endif
    ;

  public:
    ListNode(ParseNodeKind kind, const TokenPos& pos)
      : ParseNode(kind, JSOP_NOP, PN_LIST, pos)
    {
        makeEmpty();
    }

    ListNode(ParseNodeKind kind, JSOp op, const TokenPos& pos)
      : ParseNode(kind, op, PN_LIST, pos)
    {
        makeEmpty();
    }

    ListNode(ParseNodeKind kind, JSOp op, ParseNode* kid)
      : ParseNode(kind, op, PN_LIST, kid->pn_pos)
    {
        if (kid->pn_pos.begin < pn_pos.begin) {
            pn_pos.begin = kid->pn_pos.begin;
        }
        pn_pos.end = kid->pn_pos.end;

        pn_u.list.head = kid;
        pn_u.list.tail = &kid->pn_next;
        pn_u.list.count = 1;
        pn_u.list.xflags = 0;
    }

    static bool test(const ParseNode& node) {
        return node.isArity(PN_LIST);
    }

#ifdef DEBUG
    void dump(GenericPrinter& out, int indent);
#endif

    ParseNode* head() const {
        return pn_u.list.head;
    }

    ParseNode** tail() const {
        return pn_u.list.tail;
    }

    uint32_t count() const {
        return pn_u.list.count;
    }

    bool empty() const {
        return count() == 0;
    }

    MOZ_MUST_USE bool hasTopLevelFunctionDeclarations() const {
        MOZ_ASSERT(isKind(ParseNodeKind::StatementList));
        return pn_u.list.xflags & hasTopLevelFunctionDeclarationsBit;
    }

    MOZ_MUST_USE bool hasArrayHoleOrSpread() const {
        MOZ_ASSERT(isKind(ParseNodeKind::Array));
        return pn_u.list.xflags & hasArrayHoleOrSpreadBit;
    }

    MOZ_MUST_USE bool hasNonConstInitializer() const {
        MOZ_ASSERT(isKind(ParseNodeKind::Array) ||
                   isKind(ParseNodeKind::Object) ||
                   isKind(ParseNodeKind::ClassMethodList));
        return pn_u.list.xflags & hasNonConstInitializerBit;
    }

    void setHasTopLevelFunctionDeclarations() {
        MOZ_ASSERT(isKind(ParseNodeKind::StatementList));
        pn_u.list.xflags |= hasTopLevelFunctionDeclarationsBit;
    }

    void setHasArrayHoleOrSpread() {
        MOZ_ASSERT(isKind(ParseNodeKind::Array));
        pn_u.list.xflags |= hasArrayHoleOrSpreadBit;
    }

    void setHasNonConstInitializer() {
        MOZ_ASSERT(isKind(ParseNodeKind::Array) ||
                   isKind(ParseNodeKind::Object) ||
                   isKind(ParseNodeKind::ClassMethodList));
        pn_u.list.xflags |= hasNonConstInitializerBit;
    }

    /*
     * Compute a pointer to the last element in a singly-linked list. NB: list
     * must be non-empty -- this is asserted!
     */
    ParseNode* last() const {
        MOZ_ASSERT(!empty());
        //
        // ParseNode                      ParseNode
        // +-----+---------+-----+        +-----+---------+-----+
        // | ... | pn_next | ... | +-...->| ... | pn_next | ... |
        // +-----+---------+-----+ |      +-----+---------+-----+
        // ^       |               |      ^     ^
        // |       +---------------+      |     |
        // |                              |     tail()
        // |                              |
        // head()                         last()
        //
        return (ParseNode*)(uintptr_t(tail()) - offsetof(ParseNode, pn_next));
    }

    void replaceLast(ParseNode* node) {
        MOZ_ASSERT(!empty());
        pn_pos.end = node->pn_pos.end;

        ParseNode* item = head();
        ParseNode* lastNode = last();
        MOZ_ASSERT(item);
        if (item == lastNode) {
            pn_u.list.head = node;
        } else {
            while (item->pn_next != lastNode) {
                MOZ_ASSERT(item->pn_next);
                item = item->pn_next;
            }
            item->pn_next = node;
        }
        pn_u.list.tail = &node->pn_next;
    }

    void makeEmpty() {
        pn_u.list.head = nullptr;
        pn_u.list.tail = &pn_u.list.head;
        pn_u.list.count = 0;
        pn_u.list.xflags = 0;
    }

    void append(ParseNode* item) {
        MOZ_ASSERT(item->pn_pos.begin >= pn_pos.begin);
        appendWithoutOrderAssumption(item);
    }

    void appendWithoutOrderAssumption(ParseNode* item) {
        pn_pos.end = item->pn_pos.end;
        *pn_u.list.tail = item;
        pn_u.list.tail = &item->pn_next;
        pn_u.list.count++;
    }

    void prepend(ParseNode* item) {
        item->pn_next = pn_u.list.head;
        pn_u.list.head = item;
        if (pn_u.list.tail == &pn_u.list.head) {
            pn_u.list.tail = &item->pn_next;
        }
        pn_u.list.count++;
    }

    // Methods used by FoldConstants.cpp.
    // Caller is responsible for keeping the list consistent.
    ParseNode** unsafeHeadReference() {
        return &pn_u.list.head;
    }

    void unsafeReplaceTail(ParseNode** newTail) {
        pn_u.list.tail = newTail;
        checkConsistency();
    }

    void unsafeDecrementCount() {
        MOZ_ASSERT(count() > 1);
        pn_u.list.count--;
    }

  private:
    // Classes to iterate over ListNode contents:
    //
    // Usage:
    //   ListNode* list;
    //   for (ParseNode* item : list->contents()) {
    //     // item is ParseNode* typed.
    //   }
    class iterator
    {
      private:
        ParseNode* node_;

        friend class ListNode;
        explicit iterator(ParseNode* node)
          : node_(node)
        {}

      public:
        bool operator==(const iterator& other) const {
            return node_ == other.node_;
        }

        bool operator!=(const iterator& other) const {
            return !(*this == other);
        }

        iterator& operator++() {
            node_ = node_->pn_next;
            return *this;
        }

        ParseNode* operator*() {
            return node_;
        }

        const ParseNode* operator*() const {
            return node_;
        }
    };

    class range
    {
      private:
        ParseNode* begin_;
        ParseNode* end_;

        friend class ListNode;
        range(ParseNode* begin, ParseNode* end)
          : begin_(begin),
            end_(end)
        {}

      public:
        iterator begin() {
            return iterator(begin_);
        }

        iterator end() {
            return iterator(end_);
        }

        const iterator begin() const {
            return iterator(begin_);
        }

        const iterator end() const {
            return iterator(end_);
        }

        const iterator cbegin() const {
            return begin();
        }

        const iterator cend() const {
            return end();
        }
    };

#ifdef DEBUG
  MOZ_MUST_USE bool contains(ParseNode* target) const {
      MOZ_ASSERT(target);
      for (ParseNode* node : contents()) {
          if (target == node) {
              return true;
          }
      }
      return false;
  }
#endif

  public:
    range contents() {
        return range(head(), nullptr);
    }

    const range contents() const {
        return range(head(), nullptr);
    }

    range contentsFrom(ParseNode* begin) {
        MOZ_ASSERT_IF(begin, contains(begin));
        return range(begin, nullptr);
    }

    const range contentsFrom(ParseNode* begin) const {
        MOZ_ASSERT_IF(begin, contains(begin));
        return range(begin, nullptr);
    }

    range contentsTo(ParseNode* end) {
        MOZ_ASSERT_IF(end, contains(end));
        return range(head(), end);
    }

    const range contentsTo(ParseNode* end) const {
        MOZ_ASSERT_IF(end, contains(end));
        return range(head(), end);
    }
};

inline bool
ParseNode::isForLoopDeclaration() const
{
    if (isKind(ParseNodeKind::Var) || isKind(ParseNodeKind::Let) || isKind(ParseNodeKind::Const)) {
        MOZ_ASSERT(!as<ListNode>().empty());
        return true;
    }

    return false;
}

class CodeNode : public ParseNode
{
  public:
    CodeNode(ParseNodeKind kind, JSOp op, const TokenPos& pos)
      : ParseNode(kind, op, PN_CODE, pos)
    {
        MOZ_ASSERT(kind == ParseNodeKind::Function || kind == ParseNodeKind::Module);
        MOZ_ASSERT_IF(kind == ParseNodeKind::Module, op == JSOP_NOP);
        MOZ_ASSERT(op == JSOP_NOP || // statement, module
                   op == JSOP_LAMBDA_ARROW || // arrow function
                   op == JSOP_LAMBDA); // expression, method, accessor, &c.
        MOZ_ASSERT(!pn_u.code.body);
        MOZ_ASSERT(!pn_u.code.funbox);
    }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Function) || node.isKind(ParseNodeKind::Module);
        MOZ_ASSERT_IF(match, node.isArity(PN_CODE));
        return match;
    }

#ifdef DEBUG
  void dump(GenericPrinter& out, int indent);
#endif

    FunctionBox* funbox() const {
        return pn_u.code.funbox;
    }

    ListNode* body() const {
        return pn_u.code.body ? &pn_u.code.body->as<ListNode>() : nullptr;
    }

    void setFunbox(FunctionBox* funbox) {
        pn_u.code.funbox = funbox;
    }

    void setBody(ListNode* body) {
        pn_u.code.body = body;
    }

    // Methods used by FoldConstants.cpp.
    ParseNode** unsafeBodyReference() {
        return &pn_u.code.body;
    }

    bool functionIsHoisted() const {
        MOZ_ASSERT(isKind(ParseNodeKind::Function));
        MOZ_ASSERT(isOp(JSOP_LAMBDA) ||        // lambda
                   isOp(JSOP_LAMBDA_ARROW) ||  // arrow function
                   isOp(JSOP_DEFFUN) ||        // non-body-level function statement
                   isOp(JSOP_NOP) ||           // body-level function stmt in global code
                   isOp(JSOP_GETLOCAL) ||      // body-level function stmt in function code
                   isOp(JSOP_GETARG) ||        // body-level function redeclaring formal
                   isOp(JSOP_INITLEXICAL));    // block-level function stmt
        return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) && !isOp(JSOP_DEFFUN);
    }
};

class NumericLiteral : public ParseNode
{
  public:
    NumericLiteral(double value, DecimalPoint decimalPoint, const TokenPos& pos)
      : ParseNode(ParseNodeKind::Number, JSOP_NOP, PN_NUMBER, pos)
    {
        pn_u.number.value = value;
        pn_u.number.decimalPoint = decimalPoint;
    }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Number);
        MOZ_ASSERT_IF(match, node.isArity(PN_NUMBER));
        return match;
    }

#ifdef DEBUG
    void dump(GenericPrinter& out, int indent);
#endif

    double value() const {
        return pn_u.number.value;
    }

    DecimalPoint decimalPoint() const {
        return pn_u.number.decimalPoint;
    }

    void setValue(double v) {
        pn_u.number.value = v;
    }
};

class LexicalScopeNode : public ParseNode
{
  public:
    LexicalScopeNode(LexicalScope::Data* bindings, ParseNode* body)
      : ParseNode(ParseNodeKind::LexicalScope, JSOP_NOP, PN_SCOPE, body->pn_pos)
    {
        pn_u.scope.bindings = bindings;
        pn_u.scope.body = body;
    }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::LexicalScope);
        MOZ_ASSERT_IF(match, node.isArity(PN_SCOPE));
        return match;
    }

#ifdef DEBUG
    void dump(GenericPrinter& out, int indent);
#endif

    Handle<LexicalScope::Data*> scopeBindings() const {
        MOZ_ASSERT(!isEmptyScope());
        // Bindings' GC safety depend on the presence of an AutoKeepAtoms that
        // the rest of the frontend also depends on.
        return Handle<LexicalScope::Data*>::fromMarkedLocation(&pn_u.scope.bindings);
    }

    ParseNode* scopeBody() const {
        return pn_u.scope.body;
    }

    void setScopeBody(ParseNode* body) {
        pn_u.scope.body = body;
    }

    bool isEmptyScope() const {
        return !pn_u.scope.bindings;
    }

    ParseNode** unsafeScopeBodyReference() {
        return &pn_u.scope.body;
    }
};

class LabeledStatement : public NameNode
{
  public:
    LabeledStatement(PropertyName* label, ParseNode* stmt, uint32_t begin)
      : NameNode(ParseNodeKind::Label, JSOP_NOP, label, stmt, TokenPos(begin, stmt->pn_pos.end))
    {}

    PropertyName* label() const {
        return atom()->asPropertyName();
    }

    ParseNode* statement() const {
        return initializer();
    }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Label);
        MOZ_ASSERT_IF(match, node.isArity(PN_NAME));
        MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP));
        return match;
    }

    // Methods used by FoldConstants.cpp.
    ParseNode** unsafeStatementReference() {
        return unsafeInitializerReference();
    }
};

// Inside a switch statement, a CaseClause is a case-label and the subsequent
// statements. The same node type is used for DefaultClauses. The only
// difference is that their caseExpression() is null.
class CaseClause : public BinaryNode
{
  public:
    CaseClause(ParseNode* expr, ParseNode* stmts, uint32_t begin)
      : BinaryNode(ParseNodeKind::Case, JSOP_NOP, TokenPos(begin, stmts->pn_pos.end), expr, stmts)
    {}

    ParseNode* caseExpression() const {
        return left();
    }

    bool isDefault() const {
        return !caseExpression();
    }

    ListNode* statementList() const {
        return &right()->as<ListNode>();
    }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Case);
        MOZ_ASSERT_IF(match, node.is<BinaryNode>());
        MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP));
        return match;
    }
};

class LoopControlStatement : public ParseNode
{
  protected:
    LoopControlStatement(ParseNodeKind kind, PropertyName* label, const TokenPos& pos)
      : ParseNode(kind, JSOP_NOP, PN_LOOP, pos)
    {
        MOZ_ASSERT(kind == ParseNodeKind::Break || kind == ParseNodeKind::Continue);
        pn_u.loopControl.label = label;
    }

  public:
    /* Label associated with this break/continue statement, if any. */
    PropertyName* label() const {
        return pn_u.loopControl.label;
    }

#ifdef DEBUG
    void dump(GenericPrinter& out, int indent);
#endif

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Break) || node.isKind(ParseNodeKind::Continue);
        MOZ_ASSERT_IF(match, node.isArity(PN_LOOP));
        MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP));
        return match;
    }
};

class BreakStatement : public LoopControlStatement
{
  public:
    BreakStatement(PropertyName* label, const TokenPos& pos)
      : LoopControlStatement(ParseNodeKind::Break, label, pos)
    { }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Break);
        MOZ_ASSERT_IF(match, node.is<LoopControlStatement>());
        MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP));
        return match;
    }
};

class ContinueStatement : public LoopControlStatement
{
  public:
    ContinueStatement(PropertyName* label, const TokenPos& pos)
      : LoopControlStatement(ParseNodeKind::Continue, label, pos)
    { }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Continue);
        MOZ_ASSERT_IF(match, node.is<LoopControlStatement>());
        MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP));
        return match;
    }
};

class DebuggerStatement : public NullaryNode
{
  public:
    explicit DebuggerStatement(const TokenPos& pos)
      : NullaryNode(ParseNodeKind::Debugger, JSOP_NOP, pos)
    { }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Debugger);
        MOZ_ASSERT_IF(match, node.is<NullaryNode>());
        return match;
    }
};

class ConditionalExpression : public TernaryNode
{
  public:
    ConditionalExpression(ParseNode* condition, ParseNode* thenExpr, ParseNode* elseExpr)
      : TernaryNode(ParseNodeKind::Conditional, condition, thenExpr, elseExpr,
                    TokenPos(condition->pn_pos.begin, elseExpr->pn_pos.end))
    {
        MOZ_ASSERT(condition);
        MOZ_ASSERT(thenExpr);
        MOZ_ASSERT(elseExpr);
    }

    ParseNode& condition() const {
        return *kid1();
    }

    ParseNode& thenExpression() const {
        return *kid2();
    }

    ParseNode& elseExpression() const {
        return *kid3();
    }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Conditional);
        MOZ_ASSERT_IF(match, node.is<TernaryNode>());
        MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP));
        return match;
    }
};

class TryNode : public TernaryNode
{
  public:
    TryNode(uint32_t begin, ParseNode* body, LexicalScopeNode* catchScope,
            ParseNode* finallyBlock)
      : TernaryNode(ParseNodeKind::Try, body, catchScope, finallyBlock,
                    TokenPos(begin, (finallyBlock ? finallyBlock : catchScope)->pn_pos.end))
    {
        MOZ_ASSERT(body);
        MOZ_ASSERT(catchScope || finallyBlock);
    }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Try);
        MOZ_ASSERT_IF(match, node.is<TernaryNode>());
        return match;
    }

    ParseNode* body() const {
        return kid1();
    }

    LexicalScopeNode* catchScope() const {
        return kid2() ? &kid2()->as<LexicalScopeNode>() : nullptr;
    }

    ParseNode* finallyBlock() const {
        return kid3();
    }
};

class ThisLiteral : public UnaryNode
{
  public:
    ThisLiteral(const TokenPos& pos, ParseNode* thisName)
      : UnaryNode(ParseNodeKind::This, pos, thisName)
    { }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::This);
        MOZ_ASSERT_IF(match, node.is<UnaryNode>());
        return match;
    }
};

class NullLiteral : public NullaryNode
{
  public:
    explicit NullLiteral(const TokenPos& pos)
      : NullaryNode(ParseNodeKind::Null, JSOP_NULL, pos)
    { }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Null);
        MOZ_ASSERT_IF(match, node.is<NullaryNode>());
        return match;
    }
};

// This is only used internally, currently just for tagged templates.
// It represents the value 'undefined' (aka `void 0`), like NullLiteral
// represents the value 'null'.
class RawUndefinedLiteral : public NullaryNode
{
  public:
    explicit RawUndefinedLiteral(const TokenPos& pos)
      : NullaryNode(ParseNodeKind::RawUndefined, JSOP_UNDEFINED, pos) { }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::RawUndefined);
        MOZ_ASSERT_IF(match, node.is<NullaryNode>());
        return match;
    }
};

class BooleanLiteral : public NullaryNode
{
  public:
    BooleanLiteral(bool b, const TokenPos& pos)
      : NullaryNode(b ? ParseNodeKind::True : ParseNodeKind::False,
                    b ? JSOP_TRUE : JSOP_FALSE, pos)
    { }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::True) || node.isKind(ParseNodeKind::False);
        MOZ_ASSERT_IF(match, node.is<NullaryNode>());
        return match;
    }
};

class RegExpLiteral : public ParseNode
{
  public:
    RegExpLiteral(ObjectBox* reobj, const TokenPos& pos)
      : ParseNode(ParseNodeKind::RegExp, JSOP_REGEXP, PN_REGEXP, pos)
    {
        pn_u.regexp.objbox = reobj;
    }

    ObjectBox* objbox() const {
        return pn_u.regexp.objbox;
    }

#ifdef DEBUG
    void dump(GenericPrinter& out, int indent);
#endif

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::RegExp);
        MOZ_ASSERT_IF(match, node.isArity(PN_REGEXP));
        MOZ_ASSERT_IF(match, node.isOp(JSOP_REGEXP));
        return match;
    }
};

class PropertyAccess : public BinaryNode
{
  public:
    /*
     * PropertyAccess nodes can have any expression/'super' as left-hand
     * side, but the name must be a ParseNodeKind::PropertyName node.
     */
    PropertyAccess(ParseNode* lhs, NameNode* name, uint32_t begin, uint32_t end)
      : BinaryNode(ParseNodeKind::Dot, JSOP_NOP, TokenPos(begin, end), lhs, name)
    {
        MOZ_ASSERT(lhs);
        MOZ_ASSERT(name);
    }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Dot);
        MOZ_ASSERT_IF(match, node.is<BinaryNode>());
        MOZ_ASSERT_IF(match, node.as<BinaryNode>().right()->isKind(ParseNodeKind::PropertyName));
        return match;
    }

    ParseNode& expression() const {
        return *left();
    }

    NameNode& key() const {
        return right()->as<NameNode>();
    }

    // Method used by BytecodeEmitter::emitPropLHS for optimization.
    // Those methods allow expression to temporarily be nullptr for
    // optimization purpose.
    ParseNode* maybeExpression() const {
        return left();
    }

    void setExpression(ParseNode* pn) {
        pn_u.binary.left = pn;
    }

    PropertyName& name() const {
        return *right()->as<NameNode>().atom()->asPropertyName();
    }

    bool isSuper() const {
        // ParseNodeKind::SuperBase cannot result from any expression syntax.
        return expression().isKind(ParseNodeKind::SuperBase);
    }
};

class PropertyByValue : public BinaryNode
{
  public:
    PropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin, uint32_t end)
      : BinaryNode(ParseNodeKind::Elem, JSOP_NOP, TokenPos(begin, end), lhs, propExpr)
    {}

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Elem);
        MOZ_ASSERT_IF(match, node.is<BinaryNode>());
        return match;
    }

    ParseNode& expression() const {
        return *left();
    }

    ParseNode& key() const {
        return *right();
    }

    bool isSuper() const {
        return left()->isKind(ParseNodeKind::SuperBase);
    }
};

/*
 * A CallSiteNode represents the implicit call site object argument in a TaggedTemplate.
 */
class CallSiteNode : public ListNode
{
  public:
    explicit CallSiteNode(uint32_t begin): ListNode(ParseNodeKind::CallSiteObj, TokenPos(begin, begin + 1)) {}

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::CallSiteObj);
        MOZ_ASSERT_IF(match, node.is<ListNode>());
        return match;
    }

    MOZ_MUST_USE bool getRawArrayValue(JSContext* cx, MutableHandleValue vp) {
        return head()->getConstantValue(cx, AllowObjects, vp);
    }

    ListNode* rawNodes() const {
        MOZ_ASSERT(head());
        return &head()->as<ListNode>();
    }
};

class ClassMethod : public BinaryNode
{
  public:
    /*
     * Method definitions often keep a name and function body that overlap,
     * so explicitly define the beginning and end here.
     */
    ClassMethod(ParseNode* name, ParseNode* body, JSOp op, bool isStatic)
      : BinaryNode(ParseNodeKind::ClassMethod, op, TokenPos(name->pn_pos.begin, body->pn_pos.end), name, body)
    {
        pn_u.binary.isStatic = isStatic;
    }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::ClassMethod);
        MOZ_ASSERT_IF(match, node.is<BinaryNode>());
        return match;
    }

    ParseNode& name() const {
        return *left();
    }

    CodeNode& method() const {
        return right()->as<CodeNode>();
    }

    bool isStatic() const {
        return pn_u.binary.isStatic;
    }
};

class SwitchStatement : public BinaryNode
{
  public:
    SwitchStatement(uint32_t begin, ParseNode* discriminant, LexicalScopeNode* lexicalForCaseList,
                    bool hasDefault)
      : BinaryNode(ParseNodeKind::Switch, JSOP_NOP,
                   TokenPos(begin, lexicalForCaseList->pn_pos.end),
                   discriminant, lexicalForCaseList)
    {
#ifdef DEBUG
        ListNode* cases = &lexicalForCaseList->scopeBody()->as<ListNode>();
        MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList));
        bool found = false;
        for (ParseNode* item : cases->contents()) {
            CaseClause* caseNode = &item->as<CaseClause>();
            if (caseNode->isDefault()) {
                found = true;
                break;
            }
        }
        MOZ_ASSERT(found == hasDefault);
#endif

        pn_u.binary.hasDefault = hasDefault;
    }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Switch);
        MOZ_ASSERT_IF(match, node.is<BinaryNode>());
        return match;
    }

    ParseNode& discriminant() const {
        return *left();
    }

    LexicalScopeNode& lexicalForCaseList() const {
        return right()->as<LexicalScopeNode>();
    }

    bool hasDefault() const {
        return pn_u.binary.hasDefault;
    }
};

class ClassNames : public BinaryNode
{
  public:
    ClassNames(ParseNode* outerBinding, ParseNode* innerBinding, const TokenPos& pos)
      : BinaryNode(ParseNodeKind::ClassNames, JSOP_NOP, pos, outerBinding, innerBinding)
    {
        MOZ_ASSERT_IF(outerBinding, outerBinding->isKind(ParseNodeKind::Name));
        MOZ_ASSERT(innerBinding->isKind(ParseNodeKind::Name));
        MOZ_ASSERT_IF(outerBinding,
                      innerBinding->as<NameNode>().atom() == outerBinding->as<NameNode>().atom());
    }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::ClassNames);
        MOZ_ASSERT_IF(match, node.is<BinaryNode>());
        return match;
    }

    /*
     * Classes require two definitions: The first "outer" binding binds the
     * class into the scope in which it was declared. the outer binding is a
     * mutable lexial binding. The second "inner" binding binds the class by
     * name inside a block in which the methods are evaulated. It is immutable,
     * giving the methods access to the static members of the class even if
     * the outer binding has been overwritten.
     */
    NameNode* outerBinding() const {
        if (ParseNode* binding = left()) {
            return &binding->as<NameNode>();
        }
        return nullptr;
    }

    NameNode* innerBinding() const {
        return &right()->as<NameNode>();
    }
};

class ClassNode : public TernaryNode
{
  public:
    ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* methodsOrBlock,
              const TokenPos& pos)
      : TernaryNode(ParseNodeKind::Class, names, heritage, methodsOrBlock, pos)
    {
        MOZ_ASSERT_IF(names, names->is<ClassNames>());
        MOZ_ASSERT(methodsOrBlock->is<LexicalScopeNode>() ||
                   methodsOrBlock->isKind(ParseNodeKind::ClassMethodList));
    }

    static bool test(const ParseNode& node) {
        bool match = node.isKind(ParseNodeKind::Class);
        MOZ_ASSERT_IF(match, node.is<TernaryNode>());
        return match;
    }

    ClassNames* names() const {
        return kid1() ? &kid1()->as<ClassNames>() : nullptr;
    }
    ParseNode* heritage() const {
        return kid2();
    }
    ListNode* methodList() const {
        ParseNode* methodsOrBlock = kid3();
        if (methodsOrBlock->isKind(ParseNodeKind::ClassMethodList)) {
            return &methodsOrBlock->as<ListNode>();
        }

        ListNode* list = &methodsOrBlock->as<LexicalScopeNode>().scopeBody()->as<ListNode>();
        MOZ_ASSERT(list->isKind(ParseNodeKind::ClassMethodList));
        return list;
    }
    Handle<LexicalScope::Data*> scopeBindings() const {
        ParseNode* scope = kid3();
        return scope->as<LexicalScopeNode>().scopeBindings();
    }
};

#ifdef DEBUG
void DumpParseTree(ParseNode* pn, GenericPrinter& out, int indent = 0);
#endif

class ParseNodeAllocator
{
  public:
    explicit ParseNodeAllocator(JSContext* cx, LifoAlloc& alloc)
      : cx(cx), alloc(alloc)
    {}

    void* allocNode();

  private:
    JSContext* cx;
    LifoAlloc& alloc;
};

inline bool
ParseNode::isConstant()
{
    switch (pn_type) {
      case ParseNodeKind::Number:
      case ParseNodeKind::String:
      case ParseNodeKind::TemplateString:
      case ParseNodeKind::Null:
      case ParseNodeKind::RawUndefined:
      case ParseNodeKind::False:
      case ParseNodeKind::True:
        return true;
      case ParseNodeKind::Array:
      case ParseNodeKind::Object:
        return !as<ListNode>().hasNonConstInitializer();
      default:
        return false;
    }
}

class ObjectBox
{
  public:
    JSObject* object;

    ObjectBox(JSObject* object, ObjectBox* traceLink);
    bool isFunctionBox() { return object->is<JSFunction>(); }
    FunctionBox* asFunctionBox();
    virtual void trace(JSTracer* trc);

    static void TraceList(JSTracer* trc, ObjectBox* listHead);

  protected:
    friend struct CGObjectList;

    ObjectBox* traceLink;
    ObjectBox* emitLink;

    ObjectBox(JSFunction* function, ObjectBox* traceLink);
};

enum ParseReportKind
{
    ParseError,
    ParseWarning,
    ParseExtraWarning,
    ParseStrictError
};

enum class AccessorType {
    None,
    Getter,
    Setter
};

inline JSOp
AccessorTypeToJSOp(AccessorType atype)
{
    switch (atype) {
      case AccessorType::None:
        return JSOP_INITPROP;
      case AccessorType::Getter:
        return JSOP_INITPROP_GETTER;
      case AccessorType::Setter:
        return JSOP_INITPROP_SETTER;
      default:
        MOZ_CRASH("unexpected accessor type");
    }
}

enum class FunctionSyntaxKind
{
    // A non-arrow function expression.
    Expression,

    // A named function appearing as a Statement.
    Statement,

    Arrow,
    Method,
    ClassConstructor,
    DerivedClassConstructor,
    Getter,
    Setter,
};

static inline bool
IsConstructorKind(FunctionSyntaxKind kind)
{
    return kind == FunctionSyntaxKind::ClassConstructor ||
           kind == FunctionSyntaxKind::DerivedClassConstructor;
}

static inline bool
IsMethodDefinitionKind(FunctionSyntaxKind kind)
{
    return IsConstructorKind(kind) ||
           kind == FunctionSyntaxKind::Method ||
           kind == FunctionSyntaxKind::Getter ||
           kind == FunctionSyntaxKind::Setter;
}

static inline ParseNode*
FunctionFormalParametersList(ParseNode* fn, unsigned* numFormals)
{
    MOZ_ASSERT(fn->isKind(ParseNodeKind::Function));
    ListNode* argsBody = fn->as<CodeNode>().body();
    MOZ_ASSERT(argsBody->isKind(ParseNodeKind::ParamsBody));
    *numFormals = argsBody->count();
    if (*numFormals > 0 &&
        argsBody->last()->is<LexicalScopeNode>() &&
        argsBody->last()->as<LexicalScopeNode>().scopeBody()->isKind(ParseNodeKind::StatementList))
    {
        (*numFormals)--;
    }
    return argsBody->head();
}

bool
IsAnonymousFunctionDefinition(ParseNode* pn);

} /* namespace frontend */
} /* namespace js */

#endif /* frontend_ParseNode_h */
