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

#include "frontend/NameFunctions.h"

#include "mozilla/Sprintf.h"

#include "jsfun.h"
#include "jsprf.h"

#include "frontend/BytecodeCompiler.h"
#include "frontend/ParseNode.h"
#include "frontend/SharedContext.h"
#include "vm/StringBuffer.h"

using namespace js;
using namespace js::frontend;

namespace {

class NameResolver
{
    static const size_t MaxParents = 100;

    ExclusiveContext* cx;
    size_t nparents;                /* number of parents in the parents array */
    ParseNode* parents[MaxParents]; /* history of ParseNodes we've been looking at */
    StringBuffer* buf;              /* when resolving, buffer to append to */

    /* Test whether a ParseNode represents a function invocation */
    bool call(ParseNode* pn) {
        return pn && pn->isKind(PNK_CALL);
    }

    /*
     * Append a reference to a property named |name| to |buf|. If |name| is
     * a proper identifier name, then we append '.name'; otherwise, we
     * append '["name"]'.
     *
     * Note that we need the IsIdentifier check for atoms from both
     * PNK_NAME nodes and PNK_STRING nodes: given code like a["b c"], the
     * front end will produce a PNK_DOT with a PNK_NAME child whose name
     * contains spaces.
     */
    bool appendPropertyReference(JSAtom* name) {
        if (IsIdentifier(name))
            return buf->append('.') && buf->append(name);

        /* Quote the string as needed. */
        JSString* source = QuoteString(cx, name, '"');
        return source && buf->append('[') && buf->append(source) && buf->append(']');
    }

    /* Append a number to buf. */
    bool appendNumber(double n) {
        char number[30];
        int digits = SprintfLiteral(number, "%g", n);
        return buf->append(number, digits);
    }

    /* Append "[<n>]" to buf, referencing a property named by a numeric literal. */
    bool appendNumericPropertyReference(double n) {
        return buf->append("[") && appendNumber(n) && buf->append(']');
    }

    /*
     * Walk over the given ParseNode, attempting to convert it to a stringified
     * name that respresents where the function is being assigned to.
     *
     * |*foundName| is set to true if a name is found for the expression.
     */
    bool nameExpression(ParseNode* n, bool* foundName) {
        switch (n->getKind()) {
          case PNK_DOT:
            if (!nameExpression(n->expr(), foundName))
                return false;
            if (!*foundName)
                return true;
            return appendPropertyReference(n->pn_atom);

          case PNK_NAME:
            *foundName = true;
            return buf->append(n->pn_atom);

          case PNK_THIS:
            *foundName = true;
            return buf->append("this");

          case PNK_ELEM:
            if (!nameExpression(n->pn_left, foundName))
                return false;
            if (!*foundName)
                return true;
            if (!buf->append('[') || !nameExpression(n->pn_right, foundName))
                return false;
            if (!*foundName)
                return true;
            return buf->append(']');

          case PNK_NUMBER:
            *foundName = true;
            return appendNumber(n->pn_dval);

          default:
            /* We're confused as to what to call this function. */
            *foundName = false;
            return true;
        }
    }

    /*
     * When naming an anonymous function, the process works loosely by walking
     * up the AST and then translating that to a string. The stringification
     * happens from some far-up assignment and then going back down the parse
     * tree to the function definition point.
     *
     * This function will walk up the parse tree, gathering relevant nodes used
     * for naming, and return the assignment node if there is one. The provided
     * array and size will be filled in, and the returned node could be nullptr
     * if no assignment is found. The first element of the array will be the
     * innermost node relevant to naming, and the last element will be the
     * outermost node.
     */
    ParseNode* gatherNameable(ParseNode** nameable, size_t* size) {
        *size = 0;

        for (int pos = nparents - 1; pos >= 0; pos--) {
            ParseNode* cur = parents[pos];
            if (cur->isAssignment())
                return cur;

            switch (cur->getKind()) {
              case PNK_NAME:     return cur;  /* found the initialized declaration */
              case PNK_THIS:     return cur;  /* Setting a property of 'this'. */
              case PNK_FUNCTION: return nullptr; /* won't find an assignment or declaration */

              case PNK_RETURN:
                /*
                 * Normally the relevant parent of a node is its direct parent, but
                 * sometimes with code like:
                 *
                 *    var foo = (function() { return function() {}; })();
                 *
                 * the outer function is just a helper to create a scope for the
                 * returned function. Hence the name of the returned function should
                 * actually be 'foo'.  This loop sees if the current node is a
                 * PNK_RETURN, and if there is a direct function call we skip to
                 * that.
                 */
                for (int tmp = pos - 1; tmp > 0; tmp--) {
                    if (isDirectCall(tmp, cur)) {
                        pos = tmp;
                        break;
                    } else if (call(cur)) {
                        /* Don't skip too high in the tree */
                        break;
                    }
                    cur = parents[tmp];
                }
                break;

              case PNK_COLON:
              case PNK_SHORTHAND:
                /*
                 * Record the PNK_COLON/SHORTHAND but skip the PNK_OBJECT so we're not
                 * flagged as a contributor.
                 */
                pos--;
                MOZ_FALLTHROUGH;

              default:
                /* Save any other nodes we encounter on the way up. */
                MOZ_ASSERT(*size < MaxParents);
                nameable[(*size)++] = cur;
                break;
            }
        }

        return nullptr;
    }

    /*
     * Resolve the name of a function. If the function already has a name
     * listed, then it is skipped. Otherwise an intelligent name is guessed to
     * assign to the function's displayAtom field.
     */
    bool resolveFun(ParseNode* pn, HandleAtom prefix, MutableHandleAtom retAtom) {
        MOZ_ASSERT(pn != nullptr);
        MOZ_ASSERT(pn->isKind(PNK_FUNCTION));
        MOZ_ASSERT(pn->isArity(PN_CODE));
        RootedFunction fun(cx, pn->pn_funbox->function());

        StringBuffer buf(cx);
        this->buf = &buf;

        retAtom.set(nullptr);

        /* If the function already has a name, use that */
        if (fun->displayAtom() != nullptr) {
            if (prefix == nullptr) {
                retAtom.set(fun->displayAtom());
                return true;
            }
            if (!buf.append(prefix) ||
                !buf.append('/') ||
                !buf.append(fun->displayAtom()))
                return false;
            retAtom.set(buf.finishAtom());
            return !!retAtom;
        }

        /* If a prefix is specified, then it is a form of namespace */
        if (prefix != nullptr && (!buf.append(prefix) || !buf.append('/')))
            return false;

        /* Gather all nodes relevant to naming */
        ParseNode* toName[MaxParents];
        size_t size;
        ParseNode* assignment = gatherNameable(toName, &size);

        /* If the function is assigned to something, then that is very relevant */
        if (assignment) {
            if (assignment->isAssignment())
                assignment = assignment->pn_left;
            bool foundName = false;
            if (!nameExpression(assignment, &foundName))
                return false;
            if (!foundName)
                return true;
        }

        /*
         * Other than the actual assignment, other relevant nodes to naming are
         * those in object initializers and then particular nodes marking a
         * contribution.
         */
        for (int pos = size - 1; pos >= 0; pos--) {
            ParseNode* node = toName[pos];

            if (node->isKind(PNK_COLON) || node->isKind(PNK_SHORTHAND)) {
                ParseNode* left = node->pn_left;
                if (left->isKind(PNK_OBJECT_PROPERTY_NAME) || left->isKind(PNK_STRING)) {
                    if (!appendPropertyReference(left->pn_atom))
                        return false;
                } else if (left->isKind(PNK_NUMBER)) {
                    if (!appendNumericPropertyReference(left->pn_dval))
                        return false;
                } else {
                    MOZ_ASSERT(left->isKind(PNK_COMPUTED_NAME));
                }
            } else {
                /*
                 * Don't have consecutive '<' characters, and also don't start
                 * with a '<' character.
                 */
                if (!buf.empty() && buf.getChar(buf.length() - 1) != '<' && !buf.append('<'))
                    return false;
            }
        }

        /*
         * functions which are "genuinely anonymous" but are contained in some
         * other namespace are rather considered as "contributing" to the outer
         * function, so give them a contribution symbol here.
         */
        if (!buf.empty() && buf.getChar(buf.length() - 1) == '/' && !buf.append('<'))
            return false;

        if (buf.empty())
            return true;

        retAtom.set(buf.finishAtom());
        if (!retAtom)
            return false;
        fun->setGuessedAtom(retAtom);
        return true;
    }

