//===-- TypeSystemClang.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 LLDB_SOURCE_PLUGINS_TYPESYSTEM_CLANG_TYPESYSTEMCLANG_H
#define LLDB_SOURCE_PLUGINS_TYPESYSTEM_CLANG_TYPESYSTEMCLANG_H

#include <cstdint>

#include <functional>
#include <initializer_list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTFwd.h"
#include "clang/AST/TemplateBase.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/ADT/APSInt.h"
#include "llvm/ADT/SmallVector.h"

#include "Plugins/ExpressionParser/Clang/ClangPersistentVariables.h"
#include "lldb/Expression/ExpressionVariable.h"
#include "lldb/Symbol/CompilerType.h"
#include "lldb/Symbol/TypeSystem.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/Flags.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Logging.h"
#include "lldb/lldb-enumerations.h"

class DWARFASTParserClang;
class PDBASTParser;

namespace clang {
class FileManager;
class HeaderSearch;
class ModuleMap;
} // namespace clang

namespace lldb_private {

class ClangASTMetadata;
class ClangASTSource;
class Declaration;

/// A Clang module ID.
class OptionalClangModuleID {
  unsigned m_id = 0;

public:
  OptionalClangModuleID() = default;
  explicit OptionalClangModuleID(unsigned id) : m_id(id) {}
  bool HasValue() const { return m_id != 0; }
  unsigned GetValue() const { return m_id; }
};

/// The implementation of lldb::Type's m_payload field for TypeSystemClang.
class TypePayloadClang {
  /// The Layout is as follows:
  /// \verbatim
  /// bit 0..30 ... Owning Module ID.
  /// bit 31 ...... IsCompleteObjCClass.
  /// \endverbatim
  Type::Payload m_payload = 0;

public:
  TypePayloadClang() = default;
  explicit TypePayloadClang(OptionalClangModuleID owning_module,
                            bool is_complete_objc_class = false);
  explicit TypePayloadClang(uint32_t opaque_payload) : m_payload(opaque_payload) {}
  operator Type::Payload() { return m_payload; }

  static constexpr unsigned ObjCClassBit = 1 << 31;
  bool IsCompleteObjCClass() { return Flags(m_payload).Test(ObjCClassBit); }
  void SetIsCompleteObjCClass(bool is_complete_objc_class) {
    m_payload = is_complete_objc_class ? Flags(m_payload).Set(ObjCClassBit)
                                       : Flags(m_payload).Clear(ObjCClassBit);
  }
  OptionalClangModuleID GetOwningModule() {
    return OptionalClangModuleID(Flags(m_payload).Clear(ObjCClassBit));
  }
  void SetOwningModule(OptionalClangModuleID id);
  /// \}
};
  
/// A TypeSystem implementation based on Clang.
///
/// This class uses a single clang::ASTContext as the backend for storing
/// its types and declarations. Every clang::ASTContext should also just have
/// a single associated TypeSystemClang instance that manages it.
///
/// The clang::ASTContext instance can either be created by TypeSystemClang
/// itself or it can adopt an existing clang::ASTContext (for example, when
/// it is necessary to provide a TypeSystem interface for an existing
/// clang::ASTContext that was created by clang::CompilerInstance).
class TypeSystemClang : public TypeSystem {
  // LLVM RTTI support
  static char ID;

public:
  typedef void (*CompleteTagDeclCallback)(void *baton, clang::TagDecl *);
  typedef void (*CompleteObjCInterfaceDeclCallback)(void *baton,
                                                    clang::ObjCInterfaceDecl *);

  // llvm casting support
  bool isA(const void *ClassID) const override { return ClassID == &ID; }
  static bool classof(const TypeSystem *ts) { return ts->isA(&ID); }

  /// Constructs a TypeSystemClang with an ASTContext using the given triple.
  ///
  /// \param name The name for the TypeSystemClang (for logging purposes)
  /// \param triple The llvm::Triple used for the ASTContext. The triple defines
  ///               certain characteristics of the ASTContext and its types
  ///               (e.g., whether certain primitive types exist or what their
  ///               signedness is).
  explicit TypeSystemClang(llvm::StringRef name, llvm::Triple triple);

  /// Constructs a TypeSystemClang that uses an existing ASTContext internally.
  /// Useful when having an existing ASTContext created by Clang.
  ///
  /// \param name The name for the TypeSystemClang (for logging purposes)
  /// \param existing_ctxt An existing ASTContext.
  explicit TypeSystemClang(llvm::StringRef name,
                           clang::ASTContext &existing_ctxt);

  ~TypeSystemClang() override;

  void Finalize() override;

  // PluginInterface functions
  llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }

  static llvm::StringRef GetPluginNameStatic() { return "clang"; }

  static lldb::TypeSystemSP CreateInstance(lldb::LanguageType language,
                                           Module *module, Target *target);

  static LanguageSet GetSupportedLanguagesForTypes();
  static LanguageSet GetSupportedLanguagesForExpressions();

  static void Initialize();

  static void Terminate();

  static TypeSystemClang *GetASTContext(clang::ASTContext *ast_ctx);

  /// Returns the display name of this TypeSystemClang that indicates what
  /// purpose it serves in LLDB. Used for example in logs.
  llvm::StringRef getDisplayName() const { return m_display_name; }

  /// Returns the clang::ASTContext instance managed by this TypeSystemClang.
  clang::ASTContext &getASTContext();

  clang::MangleContext *getMangleContext();

  std::shared_ptr<clang::TargetOptions> &getTargetOptions();

  clang::TargetInfo *getTargetInfo();

  void setSema(clang::Sema *s);
  clang::Sema *getSema() { return m_sema; }

  const char *GetTargetTriple();

  void SetExternalSource(
      llvm::IntrusiveRefCntPtr<clang::ExternalASTSource> &ast_source_up);

  bool GetCompleteDecl(clang::Decl *decl) {
    return TypeSystemClang::GetCompleteDecl(&getASTContext(), decl);
  }

  static void DumpDeclHiearchy(clang::Decl *decl);

  static void DumpDeclContextHiearchy(clang::DeclContext *decl_ctx);

  static bool DeclsAreEquivalent(clang::Decl *lhs_decl, clang::Decl *rhs_decl);

