ilya-biryukov created this revision. Herald added a subscriber: mgorny. It will be used to pass around things like Logger and Tracer throughout clangd classes.
https://reviews.llvm.org/D40485 Files: clangd/CMakeLists.txt clangd/Context.cpp clangd/Context.h clangd/TypedValueMap.h unittests/clangd/CMakeLists.txt unittests/clangd/ContextTests.cpp
Index: unittests/clangd/ContextTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/ContextTests.cpp @@ -0,0 +1,88 @@ +//===-- ContextTests.cpp - Context tests ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Context.h" + +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { + +TEST(TypedValueMapTests, Simple) { + Key<int> IntParam; + Key<int> ExtraIntParam; + + clangd::TypedValueMap Ctx; + + ASSERT_TRUE(Ctx.emplace(IntParam, 10)); + ASSERT_TRUE(Ctx.emplace(ExtraIntParam, 20)); + + EXPECT_EQ(*Ctx.get(IntParam), 10); + EXPECT_EQ(*Ctx.get(ExtraIntParam), 20); + + ASSERT_FALSE(Ctx.emplace(IntParam, 30)); + + ASSERT_TRUE(Ctx.remove(IntParam)); + EXPECT_EQ(Ctx.get(IntParam), nullptr); + EXPECT_EQ(*Ctx.get(ExtraIntParam), 20); + + ASSERT_TRUE(Ctx.emplace(IntParam, 30)); + EXPECT_EQ(*Ctx.get(IntParam), 30); + EXPECT_EQ(*Ctx.get(ExtraIntParam), 20); +} + +TEST(TypedValueMapTests, MoveOps) { + Key<std::unique_ptr<int>> Param; + + clangd::TypedValueMap Ctx; + Ctx.emplace(Param, llvm::make_unique<int>(10)); + EXPECT_EQ(**Ctx.get(Param), 10); + + clangd::TypedValueMap NewCtx = std::move(Ctx); + EXPECT_EQ(**NewCtx.get(Param), 10); +} + +TEST(TypedValueMapTests, PtrKey) { + int Value = 10; + PtrKey<int> Param; + + clangd::TypedValueMap Ctx; + EXPECT_EQ(Ctx.get(Param), nullptr); + + Ctx.emplace(Param, &Value); + EXPECT_EQ(*Ctx.get(Param), 10); + + Ctx.remove(Param); + EXPECT_EQ(Ctx.get(Param), nullptr); + + Ctx.emplace(Param, nullptr); + EXPECT_EQ(Ctx.get(Param), nullptr); +} + +TEST(ContextTests, Builders) { + Key<int> ParentParam; + Key<int> ParentAndChildParam; + Key<int> ChildParam; + + Context ParentCtx = + buildCtx().add(ParentParam, 10).add(ParentAndChildParam, 20); + Context ChildCtx = + buildCtx(&ParentCtx).add(ParentAndChildParam, 30).add(ChildParam, 40); + + EXPECT_EQ(*ParentCtx.get(ParentParam), 10); + EXPECT_EQ(*ParentCtx.get(ParentAndChildParam), 20); + EXPECT_EQ(ParentCtx.get(ChildParam), nullptr); + + EXPECT_EQ(*ChildCtx.get(ParentParam), 10); + EXPECT_EQ(*ChildCtx.get(ParentAndChildParam), 30); + EXPECT_EQ(*ChildCtx.get(ChildParam), 40); +} + +} // namespace clangd +} // namespace clang Index: unittests/clangd/CMakeLists.txt =================================================================== --- unittests/clangd/CMakeLists.txt +++ unittests/clangd/CMakeLists.txt @@ -10,6 +10,7 @@ add_extra_unittest(ClangdTests ClangdTests.cpp + ContextTests.cpp JSONExprTests.cpp TraceTests.cpp ) Index: clangd/TypedValueMap.h =================================================================== --- /dev/null +++ clangd/TypedValueMap.h @@ -0,0 +1,123 @@ +//===--- TypedValueMap.h - Type-safe heterogenous key-value map -*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Type-safe heterogenous map. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/DenseMap.h" +#include <type_traits> + +namespace clang { +namespace clangd { + +/// Used as identity for map values. Non-movable and non-copyable. Address of +/// this object is used internally to as keys in a map. +template <class Type> class Key { +public: + static_assert(!std::is_reference<Type>::value, + "Reference arguments to Key<> are not allowed"); + + Key() = default; + + Key(Key const &) = delete; + Key &operator=(Key const &) = delete; + Key(Key &&) = delete; + Key &operator=(Key &&) = delete; +}; + +/// Similar to a Key<T*> with a slightly easier to use semantics. +/// While get(Key<T*>) returns T**, get(PtrKey<T>) returns T*. +/// Therefore PtrKey<> does not distinguish values missing from the map and +/// values equal to null. +template <class Type> class PtrKey { +public: + Key<Type *> UnderlyingKey; +}; + +/// A type-safe map from Key<T> to T. +class TypedValueMap { +public: + TypedValueMap() = default; + TypedValueMap(const TypedValueMap &) = delete; + TypedValueMap(TypedValueMap &&) = default; + + template <class Type> Type *get(Key<Type> &Key) const { + auto It = Map.find(&Key); + if (It == Map.end()) + return nullptr; + return static_cast<Type *>(It->second->getValuePtr()); + } + + template <class Type, class... Args> + bool emplace(Key<Type> &Key, Args &&... As) { + bool Added = + Map.try_emplace(&Key, + llvm::make_unique< + TypedAnyStorage<typename std::decay<Type>::type>>( + std::forward<Args>(As)...)) + .second; + return Added; + } + + template <class Type> Type *get(PtrKey<Type> &PtrKey) const { + Type **PtrVal = get(PtrKey.UnderlyingKey); + if (!PtrVal) + return nullptr; + return *PtrVal; + } + + template <class Type, class Arg> bool emplace(PtrKey<Type> &PtrKey, Arg *A) { + return emplace(PtrKey.UnderlyingKey, A); + } + + template <class Type> bool emplace(PtrKey<Type> &PtrKey, std::nullptr_t) { + return emplace(PtrKey.UnderlyingKey, nullptr); + } + + template <class Type> bool remove(Key<Type> &Key) { + auto It = Map.find(&Key); + if (It == Map.end()) + return false; + + Map.erase(It); + return true; + } + + template <class Type> bool remove(PtrKey<Type> &Key) { + return remove(Key.UnderlyingKey); + } + +private: + class AnyStorage { + public: + virtual ~AnyStorage() = default; + virtual void *getValuePtr() = 0; + }; + + template <class T> class TypedAnyStorage : public AnyStorage { + static_assert(std::is_same<typename std::decay<T>::type, T>::value, + "Argument to TypedAnyStorage must be decayed"); + + public: + template <class... Args> + TypedAnyStorage(Args &&... As) : Value(std::forward<Args>(As)...) {} + + void *getValuePtr() override { return &Value; } + + private: + T Value; + }; + + // Map keys are addresses of Key<> objects. + llvm::DenseMap<void *, std::unique_ptr<AnyStorage>> Map; +}; + +} // namespace clangd +} // namespace clang Index: clangd/Context.h =================================================================== --- /dev/null +++ clangd/Context.h @@ -0,0 +1,123 @@ +//===--- Context.h - Mechanism for passing implicit data --------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Context for storing and retrieving implicit data. Useful for passing implicit +// parameters on a per-request basis. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CONTEXT_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CONTEXT_H_ + +#include "TypedValueMap.h" +#include "llvm/ADT/DenseMap.h" +#include <type_traits> + +namespace clang { +namespace clangd { + +/// Allows to store and retrieve implicit data. A \p Context is typically +/// created on a per-request basis. Contexts are chained via the parent +/// pointers. Any values are first looked up in the current Context and, if +/// not found, parent Contexts are queried. +/// Contexts are move-only, but one should be careful to avoid moving Contexts +/// used as parents for some other Contexts. +class Context { +public: + Context(Context *Parent, TypedValueMap Data); + + /// Move-only. + Context(Context const &) = delete; + Context &operator=(const Context &) = delete; + + Context(Context &&) = default; + Context &operator=(Context &&) = default; + + /// Get data stored for a typed \p Key. If values are not found + /// \returns Pointer to the data associated with \p Key. If no data is + /// specified for \p Key, return null. + template <class Type> Type *get(Key<Type> &Key) const { + if (auto Val = Data.get(Key)) + return Val; + if (Parent) + return Parent->get(Key); + return nullptr; + } + + /// Get data stored for a typed \p Key. Note that this function does not + /// distinguish between \returns Stored pointer, or null if data is not found. + template <class Type> Type *get(PtrKey<Type> &Key) const { + if (auto Val = Data.get(Key)) + return Val; + if (Parent) + return Parent->get(Key); + return nullptr; + } + +private: + Context *Parent; + TypedValueMap Data; +}; + +/// Sets global Context object, returned by globalCtx. +/// Only one session can exist at a time and the session must be initialized +/// before any clangd functions are called. +class GlobalSession { +public: + GlobalSession(Context Ctx); + ~GlobalSession(); +}; + +/// If there is an active GlobalSession, returns the Context provided by it. +/// Otherwise returns an empty Context. +Context &globalCtx(); + +/// Fluent Builder API for creating Contexts. Instead of using it directly, +/// prefer to use the buildCtx factory functions. Provides only an r-value-only +/// API, i.e. it is intended to be used on unnamed temporaries: +/// +/// Context Ctx = buildCtx().add(LoggerKey, &MyLogger).add(TracerKey, +/// &MyTracer); +class ContextBuilder { +public: + ContextBuilder(Context *Parent); + + ContextBuilder(ContextBuilder &&) = default; + ContextBuilder(ContextBuilder const &) = delete; + + template <class T, class... Args> + ContextBuilder &&add(Key<T> &Key, Args &&... As) && { + bool Added = Data.emplace(Key, std::forward<Args>(As)...); + assert(Added && "Value was already in the map"); + return std::move(*this); + } + + template <class T, class... Args> + ContextBuilder &&add(PtrKey<T> &Key, Args &&... As) && { + bool Added = Data.emplace(Key, std::forward<Args>(As)...); + assert(Added && "Value was already in the map"); + return std::move(*this); + } + + operator Context() && { return Context(Parent, std::move(Data)); } + +private: + Context *Parent; + TypedValueMap Data; +}; + +/// Creates a new ContextBuilder, using globalCtx() as a parent. +ContextBuilder buildCtx(); +/// Creates a new ContextBuilder with explicit \p Parent. +ContextBuilder buildCtx(Context *Parent); + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_CONTEXT_H_ Index: clangd/Context.cpp =================================================================== --- /dev/null +++ clangd/Context.cpp @@ -0,0 +1,43 @@ +//===--- Context.cpp -----------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#include "Context.h" +#include <cassert> + +namespace clang { +namespace clangd { + +static Context *GlobalCtx = nullptr; +static Context EmptyContext(nullptr, {}); + +Context::Context(Context *Parent, TypedValueMap Data) + : Parent(Parent), Data(std::move(Data)) {} + +GlobalSession::GlobalSession(Context Ctx) { + assert(!GlobalCtx && "GlobalCtx was already set"); + GlobalCtx = new Context(std::move(Ctx)); +} + +GlobalSession::~GlobalSession() { + delete GlobalCtx; + GlobalCtx = nullptr; +} + +Context &globalCtx() { + if (GlobalCtx) + return *GlobalCtx; + return EmptyContext; +} + +ContextBuilder::ContextBuilder(Context *Parent) : Parent(Parent) {} + +ContextBuilder buildCtx() { return buildCtx(&globalCtx()); } +ContextBuilder buildCtx(Context *Parent) { return {Parent}; } +} // namespace clangd +} // namespace clang Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -7,6 +7,7 @@ ClangdServer.cpp ClangdUnit.cpp ClangdUnitStore.cpp + Context.cpp DraftStore.cpp GlobalCompilationDatabase.cpp JSONExpr.cpp
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits