/* 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_ObjectEmitter_h
#define frontend_ObjectEmitter_h

#include "mozilla/Attributes.h"  // MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_RAII
#include "mozilla/Maybe.h"       // Maybe

#include <stddef.h>  // size_t
#include <stdint.h>  // uint32_t

#include "frontend/EmitterScope.h"   // EmitterScope
#include "frontend/NameOpEmitter.h"  // NameOpEmitter
#include "frontend/ParseNode.h"      // AccessorType
#include "frontend/ParserAtom.h"     // TaggedParserAtomIndex
#include "frontend/TDZCheckCache.h"  // TDZCheckCache
#include "vm/Opcodes.h"              // JSOp
#include "vm/Scope.h"                // LexicalScope

namespace js {

namespace frontend {

struct BytecodeEmitter;
class SharedContext;

// Class for emitting bytecode for object and class properties.
// See ObjectEmitter and ClassEmitter for usage.
class MOZ_STACK_CLASS PropertyEmitter {
 public:
  enum class Kind {
    // Prototype property.
    Prototype,

    // Class static property.
    Static
  };

 protected:
  BytecodeEmitter* bce_;

  // True if the object is class.
  // Set by ClassEmitter.
  bool isClass_ = false;

  // True if the property is class static method.
  bool isStatic_ = false;

  // True if the property has computed or index key.
  bool isIndexOrComputed_ = false;

#ifdef DEBUG
  // The state of this emitter.
  //
  // +-------+
  // | Start |-+
  // +-------+ |
  //           |
  // +---------+
  // |
  // |  +------------------------------------------------------------+
  // |  |                                                            |
  // |  | [normal property/method/accessor]                          |
  // |  v   prepareForPropValue  +-----------+              +------+ |
  // +->+----------------------->| PropValue |-+         +->| Init |-+
  //    |                        +-----------+ |         |  +------+
  //    |                                      |         |
  //    |  +-----------------------------------+         +-----------+
  //    |  |                                                         |
  //    |  +-+---------------------------------------+               |
  //    |    |                                       |               |
  //    |    | [method with super]                   |               |
  //    |    |   emitInitHomeObject +-------------+  v               |
  //    |    +--------------------->| InitHomeObj |->+               |
  //    |                           +-------------+  |               |
  //    |                                            |               |
  //    |    +-------------------------------------- +               |
  //    |    |                                                       |
  //    |    | emitInit                                              |
  //    |    +------------------------------------------------------>+
  //    |                                                            ^
  //    | [optimized private non-static method]                      |
  //    |   prepareForPrivateMethod   +--------------------+         |
  //    +---------------------------->| PrivateMethodValue |-+       |
  //    |                             +--------------------+ |       |
  //    |                                                    |       |
  //    |  +-------------------------------------------------+       |
  //    |  |                                                         |
  //    |  +-+---------------------------------------------+         |
  //    |    |                                             |         |
  //    |    | [method with super                          |         |
  //    |    | emitInitHomeObject   +-----------------+    v         |
  //    |    +--------------------->| InitHomeObjFor- |----+         |
  //    |                           | PrivateMethod   |    |         |
  //    |                           +-----------------+    |         |
  //    |                                                  |         |
  //    |    +---------------------------------------------+         |
  //    |    |                                                       |
  //    |    | skipInit                                              |
  //    |    +------------------------------------------------------>+
  //    |                                                            ^
  //    | [private static method]                                    |
  //    |   prepareForPrivateStaticMethod  +---------------------+   |
  //    +--------------------------------->| PrivateStaticMethod |-+ |
  //    |                                  +---------------------+ | |
  //    |                                                          | |
  //    |  +-------------------------------------------------------+ |
  //    |  |                                                         |
  //    |  +-+-------------------------------------------------+     |
  //    |    |                                                 |     |
  //    |    | [method with super                              |     |
  //    |    | emitInitHomeObject   +---------------------+    v     |
  //    |    +--------------------->| InitHomeObjFor-     |----+     |
  //    |                           | PrivateStaticMethod |    |     |
  //    |                           +---------------------+    |     |
  //    |                                                      |     |
  //    |      +-----------------------------------------------+     |
  //    |      |                                                     |
  //    |      | emitPrivateStaticMethod                             |
  //    |      +---------------------------------------------------->+
  //    |                                                            ^
  //    | [index property/method/accessor]                           |
  //    |   prepareForIndexPropKey  +----------+                     |
  //    +-------------------------->| IndexKey |-+                   |
  //    |                           +----------+ |                   |
  //    |                                        |                   |
  //    |  +-------------------------------------+                   |
  //    |  |                                                         |
  //    |  | prepareForIndexPropValue +------------+                 |
  //    |  +------------------------->| IndexValue |-+               |
  //    |                             +------------+ |               |
  //    |                                            |               |
  //    |    +---------------------------------------+               |
  //    |    |                                                       |
  //    |    +-+--------------------------------------------------+  |
  //    |      |                                                  |  |
  //    |      | [method with super]                              |  |
  //    |      |   emitInitHomeObject +---------------------+     v  |
  //    |      +--------------------->| InitHomeObjForIndex |---->+  |
  //    |                             +---------------------+     |  |
  //    |                                                         |  |
  //    |      +--------------------------------------------------+  |
  //    |      |                                                     |
  //    |      | emitInitIndexOrComputed                             |
  //    |      +---------------------------------------------------->+
  //    |                                                            ^
  //    | [computed property/method/accessor]                        |
  //    |   prepareForComputedPropKey  +-------------+               |
  //    +----------------------------->| ComputedKey |-+             |
  //    |                              +-------------+ |             |
  //    |                                              |             |
  //    |  +-------------------------------------------+             |
  //    |  |                                                         |
  //    |  | prepareForComputedPropValue +---------------+           |
  //    |  +---------------------------->| ComputedValue |-+         |
  //    |                                +---------------+ |         |
  //    |                                                  |         |
  //    |    +---------------------------------------------+         |
  //    |    |                                                       |
  //    |    +-+--------------------------------------------------+  |
  //    |      |                                                  |  |
  //    |      | [method with super]                              |  |
  //    |      |   emitInitHomeObject +------------------------+  v  |
  //    |      +--------------------->| InitHomeObjForComputed |->+  |
  //    |                             +------------------------+  |  |
  //    |                                                         |  |
  //    |      +--------------------------------------------------+  |
  //    |      |                                                     |
  //    |      | emitInitIndexOrComputed                             |
  //    |      +---------------------------------------------------->+
  //    |                                                            ^
  //    |                                                            |
  //    | [__proto__]                                                |
  //    |   prepareForProtoValue  +------------+ emitMutateProto     |
  //    +------------------------>| ProtoValue |-------------------->+
  //    |                         +------------+                     ^
  //    |                                                            |
  //    | [...prop]                                                  |
  //    |   prepareForSpreadOperand +---------------+ emitSpread     |
  //    +-------------------------->| SpreadOperand |----------------+
  //                                +---------------+
  enum class PropertyState {
    // The initial state.
    Start,

    // After calling prepareForPropValue.
    PropValue,

    // After calling emitInitHomeObject, from PropValue.
    InitHomeObj,

    // After calling prepareForPrivateMethod.
    PrivateMethodValue,

    // After calling emitInitHomeObject, from PrivateMethod.
    InitHomeObjForPrivateMethod,

    // After calling prepareForPrivateStaticMethod.
    PrivateStaticMethod,

    // After calling emitInitHomeObject, from PrivateStaticMethod.
    InitHomeObjForPrivateStaticMethod,

    // After calling prepareForIndexPropKey.
    IndexKey,

    // prepareForIndexPropValue.
    IndexValue,

    // After calling emitInitHomeObject, from IndexValue.
    InitHomeObjForIndex,

    // After calling prepareForComputedPropKey.
    ComputedKey,

    // prepareForComputedPropValue.
    ComputedValue,

    // After calling emitInitHomeObject, from ComputedValue.
    InitHomeObjForComputed,

    // After calling prepareForProtoValue.
    ProtoValue,

    // After calling prepareForSpreadOperand.
    SpreadOperand,

    // After calling one of emitInit, emitInitIndexOrComputed, emitMutateProto,
    // or emitSpread.
    Init,
  };
  PropertyState propertyState_ = PropertyState::Start;
#endif

 public:
  explicit PropertyEmitter(BytecodeEmitter* bce);

  // Parameters are the offset in the source code for each character below:
  //
  // { __proto__: protoValue }
  //   ^
  //   |
  //   keyPos
  [[nodiscard]] bool prepareForProtoValue(uint32_t keyPos);
  [[nodiscard]] bool emitMutateProto();

  // { ...obj }
  //   ^
  //   |
  //   spreadPos
  [[nodiscard]] bool prepareForSpreadOperand(uint32_t spreadPos);
  [[nodiscard]] bool emitSpread();

  // { key: value }
  //   ^
  //   |
  //   keyPos
  [[nodiscard]] bool prepareForPropValue(uint32_t keyPos, Kind kind);

  [[nodiscard]] bool prepareForPrivateMethod();

  [[nodiscard]] bool prepareForPrivateStaticMethod(uint32_t keyPos);

  // { 1: value }
  //   ^
  //   |
  //   keyPos
  [[nodiscard]] bool prepareForIndexPropKey(uint32_t keyPos, Kind kind);
  [[nodiscard]] bool prepareForIndexPropValue();

  // { [ key ]: value }
  //   ^
  //   |
  //   keyPos
  [[nodiscard]] bool prepareForComputedPropKey(uint32_t keyPos, Kind kind);
  [[nodiscard]] bool prepareForComputedPropValue();

  [[nodiscard]] bool emitInitHomeObject();

  // @param key
  //        Property key
  [[nodiscard]] bool emitInit(AccessorType accessorType,
                              TaggedParserAtomIndex key);

  [[nodiscard]] bool emitInitIndexOrComputed(AccessorType accessorType);

  [[nodiscard]] bool emitPrivateStaticMethod(AccessorType accessorType);

  [[nodiscard]] bool skipInit();

 private:
  [[nodiscard]] MOZ_ALWAYS_INLINE bool prepareForProp(uint32_t keyPos,
                                                      bool isStatic,
                                                      bool isComputed);

  // @param op
  //        Opcode for initializing property
  // @param key
  //        Atom of the property if the property key is not computed
  [[nodiscard]] bool emitInit(JSOp op, TaggedParserAtomIndex key);
  [[nodiscard]] bool emitInitIndexOrComputed(JSOp op);

  [[nodiscard]] bool emitPopClassConstructor();
};

// Class for emitting bytecode for object literal.
//
// Usage: (check for the return value is omitted for simplicity)
//
//   `{}`
//     ObjectEmitter oe(this);
//     oe.emitObject(0);
//     oe.emitEnd();
//
//   `{ prop: 10 }`
//     ObjectEmitter oe(this);
//     oe.emitObject(1);
//
//     oe.prepareForPropValue(offset_of_prop);
//     emit(10);
//     oe.emitInitProp(atom_of_prop);
//
//     oe.emitEnd();
//
//   `{ prop: function() {} }`, when property value is anonymous function
//     ObjectEmitter oe(this);
//     oe.emitObject(1);
//
//     oe.prepareForPropValue(offset_of_prop);
//     emit(function);
//     oe.emitInitProp(atom_of_prop);
//
//     oe.emitEnd();
//
//   `{ get prop() { ... }, set prop(v) { ... } }`
//     ObjectEmitter oe(this);
//     oe.emitObject(2);
//
//     oe.prepareForPropValue(offset_of_prop);
//     emit(function_for_getter);
//     oe.emitInitGetter(atom_of_prop);
//
//     oe.prepareForPropValue(offset_of_prop);
//     emit(function_for_setter);
//     oe.emitInitSetter(atom_of_prop);
//
//     oe.emitEnd();
//
//   `{ 1: 10, get 2() { ... }, set 3(v) { ... } }`
//     ObjectEmitter oe(this);
//     oe.emitObject(3);
//
//     oe.prepareForIndexPropKey(offset_of_prop);
//     emit(1);
//     oe.prepareForIndexPropValue();
//     emit(10);
//     oe.emitInitIndexedProp();
//
//     oe.prepareForIndexPropKey(offset_of_opening_bracket);
//     emit(2);
//     oe.prepareForIndexPropValue();
//     emit(function_for_getter);
//     oe.emitInitIndexGetter();
//
//     oe.prepareForIndexPropKey(offset_of_opening_bracket);
//     emit(3);
//     oe.prepareForIndexPropValue();
//     emit(function_for_setter);
//     oe.emitInitIndexSetter();
//
//     oe.emitEnd();
//
//   `{ [prop1]: 10, get [prop2]() { ... }, set [prop3](v) { ... } }`
//     ObjectEmitter oe(this);
//     oe.emitObject(3);
//
//     oe.prepareForComputedPropKey(offset_of_opening_bracket);
//     emit(prop1);
//     oe.prepareForComputedPropValue();
//     emit(10);
//     oe.emitInitComputedProp();
//
//     oe.prepareForComputedPropKey(offset_of_opening_bracket);
//     emit(prop2);
//     oe.prepareForComputedPropValue();
//     emit(function_for_getter);
//     oe.emitInitComputedGetter();
//
//     oe.prepareForComputedPropKey(offset_of_opening_bracket);
//     emit(prop3);
//     oe.prepareForComputedPropValue();
//     emit(function_for_setter);
//     oe.emitInitComputedSetter();
//
//     oe.emitEnd();
//
//   `{ __proto__: obj }`
//     ObjectEmitter oe(this);
//     oe.emitObject(1);
//     oe.prepareForProtoValue(offset_of___proto__);
//     emit(obj);
//     oe.emitMutateProto();
//     oe.emitEnd();
//
//   `{ ...obj }`
//     ObjectEmitter oe(this);
//     oe.emitObject(1);
//     oe.prepareForSpreadOperand(offset_of_triple_dots);
//     emit(obj);
//     oe.emitSpread();
//     oe.emitEnd();
//
class MOZ_STACK_CLASS ObjectEmitter : public PropertyEmitter {
 private:
#ifdef DEBUG
  // The state of this emitter.
  //
  // +-------+ emitObject +--------+
  // | Start |----------->| Object |-+
  // +-------+            +--------+ |
  //                                 |
  //   +-----------------------------+
  //   |
  //   | (do PropertyEmitter operation)  emitEnd  +-----+
  //   +-------------------------------+--------->| End |
  //                                              +-----+
  enum class ObjectState {
    // The initial state.
    Start,

    // After calling emitObject.
    Object,

    // After calling emitEnd.
    End,
  };
  ObjectState objectState_ = ObjectState::Start;
#endif

 public:
  explicit ObjectEmitter(BytecodeEmitter* bce);

  [[nodiscard]] bool emitObject(size_t propertyCount);
  // Same as `emitObject()`, but start with an empty template object already on
  // the stack.
  [[nodiscard]] bool emitObjectWithTemplateOnStack();
  [[nodiscard]] bool emitEnd();
};

// Save and restore the strictness.
// Used by class declaration/expression to temporarily enable strict mode.
class MOZ_RAII AutoSaveLocalStrictMode {
  SharedContext* sc_;
  bool savedStrictness_;

 public:
  explicit AutoSaveLocalStrictMode(SharedContext* sc);
  ~AutoSaveLocalStrictMode();

  // Force restore the strictness now.
  void restore();
};

// Class for emitting bytecode for JS class.
//
// Usage: (check for the return value is omitted for simplicity)
//
//   `class { constructor() { ... } }`
//     ClassEmitter ce(this);
//     ce.emitScope(scopeBindings);
//     ce.emitClass(nullptr, nullptr, false);
//
//     emit(function_for_constructor);
//     ce.emitInitConstructor(/* needsHomeObject = */ false);
//
//     ce.emitEnd(ClassEmitter::Kind::Expression);
//
//   `class X { constructor() { ... } }`
//     ClassEmitter ce(this);
//     ce.emitScope(scopeBindings);
//     ce.emitClass(atom_of_X, nullptr, false);
//
//     emit(function_for_constructor);
//     ce.emitInitConstructor(/* needsHomeObject = */ false);
//
//     ce.emitEnd(ClassEmitter::Kind::Expression);
//
//   `class X extends Y { constructor() { ... } }`
//     ClassEmitter ce(this);
//     ce.emitScope(scopeBindings);
//
//     emit(Y);
//     ce.emitDerivedClass(atom_of_X, nullptr, false);
//
//     emit(function_for_constructor);
//     ce.emitInitConstructor(/* needsHomeObject = */ false);
//
//     ce.emitEnd(ClassEmitter::Kind::Expression);
//
//   `class X extends Y { constructor() { ... super.f(); ... } }`
//     ClassEmitter ce(this);
//     ce.emitScope(scopeBindings);
//
//     emit(Y);
//     ce.emitDerivedClass(atom_of_X, nullptr, false);
//
//     emit(function_for_constructor);
//     // pass true if constructor contains super.prop access
//     ce.emitInitConstructor(/* needsHomeObject = */ true);
//
//     ce.emitEnd(ClassEmitter::Kind::Expression);
//
//   `class X extends Y { field0 = expr0; ... }`
//     ClassEmitter ce(this);
//     ce.emitScope(scopeBindings);
//     emit(Y);
//     ce.emitDerivedClass(atom_of_X, nullptr, false);
//
//     ce.prepareForMemberInitializers(fields.length());
//     for (auto field : fields) {
//       emit(field.initializer_method());
//       ce.emitStoreMemberInitializer();
//     }
//     ce.emitMemberInitializersEnd();
//
//     emit(function_for_constructor);
//     ce.emitInitConstructor(/* needsHomeObject = */ false);
//     ce.emitEnd(ClassEmitter::Kind::Expression);
//
//   `class X { field0 = super.method(); ... }`
//     // after emitClass/emitDerivedClass
//     ce.prepareForMemberInitializers(1);
//     for (auto field : fields) {
//       emit(field.initializer_method());
//       if (field.initializer_contains_super_or_eval()) {
//         ce.emitMemberInitializerHomeObject();
//       }
//       ce.emitStoreMemberInitializer();
//     }
//     ce.emitMemberInitializersEnd();
//
//   `m() {}` in class
//     // after emitInitConstructor
//     ce.prepareForPropValue(offset_of_m);
//     emit(function_for_m);
//     ce.emitInitProp(atom_of_m);
//
//   `m() { super.f(); }` in class
//     // after emitInitConstructor
//     ce.prepareForPropValue(offset_of_m);
//     emit(function_for_m);
//     ce.emitInitHomeObject();
//     ce.emitInitProp(atom_of_m);
//
//   `async m() { super.f(); }` in class
//     // after emitInitConstructor
//     ce.prepareForPropValue(offset_of_m);
//     emit(function_for_m);
//     ce.emitInitHomeObject();
//     ce.emitInitProp(atom_of_m);
//
//   `get p() { super.f(); }` in class
//     // after emitInitConstructor
//     ce.prepareForPropValue(offset_of_p);
//     emit(function_for_p);
//     ce.emitInitHomeObject();
//     ce.emitInitGetter(atom_of_m);
//
//   `static m() {}` in class
//     // after emitInitConstructor
//     ce.prepareForPropValue(offset_of_m,
//                            PropertyEmitter::Kind::Static);
//     emit(function_for_m);
//     ce.emitInitProp(atom_of_m);
//
//   `static get [p]() { super.f(); }` in class
//     // after emitInitConstructor
//     ce.prepareForComputedPropValue(offset_of_m,
//                                    PropertyEmitter::Kind::Static);
//     emit(p);
//     ce.prepareForComputedPropValue();
//     emit(function_for_m);
//     ce.emitInitHomeObject();
//     ce.emitInitComputedGetter();
//
class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter {
 public:
  enum class Kind {
    // Class expression.
    Expression,

