//===-- include/flang/Semantics/symbol.h ------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef FORTRAN_SEMANTICS_SYMBOL_H_
#define FORTRAN_SEMANTICS_SYMBOL_H_

#include "type.h"
#include "flang/Common/enum-set.h"
#include "flang/Common/reference.h"
#include "flang/Common/visit.h"
#include "flang/Semantics/module-dependences.h"
#include "flang/Support/Fortran.h"
#include "llvm/ADT/DenseMapInfo.h"

#include <array>
#include <functional>
#include <list>
#include <optional>
#include <set>
#include <variant>
#include <vector>

namespace llvm {
class raw_ostream;
}
namespace Fortran::parser {
struct Expr;
struct OpenMPDeclareReductionConstruct;
struct OmpMetadirectiveDirective;
}

namespace Fortran::semantics {

/// A Symbol consists of common information (name, owner, and attributes)
/// and details information specific to the kind of symbol, represented by the
/// *Details classes.

class Scope;
class Symbol;
class ProgramTree;

using SymbolRef = common::Reference<const Symbol>;
using SymbolVector = std::vector<SymbolRef>;
using MutableSymbolRef = common::Reference<Symbol>;
using MutableSymbolVector = std::vector<MutableSymbolRef>;

// Mixin for details with OpenMP declarative constructs.
class WithOmpDeclarative {
  using OmpAtomicOrderType = common::OmpMemoryOrderType;

public:
  ENUM_CLASS(RequiresFlag, ReverseOffload, UnifiedAddress, UnifiedSharedMemory,
      DynamicAllocators);
  using RequiresFlags = common::EnumSet<RequiresFlag, RequiresFlag_enumSize>;

  bool has_ompRequires() const { return ompRequires_.has_value(); }
  const RequiresFlags *ompRequires() const {
    return ompRequires_ ? &*ompRequires_ : nullptr;
  }
  void set_ompRequires(RequiresFlags flags) { ompRequires_ = flags; }

  bool has_ompAtomicDefaultMemOrder() const {
    return ompAtomicDefaultMemOrder_.has_value();
  }
  const OmpAtomicOrderType *ompAtomicDefaultMemOrder() const {
    return ompAtomicDefaultMemOrder_ ? &*ompAtomicDefaultMemOrder_ : nullptr;
  }
  void set_ompAtomicDefaultMemOrder(OmpAtomicOrderType flags) {
    ompAtomicDefaultMemOrder_ = flags;
  }

private:
  std::optional<RequiresFlags> ompRequires_;
  std::optional<OmpAtomicOrderType> ompAtomicDefaultMemOrder_;
};

// A module or submodule.
class ModuleDetails : public WithOmpDeclarative {
public:
  ModuleDetails(bool isSubmodule = false) : isSubmodule_{isSubmodule} {}
  bool isSubmodule() const { return isSubmodule_; }
  const Scope *scope() const { return scope_; }
  const Scope *ancestor() const; // for submodule; nullptr for module
  const Scope *parent() const; // for submodule; nullptr for module
  void set_scope(const Scope *);
  bool isDefaultPrivate() const { return isDefaultPrivate_; }
  void set_isDefaultPrivate(bool yes = true) { isDefaultPrivate_ = yes; }
  std::optional<ModuleCheckSumType> moduleFileHash() const {
    return moduleFileHash_;
  }
  void set_moduleFileHash(ModuleCheckSumType x) { moduleFileHash_ = x; }
  const Symbol *previous() const { return previous_; }
  void set_previous(const Symbol *p) { previous_ = p; }

private:
  bool isSubmodule_;
  bool isDefaultPrivate_{false};
  const Scope *scope_{nullptr};
  std::optional<ModuleCheckSumType> moduleFileHash_;
  const Symbol *previous_{nullptr}; // same name, different module file hash
};

class MainProgramDetails : public WithOmpDeclarative {
public:
private:
};

class WithBindName {
public:
  const std::string *bindName() const {
    return bindName_ ? &*bindName_ : nullptr;
  }
  bool isExplicitBindName() const { return isExplicitBindName_; }
  void set_bindName(std::string &&name) { bindName_ = std::move(name); }
  void set_isExplicitBindName(bool yes) { isExplicitBindName_ = yes; }
  bool isCDefined() const { return isCDefined_; }
  void set_isCDefined(bool yes) { isCDefined_ = yes; }

private:
  std::optional<std::string> bindName_;
  bool isExplicitBindName_{false};
  bool isCDefined_{false};
};

// Device type specific OpenACC routine information
class OpenACCRoutineDeviceTypeInfo {
public:
  explicit OpenACCRoutineDeviceTypeInfo(
      Fortran::common::OpenACCDeviceType dType)
      : deviceType_{dType} {}
  bool isSeq() const { return isSeq_; }
  void set_isSeq(bool value = true) { isSeq_ = value; }
  bool isVector() const { return isVector_; }
  void set_isVector(bool value = true) { isVector_ = value; }
  bool isWorker() const { return isWorker_; }
  void set_isWorker(bool value = true) { isWorker_ = value; }
  bool isGang() const { return isGang_; }
  void set_isGang(bool value = true) { isGang_ = value; }
  unsigned gangDim() const { return gangDim_; }
  void set_gangDim(unsigned value) { gangDim_ = value; }
  const std::variant<std::string, SymbolRef> *bindName() const {
    return bindName_.has_value() ? &*bindName_ : nullptr;
  }
  const std::optional<std::variant<std::string, SymbolRef>> &
  bindNameOpt() const {
    return bindName_;
  }
  void set_bindName(std::string &&name) { bindName_.emplace(std::move(name)); }
  void set_bindName(SymbolRef symbol) { bindName_.emplace(symbol); }

  Fortran::common::OpenACCDeviceType dType() const { return deviceType_; }