  static bool GetCompleteDecl(clang::ASTContext *ast, clang::Decl *decl);

  void SetMetadataAsUserID(const clang::Decl *decl, lldb::user_id_t user_id);
  void SetMetadataAsUserID(const clang::Type *type, lldb::user_id_t user_id);

  void SetMetadata(const clang::Decl *object, ClangASTMetadata &meta_data);

  void SetMetadata(const clang::Type *object, ClangASTMetadata &meta_data);
  ClangASTMetadata *GetMetadata(const clang::Decl *object);
  ClangASTMetadata *GetMetadata(const clang::Type *object);

  void SetCXXRecordDeclAccess(const clang::CXXRecordDecl *object,
                              clang::AccessSpecifier access);
  clang::AccessSpecifier
  GetCXXRecordDeclAccess(const clang::CXXRecordDecl *object);

  // Basic Types
  CompilerType GetBuiltinTypeForEncodingAndBitSize(lldb::Encoding encoding,
                                                   size_t bit_size) override;

  CompilerType GetBasicType(lldb::BasicType type);

  static lldb::BasicType GetBasicTypeEnumeration(ConstString name);

  CompilerType
  GetBuiltinTypeForDWARFEncodingAndBitSize(llvm::StringRef type_name,
                                           uint32_t dw_ate, uint32_t bit_size);

  CompilerType GetCStringType(bool is_const);

  static clang::DeclContext *GetDeclContextForType(clang::QualType type);

  static clang::DeclContext *GetDeclContextForType(const CompilerType &type);

  uint32_t GetPointerByteSize() override;

  clang::TranslationUnitDecl *GetTranslationUnitDecl() {
    return getASTContext().getTranslationUnitDecl();
  }

  static bool AreTypesSame(CompilerType type1, CompilerType type2,
                           bool ignore_qualifiers = false);

  /// Creates a CompilerType form the given QualType with the current
  /// TypeSystemClang instance as the CompilerType's typesystem.
  /// \param qt The QualType for a type that belongs to the ASTContext of this
  ///           TypeSystemClang.
  /// \return The CompilerType representing the given QualType. If the
  ///         QualType's type pointer is a nullptr then the function returns an
  ///         invalid CompilerType.
  CompilerType GetType(clang::QualType qt) {
    if (qt.getTypePtrOrNull() == nullptr)
      return CompilerType();
    // Check that the type actually belongs to this TypeSystemClang.
    assert(qt->getAsTagDecl() == nullptr ||
           &qt->getAsTagDecl()->getASTContext() == &getASTContext());
    return CompilerType(this, qt.getAsOpaquePtr());
  }

  CompilerType GetTypeForDecl(clang::NamedDecl *decl);

  CompilerType GetTypeForDecl(clang::TagDecl *decl);

  CompilerType GetTypeForDecl(clang::ObjCInterfaceDecl *objc_decl);

  template <typename RecordDeclType>
  CompilerType
  GetTypeForIdentifier(ConstString type_name,
                       clang::DeclContext *decl_context = nullptr) {
    CompilerType compiler_type;

    if (type_name.GetLength()) {
      clang::ASTContext &ast = getASTContext();
      if (!decl_context)
        decl_context = ast.getTranslationUnitDecl();

      clang::IdentifierInfo &myIdent = ast.Idents.get(type_name.GetCString());
      clang::DeclarationName myName =
          ast.DeclarationNames.getIdentifier(&myIdent);

      clang::DeclContext::lookup_result result = decl_context->lookup(myName);

      if (!result.empty()) {
        clang::NamedDecl *named_decl = *result.begin();
        if (const RecordDeclType *record_decl =
                llvm::dyn_cast<RecordDeclType>(named_decl))
          compiler_type.SetCompilerType(
              this, clang::QualType(record_decl->getTypeForDecl(), 0)
                        .getAsOpaquePtr());
      }
    }

    return compiler_type;
  }

  CompilerType CreateStructForIdentifier(
      ConstString type_name,
      const std::initializer_list<std::pair<const char *, CompilerType>>
          &type_fields,
      bool packed = false);

  CompilerType GetOrCreateStructForIdentifier(
      ConstString type_name,
      const std::initializer_list<std::pair<const char *, CompilerType>>
          &type_fields,
      bool packed = false);

  static bool IsOperator(llvm::StringRef name,
                         clang::OverloadedOperatorKind &op_kind);

  // Structure, Unions, Classes

  static clang::AccessSpecifier
  ConvertAccessTypeToAccessSpecifier(lldb::AccessType access);

  static clang::AccessSpecifier
  UnifyAccessSpecifiers(clang::AccessSpecifier lhs, clang::AccessSpecifier rhs);

  static uint32_t GetNumBaseClasses(const clang::CXXRecordDecl *cxx_record_decl,
                                    bool omit_empty_base_classes);

  /// Synthesize a clang::Module and return its ID or a default-constructed ID.
  OptionalClangModuleID GetOrCreateClangModule(llvm::StringRef name,
                                               OptionalClangModuleID parent,
                                               bool is_framework = false,
                                               bool is_explicit = false);

  CompilerType CreateRecordType(clang::DeclContext *decl_ctx,
                                OptionalClangModuleID owning_module,
                                lldb::AccessType access_type,
                                llvm::StringRef name, int kind,
                                lldb::LanguageType language,
                                ClangASTMetadata *metadata = nullptr,
                                bool exports_symbols = false);

  class TemplateParameterInfos {
  public:
    bool IsValid() const {
      // Having a pack name but no packed args doesn't make sense, so mark
      // these template parameters as invalid.
      if (pack_name && !packed_args)
        return false;
      return args.size() == names.size() &&
        (!packed_args || !packed_args->packed_args);
    }

    bool hasParameterPack() const { return static_cast<bool>(packed_args); }

    llvm::SmallVector<const char *, 2> names;
    llvm::SmallVector<clang::TemplateArgument, 2> args;
    
    const char * pack_name = nullptr;
    std::unique_ptr<TemplateParameterInfos> packed_args;
  };

  clang::FunctionTemplateDecl *CreateFunctionTemplateDecl(
      clang::DeclContext *decl_ctx, OptionalClangModuleID owning_module,
      clang::FunctionDecl *func_decl, const TemplateParameterInfos &infos);