    /*
     * Tests whether parents[pos] is a function call whose callee is cur.
     * This is the case for functions which do things like simply create a scope
     * for new variables and then return an anonymous function using this scope.
     */
    bool isDirectCall(int pos, ParseNode* cur) {
        return pos >= 0 && call(parents[pos]) && parents[pos]->pn_head == cur;
    }

    bool resolveTemplateLiteral(ParseNode* node, HandleAtom prefix) {
        MOZ_ASSERT(node->isKind(PNK_TEMPLATE_STRING_LIST));
        ParseNode* element = node->pn_head;
        while (true) {
            MOZ_ASSERT(element->isKind(PNK_TEMPLATE_STRING));

            element = element->pn_next;
            if (!element)
                return true;

            if (!resolve(element, prefix))
                return false;

            element = element->pn_next;
        }
    }

    bool resolveTaggedTemplate(ParseNode* node, HandleAtom prefix) {
        MOZ_ASSERT(node->isKind(PNK_TAGGED_TEMPLATE));

        ParseNode* element = node->pn_head;

        // The list head is a leading expression, e.g. |tag| in |tag`foo`|,
        // that might contain functions.
        if (!resolve(element, prefix))
            return false;

        // Next is the callsite object node.  This node only contains
        // internal strings and an array -- no user-controlled expressions.
        element = element->pn_next;
#ifdef DEBUG
        {
            MOZ_ASSERT(element->isKind(PNK_CALLSITEOBJ));
            ParseNode* array = element->pn_head;
            MOZ_ASSERT(array->isKind(PNK_ARRAY));
            for (ParseNode* kid = array->pn_head; kid; kid = kid->pn_next)
                MOZ_ASSERT(kid->isKind(PNK_TEMPLATE_STRING));
            for (ParseNode* next = array->pn_next; next; next = next->pn_next)
                MOZ_ASSERT(next->isKind(PNK_TEMPLATE_STRING));
        }
#endif

        // Next come any interpolated expressions in the tagged template.
        ParseNode* interpolated = element->pn_next;
        for (; interpolated; interpolated = interpolated->pn_next) {
            if (!resolve(interpolated, prefix))
                return false;
        }

        return true;
    }

  public:
    explicit NameResolver(ExclusiveContext* cx) : cx(cx), nparents(0), buf(nullptr) {}