  friend llvm::raw_ostream &operator<<(
      llvm::raw_ostream &, const OpenACCRoutineDeviceTypeInfo &);

private:
  bool isSeq_{false};
  bool isVector_{false};
  bool isWorker_{false};
  bool isGang_{false};
  unsigned gangDim_{0};
  // bind("name") -> std::string
  // bind(sym) -> SymbolRef (requires namemangling in lowering)
  std::optional<std::variant<std::string, SymbolRef>> bindName_;
  Fortran::common::OpenACCDeviceType deviceType_{
      Fortran::common::OpenACCDeviceType::None};
};

// OpenACC routine information. Device independent info are stored on the
// OpenACCRoutineInfo instance while device dependent info are stored
// in as objects in the OpenACCRoutineDeviceTypeInfo list.
class OpenACCRoutineInfo : public OpenACCRoutineDeviceTypeInfo {
public:
  OpenACCRoutineInfo()
      : OpenACCRoutineDeviceTypeInfo(Fortran::common::OpenACCDeviceType::None) {
  }
  bool isNohost() const { return isNohost_; }
  void set_isNohost(bool value = true) { isNohost_ = value; }
  const std::list<OpenACCRoutineDeviceTypeInfo> &deviceTypeInfos() const {
    return deviceTypeInfos_;
  }

  OpenACCRoutineDeviceTypeInfo &add_deviceTypeInfo(
      Fortran::common::OpenACCDeviceType type) {
    return add_deviceTypeInfo(OpenACCRoutineDeviceTypeInfo(type));
  }

  OpenACCRoutineDeviceTypeInfo &add_deviceTypeInfo(
      OpenACCRoutineDeviceTypeInfo &&info) {
    deviceTypeInfos_.push_back(std::move(info));
    return deviceTypeInfos_.back();
  }

  friend llvm::raw_ostream &operator<<(
      llvm::raw_ostream &, const OpenACCRoutineInfo &);

private:
  std::list<OpenACCRoutineDeviceTypeInfo> deviceTypeInfos_;
  bool isNohost_{false};
};

// A subroutine or function definition, or a subprogram interface defined
// in an INTERFACE block as part of the definition of a dummy procedure
// or a procedure pointer (with just POINTER).
class SubprogramDetails : public WithBindName, public WithOmpDeclarative {
public:
  bool isFunction() const { return result_ != nullptr; }
  bool isInterface() const { return isInterface_; }
  void set_isInterface(bool value = true) { isInterface_ = value; }
  bool isDummy() const { return isDummy_; }
  void set_isDummy(bool value = true) { isDummy_ = value; }
  Scope *entryScope() { return entryScope_; }
  const Scope *entryScope() const { return entryScope_; }
  void set_entryScope(Scope &scope) { entryScope_ = &scope; }
  const Symbol &result() const {
    CHECK(isFunction());
    return *result_;
  }
  void set_result(Symbol &result) {
    CHECK(!result_);
    result_ = &result;
  }
  const std::vector<Symbol *> &dummyArgs() const { return dummyArgs_; }
  void add_dummyArg(Symbol &symbol) { dummyArgs_.push_back(&symbol); }
  void add_alternateReturn() { dummyArgs_.push_back(nullptr); }
  const MaybeExpr &stmtFunction() const { return stmtFunction_; }
  void set_stmtFunction(SomeExpr &&expr) { stmtFunction_ = std::move(expr); }
  Symbol *moduleInterface() { return moduleInterface_; }
  const Symbol *moduleInterface() const { return moduleInterface_; }
  void set_moduleInterface(Symbol &);
  void ReplaceResult(Symbol &result) {
    CHECK(result_ != nullptr);
    result_ = &result;
  }
  bool defaultIgnoreTKR() const { return defaultIgnoreTKR_; }
  void set_defaultIgnoreTKR(bool yes) { defaultIgnoreTKR_ = yes; }
  std::optional<common::CUDASubprogramAttrs> cudaSubprogramAttrs() const {
    return cudaSubprogramAttrs_;
  }
  void set_cudaSubprogramAttrs(common::CUDASubprogramAttrs csas) {
    cudaSubprogramAttrs_ = csas;
  }
  std::vector<std::int64_t> &cudaLaunchBounds() { return cudaLaunchBounds_; }
  const std::vector<std::int64_t> &cudaLaunchBounds() const {
    return cudaLaunchBounds_;
  }
  void set_cudaLaunchBounds(std::vector<std::int64_t> &&x) {
    cudaLaunchBounds_ = std::move(x);
  }
  std::vector<std::int64_t> &cudaClusterDims() { return cudaClusterDims_; }
  const std::vector<std::int64_t> &cudaClusterDims() const {
    return cudaClusterDims_;
  }
  void set_cudaClusterDims(std::vector<std::int64_t> &&x) {
    cudaClusterDims_ = std::move(x);
  }
  const std::vector<OpenACCRoutineInfo> &openACCRoutineInfos() const {
    return openACCRoutineInfos_;
  }
  void add_openACCRoutineInfo(OpenACCRoutineInfo info) {
    openACCRoutineInfos_.push_back(info);
  }

private:
  bool isInterface_{false}; // true if this represents an interface-body
  bool isDummy_{false}; // true when interface of dummy procedure
  std::vector<Symbol *> dummyArgs_; // nullptr -> alternate return indicator
  Symbol *result_{nullptr};
  Scope *entryScope_{nullptr}; // if ENTRY, points to subprogram's scope
  MaybeExpr stmtFunction_;
  // For MODULE FUNCTION or SUBROUTINE, this is the symbol of its declared
  // interface.  For MODULE PROCEDURE, this is the declared interface if it
  // appeared in an ancestor (sub)module.
  Symbol *moduleInterface_{nullptr};
  bool defaultIgnoreTKR_{false};
  // CUDA ATTRIBUTES(...) from subroutine/function prefix
  std::optional<common::CUDASubprogramAttrs> cudaSubprogramAttrs_;
  // CUDA LAUNCH_BOUNDS(...) & CLUSTER_DIMS(...) from prefix
  std::vector<std::int64_t> cudaLaunchBounds_, cudaClusterDims_;
  // OpenACC routine information
  std::vector<OpenACCRoutineInfo> openACCRoutineInfos_;