  void CreateFunctionTemplateSpecializationInfo(
      clang::FunctionDecl *func_decl, clang::FunctionTemplateDecl *Template,
      const TemplateParameterInfos &infos);

  clang::ClassTemplateDecl *CreateClassTemplateDecl(
      clang::DeclContext *decl_ctx, OptionalClangModuleID owning_module,
      lldb::AccessType access_type, llvm::StringRef class_name, int kind,
      const TemplateParameterInfos &infos);

  clang::TemplateTemplateParmDecl *
  CreateTemplateTemplateParmDecl(const char *template_name);

  clang::ClassTemplateSpecializationDecl *CreateClassTemplateSpecializationDecl(
      clang::DeclContext *decl_ctx, OptionalClangModuleID owning_module,
      clang::ClassTemplateDecl *class_template_decl, int kind,
      const TemplateParameterInfos &infos);

  CompilerType
  CreateClassTemplateSpecializationType(clang::ClassTemplateSpecializationDecl *
                                            class_template_specialization_decl);

  static clang::DeclContext *
  GetAsDeclContext(clang::FunctionDecl *function_decl);

  static bool CheckOverloadedOperatorKindParameterCount(
      bool is_method, clang::OverloadedOperatorKind op_kind,
      uint32_t num_params);

  bool FieldIsBitfield(clang::FieldDecl *field, uint32_t &bitfield_bit_size);

  static bool RecordHasFields(const clang::RecordDecl *record_decl);

  CompilerType CreateObjCClass(llvm::StringRef name,
                               clang::DeclContext *decl_ctx,
                               OptionalClangModuleID owning_module,
                               bool isForwardDecl, bool isInternal,
                               ClangASTMetadata *metadata = nullptr);

  // Returns a mask containing bits from the TypeSystemClang::eTypeXXX
  // enumerations

  // Namespace Declarations

  clang::NamespaceDecl *
  GetUniqueNamespaceDeclaration(const char *name, clang::DeclContext *decl_ctx,
                                OptionalClangModuleID owning_module,
                                bool is_inline = false);

  // Function Types

  clang::FunctionDecl *CreateFunctionDeclaration(
      clang::DeclContext *decl_ctx, OptionalClangModuleID owning_module,
      llvm::StringRef name, const CompilerType &function_Type,
      clang::StorageClass storage, bool is_inline);

  CompilerType CreateFunctionType(const CompilerType &result_type,
                                  const CompilerType *args, unsigned num_args,
                                  bool is_variadic, unsigned type_quals,
                                  clang::CallingConv cc = clang::CC_C);

  clang::ParmVarDecl *
  CreateParameterDeclaration(clang::DeclContext *decl_ctx,
                             OptionalClangModuleID owning_module,
                             const char *name, const CompilerType &param_type,
                             int storage, bool add_decl = false);

  void SetFunctionParameters(clang::FunctionDecl *function_decl,
                             llvm::ArrayRef<clang::ParmVarDecl *> params);

  CompilerType CreateBlockPointerType(const CompilerType &function_type);

  // Array Types

  CompilerType CreateArrayType(const CompilerType &element_type,
                               size_t element_count, bool is_vector);

  // Enumeration Types
  CompilerType CreateEnumerationType(llvm::StringRef name,
                                     clang::DeclContext *decl_ctx,
                                     OptionalClangModuleID owning_module,
                                     const Declaration &decl,
                                     const CompilerType &integer_qual_type,
                                     bool is_scoped);

  // Integer type functions

  CompilerType GetIntTypeFromBitSize(size_t bit_size, bool is_signed);

  CompilerType GetPointerSizedIntType(bool is_signed);

  // Floating point functions

  static CompilerType GetFloatTypeFromBitSize(clang::ASTContext *ast,
                                              size_t bit_size);

  // TypeSystem methods
  DWARFASTParser *GetDWARFParser() override;
  PDBASTParser *GetPDBParser() override;

  // TypeSystemClang callbacks for external source lookups.
  void CompleteTagDecl(clang::TagDecl *);

  void CompleteObjCInterfaceDecl(clang::ObjCInterfaceDecl *);

  bool LayoutRecordType(
      const clang::RecordDecl *record_decl, uint64_t &size, uint64_t &alignment,
      llvm::DenseMap<const clang::FieldDecl *, uint64_t> &field_offsets,
      llvm::DenseMap<const clang::CXXRecordDecl *, clang::CharUnits>
          &base_offsets,
      llvm::DenseMap<const clang::CXXRecordDecl *, clang::CharUnits>
          &vbase_offsets);

  /// Creates a CompilerDecl from the given Decl with the current
  /// TypeSystemClang instance as its typesystem.
  /// The Decl has to come from the ASTContext of this
  /// TypeSystemClang.
  CompilerDecl GetCompilerDecl(clang::Decl *decl) {
    assert(&decl->getASTContext() == &getASTContext() &&
           "CreateCompilerDecl for Decl from wrong ASTContext?");
    return CompilerDecl(this, decl);
  }

  // CompilerDecl override functions
  ConstString DeclGetName(void *opaque_decl) override;

  ConstString DeclGetMangledName(void *opaque_decl) override;

  CompilerDeclContext DeclGetDeclContext(void *opaque_decl) override;

  CompilerType DeclGetFunctionReturnType(void *opaque_decl) override;

  size_t DeclGetFunctionNumArguments(void *opaque_decl) override;

  CompilerType DeclGetFunctionArgumentType(void *opaque_decl,
                                           size_t arg_idx) override;

  CompilerType GetTypeForDecl(void *opaque_decl) override;

  // CompilerDeclContext override functions

  /// Creates a CompilerDeclContext from the given DeclContext
  /// with the current TypeSystemClang instance as its typesystem.
  /// The DeclContext has to come from the ASTContext of this
  /// TypeSystemClang.
  CompilerDeclContext CreateDeclContext(clang::DeclContext *ctx);

  /// Set the owning module for \p decl.
  static void SetOwningModule(clang::Decl *decl,
                              OptionalClangModuleID owning_module);

  std::vector<CompilerDecl>
  DeclContextFindDeclByName(void *opaque_decl_ctx, ConstString name,
                            const bool ignore_using_decls) override;

  ConstString DeclContextGetName(void *opaque_decl_ctx) override;