    // Class declaration.
    Declaration,
  };

 private:
  // Pseudocode for class declarations:
  //
  //     class extends BaseExpression {
  //       constructor() { ... }
  //       ...
  //       }
  //
  //
  //   if defined <BaseExpression> {
  //     let heritage = BaseExpression;
  //
  //     if (heritage !== null) {
  //       funProto = heritage;
  //       objProto = heritage.prototype;
  //     } else {
  //       funProto = %FunctionPrototype%;
  //       objProto = null;
  //     }
  //   } else {
  //     objProto = %ObjectPrototype%;
  //   }
  //
  //   let homeObject = ObjectCreate(objProto);
  //
  //   if defined <constructor> {
  //     if defined <BaseExpression> {
  //       cons = DefineMethod(<constructor>, proto=homeObject,
  //       funProto=funProto);
  //     } else {
  //       cons = DefineMethod(<constructor>, proto=homeObject);
  //     }
  //   } else {
  //     if defined <BaseExpression> {
  //       cons = DefaultDerivedConstructor(proto=homeObject,
  //       funProto=funProto);
  //     } else {
  //       cons = DefaultConstructor(proto=homeObject);
  //     }
  //   }
  //
  //   cons.prototype = homeObject;
  //   homeObject.constructor = cons;
  //
  //   EmitPropertyList(...)

  bool isDerived_ = false;

  mozilla::Maybe<TDZCheckCache> tdzCache_;
  mozilla::Maybe<EmitterScope> innerScope_;
  mozilla::Maybe<TDZCheckCache> bodyTdzCache_;
  mozilla::Maybe<EmitterScope> bodyScope_;
  AutoSaveLocalStrictMode strictMode_;

#ifdef DEBUG
  // The state of this emitter.
  //
  // clang-format off
  // +-------+
  // | Start |-+------------------------>+--+------------------------------>+--+
  // +-------+ |                         ^  |                               ^  |
  //           | [has scope]             |  | [has body scope]              |  |
  //           |   emitScope   +-------+ |  |  emitBodyScope  +-----------+ |  |
  //           +-------------->| Scope |-+  +---------------->| BodyScope |-+  |
  //                           +-------+                      +-----------+    |
  //                                                                           |
  //   +-----------------------------------------------------------------------+
  //   |
  //   |   emitClass           +-------+
  //   +-+----------------->+->| Class |-+
  //     |                  ^  +-------+ |
  //     | emitDerivedClass |            |
  //     +------------------+            |
  //                                     |
  //     +-------------------------------+
  //     |
  //     |
  //     |  prepareForMemberInitializers(isStatic = false)
  //     +---------------+
  //     |               |
  //     |      +--------v-------------------+
  //     |      | InstanceMemberInitializers |
  //     |      +----------------------------+
  //     |               |
  //                     | emitMemberInitializersEnd
  //     |               |
  //     |      +--------v----------------------+
  //     |      | InstanceMemberInitializersEnd |
  //     |      +-------------------------------+
  //     |               |
  //     +<--------------+
  //     |
  //     |   emitInitConstructor           +-----------------+
  //     +-------------------------------->| InitConstructor |-+
  //                                       +-----------------+ |
  //                                                           |
  //                                                           |
  //                                                           |
  //     +-----------------------------------------------------+
  //     |
  //     |  prepareForMemberInitializers(isStatic = true)
  //     +---------------+
  //     |               |
  //     |      +--------v-----------------+
  //     |      | StaticMemberInitializers |
  //     |      +--------------------------+
  //     |               |
  //     |               | emitMemberInitializersEnd
  //     |               |
  //     |      +--------v--------------------+
  //     |      | StaticMemberInitializersEnd |
  //     |      +-----------------------------+
  //     |               |
  //     +<--------------+
  //     |
  //     | (do PropertyEmitter operation)
  //     +--------------------------------+
  //                                      |
  //     +-------------+    emitBinding   |
  //     |  BoundName  |<-----------------+
  //     +--+----------+
  //        |
  //        | emitEnd
  //        |
  //     +--v----+
  //     |  End  |
  //     +-------+
  //
  // clang-format on
  enum class ClassState {
    // The initial state.
    Start,

    // After calling emitScope.
    Scope,

    // After calling emitBodyScope.
    BodyScope,

    // After calling emitClass or emitDerivedClass.
    Class,

    // After calling emitInitConstructor.
    InitConstructor,

    // After calling prepareForMemberInitializers(isStatic = false).
    InstanceMemberInitializers,

    // After calling emitMemberInitializersEnd.
    InstanceMemberInitializersEnd,

    // After calling prepareForMemberInitializers(isStatic = true).
    StaticMemberInitializers,

    // After calling emitMemberInitializersEnd.
    StaticMemberInitializersEnd,

    // After calling emitBinding.
    BoundName,

    // After calling emitEnd.
    End,
  };
  ClassState classState_ = ClassState::Start;

  // The state of the members emitter.
  //
  // clang-format off
  //
  //   +-------+
  //   | Start +<-----------------------------+
  //   +-------+                              |
  //       |                                  |
  //       | prepareForMemberInitializer      | emitStoreMemberInitializer
  //       v                                  |
  // +-------------+                          |
  // | Initializer +------------------------->+
  // +-------------+                          |
  //       |                                  |
  //       | emitMemberInitializerHomeObject  |
  //       v                                  |
  // +---------------------------+            |
  // | InitializerWithHomeObject +------------+
  // +---------------------------+
  //
  // clang-format on
  enum class MemberState {
    // After calling prepareForMemberInitializers
    // and 0 or more calls to emitStoreMemberInitializer.
    Start,

    // After calling prepareForMemberInitializer
    Initializer,

    // After calling emitMemberInitializerHomeObject
    InitializerWithHomeObject,
  };
  MemberState memberState_ = MemberState::Start;

  size_t numInitializers_ = 0;
#endif

  TaggedParserAtomIndex name_;
  TaggedParserAtomIndex nameForAnonymousClass_;
  bool hasNameOnStack_ = false;
  mozilla::Maybe<NameOpEmitter> initializersAssignment_;
  size_t initializerIndex_ = 0;

 public:
  explicit ClassEmitter(BytecodeEmitter* bce);

  bool emitScope(LexicalScope::ParserData* scopeBindings);
  bool emitBodyScope(ClassBodyScope::ParserData* scopeBindings);

  // @param name
  //        Name of the class (nullptr if this is anonymous class)
  // @param nameForAnonymousClass
  //        Statically inferred name of the class (only for anonymous classes)
  // @param hasNameOnStack
  //        If true the name is on the stack (only for anonymous classes)
  [[nodiscard]] bool emitClass(TaggedParserAtomIndex name,
                               TaggedParserAtomIndex nameForAnonymousClass,
                               bool hasNameOnStack, uint8_t membersCount);
  [[nodiscard]] bool emitDerivedClass(
      TaggedParserAtomIndex name, TaggedParserAtomIndex nameForAnonymousClass,
      bool hasNameOnStack);