  friend llvm::raw_ostream &operator<<(
      llvm::raw_ostream &, const SubprogramDetails &);
};

// For SubprogramNameDetails, the kind indicates whether it is the name
// of a module subprogram or an internal subprogram or ENTRY.
ENUM_CLASS(SubprogramKind, Module, Internal)

// Symbol with SubprogramNameDetails is created when we scan for module and
// internal procedure names, to record that there is a subprogram with this
// name. Later they are replaced by SubprogramDetails with dummy and result
// type information.
class SubprogramNameDetails {
public:
  SubprogramNameDetails(SubprogramKind kind, ProgramTree &node)
      : kind_{kind}, node_{node} {}
  SubprogramNameDetails() = delete;
  SubprogramKind kind() const { return kind_; }
  ProgramTree &node() const { return *node_; }

private:
  SubprogramKind kind_;
  common::Reference<ProgramTree> node_;
};

// A name from an entity-decl -- could be object or function.
class EntityDetails : public WithBindName {
public:
  explicit EntityDetails(bool isDummy = false) : isDummy_{isDummy} {}
  const DeclTypeSpec *type() const { return type_; }
  void set_type(const DeclTypeSpec &);
  void ReplaceType(const DeclTypeSpec &);
  bool isDummy() const { return isDummy_; }
  void set_isDummy(bool value = true) { isDummy_ = value; }
  bool isFuncResult() const { return isFuncResult_; }
  void set_funcResult(bool x) { isFuncResult_ = x; }

private:
  bool isDummy_{false};
  bool isFuncResult_{false};
  const DeclTypeSpec *type_{nullptr};
  friend llvm::raw_ostream &operator<<(
      llvm::raw_ostream &, const EntityDetails &);
};

// Symbol is associated with a name or expression in an ASSOCIATE,
// SELECT TYPE, or SELECT RANK construct.
class AssocEntityDetails : public EntityDetails {
public:
  AssocEntityDetails() {}
  explicit AssocEntityDetails(SomeExpr &&expr) : expr_{std::move(expr)} {}
  AssocEntityDetails(const AssocEntityDetails &) = default;
  AssocEntityDetails(AssocEntityDetails &&) = default;
  AssocEntityDetails &operator=(const AssocEntityDetails &) = default;
  AssocEntityDetails &operator=(AssocEntityDetails &&) = default;
  const MaybeExpr &expr() const { return expr_; }

  // SELECT RANK's rank cases will return a populated result for
  // RANK(n) and RANK(*), and IsAssumedRank() will be true for
  // RANK DEFAULT.
  std::optional<int> rank() const {
    int r{rank_.value_or(0)};
    if (r == isAssumedSize) {
      return 1; // RANK(*)
    } else if (r == isAssumedRank) {
      return std::nullopt; // RANK DEFAULT
    } else {
      return rank_;
    }
  }
  bool IsAssumedSize() const { return rank_.value_or(0) == isAssumedSize; }
  bool IsAssumedRank() const { return rank_.value_or(0) == isAssumedRank; }
  bool isTypeGuard() const { return isTypeGuard_; }
  void set_rank(int rank);
  void set_IsAssumedSize();
  void set_IsAssumedRank();
  void set_isTypeGuard(bool yes = true);

private:
  MaybeExpr expr_;
  // Populated for SELECT RANK with rank (n>=0) for RANK(n),
  // isAssumedSize for RANK(*), or isAssumedRank for RANK DEFAULT.
  static constexpr int isAssumedSize{-1}; // RANK(*)
  static constexpr int isAssumedRank{-2}; // RANK DEFAULT
  std::optional<int> rank_;
  bool isTypeGuard_{false}; // TYPE IS or CLASS IS, but not CLASS(DEFAULT)
};
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const AssocEntityDetails &);

// An entity known to be an object.
class ObjectEntityDetails : public EntityDetails {
public:
  explicit ObjectEntityDetails(EntityDetails &&);
  ObjectEntityDetails(const ObjectEntityDetails &) = default;
  ObjectEntityDetails(ObjectEntityDetails &&) = default;
  ObjectEntityDetails &operator=(const ObjectEntityDetails &) = default;
  ObjectEntityDetails(bool isDummy = false) : EntityDetails(isDummy) {}
  MaybeExpr &init() { return init_; }
  const MaybeExpr &init() const { return init_; }
  void set_init(MaybeExpr &&expr) { init_ = std::move(expr); }
  const parser::Expr *unanalyzedPDTComponentInit() const {
    return unanalyzedPDTComponentInit_;
  }
  void set_unanalyzedPDTComponentInit(const parser::Expr *expr) {
    unanalyzedPDTComponentInit_ = expr;
  }
  ArraySpec &shape() { return shape_; }
  const ArraySpec &shape() const { return shape_; }
  ArraySpec &coshape() { return coshape_; }
  const ArraySpec &coshape() const { return coshape_; }
  void set_shape(const ArraySpec &);
  void set_coshape(const ArraySpec &);
  const Symbol *commonBlock() const { return commonBlock_; }
  void set_commonBlock(const Symbol &commonBlock) {
    commonBlock_ = &commonBlock;
  }
  common::IgnoreTKRSet ignoreTKR() const { return ignoreTKR_; }
  void set_ignoreTKR(common::IgnoreTKRSet set) { ignoreTKR_ = set; }
  bool IsArray() const { return !shape_.empty(); }
  bool IsCoarray() const { return !coshape_.empty(); }
  bool IsAssumedShape() const {
    return isDummy() && shape_.CanBeAssumedShape();
  }
  bool CanBeDeferredShape() const { return shape_.CanBeDeferredShape(); }
  bool IsAssumedRank() const { return isDummy() && shape_.IsAssumedRank(); }
  std::optional<common::CUDADataAttr> cudaDataAttr() const {
    return cudaDataAttr_;
  }
  void set_cudaDataAttr(std::optional<common::CUDADataAttr> attr) {
    cudaDataAttr_ = attr;
  }

private:
  MaybeExpr init_;
  const parser::Expr *unanalyzedPDTComponentInit_{nullptr};
  ArraySpec shape_;
  ArraySpec coshape_;
  common::IgnoreTKRSet ignoreTKR_;
  const Symbol *commonBlock_{nullptr}; // common block this object is in
  std::optional<common::CUDADataAttr> cudaDataAttr_;
  friend llvm::raw_ostream &operator<<(
      llvm::raw_ostream &, const ObjectEntityDetails &);
};