  ConstString DeclContextGetScopeQualifiedName(void *opaque_decl_ctx) override;

  bool DeclContextIsClassMethod(void *opaque_decl_ctx,
                                lldb::LanguageType *language_ptr,
                                bool *is_instance_method_ptr,
                                ConstString *language_object_name_ptr) override;

  bool DeclContextIsContainedInLookup(void *opaque_decl_ctx,
                                      void *other_opaque_decl_ctx) override;

  // Clang specific clang::DeclContext functions

  static clang::DeclContext *
  DeclContextGetAsDeclContext(const CompilerDeclContext &dc);

  static clang::ObjCMethodDecl *
  DeclContextGetAsObjCMethodDecl(const CompilerDeclContext &dc);

  static clang::CXXMethodDecl *
  DeclContextGetAsCXXMethodDecl(const CompilerDeclContext &dc);

  static clang::FunctionDecl *
  DeclContextGetAsFunctionDecl(const CompilerDeclContext &dc);

  static clang::NamespaceDecl *
  DeclContextGetAsNamespaceDecl(const CompilerDeclContext &dc);

  static ClangASTMetadata *DeclContextGetMetaData(const CompilerDeclContext &dc,
                                                  const clang::Decl *object);

  static clang::ASTContext *
  DeclContextGetTypeSystemClang(const CompilerDeclContext &dc);

  // Tests

#ifndef NDEBUG
  bool Verify(lldb::opaque_compiler_type_t type) override;
#endif
  
  bool IsArrayType(lldb::opaque_compiler_type_t type,
                   CompilerType *element_type, uint64_t *size,
                   bool *is_incomplete) override;

  bool IsVectorType(lldb::opaque_compiler_type_t type,
                    CompilerType *element_type, uint64_t *size) override;

  bool IsAggregateType(lldb::opaque_compiler_type_t type) override;

  bool IsAnonymousType(lldb::opaque_compiler_type_t type) override;

  bool IsBeingDefined(lldb::opaque_compiler_type_t type) override;

  bool IsCharType(lldb::opaque_compiler_type_t type) override;

  bool IsCompleteType(lldb::opaque_compiler_type_t type) override;

  bool IsConst(lldb::opaque_compiler_type_t type) override;

  bool IsCStringType(lldb::opaque_compiler_type_t type,
                     uint32_t &length) override;

  static bool IsCXXClassType(const CompilerType &type);

  bool IsDefined(lldb::opaque_compiler_type_t type) override;

  bool IsFloatingPointType(lldb::opaque_compiler_type_t type, uint32_t &count,
                           bool &is_complex) override;

  bool IsFunctionType(lldb::opaque_compiler_type_t type) override;

  uint32_t IsHomogeneousAggregate(lldb::opaque_compiler_type_t type,
                                  CompilerType *base_type_ptr) override;

  size_t
  GetNumberOfFunctionArguments(lldb::opaque_compiler_type_t type) override;

  CompilerType GetFunctionArgumentAtIndex(lldb::opaque_compiler_type_t type,
                                          const size_t index) override;

  bool IsFunctionPointerType(lldb::opaque_compiler_type_t type) override;

  bool IsBlockPointerType(lldb::opaque_compiler_type_t type,
                          CompilerType *function_pointer_type_ptr) override;

  bool IsIntegerType(lldb::opaque_compiler_type_t type,
                     bool &is_signed) override;

  bool IsEnumerationType(lldb::opaque_compiler_type_t type,
                         bool &is_signed) override;

  bool IsScopedEnumerationType(lldb::opaque_compiler_type_t type) override;

  static bool IsObjCClassType(const CompilerType &type);

  static bool IsObjCClassTypeAndHasIVars(const CompilerType &type,
                                         bool check_superclass);

  static bool IsObjCObjectOrInterfaceType(const CompilerType &type);

  static bool IsObjCObjectPointerType(const CompilerType &type,
                                      CompilerType *target_type = nullptr);

  bool IsPolymorphicClass(lldb::opaque_compiler_type_t type) override;

  static bool IsClassType(lldb::opaque_compiler_type_t type);

  static bool IsEnumType(lldb::opaque_compiler_type_t type);

  bool IsPossibleDynamicType(lldb::opaque_compiler_type_t type,
                             CompilerType *target_type, // Can pass nullptr
                             bool check_cplusplus, bool check_objc) override;

  bool IsRuntimeGeneratedType(lldb::opaque_compiler_type_t type) override;

  bool IsPointerType(lldb::opaque_compiler_type_t type,
                     CompilerType *pointee_type) override;

  bool IsPointerOrReferenceType(lldb::opaque_compiler_type_t type,
                                CompilerType *pointee_type) override;

  bool IsReferenceType(lldb::opaque_compiler_type_t type,
                       CompilerType *pointee_type, bool *is_rvalue) override;

  bool IsScalarType(lldb::opaque_compiler_type_t type) override;

  bool IsTypedefType(lldb::opaque_compiler_type_t type) override;

  bool IsVoidType(lldb::opaque_compiler_type_t type) override;

  bool CanPassInRegisters(const CompilerType &type) override;

  bool SupportsLanguage(lldb::LanguageType language) override;

  static llvm::Optional<std::string> GetCXXClassName(const CompilerType &type);

  // Type Completion

  bool GetCompleteType(lldb::opaque_compiler_type_t type) override;

  // Accessors

  ConstString GetTypeName(lldb::opaque_compiler_type_t type) override;

  ConstString GetDisplayTypeName(lldb::opaque_compiler_type_t type) override;

  uint32_t GetTypeInfo(lldb::opaque_compiler_type_t type,
                       CompilerType *pointee_or_element_compiler_type) override;

  lldb::LanguageType
  GetMinimumLanguage(lldb::opaque_compiler_type_t type) override;

  lldb::TypeClass GetTypeClass(lldb::opaque_compiler_type_t type) override;

  unsigned GetTypeQualifiers(lldb::opaque_compiler_type_t type) override;

  // Creating related types

  CompilerType GetArrayElementType(lldb::opaque_compiler_type_t type,
                                   ExecutionContextScope *exe_scope) override;

  CompilerType GetArrayType(lldb::opaque_compiler_type_t type,
                            uint64_t size) override;

  CompilerType GetCanonicalType(lldb::opaque_compiler_type_t type) override;