    /*
     * Resolve all names for anonymous functions recursively within the
     * ParseNode instance given. The prefix is for each subsequent name, and
     * should initially be nullptr.
     */
    bool resolve(ParseNode* cur, HandleAtom prefixArg = nullptr) {
        RootedAtom prefix(cx, prefixArg);
        if (cur == nullptr)
            return true;

        MOZ_ASSERT((cur->isKind(PNK_FUNCTION) || cur->isKind(PNK_MODULE)) == cur->isArity(PN_CODE));
        if (cur->isKind(PNK_FUNCTION)) {
            RootedAtom prefix2(cx);
            if (!resolveFun(cur, prefix, &prefix2))
                return false;

            /*
             * If a function looks like (function(){})() where the parent node
             * of the definition of the function is a call, then it shouldn't
             * contribute anything to the namespace, so don't bother updating
             * the prefix to whatever was returned.
             */
            if (!isDirectCall(nparents - 1, cur))
                prefix = prefix2;
        }
        if (nparents >= MaxParents)
            return true;
        parents[nparents++] = cur;

        switch (cur->getKind()) {
          // Nodes with no children that might require name resolution need no
          // further work.
          case PNK_NOP:
          case PNK_STRING:
          case PNK_TEMPLATE_STRING:
          case PNK_REGEXP:
          case PNK_TRUE:
          case PNK_FALSE:
          case PNK_NULL:
          case PNK_ELISION:
          case PNK_GENERATOR:
          case PNK_NUMBER:
          case PNK_BREAK:
          case PNK_CONTINUE:
          case PNK_DEBUGGER:
          case PNK_EXPORT_BATCH_SPEC:
          case PNK_OBJECT_PROPERTY_NAME:
          case PNK_POSHOLDER:
            MOZ_ASSERT(cur->isArity(PN_NULLARY));
            break;

          case PNK_TYPEOFNAME:
          case PNK_SUPERBASE:
            MOZ_ASSERT(cur->isArity(PN_UNARY));
            MOZ_ASSERT(cur->pn_kid->isKind(PNK_NAME));
            MOZ_ASSERT(!cur->pn_kid->expr());
            break;

          case PNK_NEWTARGET:
            MOZ_ASSERT(cur->isArity(PN_BINARY));
            MOZ_ASSERT(cur->pn_left->isKind(PNK_POSHOLDER));
            MOZ_ASSERT(cur->pn_right->isKind(PNK_POSHOLDER));
            break;

          // Nodes with a single non-null child requiring name resolution.
          case PNK_TYPEOFEXPR:
          case PNK_VOID:
          case PNK_NOT:
          case PNK_BITNOT:
          case PNK_THROW:
          case PNK_DELETENAME:
          case PNK_DELETEPROP:
          case PNK_DELETEELEM:
          case PNK_DELETEEXPR:
          case PNK_NEG:
          case PNK_POS:
          case PNK_PREINCREMENT:
          case PNK_POSTINCREMENT:
          case PNK_PREDECREMENT:
          case PNK_POSTDECREMENT:
          case PNK_COMPUTED_NAME:
          case PNK_ARRAYPUSH:
          case PNK_SPREAD:
          case PNK_MUTATEPROTO:
          case PNK_EXPORT:
            MOZ_ASSERT(cur->isArity(PN_UNARY));
            if (!resolve(cur->pn_kid, prefix))
                return false;
            break;

          // Nodes with a single nullable child.
          case PNK_SEMI:
          case PNK_THIS:
            MOZ_ASSERT(cur->isArity(PN_UNARY));
            if (ParseNode* expr = cur->pn_kid) {
                if (!resolve(expr, prefix))
                    return false;
            }
            break;

          // Binary nodes with two non-null children.
          case PNK_ASSIGN:
          case PNK_ADDASSIGN:
          case PNK_SUBASSIGN:
          case PNK_BITORASSIGN:
          case PNK_BITXORASSIGN:
          case PNK_BITANDASSIGN:
          case PNK_LSHASSIGN:
          case PNK_RSHASSIGN:
          case PNK_URSHASSIGN:
          case PNK_MULASSIGN:
          case PNK_DIVASSIGN:
          case PNK_MODASSIGN:
          case PNK_POWASSIGN:
          case PNK_COLON:
          case PNK_SHORTHAND:
          case PNK_DOWHILE:
          case PNK_WHILE:
          case PNK_SWITCH:
          case PNK_FOR:
          case PNK_COMPREHENSIONFOR:
          case PNK_CLASSMETHOD:
          case PNK_SETTHIS:
            MOZ_ASSERT(cur->isArity(PN_BINARY));
            if (!resolve(cur->pn_left, prefix))
                return false;
            if (!resolve(cur->pn_right, prefix))
                return false;
            break;

          case PNK_ELEM:
            MOZ_ASSERT(cur->isArity(PN_BINARY));
            if (!cur->as<PropertyByValue>().isSuper() && !resolve(cur->pn_left, prefix))
                return false;
            if (!resolve(cur->pn_right, prefix))
                return false;
            break;

          case PNK_WITH:
            MOZ_ASSERT(cur->isArity(PN_BINARY));
            if (!resolve(cur->pn_left, prefix))
                return false;
            if (!resolve(cur->pn_right, prefix))
                return false;
            break;

          case PNK_CASE:
            MOZ_ASSERT(cur->isArity(PN_BINARY));
            if (ParseNode* caseExpr = cur->pn_left) {
                if (!resolve(caseExpr, prefix))
                    return false;
            }
            if (!resolve(cur->pn_right, prefix))
                return false;
            break;

          case PNK_YIELD_STAR:
            MOZ_ASSERT(cur->isArity(PN_BINARY));
            MOZ_ASSERT(cur->pn_right->isKind(PNK_NAME));
            if (!resolve(cur->pn_left, prefix))
                return false;
            break;

          case PNK_YIELD:
          case PNK_AWAIT:
            MOZ_ASSERT(cur->isArity(PN_BINARY));
            if (cur->pn_left) {
                if (!resolve(cur->pn_left, prefix))
                    return false;
            }
            MOZ_ASSERT(cur->pn_right->isKind(PNK_NAME) ||
                       (cur->pn_right->isKind(PNK_ASSIGN) &&
                        cur->pn_right->pn_left->isKind(PNK_NAME) &&
                        cur->pn_right->pn_right->isKind(PNK_GENERATOR)));
            break;

          case PNK_RETURN:
            MOZ_ASSERT(cur->isArity(PN_UNARY));
            if (ParseNode* returnValue = cur->pn_kid) {
                if (!resolve(returnValue, prefix))
                    return false;
            }
            break;

          case PNK_IMPORT:
          case PNK_EXPORT_FROM:
          case PNK_EXPORT_DEFAULT:
            MOZ_ASSERT(cur->isArity(PN_BINARY));
            // The left halves of these nodes don't contain any unconstrained
            // expressions, but it's very hard to assert this to safely rely on
            // it.  So recur anyway.
            if (!resolve(cur->pn_left, prefix))
                return false;
            MOZ_ASSERT_IF(!cur->isKind(PNK_EXPORT_DEFAULT),
                          cur->pn_right->isKind(PNK_STRING));
            break;

          // Ternary nodes with three expression children.
          case PNK_CONDITIONAL:
            MOZ_ASSERT(cur->isArity(PN_TERNARY));
            if (!resolve(cur->pn_kid1, prefix))
                return false;
            if (!resolve(cur->pn_kid2, prefix))
                return false;
            if (!resolve(cur->pn_kid3, prefix))
                return false;
            break;

          // The first part of a for-in/of is the declaration in the loop (or
          // null if no declaration).  The latter two parts are the location
          // assigned each loop and the value being looped over; obviously,
          // either might contain functions to name.  Declarations may (through
          // computed property names, and possibly through [deprecated!]
          // initializers) also contain functions to name.
          case PNK_FORIN:
          case PNK_FOROF:
            MOZ_ASSERT(cur->isArity(PN_TERNARY));
            if (ParseNode* decl = cur->pn_kid1) {
                if (!resolve(decl, prefix))
                    return false;
            }
            if (!resolve(cur->pn_kid2, prefix))
                return false;
            if (!resolve(cur->pn_kid3, prefix))
                return false;
            break;

          // Every part of a for(;;) head may contain a function needing name
          // resolution.
          case PNK_FORHEAD:
            MOZ_ASSERT(cur->isArity(PN_TERNARY));
            if (ParseNode* init = cur->pn_kid1) {
                if (!resolve(init, prefix))
                    return false;
            }
            if (ParseNode* cond = cur->pn_kid2) {
                if (!resolve(cond, prefix))
                    return false;
            }
            if (ParseNode* step = cur->pn_kid3) {
                if (!resolve(step, prefix))
                    return false;
            }
            break;

          // The first child of a class is a pair of names referring to it,
          // inside and outside the class.  The second is the class's heritage,
          // if any.  The third is the class body.
          case PNK_CLASS:
            MOZ_ASSERT(cur->isArity(PN_TERNARY));
            MOZ_ASSERT_IF(cur->pn_kid1, cur->pn_kid1->isKind(PNK_CLASSNAMES));
            MOZ_ASSERT_IF(cur->pn_kid1, cur->pn_kid1->isArity(PN_BINARY));
            MOZ_ASSERT_IF(cur->pn_kid1 && cur->pn_kid1->pn_left,
                          cur->pn_kid1->pn_left->isKind(PNK_NAME));
            MOZ_ASSERT_IF(cur->pn_kid1 && cur->pn_kid1->pn_left,
                          !cur->pn_kid1->pn_left->expr());
            MOZ_ASSERT_IF(cur->pn_kid1, cur->pn_kid1->pn_right->isKind(PNK_NAME));
            MOZ_ASSERT_IF(cur->pn_kid1, !cur->pn_kid1->pn_right->expr());
            if (cur->pn_kid2) {
                if (!resolve(cur->pn_kid2, prefix))
                    return false;
            }
            if (!resolve(cur->pn_kid3, prefix))
                return false;
            break;

          // The condition and consequent are non-optional, but the alternative
          // might be omitted.
          case PNK_IF:
            MOZ_ASSERT(cur->isArity(PN_TERNARY));
            if (!resolve(cur->pn_kid1, prefix))
                return false;
            if (!resolve(cur->pn_kid2, prefix))
                return false;
            if (cur->pn_kid3) {
                if (!resolve(cur->pn_kid3, prefix))
                    return false;
            }
            break;

          // The statements in the try-block are mandatory.  The catch-blocks
          // and finally block are optional (but at least one or the other must
          // be present).
          case PNK_TRY:
            MOZ_ASSERT(cur->isArity(PN_TERNARY));
            if (!resolve(cur->pn_kid1, prefix))
                return false;
            MOZ_ASSERT(cur->pn_kid2 || cur->pn_kid3);
            if (ParseNode* catchList = cur->pn_kid2) {
                MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST));
                if (!resolve(catchList, prefix))
                    return false;
            }
            if (ParseNode* finallyBlock = cur->pn_kid3) {
                if (!resolve(finallyBlock, prefix))
                    return false;
            }
            break;