  // @param needsHomeObject
  //        True if the constructor contains `super.foo`
  [[nodiscard]] bool emitInitConstructor(bool needsHomeObject);

  [[nodiscard]] bool prepareForMemberInitializers(size_t numInitializers,
                                                  bool isStatic);
  [[nodiscard]] bool prepareForMemberInitializer();
  [[nodiscard]] bool emitMemberInitializerHomeObject(bool isStatic);
  [[nodiscard]] bool emitStoreMemberInitializer();
  [[nodiscard]] bool emitMemberInitializersEnd();

#ifdef ENABLE_DECORATORS
  // TODO!: When we've enabled decorators, update the states and transition
  //        diagram to reflect this new state.
  [[nodiscard]] bool prepareForExtraInitializers(
      TaggedParserAtomIndex initializers);
#endif

  [[nodiscard]] bool emitBinding();

#ifdef ENABLE_DECORATORS
  // TODO!: When we've enabled decorators, update the states and transition
  //        diagram to reflect this new state.
  [[nodiscard]] bool prepareForDecorators();
#endif

  [[nodiscard]] bool emitEnd(Kind kind);

 private:
  [[nodiscard]] bool initProtoAndCtor();

  [[nodiscard]] bool leaveBodyAndInnerScope();
};

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

#endif /* frontend_ObjectEmitter_h */