  CompilerType
  GetFullyUnqualifiedType(lldb::opaque_compiler_type_t type) override;

  CompilerType
  GetEnumerationIntegerType(lldb::opaque_compiler_type_t type) override;

  // Returns -1 if this isn't a function of if the function doesn't have a
  // prototype Returns a value >= 0 if there is a prototype.
  int GetFunctionArgumentCount(lldb::opaque_compiler_type_t type) override;

  CompilerType GetFunctionArgumentTypeAtIndex(lldb::opaque_compiler_type_t type,
                                              size_t idx) override;

  CompilerType
  GetFunctionReturnType(lldb::opaque_compiler_type_t type) override;

  size_t GetNumMemberFunctions(lldb::opaque_compiler_type_t type) override;

  TypeMemberFunctionImpl
  GetMemberFunctionAtIndex(lldb::opaque_compiler_type_t type,
                           size_t idx) override;

  CompilerType GetNonReferenceType(lldb::opaque_compiler_type_t type) override;

  CompilerType GetPointeeType(lldb::opaque_compiler_type_t type) override;

  CompilerType GetPointerType(lldb::opaque_compiler_type_t type) override;

  CompilerType
  GetLValueReferenceType(lldb::opaque_compiler_type_t type) override;

  CompilerType
  GetRValueReferenceType(lldb::opaque_compiler_type_t type) override;

  CompilerType GetAtomicType(lldb::opaque_compiler_type_t type) override;

  CompilerType AddConstModifier(lldb::opaque_compiler_type_t type) override;

  CompilerType AddVolatileModifier(lldb::opaque_compiler_type_t type) override;

  CompilerType AddRestrictModifier(lldb::opaque_compiler_type_t type) override;

  /// Using the current type, create a new typedef to that type using
  /// "typedef_name" as the name and "decl_ctx" as the decl context.
  /// \param opaque_payload is an opaque TypePayloadClang.
  CompilerType CreateTypedef(lldb::opaque_compiler_type_t type,
                             const char *name,
                             const CompilerDeclContext &decl_ctx,
                             uint32_t opaque_payload) override;

  // If the current object represents a typedef type, get the underlying type
  CompilerType GetTypedefedType(lldb::opaque_compiler_type_t type) override;

  // Create related types using the current type's AST
  CompilerType GetBasicTypeFromAST(lldb::BasicType basic_type) override;

  // Exploring the type

  const llvm::fltSemantics &GetFloatTypeSemantics(size_t byte_size) override;

  llvm::Optional<uint64_t> GetByteSize(lldb::opaque_compiler_type_t type,
                       ExecutionContextScope *exe_scope) {
    if (llvm::Optional<uint64_t> bit_size = GetBitSize(type, exe_scope))
      return (*bit_size + 7) / 8;
    return llvm::None;
  }

  llvm::Optional<uint64_t>
  GetBitSize(lldb::opaque_compiler_type_t type,
             ExecutionContextScope *exe_scope) override;

  lldb::Encoding GetEncoding(lldb::opaque_compiler_type_t type,
                             uint64_t &count) override;

  lldb::Format GetFormat(lldb::opaque_compiler_type_t type) override;

  llvm::Optional<size_t>
  GetTypeBitAlign(lldb::opaque_compiler_type_t type,
                  ExecutionContextScope *exe_scope) override;

  uint32_t GetNumChildren(lldb::opaque_compiler_type_t type,
                          bool omit_empty_base_classes,
                          const ExecutionContext *exe_ctx) override;

  CompilerType GetBuiltinTypeByName(ConstString name) override;

  lldb::BasicType
  GetBasicTypeEnumeration(lldb::opaque_compiler_type_t type) override;

  static lldb::BasicType
  GetBasicTypeEnumeration(lldb::opaque_compiler_type_t type,
                          ConstString name);

  void ForEachEnumerator(
      lldb::opaque_compiler_type_t type,
      std::function<bool(const CompilerType &integer_type,
                         ConstString name,
                         const llvm::APSInt &value)> const &callback) override;

  uint32_t GetNumFields(lldb::opaque_compiler_type_t type) override;

  CompilerType GetFieldAtIndex(lldb::opaque_compiler_type_t type, size_t idx,
                               std::string &name, uint64_t *bit_offset_ptr,
                               uint32_t *bitfield_bit_size_ptr,
                               bool *is_bitfield_ptr) override;

  uint32_t GetNumDirectBaseClasses(lldb::opaque_compiler_type_t type) override;

  uint32_t GetNumVirtualBaseClasses(lldb::opaque_compiler_type_t type) override;

  CompilerType GetDirectBaseClassAtIndex(lldb::opaque_compiler_type_t type,
                                         size_t idx,
                                         uint32_t *bit_offset_ptr) override;

  CompilerType GetVirtualBaseClassAtIndex(lldb::opaque_compiler_type_t type,
                                          size_t idx,
                                          uint32_t *bit_offset_ptr) override;

  static uint32_t GetNumPointeeChildren(clang::QualType type);

  CompilerType GetChildCompilerTypeAtIndex(
      lldb::opaque_compiler_type_t type, ExecutionContext *exe_ctx, size_t idx,
      bool transparent_pointers, bool omit_empty_base_classes,
      bool ignore_array_bounds, std::string &child_name,
      uint32_t &child_byte_size, int32_t &child_byte_offset,
      uint32_t &child_bitfield_bit_size, uint32_t &child_bitfield_bit_offset,
      bool &child_is_base_class, bool &child_is_deref_of_parent,
      ValueObject *valobj, uint64_t &language_flags) override;

  // Lookup a child given a name. This function will match base class names and
  // member member names in "clang_type" only, not descendants.
  uint32_t GetIndexOfChildWithName(lldb::opaque_compiler_type_t type,
                                   const char *name,
                                   bool omit_empty_base_classes) override;

  // Lookup a child member given a name. This function will match member names
  // only and will descend into "clang_type" children in search for the first
  // member in this class, or any base class that matches "name".
  // TODO: Return all matches for a given name by returning a
  // vector<vector<uint32_t>>
  // so we catch all names that match a given child name, not just the first.
  size_t
  GetIndexOfChildMemberWithName(lldb::opaque_compiler_type_t type,
                                const char *name, bool omit_empty_base_classes,
                                std::vector<uint32_t> &child_indexes) override;