// Mixin for details with passed-object dummy argument.
// If a procedure pointer component or type-bound procedure does not have
// the NOPASS attribute on its symbol, then PASS is assumed; the name
// is optional; if it is missing, the first dummy argument of the procedure's
// interface is the passed-object dummy argument.
class WithPassArg {
public:
  std::optional<SourceName> passName() const { return passName_; }
  void set_passName(const SourceName &passName) { passName_ = passName; }

private:
  std::optional<SourceName> passName_;
};

// A procedure pointer (other than one defined with POINTER and an
// INTERFACE block), a dummy procedure (without an INTERFACE but with
// EXTERNAL or use in a procedure reference), or external procedure.
class ProcEntityDetails : public EntityDetails, public WithPassArg {
public:
  ProcEntityDetails() = default;
  explicit ProcEntityDetails(EntityDetails &&);
  ProcEntityDetails(const ProcEntityDetails &) = default;
  ProcEntityDetails(ProcEntityDetails &&) = default;
  ProcEntityDetails &operator=(const ProcEntityDetails &) = default;

  const Symbol *rawProcInterface() const { return rawProcInterface_; }
  const Symbol *procInterface() const { return procInterface_; }
  void set_procInterfaces(const Symbol &raw, const Symbol &resolved) {
    rawProcInterface_ = &raw;
    procInterface_ = &resolved;
  }
  inline bool HasExplicitInterface() const;

  // Be advised: !init().has_value() => uninitialized pointer,
  // while *init() == nullptr => explicit NULL() initialization.
  std::optional<const Symbol *> init() const { return init_; }
  void set_init(const Symbol &symbol) { init_ = &symbol; }
  void set_init(std::nullptr_t) { init_ = nullptr; }
  bool isCUDAKernel() const { return isCUDAKernel_; }
  void set_isCUDAKernel(bool yes = true) { isCUDAKernel_ = yes; }
  std::optional<SourceName> usedAsProcedureHere() const {
    return usedAsProcedureHere_;
  }
  void set_usedAsProcedureHere(SourceName here) { usedAsProcedureHere_ = here; }

private:
  const Symbol *rawProcInterface_{nullptr};
  const Symbol *procInterface_{nullptr};
  std::optional<const Symbol *> init_;
  bool isCUDAKernel_{false};
  std::optional<SourceName> usedAsProcedureHere_;
  friend llvm::raw_ostream &operator<<(
      llvm::raw_ostream &, const ProcEntityDetails &);
};

// These derived type details represent the characteristics of a derived
// type definition that are shared by all instantiations of that type.
// The DerivedTypeSpec instances whose type symbols share these details
// each own a scope into which the components' symbols have been cloned
// and specialized for each distinct set of type parameter values.
class DerivedTypeDetails {
public:
  const SymbolVector &paramNameOrder() const { return paramNameOrder_; }
  const SymbolVector &paramDeclOrder() const { return paramDeclOrder_; }
  bool sequence() const { return sequence_; }
  bool isDECStructure() const { return isDECStructure_; }
  std::map<SourceName, SymbolRef> &finals() { return finals_; }
  const std::map<SourceName, SymbolRef> &finals() const { return finals_; }
  bool isForwardReferenced() const { return isForwardReferenced_; }
  void add_paramNameOrder(const Symbol &symbol) {
    paramNameOrder_.push_back(symbol);
  }
  void add_paramDeclOrder(const Symbol &symbol) {
    paramDeclOrder_.push_back(symbol);
  }
  void add_component(const Symbol &);
  void set_sequence(bool x = true) { sequence_ = x; }
  void set_isDECStructure(bool x = true) { isDECStructure_ = x; }
  void set_isForwardReferenced(bool value) { isForwardReferenced_ = value; }
  const std::list<SourceName> &componentNames() const {
    return componentNames_;
  }

  // If this derived type extends another, locate the parent component's symbol.
  const Symbol *GetParentComponent(const Scope &) const;

  std::optional<SourceName> GetParentComponentName() const {
    if (componentNames_.empty()) {
      return std::nullopt;
    } else {
      return componentNames_.front();
    }
  }

  const Symbol *GetFinalForRank(int) const;

private:
  // These are (1) the symbols of the derived type parameters in the order
  // in which they appear on the type definition statement(s), and (2) the
  // symbols that correspond to those names in the order in which their
  // declarations appear in the derived type definition(s).
  SymbolVector paramNameOrder_;
  SymbolVector paramDeclOrder_;
  // These are the names of the derived type's components in component
  // order.  A parent component, if any, appears first in this list.
  std::list<SourceName> componentNames_;
  std::map<SourceName, SymbolRef> finals_; // FINAL :: subr
  bool sequence_{false};
  bool isDECStructure_{false};
  bool isForwardReferenced_{false};
  friend llvm::raw_ostream &operator<<(
      llvm::raw_ostream &, const DerivedTypeDetails &);
};

class ProcBindingDetails : public WithPassArg {
public:
  explicit ProcBindingDetails(const Symbol &symbol) : symbol_{symbol} {}
  const Symbol &symbol() const { return symbol_; }
  void ReplaceSymbol(const Symbol &symbol) { symbol_ = symbol; }
  int numPrivatesNotOverridden() const { return numPrivatesNotOverridden_; }
  void set_numPrivatesNotOverridden(int n) { numPrivatesNotOverridden_ = n; }

private:
  SymbolRef symbol_; // procedure bound to; may be forward
  // Homonymous private bindings in ancestor types from other modules
  int numPrivatesNotOverridden_{0};
};

class NamelistDetails {
public:
  const SymbolVector &objects() const { return objects_; }
  void add_object(const Symbol &object) { objects_.push_back(object); }
  void add_objects(const SymbolVector &objects) {
    objects_.insert(objects_.end(), objects.begin(), objects.end());
  }

private:
  SymbolVector objects_;
};

