// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_OBJECTS_MODULE_H_
#define V8_OBJECTS_MODULE_H_

#include "include/v8-script.h"
#include "include/v8-template.h"
#include "src/objects/js-objects.h"
#include "src/objects/objects.h"
#include "src/objects/struct.h"

// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"

namespace v8 {
namespace internal {

class JSModuleNamespace;
class SourceTextModuleDescriptor;
class SourceTextModuleInfo;
class SourceTextModuleInfoEntry;
class Zone;
template <typename T>
class ZoneForwardList;

#include "torque-generated/src/objects/module-tq.inc"

// Module is the base class for ECMAScript module types, roughly corresponding
// to Abstract Module Record.
// https://tc39.es/ecma262/#sec-abstract-module-records
V8_OBJECT class Module : public HeapObject {
 public:
  DECL_VERIFIER(Module)
  DECL_PRINTER(Module)

  enum Status {
    // Order matters!
    kUnlinked,
    kPreLinking,
    kLinking,
    kLinked,
    kEvaluating,
    kEvaluatingAsync,
    kEvaluated,
    kErrored
  };

#ifdef DEBUG
  static const char* StatusString(Module::Status status);
#endif  // DEBUG

  // The exception in the case {status} is kErrored.
  Tagged<Object> GetException();

  // Returns if this module or any transitively requested module is [[Async]],
  // i.e. has a top-level await.
  V8_WARN_UNUSED_RESULT bool IsGraphAsync(Isolate* isolate) const;

  struct UserResolveCallbacks {
    v8::Module::ResolveModuleCallback module_callback = nullptr;
    v8::Module::ResolveSourceCallback source_callback = nullptr;
    v8::Module::ResolveModuleByIndexCallback module_callback_by_index = nullptr;
    v8::Module::ResolveSourceByIndexCallback source_callback_by_index = nullptr;
  };

  // Implementation of spec operation ModuleDeclarationInstantiation.
  // Returns false if an exception occurred during instantiation, true
  // otherwise. (In the case where the callback throws an exception, that
  // exception is propagated.)
  static V8_WARN_UNUSED_RESULT bool Instantiate(
      Isolate* isolate, Handle<Module> module, v8::Local<v8::Context> context,
      const UserResolveCallbacks& callbacks);

  // Implementation of spec operation ModuleEvaluation.
  static V8_WARN_UNUSED_RESULT MaybeDirectHandle<Object> Evaluate(
      Isolate* isolate, Handle<Module> module);

  // Get the namespace object for [module].  If it doesn't exist yet, it is
  // created.
  static Handle<Cell> GetModuleNamespaceCell(
      Isolate* isolate, Handle<Module> module,
      ModuleImportPhase phase = ModuleImportPhase::kEvaluation);
  static DirectHandle<JSModuleNamespace> GetModuleNamespace(
      Isolate* isolate, Handle<Module> module,
      ModuleImportPhase phase = ModuleImportPhase::kEvaluation);

  inline Tagged<ObjectHashTable> exports() const;
  inline void set_exports(Tagged<ObjectHashTable> value,
                          WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

  inline int hash() const;
  inline void set_hash(int value);

  inline int status() const;
  inline void set_status(int value);

  inline Tagged<UnionOf<Cell, Undefined>> module_namespace() const;
  inline void set_module_namespace(
      Tagged<UnionOf<Cell, Undefined>> value,
      WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

  inline Tagged<UnionOf<Cell, Undefined>> deferred_module_namespace() const;
  inline void set_deferred_module_namespace(
      Tagged<UnionOf<Cell, Undefined>> value,
      WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

  inline Tagged<Object> exception() const;
  inline void set_exception(Tagged<Object> value,
                            WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

  inline Tagged<UnionOf<JSPromise, Undefined>> top_level_capability() const;
  inline void set_top_level_capability(
      Tagged<UnionOf<JSPromise, Undefined>> value,
      WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

  struct Hash;

 protected:
  friend class Factory;

  // The [must_resolve] argument indicates whether or not an exception should be
  // thrown in case the module does not provide an export named [name]
  // (including when a cycle is detected).  An exception is always thrown in the
  // case of conflicting star exports.
  //
  // If [must_resolve] is true, a null result indicates an exception. If
  // [must_resolve] is false, a null result may or may not indicate an
  // exception (so check manually!).
  class ResolveSet;
  static V8_WARN_UNUSED_RESULT MaybeHandle<Cell> ResolveExport(
      Isolate* isolate, Handle<Module> module,
      DirectHandle<String> module_specifier, Handle<String> export_name,
      MessageLocation loc, bool must_resolve, ResolveSet* resolve_set);

  static V8_WARN_UNUSED_RESULT bool PrepareInstantiate(
      Isolate* isolate, DirectHandle<Module> module,
      v8::Local<v8::Context> context, const UserResolveCallbacks& callbacks);
  static V8_WARN_UNUSED_RESULT bool FinishInstantiate(
      Isolate* isolate, Handle<Module> module,
      ZoneForwardList<Handle<SourceTextModule>>* stack, unsigned* dfs_index,
      Zone* zone, unsigned depth, unsigned* max_depth);

  // Set module's status back to kUnlinked and reset other internal state.
  // This is used when instantiation fails.
  static void Reset(Isolate* isolate, DirectHandle<Module> module);
  static void ResetGraph(Isolate* isolate, DirectHandle<Module> module);

  // To set status to kErrored, RecordError should be used.
  void SetStatus(Status status);
  void RecordError(Isolate* isolate, Tagged<Object> error);

 public:
  TaggedMember<ObjectHashTable> exports_;
  TaggedMember<Smi> hash_;
  TaggedMember<Smi> status_;
  TaggedMember<UnionOf<Cell, Undefined>> module_namespace_;
  TaggedMember<UnionOf<Cell, Undefined>> deferred_module_namespace_;
  TaggedMember<Object> exception_;
  TaggedMember<UnionOf<JSPromise, Undefined>> top_level_capability_;
} V8_OBJECT_END;

template <>
struct ObjectTraits<Module> {
  using BodyDescriptor = FixedBodyDescriptor<offsetof(Module, exports_),
                                             sizeof(Module), sizeof(Module)>;
};

// When importing a module namespace (import * as foo from "bar"), a
// JSModuleNamespace object (representing module "bar") is created and bound to
// the declared variable (foo).  A module can have at most one namespace object.
V8_OBJECT class JSModuleNamespace : public JSSpecialObject {
 public:
  inline Tagged<Module> module() const;
  inline void set_module(Tagged<Module> value,
                         WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

  DECL_PRINTER(JSModuleNamespace)
  DECL_VERIFIER(JSModuleNamespace)

  // Retrieve the value exported by [module] under the given [name]. If there is
  // no such export, return Just(undefined). If the export is uninitialized,
  // schedule an exception and return Nothing.
  V8_WARN_UNUSED_RESULT MaybeDirectHandle<Object> GetExport(
      Isolate* isolate, DirectHandle<String> name);

  bool HasExport(Isolate* isolate, DirectHandle<String> name);

  // Return the (constant) property attributes for the referenced property,
  // which is assumed to correspond to an export. If the export is
  // uninitialized, schedule an exception and return Nothing.
  static V8_WARN_UNUSED_RESULT Maybe<PropertyAttributes> GetPropertyAttributes(
      LookupIterator* it);

  static V8_WARN_UNUSED_RESULT Maybe<bool> DefineOwnProperty(
      Isolate* isolate, DirectHandle<JSModuleNamespace> o,
      DirectHandle<Object> key, PropertyDescriptor* desc,
      Maybe<ShouldThrow> should_throw);

  // In-object fields. These indices represent both the in-object field index,
  // as well as the descriptor index.
  enum {
    kToStringTagIndex,
    kInObjectFieldCount,
  };

  static const int kHeaderSize;
  // We need to include in-object fields
  // TODO(v8:8944): improve handling of in-object fields
  static const int kSize;

 public:
  TaggedMember<Module> module_;
} V8_OBJECT_END;

inline constexpr int JSModuleNamespace::kHeaderSize = sizeof(JSModuleNamespace);
inline constexpr int JSModuleNamespace::kSize =
    JSModuleNamespace::kHeaderSize + (kTaggedSize * kInObjectFieldCount);

V8_OBJECT class JSDeferredModuleNamespace : public JSModuleNamespace {
 public:
  DECL_PRINTER(JSDeferredModuleNamespace)
  DECL_VERIFIER(JSDeferredModuleNamespace)

  static void EvaluateModuleSync(
      Isolate* isolate, DirectHandle<JSDeferredModuleNamespace> holder);
  static bool TriggersEvaluation(LookupIterator* it);

  // We need to include in-object fields
  // TODO(v8:8944): improve handling of in-object fields
  static const int kSize;
} V8_OBJECT_END;

inline constexpr int JSDeferredModuleNamespace::kSize =
    sizeof(JSDeferredModuleNamespace) + (kTaggedSize * kInObjectFieldCount);

V8_OBJECT class ScriptOrModule : public Struct {
 public:
  inline Tagged<Object> resource_name() const;
  inline void set_resource_name(Tagged<Object> value,
                                WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

  inline Tagged<FixedArray> host_defined_options() const;
  inline void set_host_defined_options(
      Tagged<FixedArray> value, WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

  DECL_PRINTER(ScriptOrModule)
  DECL_VERIFIER(ScriptOrModule)

  using BodyDescriptor = StructBodyDescriptor;

 private:
  friend class TorqueGeneratedScriptOrModuleAsserts;

  TaggedMember<Object> resource_name_;
  TaggedMember<FixedArray> host_defined_options_;
} V8_OBJECT_END;

}  // namespace internal
}  // namespace v8

#include "src/objects/object-macros-undef.h"

#endif  // V8_OBJECTS_MODULE_H_