          // The first child, the catch-pattern, may contain functions via
          // computed property names.  The optional catch-conditions may
          // contain any expression.  The catch statements, of course, may
          // contain arbitrary expressions.
          case PNK_CATCH:
            MOZ_ASSERT(cur->isArity(PN_TERNARY));
            if (!resolve(cur->pn_kid1, prefix))
                return false;
            if (cur->pn_kid2) {
                if (!resolve(cur->pn_kid2, prefix))
                    return false;
            }
            if (!resolve(cur->pn_kid3, prefix))
                return false;
            break;

          // Nodes with arbitrary-expression children.
          case PNK_OR:
          case PNK_AND:
          case PNK_BITOR:
          case PNK_BITXOR:
          case PNK_BITAND:
          case PNK_STRICTEQ:
          case PNK_EQ:
          case PNK_STRICTNE:
          case PNK_NE:
          case PNK_LT:
          case PNK_LE:
          case PNK_GT:
          case PNK_GE:
          case PNK_INSTANCEOF:
          case PNK_IN:
          case PNK_LSH:
          case PNK_RSH:
          case PNK_URSH:
          case PNK_ADD:
          case PNK_SUB:
          case PNK_STAR:
          case PNK_DIV:
          case PNK_MOD:
          case PNK_POW:
          case PNK_COMMA:
          case PNK_NEW:
          case PNK_CALL:
          case PNK_SUPERCALL:
          case PNK_GENEXP:
          case PNK_ARRAY:
          case PNK_STATEMENTLIST:
          case PNK_PARAMSBODY:
          // Initializers for individual variables, and computed property names
          // within destructuring patterns, may contain unnamed functions.
          case PNK_VAR:
          case PNK_CONST:
          case PNK_LET:
            MOZ_ASSERT(cur->isArity(PN_LIST));
            for (ParseNode* element = cur->pn_head; element; element = element->pn_next) {
                if (!resolve(element, prefix))
                    return false;
            }
            break;