  size_t GetNumTemplateArguments(lldb::opaque_compiler_type_t type) override;

  lldb::TemplateArgumentKind
  GetTemplateArgumentKind(lldb::opaque_compiler_type_t type,
                          size_t idx) override;
  CompilerType GetTypeTemplateArgument(lldb::opaque_compiler_type_t type,
                                       size_t idx) override;
  llvm::Optional<CompilerType::IntegralTemplateArgument>
  GetIntegralTemplateArgument(lldb::opaque_compiler_type_t type,
                              size_t idx) override;

  CompilerType GetTypeForFormatters(void *type) override;

#define LLDB_INVALID_DECL_LEVEL UINT32_MAX
  // LLDB_INVALID_DECL_LEVEL is returned by CountDeclLevels if child_decl_ctx
  // could not be found in decl_ctx.
  uint32_t CountDeclLevels(clang::DeclContext *frame_decl_ctx,
                           clang::DeclContext *child_decl_ctx,
                           ConstString *child_name = nullptr,
                           CompilerType *child_type = nullptr);

  // Modifying RecordType
  static clang::FieldDecl *AddFieldToRecordType(const CompilerType &type,
                                                llvm::StringRef name,
                                                const CompilerType &field_type,
                                                lldb::AccessType access,
                                                uint32_t bitfield_bit_size);

  static void BuildIndirectFields(const CompilerType &type);

  static void SetIsPacked(const CompilerType &type);

  static clang::VarDecl *AddVariableToRecordType(const CompilerType &type,
                                                 llvm::StringRef name,
                                                 const CompilerType &var_type,
                                                 lldb::AccessType access);

  /// Initializes a variable with an integer value.
  /// \param var The variable to initialize. Must not already have an
  ///            initializer and must have an integer or enum type.
  /// \param init_value The integer value that the variable should be
  ///                   initialized to. Has to match the bit width of the
  ///                   variable type.
  static void SetIntegerInitializerForVariable(clang::VarDecl *var,
                                               const llvm::APInt &init_value);

  /// Initializes a variable with a floating point value.
  /// \param var The variable to initialize. Must not already have an
  ///            initializer and must have a floating point type.
  /// \param init_value The float value that the variable should be
  ///                   initialized to.
  static void
  SetFloatingInitializerForVariable(clang::VarDecl *var,
                                    const llvm::APFloat &init_value);

  clang::CXXMethodDecl *AddMethodToCXXRecordType(
      lldb::opaque_compiler_type_t type, llvm::StringRef name,
      const char *mangled_name, const CompilerType &method_type,
      lldb::AccessType access, bool is_virtual, bool is_static, bool is_inline,
      bool is_explicit, bool is_attr_used, bool is_artificial);

  void AddMethodOverridesForCXXRecordType(lldb::opaque_compiler_type_t type);

  // C++ Base Classes
  std::unique_ptr<clang::CXXBaseSpecifier>
  CreateBaseClassSpecifier(lldb::opaque_compiler_type_t type,
                           lldb::AccessType access, bool is_virtual,
                           bool base_of_class);

  bool TransferBaseClasses(
      lldb::opaque_compiler_type_t type,
      std::vector<std::unique_ptr<clang::CXXBaseSpecifier>> bases);

  static bool SetObjCSuperClass(const CompilerType &type,
                                const CompilerType &superclass_compiler_type);

  static bool AddObjCClassProperty(const CompilerType &type,
                                   const char *property_name,
                                   const CompilerType &property_compiler_type,
                                   clang::ObjCIvarDecl *ivar_decl,
                                   const char *property_setter_name,
                                   const char *property_getter_name,
                                   uint32_t property_attributes,
                                   ClangASTMetadata *metadata);

  static clang::ObjCMethodDecl *AddMethodToObjCObjectType(
      const CompilerType &type,
      const char *name, // the full symbol name as seen in the symbol table
                        // (lldb::opaque_compiler_type_t type, "-[NString
                        // stringWithCString:]")
      const CompilerType &method_compiler_type, lldb::AccessType access,
      bool is_artificial, bool is_variadic, bool is_objc_direct_call);

  static bool SetHasExternalStorage(lldb::opaque_compiler_type_t type,
                                    bool has_extern);

  // Tag Declarations
  static bool StartTagDeclarationDefinition(const CompilerType &type);

  static bool CompleteTagDeclarationDefinition(const CompilerType &type);

  // Modifying Enumeration types
  clang::EnumConstantDecl *AddEnumerationValueToEnumerationType(
      const CompilerType &enum_type, const Declaration &decl, const char *name,
      int64_t enum_value, uint32_t enum_value_bit_size);
  clang::EnumConstantDecl *AddEnumerationValueToEnumerationType(
      const CompilerType &enum_type, const Declaration &decl, const char *name,
      const llvm::APSInt &value);

  /// Returns the underlying integer type for an enum type. If the given type
  /// is invalid or not an enum-type, the function returns an invalid
  /// CompilerType.
  CompilerType GetEnumerationIntegerType(CompilerType type);

  // Pointers & References

  // Call this function using the class type when you want to make a member
  // pointer type to pointee_type.
  static CompilerType CreateMemberPointerType(const CompilerType &type,
                                              const CompilerType &pointee_type);

  // Dumping types
#ifndef NDEBUG
  /// Convenience LLVM-style dump method for use in the debugger only.
  /// In contrast to the other \p Dump() methods this directly invokes
  /// \p clang::QualType::dump().
  LLVM_DUMP_METHOD void dump(lldb::opaque_compiler_type_t type) const override;
#endif

  /// \see lldb_private::TypeSystem::Dump
  void Dump(llvm::raw_ostream &output) override;

  /// Dump clang AST types from the symbol file.
  ///
  /// \param[in] s
  ///       A stream to send the dumped AST node(s) to
  /// \param[in] symbol_name
  ///       The name of the symbol to dump, if it is empty dump all the symbols
  void DumpFromSymbolFile(Stream &s, llvm::StringRef symbol_name);

  void DumpValue(lldb::opaque_compiler_type_t type, ExecutionContext *exe_ctx,
                 Stream *s, lldb::Format format, const DataExtractor &data,
                 lldb::offset_t data_offset, size_t data_byte_size,
                 uint32_t bitfield_bit_size, uint32_t bitfield_bit_offset,
                 bool show_types, bool show_summary, bool verbose,
                 uint32_t depth) override;

