//--------------------------------------------------------------------*- C++ -*-
// CLING - the C++ LLVM-based InterpreterG :)
// author:  Vassil Vassilev <vvasilev@cern.ch>
//
// This file is dual-licensed: you can choose to license it under the University
// of Illinois Open Source License or the GNU Lesser General Public License. See
// LICENSE.TXT for details.
//------------------------------------------------------------------------------

#ifndef CLING_DECL_UNLOADER
#define CLING_DECL_UNLOADER

#include "cling/Interpreter/Transaction.h"

#include "clang/AST/DeclVisitor.h"


namespace clang {
  class CodeGenerator;
  class GlobalDecl;
}

namespace cling {

  ///\brief The class does the actual work of removing a declaration and
  /// resetting the internal structures of the compiler
  ///
  class DeclUnloader : public clang::DeclVisitor<cling::DeclUnloader, bool> {
  private:
    typedef llvm::DenseSet<clang::FileID> FileIDs;

    ///\brief The Sema object being unloaded (contains the AST as well).
    ///
    clang::Sema* m_Sema;

    ///\brief The clang code generator, being recovered.
    ///
    clang::CodeGenerator* m_CodeGen;

    ///\brief The current transaction being unloaded.
    ///
    const Transaction* m_CurTransaction;

    ///\brief Unloaded declaration contains a SourceLocation, representing a
    /// place in the file where it was seen. Clang caches that file and even if
    /// a declaration is removed and the file is edited we hit the cached entry.
    /// This ADT keeps track of the files from which the unloaded declarations
    /// came from so that in the end they could be removed from clang's cache.
    ///
    FileIDs m_FilesToUncache;

  public:
    DeclUnloader(clang::Sema* S, clang::CodeGenerator* CG, const Transaction* T)
      : m_Sema(S), m_CodeGen(CG), m_CurTransaction(T) { }
    ~DeclUnloader();

    ///\brief Forwards to Visit(), excluding PCH declarations (known to cause
    /// problems).  If unsure, call this function instead of plain `Visit()'.
    ///\param[in] D - The declaration to unload
    ///\returns true on success.
    ///
    bool UnloadDecl(clang::Decl* D) {
      if (D->isFromASTFile())
        return true;
      return Visit(D);
    }

    ///\brief If it falls back in the base class just remove the declaration
    /// only from the declaration context.
    /// @param[in] D - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitDecl(clang::Decl* D);

    ///\brief Removes the declaration from the lookup chains and from the
    /// declaration context.
    /// @param[in] ND - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitNamedDecl(clang::NamedDecl* ND);

    ///\brief Removes the declaration from Sema's unused decl registry
    /// @param[in] DD - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitDeclaratorDecl(clang::DeclaratorDecl* DD);

    ///\brief Removes a using shadow declaration, created in the cases:
    ///\code
    /// namespace A {
    ///   void foo();
    /// }
    /// namespace B {
    ///   using A::foo; // <- a UsingDecl
    ///                 // Also creates a UsingShadowDecl for A::foo() in B
    /// }
    ///\endcode
    ///\param[in] USD - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitUsingShadowDecl(clang::UsingShadowDecl* USD);

    ///\brief Removes a typedef name decls. A base class for TypedefDecls and
    /// TypeAliasDecls.
    ///\param[in] TND - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitTypedefNameDecl(clang::TypedefNameDecl* TND);

    ///\brief Removes the declaration from the lookup chains and from the
    /// declaration context and it rebuilds the redeclaration chain.
    /// @param[in] VD - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitVarDecl(clang::VarDecl* VD);

    ///\brief Removes the declaration from the lookup chains and from the
    /// declaration context and it rebuilds the redeclaration chain.
    /// @param[in] FD - The declaration to be removed.
    /// @param[in] RemoveSpec - If a template specialization, whether to remove
    ///    it from the parent.
    ///
    ///\returns true on success.
    ///
    bool VisitFunctionDecl(clang::FunctionDecl* FD, bool RemoveSpec);

    ///\brief Removes the declaration from the lookup chains and from the
    /// declaration context and it rebuilds the redeclaration chain.
    /// @param[in] FD - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitFunctionDecl(clang::FunctionDecl* FD);

    ///\brief Specialize the removal of constructors due to the fact the we need
    /// the constructor type (aka CXXCtorType). The information is located in
    /// the CXXConstructExpr of usually VarDecls.
    /// See clang::CodeGen::CodeGenFunction::EmitCXXConstructExpr.
    ///
    /// What we will do instead is to brute-force and try to remove from the
    /// llvm::Module all ctors of this class with all the types.
    ///
    ///\param[in] CXXCtor - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitCXXConstructorDecl(clang::CXXConstructorDecl* CXXCtor);

    ///\brief Specialize the removal of destructors due to the fact the we need
    /// the to erase the dtor decl and the deleting operator.
    ///
    /// We will brute-force and try to remove from the llvm::Module all dtors of
    /// this class with all the types.
    ///
    ///\param[in] CXXDtor - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitCXXDestructorDecl(clang::CXXDestructorDecl* CXXDtor);

    ///\brief Removes the DeclCotnext and its decls.
    /// @param[in] DC - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitDeclContext(clang::DeclContext* DC);