class CommonBlockDetails : public WithBindName {
public:
  MutableSymbolVector &objects() { return objects_; }
  const MutableSymbolVector &objects() const { return objects_; }
  void add_object(Symbol &object) { objects_.emplace_back(object); }
  void replace_object(Symbol &object, unsigned index) {
    CHECK(index < (unsigned)objects_.size());
    objects_[index] = object;
  }
  std::size_t alignment() const { return alignment_; }
  void set_alignment(std::size_t alignment) { alignment_ = alignment; }

private:
  MutableSymbolVector objects_;
  std::size_t alignment_{0}; // required alignment in bytes
};

class MiscDetails {
public:
  ENUM_CLASS(Kind, None, ConstructName, ScopeName, PassName, ComplexPartRe,
      ComplexPartIm, KindParamInquiry, LenParamInquiry, SelectRankAssociateName,
      SelectTypeAssociateName, TypeBoundDefinedOp);
  MiscDetails(Kind kind) : kind_{kind} {}
  Kind kind() const { return kind_; }

private:
  Kind kind_;
};

class TypeParamDetails {
public:
  TypeParamDetails() = default;
  TypeParamDetails(const TypeParamDetails &) = default;
  TypeParamDetails &operator=(const TypeParamDetails &) = default;
  std::optional<common::TypeParamAttr> attr() const { return attr_; }
  TypeParamDetails &set_attr(common::TypeParamAttr);
  MaybeIntExpr &init() { return init_; }
  const MaybeIntExpr &init() const { return init_; }
  void set_init(MaybeIntExpr &&expr) { init_ = std::move(expr); }
  const DeclTypeSpec *type() const { return type_; }
  TypeParamDetails &set_type(const DeclTypeSpec &);
  void ReplaceType(const DeclTypeSpec &);

private:
  std::optional<common::TypeParamAttr> attr_;
  MaybeIntExpr init_;
  const DeclTypeSpec *type_{nullptr};
};

// Record the USE of a symbol: location is where (USE statement or renaming);
// symbol is in the USEd module.
class UseDetails {
public:
  UseDetails(const SourceName &location, const Symbol &symbol)
      : location_{location}, symbol_{symbol} {}
  const SourceName &location() const { return location_; }
  const Symbol &symbol() const { return symbol_; }

private:
  SourceName location_;
  SymbolRef symbol_;
};

// A symbol with ambiguous use-associations. Record where they were so
// we can report the error if it is used.
class UseErrorDetails {
public:
  UseErrorDetails(const UseDetails &);
  UseErrorDetails &add_occurrence(const SourceName &, const Symbol &);
  using ListType = std::list<std::pair<SourceName, const Symbol *>>;
  const ListType occurrences() const { return occurrences_; };

private:
  ListType occurrences_;
};

// A symbol host-associated from an enclosing scope.
class HostAssocDetails {
public:
  HostAssocDetails(const Symbol &symbol) : symbol_{symbol} {}
  const Symbol &symbol() const { return symbol_; }
  bool implicitOrSpecExprError{false};
  bool implicitOrExplicitTypeError{false};

private:
  SymbolRef symbol_;
};

// A GenericKind is one of: generic name, defined operator,
// defined assignment, intrinsic operator, or defined I/O.
struct GenericKind {
  ENUM_CLASS(OtherKind, Name, DefinedOp, Assignment, Concat)
  GenericKind() : u{OtherKind::Name} {}
  template <typename T> GenericKind(const T &x) { u = x; }
  bool IsName() const { return Is(OtherKind::Name); }
  bool IsAssignment() const { return Is(OtherKind::Assignment); }
  bool IsDefinedOperator() const { return Is(OtherKind::DefinedOp); }
  bool IsIntrinsicOperator() const;
  bool IsOperator() const;
  std::string ToString() const;
  static SourceName AsFortran(common::DefinedIo);
  std::variant<OtherKind, common::NumericOperator, common::LogicalOperator,
      common::RelationalOperator, common::DefinedIo>
      u;

private:
  template <typename T> bool Has() const {
    return std::holds_alternative<T>(u);
  }
  bool Is(OtherKind) const;
};

// A generic interface or type-bound generic.
class GenericDetails {
public:
  GenericDetails() {}

  GenericKind kind() const { return kind_; }
  void set_kind(GenericKind kind) { kind_ = kind; }

  const SymbolVector &specificProcs() const { return specificProcs_; }
  const std::vector<SourceName> &bindingNames() const { return bindingNames_; }
  void AddSpecificProc(const Symbol &, SourceName bindingName);
  const SymbolVector &uses() const { return uses_; }

  // specific and derivedType indicate a specific procedure or derived type
  // with the same name as this generic. Only one of them may be set in
  // a scope that declares them, but both can be set during USE association
  // when generics are combined.
  Symbol *specific() { return specific_; }
  const Symbol *specific() const { return specific_; }
  void set_specific(Symbol &specific);
  void clear_specific();
  Symbol *derivedType() { return derivedType_; }
  const Symbol *derivedType() const { return derivedType_; }
  void set_derivedType(Symbol &derivedType);
  void clear_derivedType();
  void AddUse(const Symbol &);

  // Copy in specificProcs, specific, and derivedType from another generic
  void CopyFrom(const GenericDetails &);

  // Check that specific is one of the specificProcs. If not, return the
  // specific as a raw pointer.
  const Symbol *CheckSpecific() const;
  Symbol *CheckSpecific();

private:
  GenericKind kind_;
  // all of the specific procedures for this generic
  SymbolVector specificProcs_;
  std::vector<SourceName> bindingNames_;
  // Symbols used from other modules merged into this one
  SymbolVector uses_;
  // a specific procedure with the same name as this generic, if any
  Symbol *specific_{nullptr};
  // a derived type with the same name as this generic, if any
  Symbol *derivedType_{nullptr};
};
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const GenericDetails &);