          // Array comprehension nodes are lists with a single child:
          // PNK_COMPREHENSIONFOR for comprehensions, PNK_LEXICALSCOPE for
          // legacy comprehensions.  Probably this should be a non-list
          // eventually.
          case PNK_ARRAYCOMP:
            MOZ_ASSERT(cur->isArity(PN_LIST));
            MOZ_ASSERT(cur->pn_count == 1);
            MOZ_ASSERT(cur->pn_head->isKind(PNK_LEXICALSCOPE) ||
                       cur->pn_head->isKind(PNK_COMPREHENSIONFOR));
            if (!resolve(cur->pn_head, prefix))
                return false;
            break;

          case PNK_OBJECT:
          case PNK_CLASSMETHODLIST:
            MOZ_ASSERT(cur->isArity(PN_LIST));
            for (ParseNode* element = cur->pn_head; element; element = element->pn_next) {
                if (!resolve(element, prefix))
                    return false;
            }
            break;

          // A template string list's contents alternate raw template string
          // contents with expressions interpolated into the overall literal.
          case PNK_TEMPLATE_STRING_LIST:
            MOZ_ASSERT(cur->isArity(PN_LIST));
            if (!resolveTemplateLiteral(cur, prefix))
                return false;
            break;

          case PNK_TAGGED_TEMPLATE:
            MOZ_ASSERT(cur->isArity(PN_LIST));
            if (!resolveTaggedTemplate(cur, prefix))
                return false;
            break;

