//===--- ASTTypeTraits.h ----------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
//  Provides a dynamically typed node container that can be used to store
//  an AST base node at runtime in the same storage in a type safe way.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_AST_AST_TYPE_TRAITS_H
#define LLVM_CLANG_AST_AST_TYPE_TRAITS_H

#include "clang/AST/Decl.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/TypeLoc.h"
#include "llvm/Support/AlignOf.h"

namespace clang {
namespace ast_type_traits {

/// \brief A dynamically typed AST node container.
///
/// Stores an AST node in a type safe way. This allows writing code that
/// works with different kinds of AST nodes, despite the fact that they don't
/// have a common base class.
///
/// Use \c create(Node) to create a \c DynTypedNode from an AST node,
/// and \c get<T>() to retrieve the node as type T if the types match.
///
/// See \c NodeTypeTag for which node base types are currently supported;
/// You can create DynTypedNodes for all nodes in the inheritance hierarchy of
/// the supported base types.
class DynTypedNode {
public:
  /// \brief Creates a \c DynTypedNode from \c Node.
  template <typename T>
  static DynTypedNode create(const T &Node) {
    return BaseConverter<T>::create(Node);
  }

  /// \brief Retrieve the stored node as type \c T.
  ///
  /// Returns NULL if the stored node does not have a type that is
  /// convertible to \c T.
  ///
  /// For types that have identity via their pointer in the AST
  /// (like \c Stmt and \c Decl) the returned pointer points to the
  /// referenced AST node.
  /// For other types (like \c QualType) the value is stored directly
  /// in the \c DynTypedNode, and the returned pointer points at
  /// the storage inside DynTypedNode. For those nodes, do not
  /// use the pointer outside the scope of the DynTypedNode.
  template <typename T>
  const T *get() const {
    return BaseConverter<T>::get(Tag, Storage.buffer);
  }

  /// \brief Returns a pointer that identifies the stored AST node.
  ///
  /// Note that this is not supported by all AST nodes. For AST nodes
  /// that don't have a pointer-defined identity inside the AST, this
  /// method returns NULL.
  const void *getMemoizationData() const;

private:
  /// \brief Takes care of converting from and to \c T.
  template <typename T, typename EnablerT = void> struct BaseConverter;

  /// \brief Supported base node types.
  enum NodeTypeTag {
    NT_Decl,
    NT_Stmt,
    NT_NestedNameSpecifier,
    NT_NestedNameSpecifierLoc,
    NT_QualType,
    NT_Type,
    NT_TypeLoc
  } Tag;