// Used for OpenMP DECLARE REDUCTION, it holds the information
// needed to resolve which declaration (there could be multiple
// with the same name) to use for a given type.
class UserReductionDetails {
public:
  using TypeVector = std::vector<const DeclTypeSpec *>;
  using DeclInfo = std::variant<const parser::OpenMPDeclareReductionConstruct *,
      const parser::OmpMetadirectiveDirective *>;
  using DeclVector = std::vector<DeclInfo>;

  UserReductionDetails() = default;

  void AddType(const DeclTypeSpec &type) { typeList_.push_back(&type); }
  const TypeVector &GetTypeList() const { return typeList_; }

  bool SupportsType(const DeclTypeSpec &type) const {
    // We have to compare the actual type, not the pointer, as some
    // types are not guaranteed to be the same object.
    for (auto t : typeList_) {
      if (*t == type) {
        return true;
      }
    }
    return false;
  }

  void AddDecl(const DeclInfo &decl) { declList_.emplace_back(decl); }
  const DeclVector &GetDeclList() const { return declList_; }

private:
  TypeVector typeList_;
  DeclVector declList_;
};

class UnknownDetails {};

using Details = std::variant<UnknownDetails, MainProgramDetails, ModuleDetails,
    SubprogramDetails, SubprogramNameDetails, EntityDetails,
    ObjectEntityDetails, ProcEntityDetails, AssocEntityDetails,
    DerivedTypeDetails, UseDetails, UseErrorDetails, HostAssocDetails,
    GenericDetails, ProcBindingDetails, NamelistDetails, CommonBlockDetails,
    TypeParamDetails, MiscDetails, UserReductionDetails>;
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Details &);
std::string DetailsToString(const Details &);

class Symbol {
public:
  ENUM_CLASS(Flag,
      Function, // symbol is a function or statement function
      Subroutine, // symbol is a subroutine
      StmtFunction, // symbol is a statement function or result
      Implicit, // symbol is implicitly typed
      ImplicitOrError, // symbol must be implicitly typed or it's an error
      ModFile, // symbol came from .mod file
      ParentComp, // symbol is the "parent component" of an extended type
      CrayPointer, CrayPointee,
      LocalityLocal, // named in LOCAL locality-spec
      LocalityLocalInit, // named in LOCAL_INIT locality-spec
      LocalityReduce, // named in REDUCE locality-spec
      LocalityShared, // named in SHARED locality-spec
      InDataStmt, // initialized in a DATA statement, =>object, or /init/
      InNamelist, // in a Namelist group
      InCommonBlock, // referenced in a common block
      EntryDummyArgument,
      CompilerCreated, // A compiler created symbol
      // For compiler created symbols that are constant but cannot legally have
      // the PARAMETER attribute.
      ReadOnly,
      // OpenACC data-sharing attribute
      AccPrivate, AccFirstPrivate, AccShared,
      // OpenACC data-mapping attribute
      AccCopy, AccCopyIn, AccCopyInReadOnly, AccCopyOut, AccCreate, AccDelete,
      AccPresent, AccLink, AccDeviceResident, AccDevicePtr,
      // OpenACC declare
      AccDeclare,
      // OpenACC data-movement attribute
      AccDevice, AccHost, AccSelf,
      // OpenACC miscellaneous flags
      AccCommonBlock, AccThreadPrivate, AccReduction, AccNone, AccPreDetermined,
      // OpenMP data-sharing attribute
      OmpShared, OmpPrivate, OmpLinear, OmpFirstPrivate, OmpLastPrivate,
      OmpGroupPrivate,
      // OpenMP data-mapping attribute
      OmpMapTo, OmpMapFrom, OmpMapToFrom, OmpMapStorage, OmpMapDelete,
      OmpUseDevicePtr, OmpUseDeviceAddr, OmpIsDevicePtr, OmpHasDeviceAddr,
      // OpenMP data-copying attribute
      OmpCopyIn, OmpCopyPrivate,
      // OpenMP miscellaneous flags
      OmpCommonBlock, OmpReduction, OmpInReduction, OmpAligned, OmpNontemporal,
      OmpAllocate, OmpDeclarativeAllocateDirective,
      OmpExecutableAllocateDirective, OmpDeclareSimd, OmpDeclareTarget,
      OmpThreadprivate, OmpDeclareReduction, OmpFlushed, OmpCriticalLock,
      OmpIfSpecified, OmpNone, OmpPreDetermined, OmpExplicit, OmpImplicit,
      OmpDependObject, OmpInclusiveScan, OmpExclusiveScan, OmpInScanReduction,
      OmpUniform);
  using Flags = common::EnumSet<Flag, Flag_enumSize>;

  const Scope &owner() const { return *owner_; }
  const SourceName &name() const { return name_; }
  Attrs &attrs() { return attrs_; }
  const Attrs &attrs() const { return attrs_; }
  Attrs &implicitAttrs() { return implicitAttrs_; }
  const Attrs &implicitAttrs() const { return implicitAttrs_; }
  Flags &flags() { return flags_; }
  const Flags &flags() const { return flags_; }
  bool test(Flag flag) const { return flags_.test(flag); }
  void set(Flag flag, bool value = true) { flags_.set(flag, value); }
  // The Scope introduced by this symbol, if any.
  Scope *scope() { return scope_; }
  const Scope *scope() const { return scope_; }
  void set_scope(Scope *scope) { scope_ = scope; }
  std::size_t size() const { return size_; }
  void set_size(std::size_t size) { size_ = size; }
  std::size_t offset() const { return offset_; }
  void set_offset(std::size_t offset) { offset_ = offset; }
  // Give the symbol a name with a different source location but same chars.
  void ReplaceName(const SourceName &);
  static std::string OmpFlagToClauseName(Flag ompFlag);

  // Does symbol have this type of details?
  template <typename D> bool has() const {
    return std::holds_alternative<D>(details_);
  }

  // Return a non-owning pointer to details if it is type D, else nullptr.
  template <typename D> D *detailsIf() { return std::get_if<D>(&details_); }
  template <typename D> const D *detailsIf() const {
    return std::get_if<D>(&details_);
  }