          // Import/export spec lists contain import/export specs containing
          // only pairs of names. Alternatively, an export spec lists may
          // contain a single export batch specifier.
          case PNK_EXPORT_SPEC_LIST:
          case PNK_IMPORT_SPEC_LIST: {
            MOZ_ASSERT(cur->isArity(PN_LIST));
#ifdef DEBUG
            bool isImport = cur->isKind(PNK_IMPORT_SPEC_LIST);
            ParseNode* item = cur->pn_head;
            if (!isImport && item && item->isKind(PNK_EXPORT_BATCH_SPEC)) {
                MOZ_ASSERT(item->isArity(PN_NULLARY));
                break;
            }
            for (; item; item = item->pn_next) {
                MOZ_ASSERT(item->isKind(isImport ? PNK_IMPORT_SPEC : PNK_EXPORT_SPEC));
                MOZ_ASSERT(item->isArity(PN_BINARY));
                MOZ_ASSERT(item->pn_left->isKind(PNK_NAME));
                MOZ_ASSERT(!item->pn_left->expr());
                MOZ_ASSERT(item->pn_right->isKind(PNK_NAME));
                MOZ_ASSERT(!item->pn_right->expr());
            }
#endif
            break;
          }

          case PNK_CATCHLIST: {
            MOZ_ASSERT(cur->isArity(PN_LIST));
            for (ParseNode* catchNode = cur->pn_head; catchNode; catchNode = catchNode->pn_next) {
                MOZ_ASSERT(catchNode->isKind(PNK_LEXICALSCOPE));
                MOZ_ASSERT(catchNode->scopeBody()->isKind(PNK_CATCH));
                MOZ_ASSERT(catchNode->scopeBody()->isArity(PN_TERNARY));
                if (!resolve(catchNode->scopeBody(), prefix))
                    return false;
            }
            break;
          }

          case PNK_DOT:
            MOZ_ASSERT(cur->isArity(PN_NAME));

            // Super prop nodes do not have a meaningful LHS
            if (cur->as<PropertyAccess>().isSuper())
                break;
            if (!resolve(cur->expr(), prefix))
                return false;
            break;

          case PNK_LABEL:
            MOZ_ASSERT(cur->isArity(PN_NAME));
            if (!resolve(cur->expr(), prefix))
                return false;
            break;

          case PNK_NAME:
            MOZ_ASSERT(cur->isArity(PN_NAME));
            if (!resolve(cur->expr(), prefix))
                return false;
            break;

          case PNK_LEXICALSCOPE:
            MOZ_ASSERT(cur->isArity(PN_SCOPE));
            if (!resolve(cur->scopeBody(), prefix))
                return false;
            break;

          case PNK_FUNCTION:
          case PNK_MODULE:
            MOZ_ASSERT(cur->isArity(PN_CODE));
            if (!resolve(cur->pn_body, prefix))
                return false;
            break;

          // Kinds that should be handled by parent node resolution.

          case PNK_IMPORT_SPEC: // by PNK_IMPORT_SPEC_LIST
          case PNK_EXPORT_SPEC: // by PNK_EXPORT_SPEC_LIST
          case PNK_CALLSITEOBJ: // by PNK_TAGGED_TEMPLATE
          case PNK_CLASSNAMES:  // by PNK_CLASS
            MOZ_CRASH("should have been handled by a parent node");

          case PNK_LIMIT: // invalid sentinel value
            MOZ_CRASH("invalid node kind");
        }

        nparents--;
        return true;
    }
};

} /* anonymous namespace */

bool
frontend::NameFunctions(ExclusiveContext* cx, ParseNode* pn)
{
    NameResolver nr(cx);
    return nr.resolve(pn);
}