  /// \brief Stores the data of the node.
  ///
  /// Note that we can store \c Decls and \c Stmts by pointer as they are
  /// guaranteed to be unique pointers pointing to dedicated storage in the
  /// AST. \c QualTypes on the other hand do not have storage or unique
  /// pointers and thus need to be stored by value.
  llvm::AlignedCharArrayUnion<Decl *, Stmt *, NestedNameSpecifier,
                              NestedNameSpecifierLoc, QualType, Type,
                              TypeLoc> Storage;
};

// FIXME: Pull out abstraction for the following.
template<typename T> struct DynTypedNode::BaseConverter<T,
    typename llvm::enable_if<llvm::is_base_of<Decl, T> >::type> {
  static const T *get(NodeTypeTag Tag, const char Storage[]) {
    if (Tag == NT_Decl)
      return dyn_cast<T>(*reinterpret_cast<Decl*const*>(Storage));
    return NULL;
  }
  static DynTypedNode create(const Decl &Node) {
    DynTypedNode Result;
    Result.Tag = NT_Decl;
    new (Result.Storage.buffer) const Decl*(&Node);
    return Result;
  }
};
template<typename T> struct DynTypedNode::BaseConverter<T,
    typename llvm::enable_if<llvm::is_base_of<Stmt, T> >::type> {
  static const T *get(NodeTypeTag Tag, const char Storage[]) {
    if (Tag == NT_Stmt)
      return dyn_cast<T>(*reinterpret_cast<Stmt*const*>(Storage));
    return NULL;
  }
  static DynTypedNode create(const Stmt &Node) {
    DynTypedNode Result;
    Result.Tag = NT_Stmt;
    new (Result.Storage.buffer) const Stmt*(&Node);
    return Result;
  }
};
template<typename T> struct DynTypedNode::BaseConverter<T,
    typename llvm::enable_if<llvm::is_base_of<Type, T> >::type> {
  static const T *get(NodeTypeTag Tag, const char Storage[]) {
    if (Tag == NT_Type)
      return dyn_cast<T>(*reinterpret_cast<Type*const*>(Storage));
    return NULL;
  }
  static DynTypedNode create(const Type &Node) {
    DynTypedNode Result;
    Result.Tag = NT_Type;
    new (Result.Storage.buffer) const Type*(&Node);
    return Result;
  }
};
template<> struct DynTypedNode::BaseConverter<NestedNameSpecifier, void> {
  static const NestedNameSpecifier *get(NodeTypeTag Tag, const char Storage[]) {
    if (Tag == NT_NestedNameSpecifier)
      return *reinterpret_cast<NestedNameSpecifier*const*>(Storage);
    return NULL;
  }
  static DynTypedNode create(const NestedNameSpecifier &Node) {
    DynTypedNode Result;
    Result.Tag = NT_NestedNameSpecifier;
    new (Result.Storage.buffer) const NestedNameSpecifier*(&Node);
    return Result;
  }
};
template<> struct DynTypedNode::BaseConverter<NestedNameSpecifierLoc, void> {
  static const NestedNameSpecifierLoc *get(NodeTypeTag Tag,
                                           const char Storage[]) {
    if (Tag == NT_NestedNameSpecifierLoc)
      return reinterpret_cast<const NestedNameSpecifierLoc*>(Storage);
    return NULL;
  }
  static DynTypedNode create(const NestedNameSpecifierLoc &Node) {
    DynTypedNode Result;
    Result.Tag = NT_NestedNameSpecifierLoc;
    new (Result.Storage.buffer) NestedNameSpecifierLoc(Node);
    return Result;
  }
};
template<> struct DynTypedNode::BaseConverter<QualType, void> {
  static const QualType *get(NodeTypeTag Tag, const char Storage[]) {
    if (Tag == NT_QualType)
      return reinterpret_cast<const QualType*>(Storage);
    return NULL;
  }
  static DynTypedNode create(const QualType &Node) {
    DynTypedNode Result;
    Result.Tag = NT_QualType;
    new (Result.Storage.buffer) QualType(Node);
    return Result;
  }
};
template<> struct DynTypedNode::BaseConverter<TypeLoc, void> {
  static const TypeLoc *get(NodeTypeTag Tag, const char Storage[]) {
    if (Tag == NT_TypeLoc)
      return reinterpret_cast<const TypeLoc*>(Storage);
    return NULL;
  }
  static DynTypedNode create(const TypeLoc &Node) {
    DynTypedNode Result;
    Result.Tag = NT_TypeLoc;
    new (Result.Storage.buffer) TypeLoc(Node);
    return Result;
  }
};
// The only operation we allow on unsupported types is \c get.
// This allows to conveniently use \c DynTypedNode when having an arbitrary
// AST node that is not supported, but prevents misuse - a user cannot create
// a DynTypedNode from arbitrary types.
template <typename T, typename EnablerT> struct DynTypedNode::BaseConverter {
  static const T *get(NodeTypeTag Tag, const char Storage[]) { return NULL; }
};

inline const void *DynTypedNode::getMemoizationData() const {
  switch (Tag) {
    case NT_Decl: return BaseConverter<Decl>::get(Tag, Storage.buffer);
    case NT_Stmt: return BaseConverter<Stmt>::get(Tag, Storage.buffer);
    default: return NULL;
  };
}

} // end namespace ast_type_traits
} // end namespace clang

#endif // LLVM_CLANG_AST_AST_TYPE_TRAITS_H