  // Return a reference to the details which must be of type D.
  template <typename D> D &get() {
    return const_cast<D &>(const_cast<const Symbol *>(this)->get<D>());
  }
  template <typename D> const D &get() const {
    const auto *p{detailsIf<D>()};
    CHECK(p);
    return *p;
  }

  Details &details() { return details_; }
  const Details &details() const { return details_; }
  // Assign the details of the symbol from one of the variants.
  // Only allowed in certain cases.
  void set_details(Details &&);

  // Can the details of this symbol be replaced with the given details?
  bool CanReplaceDetails(const Details &details) const;

  // Follow use-associations and host-associations to get the ultimate entity.
  inline Symbol &GetUltimate();
  inline const Symbol &GetUltimate() const;

  inline DeclTypeSpec *GetType();
  inline const DeclTypeSpec *GetType() const;
  void SetType(const DeclTypeSpec &);

  const std::string *GetBindName() const;
  void SetBindName(std::string &&);
  bool GetIsExplicitBindName() const;
  void SetIsExplicitBindName(bool);
  void SetIsCDefined(bool);
  bool IsFuncResult() const;
  bool IsObjectArray() const;
  const ArraySpec *GetShape() const;
  bool IsSubprogram() const;
  bool IsFromModFile() const;
  bool HasExplicitInterface() const {
    return common::visit(
        common::visitors{
            [](const SubprogramDetails &) { return true; },
            [](const SubprogramNameDetails &) { return true; },
            [&](const ProcEntityDetails &x) {
              return attrs_.test(Attr::INTRINSIC) || x.HasExplicitInterface();
            },
            [](const ProcBindingDetails &x) {
              return x.symbol().HasExplicitInterface();
            },
            [](const UseDetails &x) {
              return x.symbol().HasExplicitInterface();
            },
            [](const HostAssocDetails &x) {
              return x.symbol().HasExplicitInterface();
            },
            [](const GenericDetails &x) {
              return x.specific() && x.specific()->HasExplicitInterface();
            },
            [](const auto &) { return false; },
        },
        details_);
  }
  bool HasLocalLocality() const {
    return test(Flag::LocalityLocal) || test(Flag::LocalityLocalInit);
  }

  bool operator==(const Symbol &that) const { return this == &that; }
  bool operator!=(const Symbol &that) const { return !(*this == that); }

  int Rank() const { return RankImpl(); }
  int Corank() const { return CorankImpl(); }

  // If there is a parent component, return a pointer to its derived type spec.
  // The Scope * argument defaults to this->scope_ but should be overridden
  // for a parameterized derived type instantiation with the instance's scope.
  const DerivedTypeSpec *GetParentTypeSpec(const Scope * = nullptr) const;

  // If a derived type's symbol refers to an extended derived type,
  // return the parent component's symbol.  The scope of the derived type
  // can be overridden.
  const Symbol *GetParentComponent(const Scope * = nullptr) const;

  SemanticsContext &GetSemanticsContext() const;
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
  LLVM_DUMP_METHOD void dump() const;
#endif

private:
  const Scope *owner_;
  SourceName name_;
  Attrs attrs_;
  Attrs implicitAttrs_; // subset of attrs_ that were not explicit
  Flags flags_;
  Scope *scope_{nullptr};
  std::size_t size_{0}; // size in bytes
  std::size_t offset_{0}; // byte offset in scope or common block
  Details details_;

  Symbol() {} // only created in class Symbols
  std::string GetDetailsName() const;
  friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Symbol &);
  friend llvm::raw_ostream &DumpForUnparse(
      llvm::raw_ostream &, const Symbol &, bool);

  static constexpr int startRecursionDepth{100};

  inline const DeclTypeSpec *GetTypeImpl(int depth = startRecursionDepth) const;
  inline int RankImpl(int depth = startRecursionDepth) const {
    if (depth-- == 0) {
      return 0;
    }
    return common::visit(
        common::visitors{
            [&](const SubprogramDetails &sd) {
              return sd.isFunction() ? sd.result().RankImpl(depth) : 0;
            },
            [](const GenericDetails &) {
              return 0; /*TODO*/
            },
            [&](const ProcBindingDetails &x) {
              return x.symbol().RankImpl(depth);
            },
            [&](const UseDetails &x) { return x.symbol().RankImpl(depth); },
            [&](const HostAssocDetails &x) {
              return x.symbol().RankImpl(depth);
            },
            [](const ObjectEntityDetails &oed) { return oed.shape().Rank(); },
            [&](const ProcEntityDetails &ped) {
              const Symbol *iface{ped.procInterface()};
              return iface ? iface->RankImpl(depth) : 0;
            },
            [](const AssocEntityDetails &aed) {
              if (auto assocRank{aed.rank()}) {
                // RANK(n) & RANK(*)
                return *assocRank;
              } else if (aed.IsAssumedRank()) {
                // RANK DEFAULT
                return 0;
              } else if (const auto &expr{aed.expr()}) {
                return expr->Rank();
              } else {
                return 0;
              }
            },
            [](const auto &) { return 0; },
        },
        details_);
  }
  inline int CorankImpl(int depth = startRecursionDepth) const {
    if (depth-- == 0) {
      return 0;
    }
    return common::visit(
        common::visitors{
            [&](const SubprogramDetails &sd) {
              return sd.isFunction() ? sd.result().CorankImpl(depth) : 0;
            },
            [](const GenericDetails &) { return 0; },
            [&](const ProcEntityDetails &ped) {
              const Symbol *iface{ped.procInterface()};
              return iface ? iface->CorankImpl(depth) : 0;
            },
            [&](const UseDetails &x) { return x.symbol().CorankImpl(depth); },
            [&](const HostAssocDetails &x) {
              return x.symbol().CorankImpl(depth);
            },
            [](const ObjectEntityDetails &oed) { return oed.coshape().Rank(); },
            [](const AssocEntityDetails &aed) {
              return aed.expr() ? aed.expr()->Corank() : 0;
            },
            [](const auto &) { return 0; },
        },
        details_);
  }
  template <std::size_t> friend class Symbols;
  template <class, std::size_t> friend class std::array;
};