  bool DumpTypeValue(lldb::opaque_compiler_type_t type, Stream *s,
                     lldb::Format format, const DataExtractor &data,
                     lldb::offset_t data_offset, size_t data_byte_size,
                     uint32_t bitfield_bit_size, uint32_t bitfield_bit_offset,
                     ExecutionContextScope *exe_scope) override;

  void DumpSummary(lldb::opaque_compiler_type_t type, ExecutionContext *exe_ctx,
                   Stream *s, const DataExtractor &data,
                   lldb::offset_t data_offset, size_t data_byte_size) override;

  void DumpTypeDescription(
      lldb::opaque_compiler_type_t type,
      lldb::DescriptionLevel level = lldb::eDescriptionLevelFull) override;

  void DumpTypeDescription(
      lldb::opaque_compiler_type_t type, Stream *s,
      lldb::DescriptionLevel level = lldb::eDescriptionLevelFull) override;

  static void DumpTypeName(const CompilerType &type);

  static clang::EnumDecl *GetAsEnumDecl(const CompilerType &type);

  static clang::RecordDecl *GetAsRecordDecl(const CompilerType &type);

  static clang::TagDecl *GetAsTagDecl(const CompilerType &type);

  static clang::TypedefNameDecl *GetAsTypedefDecl(const CompilerType &type);

  static clang::CXXRecordDecl *
  GetAsCXXRecordDecl(lldb::opaque_compiler_type_t type);

  static clang::ObjCInterfaceDecl *
  GetAsObjCInterfaceDecl(const CompilerType &type);

  clang::ClassTemplateDecl *ParseClassTemplateDecl(
      clang::DeclContext *decl_ctx, OptionalClangModuleID owning_module,
      lldb::AccessType access_type, const char *parent_name, int tag_decl_kind,
      const TypeSystemClang::TemplateParameterInfos &template_param_infos);

  clang::BlockDecl *CreateBlockDeclaration(clang::DeclContext *ctx,
                                           OptionalClangModuleID owning_module);

  clang::UsingDirectiveDecl *
  CreateUsingDirectiveDeclaration(clang::DeclContext *decl_ctx,
                                  OptionalClangModuleID owning_module,
                                  clang::NamespaceDecl *ns_decl);

  clang::UsingDecl *CreateUsingDeclaration(clang::DeclContext *current_decl_ctx,
                                           OptionalClangModuleID owning_module,
                                           clang::NamedDecl *target);

  clang::VarDecl *CreateVariableDeclaration(clang::DeclContext *decl_context,
                                            OptionalClangModuleID owning_module,
                                            const char *name,
                                            clang::QualType type);

  static lldb::opaque_compiler_type_t
  GetOpaqueCompilerType(clang::ASTContext *ast, lldb::BasicType basic_type);

  static clang::QualType GetQualType(lldb::opaque_compiler_type_t type) {
    if (type)
      return clang::QualType::getFromOpaquePtr(type);
    return clang::QualType();
  }

  static clang::QualType
  GetCanonicalQualType(lldb::opaque_compiler_type_t type) {
    if (type)
      return clang::QualType::getFromOpaquePtr(type).getCanonicalType();
    return clang::QualType();
  }

  clang::DeclarationName
  GetDeclarationName(llvm::StringRef name,
                     const CompilerType &function_clang_type);

  clang::LangOptions *GetLangOpts() const {
    return m_language_options_up.get();
  }
  clang::SourceManager *GetSourceMgr() const {
    return m_source_manager_up.get();
  }

private:
  /// Returns the PrintingPolicy used when generating the internal type names.
  /// These type names are mostly used for the formatter selection.
  clang::PrintingPolicy GetTypePrintingPolicy();
  /// Returns the internal type name for the given NamedDecl using the
  /// type printing policy.
  std::string GetTypeNameForDecl(const clang::NamedDecl *named_decl);

  const clang::ClassTemplateSpecializationDecl *
  GetAsTemplateSpecialization(lldb::opaque_compiler_type_t type);

  // Classes that inherit from TypeSystemClang can see and modify these
  std::string m_target_triple;
  std::unique_ptr<clang::ASTContext> m_ast_up;
  std::unique_ptr<clang::LangOptions> m_language_options_up;
  std::unique_ptr<clang::FileManager> m_file_manager_up;
  std::unique_ptr<clang::SourceManager> m_source_manager_up;
  std::unique_ptr<clang::DiagnosticsEngine> m_diagnostics_engine_up;
  std::unique_ptr<clang::DiagnosticConsumer> m_diagnostic_consumer_up;
  std::shared_ptr<clang::TargetOptions> m_target_options_rp;
  std::unique_ptr<clang::TargetInfo> m_target_info_up;
  std::unique_ptr<clang::IdentifierTable> m_identifier_table_up;
  std::unique_ptr<clang::SelectorTable> m_selector_table_up;
  std::unique_ptr<clang::Builtin::Context> m_builtins_up;
  std::unique_ptr<clang::HeaderSearch> m_header_search_up;
  std::unique_ptr<clang::ModuleMap> m_module_map_up;
  std::unique_ptr<DWARFASTParserClang> m_dwarf_ast_parser_up;
  std::unique_ptr<PDBASTParser> m_pdb_ast_parser_up;
  std::unique_ptr<clang::MangleContext> m_mangle_ctx_up;
  uint32_t m_pointer_byte_size = 0;
  bool m_ast_owned = false;
  /// A string describing what this TypeSystemClang represents (e.g.,
  /// AST for debug information, an expression, some other utility ClangAST).
  /// Useful for logging and debugging.
  std::string m_display_name;

  typedef llvm::DenseMap<const clang::Decl *, ClangASTMetadata> DeclMetadataMap;
  /// Maps Decls to their associated ClangASTMetadata.
  DeclMetadataMap m_decl_metadata;

  typedef llvm::DenseMap<const clang::Type *, ClangASTMetadata> TypeMetadataMap;
  /// Maps Types to their associated ClangASTMetadata.
  TypeMetadataMap m_type_metadata;

  typedef llvm::DenseMap<const clang::CXXRecordDecl *, clang::AccessSpecifier>
      CXXRecordDeclAccessMap;
  /// Maps CXXRecordDecl to their most recent added method/field's
  /// AccessSpecifier.
  CXXRecordDeclAccessMap m_cxx_record_decl_access;

  /// The sema associated that is currently used to build this ASTContext.
  /// May be null if we are already done parsing this ASTContext or the
  /// ASTContext wasn't created by parsing source code.
  clang::Sema *m_sema = nullptr;

  // For TypeSystemClang only
  TypeSystemClang(const TypeSystemClang &);
  const TypeSystemClang &operator=(const TypeSystemClang &);
  /// Creates the internal ASTContext.
  void CreateASTContext();
  void SetTargetTriple(llvm::StringRef target_triple);
};

/// The TypeSystemClang instance used for the scratch ASTContext in a
/// lldb::Target.
class ScratchTypeSystemClang : public TypeSystemClang {
  /// LLVM RTTI support
  static char ID;

public:
  ScratchTypeSystemClang(Target &target, llvm::Triple triple);

  ~ScratchTypeSystemClang() override = default;

  void Finalize() override;

  /// The different kinds of isolated ASTs within the scratch TypeSystem.
  ///
  /// These ASTs are isolated from the main scratch AST and are each
  /// dedicated to a special language option/feature that makes the contained
  /// AST nodes incompatible with other AST nodes.
  enum IsolatedASTKind {
    /// The isolated AST for declarations/types from expressions that imported
    /// type information from a C++ module. The templates from a C++ module
    /// often conflict with the templates we generate from debug information,
    /// so we put these types in their own AST.
    CppModules
  };

  /// Alias for requesting the default scratch TypeSystemClang in GetForTarget.
  // This isn't constexpr as gtest/llvm::Optional comparison logic is trying
  // to get the address of this for pretty-printing.
  static const llvm::NoneType DefaultAST;

  /// Infers the appropriate sub-AST from Clang's LangOptions.
  static llvm::Optional<IsolatedASTKind>
  InferIsolatedASTKindFromLangOpts(const clang::LangOptions &l) {
    // If modules are activated we want the dedicated C++ module AST.
    // See IsolatedASTKind::CppModules for more info.
    if (l.Modules)
      return IsolatedASTKind::CppModules;
    return DefaultAST;
  }

  /// Returns the scratch TypeSystemClang for the given target.
  /// \param target The Target which scratch TypeSystemClang should be returned.
  /// \param ast_kind Allows requesting a specific sub-AST instead of the
  ///                 default scratch AST. See also `IsolatedASTKind`.
  /// \param create_on_demand If the scratch TypeSystemClang instance can be
  /// created by this call if it doesn't exist yet. If it doesn't exist yet and
  /// this parameter is false, this function returns a nullptr.
  /// \return The scratch type system of the target or a nullptr in case an
  ///         error occurred.
  static TypeSystemClang *
  GetForTarget(Target &target,
               llvm::Optional<IsolatedASTKind> ast_kind = DefaultAST,
               bool create_on_demand = true);

  /// Returns the scratch TypeSystemClang for the given target. The returned
  /// TypeSystemClang will be the scratch AST or a sub-AST, depending on which
  /// fits best to the passed LangOptions.
  /// \param target The Target which scratch TypeSystemClang should be returned.
  /// \param lang_opts The LangOptions of a clang ASTContext that the caller
  ///                  wants to export type information from. This is used to
  ///                  find the best matching sub-AST that will be returned.
  static TypeSystemClang *GetForTarget(Target &target,
                                       const clang::LangOptions &lang_opts) {
    return GetForTarget(target, InferIsolatedASTKindFromLangOpts(lang_opts));
  }

  /// \see lldb_private::TypeSystem::Dump
  void Dump(llvm::raw_ostream &output) override;

  UserExpression *
  GetUserExpression(llvm::StringRef expr, llvm::StringRef prefix,
                    lldb::LanguageType language,
                    Expression::ResultType desired_type,
                    const EvaluateExpressionOptions &options,
                    ValueObject *ctx_obj) override;

  FunctionCaller *GetFunctionCaller(const CompilerType &return_type,
                                    const Address &function_address,
                                    const ValueList &arg_value_list,
                                    const char *name) override;

  std::unique_ptr<UtilityFunction>
  CreateUtilityFunction(std::string text, std::string name) override;

  PersistentExpressionState *GetPersistentExpressionState() override;

  /// Unregisters the given ASTContext as a source from the scratch AST (and
  /// all sub-ASTs).
  /// \see ClangASTImporter::ForgetSource
  void ForgetSource(clang::ASTContext *src_ctx, ClangASTImporter &importer);

  // llvm casting support
  bool isA(const void *ClassID) const override {
    return ClassID == &ID || TypeSystemClang::isA(ClassID);
  }
  static bool classof(const TypeSystem *ts) { return ts->isA(&ID); }

private:
  std::unique_ptr<ClangASTSource> CreateASTSource();
  /// Returns the requested sub-AST.
  /// Will lazily create the sub-AST if it hasn't been created before.
  TypeSystemClang &GetIsolatedAST(IsolatedASTKind feature);

  /// The target triple.
  /// This was potentially adjusted and might not be identical to the triple
  /// of `m_target_wp`.
  llvm::Triple m_triple;
  lldb::TargetWP m_target_wp;
  /// The persistent variables associated with this process for the expression
  /// parser.
  std::unique_ptr<ClangPersistentVariables> m_persistent_variables;
  /// The ExternalASTSource that performs lookups and completes minimally
  /// imported types.
  std::unique_ptr<ClangASTSource> m_scratch_ast_source_up;

  // FIXME: GCC 5.x doesn't support enum as map keys.
  typedef int IsolatedASTKey;

  /// Map from IsolatedASTKind to their actual TypeSystemClang instance.
  /// This map is lazily filled with sub-ASTs and should be accessed via
  /// `GetSubAST` (which lazily fills this map).
  std::unordered_map<IsolatedASTKey, std::unique_ptr<TypeSystemClang>>
      m_isolated_asts;
};

} // namespace lldb_private

#endif // LLDB_SOURCE_PLUGINS_TYPESYSTEM_CLANG_TYPESYSTEMCLANG_H