    ///\brief Removes the namespace.
    /// @param[in] NSD - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitNamespaceDecl(clang::NamespaceDecl* NSD);

    ///\brief Removes all extern "C" declarations.
    /// @param[in] LSD - The declaration context to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitLinkageSpecDecl(clang::LinkageSpecDecl* LSD);

    ///\brief Removes a Tag (class/union/struct/enum). Most of the other
    /// containers fall back into that case.
    /// @param[in] TD - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitTagDecl(clang::TagDecl* TD);

    ///\brief Removes a RecordDecl. We shouldn't remove the implicit class
    /// declaration.
    ///\param[in] RD - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitRecordDecl(clang::RecordDecl* RD);

    ///\brief Remove the macro from the Preprocessor.
    /// @param[in] MD - The MacroDirectiveInfo containing the IdentifierInfo and
    ///                MacroDirective to forward.
    ///
    ///\returns true on success.
    ///
    bool VisitMacro(const Transaction::MacroDirectiveInfo MD);

    ///@name Templates
    ///@{

    ///\brief Removes template from the redecl chain. Templates are
    /// redeclarables also.
    /// @param[in] R - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitRedeclarableTemplateDecl(clang::RedeclarableTemplateDecl* R);


    ///\brief Removes the declaration clang's internal structures. This case
    /// looks very much to VisitFunctionDecl, but FunctionTemplateDecl doesn't
    /// derive from FunctionDecl and thus we need to handle it 'by hand'.
    /// @param[in] FTD - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitFunctionTemplateDecl(clang::FunctionTemplateDecl* FTD);

    ///\brief Removes a class template declaration from clang's internal
    /// structures.
    /// @param[in] CTD - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitClassTemplateDecl(clang::ClassTemplateDecl* CTD);

    ///\brief Removes a class template specialization declaration from clang's
    /// internal structures.
    /// @param[in] CTSD - The declaration to be removed.
    /// @param[in] RemoveSpec - Whether to remove the specialization from its
    /// parent.
    ///
    ///\returns true on success.
    ///
    bool VisitClassTemplateSpecializationDecl(
        clang::ClassTemplateSpecializationDecl* CTSD, bool RemoveSpec);

    ///\brief Removes a class template specialization declaration from clang's
    /// internal structures.
    /// @param[in] CTSD - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitClassTemplateSpecializationDecl(
        clang::ClassTemplateSpecializationDecl* CTSD);

    ///\brief Removes a var template declaration from clang's internal
    /// structures.
    /// @param[in] VTD - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitVarTemplateDecl(clang::VarTemplateDecl* VTD);

    ///\brief Removes a var template specialization declaration from clang's
    /// internal structures.
    /// @param[in] CTSD - The declaration to be removed.
    /// @param[in] RemoveSpec - Whether to remove the specialization from its
    /// parent.
    ///
    ///\returns true on success.
    ///
    bool VisitVarTemplateSpecializationDecl(
        clang::VarTemplateSpecializationDecl* VTSD, bool RemoveSpec);

    ///\brief Removes a var template specialization declaration from clang's
    /// internal structures.
    /// @param[in] CTSD - The declaration to be removed.
    ///
    ///\returns true on success.
    ///
    bool VisitVarTemplateSpecializationDecl(
        clang::VarTemplateSpecializationDecl* VTSD);

    ///@}

    void MaybeRemoveDeclFromModule(clang::GlobalDecl& GD) const;

    /// @name Helpers
    /// @{

    ///\brief Interface with nice name, forwarding to Visit.
    ///
    ///\param[in] MD - The MacroDirectiveInfo containing the IdentifierInfo and
    ///                MacroDirective to forward.
    ///\returns true on success.
    ///
    bool UnloadMacro(Transaction::MacroDirectiveInfo MD) {
      return VisitMacro(MD);
    }
    /// @}

    static void resetDefinitionData(void*) {
      llvm_unreachable("resetDefinitionData on non-cxx record declaration");
    }

    static void resetDefinitionData(clang::TagDecl *decl);

  private:
    ///\brief Function that collects the files which we must reread from disk.
    ///
    /// For example: We must uncache the cached include, which brought a
    /// declaration or a macro directive definition in the AST.
    ///\param[in] Loc - The source location of the unloaded declaration.
    ///
    void CollectFilesToUncache(clang::SourceLocation Loc);

    template <typename T>
    bool VisitRedeclarable(clang::Redeclarable<T>* R, clang::DeclContext* DC);
  };

  /// \brief Unload a Decl from the AST, but not from CodeGen or Module.
  inline bool UnloadDecl(clang::Sema* S, clang::Decl* D) {
    DeclUnloader Unloader(S, nullptr, nullptr);
    return Unloader.UnloadDecl(D);
  }

  /// \brief Unload a Decl from the AST and CodeGen, but not from the Module.
  inline bool UnloadDecl(clang::Sema* S, clang::CodeGenerator* CG, clang::Decl* D) {
    DeclUnloader Unloader(S, CG, nullptr);
    return Unloader.UnloadDecl(D);
  }

} // namespace cling

#endif // CLING_DECL_UNLOADER