llvm::raw_ostream &operator<<(llvm::raw_ostream &, Symbol::Flag);

// Manage memory for all symbols. BLOCK_SIZE symbols at a time are allocated.
// Make() returns a reference to the next available one. They are never
// deleted.
template <std::size_t BLOCK_SIZE> class Symbols {
public:
  Symbol &Make(const Scope &owner, const SourceName &name, const Attrs &attrs,
      Details &&details) {
    Symbol &symbol = Get();
    symbol.owner_ = &owner;
    symbol.name_ = name;
    symbol.attrs_ = attrs;
    symbol.details_ = std::move(details);
    return symbol;
  }

private:
  using blockType = std::array<Symbol, BLOCK_SIZE>;
  std::list<blockType *> blocks_;
  std::size_t nextIndex_{0};
  blockType *currBlock_{nullptr};

  Symbol &Get() {
    if (nextIndex_ == 0) {
      blocks_.push_back(new blockType());
      currBlock_ = blocks_.back();
    }
    Symbol &result = (*currBlock_)[nextIndex_];
    if (++nextIndex_ >= BLOCK_SIZE) {
      nextIndex_ = 0; // allocate a new block next time
    }
    return result;
  }
};

// Define a few member functions here in the header so that they
// can be used by lib/Evaluate without inducing a dependence cycle
// between the two shared libraries.

inline bool ProcEntityDetails::HasExplicitInterface() const {
  return procInterface_ && procInterface_->HasExplicitInterface();
}

inline Symbol &Symbol::GetUltimate() {
  return const_cast<Symbol &>(const_cast<const Symbol *>(this)->GetUltimate());
}
inline const Symbol &Symbol::GetUltimate() const {
  if (const auto *details{detailsIf<UseDetails>()}) {
    return details->symbol().GetUltimate();
  } else if (const auto *details{detailsIf<HostAssocDetails>()}) {
    return details->symbol().GetUltimate();
  } else {
    return *this;
  }
}

inline DeclTypeSpec *Symbol::GetType() {
  return const_cast<DeclTypeSpec *>(
      const_cast<const Symbol *>(this)->GetType());
}

inline const DeclTypeSpec *Symbol::GetTypeImpl(int depth) const {
  if (depth-- == 0) {
    return nullptr;
  }
  return common::visit(
      common::visitors{
          [](const EntityDetails &x) { return x.type(); },
          [](const ObjectEntityDetails &x) { return x.type(); },
          [](const AssocEntityDetails &x) { return x.type(); },
          [&](const SubprogramDetails &x) {
            return x.isFunction() ? x.result().GetTypeImpl(depth) : nullptr;
          },
          [&](const ProcEntityDetails &x) {
            const Symbol *symbol{x.procInterface()};
            return symbol ? symbol->GetTypeImpl(depth) : x.type();
          },
          [&](const ProcBindingDetails &x) {
            return x.symbol().GetTypeImpl(depth);
          },
          [](const TypeParamDetails &x) { return x.type(); },
          [&](const UseDetails &x) { return x.symbol().GetTypeImpl(depth); },
          [&](const HostAssocDetails &x) {
            return x.symbol().GetTypeImpl(depth);
          },
          [](const auto &) -> const DeclTypeSpec * { return nullptr; },
      },
      details_);
}

inline const DeclTypeSpec *Symbol::GetType() const { return GetTypeImpl(); }

// Sets and maps keyed by Symbols

struct SymbolAddressCompare {
  bool operator()(const SymbolRef &x, const SymbolRef &y) const {
    return &*x < &*y;
  }
  bool operator()(const MutableSymbolRef &x, const MutableSymbolRef &y) const {
    return &*x < &*y;
  }
};

// Symbol comparison is usually based on the order of cooked source
// stream creation and, when both are from the same cooked source,
// their positions in that cooked source stream.
// Don't use this comparator or SourceOrderedSymbolSet to hold
// Symbols that might be subject to ReplaceName().
struct SymbolSourcePositionCompare {
  // These functions are implemented in Evaluate/tools.cpp to
  // satisfy complicated shared library interdependency.
  bool operator()(const SymbolRef &, const SymbolRef &) const;
  bool operator()(const MutableSymbolRef &, const MutableSymbolRef &) const;
};

struct SymbolOffsetCompare {
  bool operator()(const SymbolRef &, const SymbolRef &) const;
  bool operator()(const MutableSymbolRef &, const MutableSymbolRef &) const;
};

using UnorderedSymbolSet = std::set<SymbolRef, SymbolAddressCompare>;
using SourceOrderedSymbolSet = std::set<SymbolRef, SymbolSourcePositionCompare>;

template <typename A>
SourceOrderedSymbolSet OrderBySourcePosition(const A &container) {
  SourceOrderedSymbolSet result;
  for (SymbolRef x : container) {
    result.emplace(x);
  }
  return result;
}

} // namespace Fortran::semantics

// Define required  info so that SymbolRef can be used inside llvm::DenseMap.
namespace llvm {
template <> struct DenseMapInfo<Fortran::semantics::SymbolRef> {
  static inline Fortran::semantics::SymbolRef getEmptyKey() {
    auto ptr = DenseMapInfo<const Fortran::semantics::Symbol *>::getEmptyKey();
    return *reinterpret_cast<Fortran::semantics::SymbolRef *>(&ptr);
  }

  static inline Fortran::semantics::SymbolRef getTombstoneKey() {
    auto ptr =
        DenseMapInfo<const Fortran::semantics::Symbol *>::getTombstoneKey();
    return *reinterpret_cast<Fortran::semantics::SymbolRef *>(&ptr);
  }

  static unsigned getHashValue(const Fortran::semantics::SymbolRef &sym) {
    return DenseMapInfo<const Fortran::semantics::Symbol *>::getHashValue(
        &sym.get());
  }

  static bool isEqual(const Fortran::semantics::SymbolRef &LHS,
      const Fortran::semantics::SymbolRef &RHS) {
    return LHS == RHS;
  }
};
} // namespace llvm
#endif // FORTRAN_SEMANTICS_SYMBOL_H_
