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 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 + +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 Type *get(Key &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 Type *get(PtrKey &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 + ContextBuilder &&add(Key &Key, Args &&... As) && { + bool Added = Data.emplace(Key, std::forward(As)...); + assert(Added && "Value was already in the map"); + return std::move(*this); + } + + template + ContextBuilder &&add(PtrKey &Key, Args &&... As) && { + bool Added = Data.emplace(Key, std::forward(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 + +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/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 + +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 Key { +public: + static_assert(!std::is_reference::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 with a slightly easier to use semantics. +/// While get(Key) returns T**, get(PtrKey) returns T*. +/// Therefore PtrKey<> does not distinguish values missing from the map and +/// values equal to null. +template class PtrKey { +public: + Key UnderlyingKey; +}; + +/// A type-safe map from Key to T. +class TypedValueMap { +public: + TypedValueMap() = default; + TypedValueMap(const TypedValueMap &) = delete; + TypedValueMap(TypedValueMap &&) = default; + + template Type *get(Key &Key) const { + auto It = Map.find(&Key); + if (It == Map.end()) + return nullptr; + return static_cast(It->second->getValuePtr()); + } + + template + bool emplace(Key &Key, Args &&... As) { + bool Added = + Map.try_emplace(&Key, + llvm::make_unique< + TypedAnyStorage::type>>( + std::forward(As)...)) + .second; + return Added; + } + + template Type *get(PtrKey &PtrKey) const { + Type **PtrVal = get(PtrKey.UnderlyingKey); + if (!PtrVal) + return nullptr; + return *PtrVal; + } + + template bool emplace(PtrKey &PtrKey, Arg *A) { + return emplace(PtrKey.UnderlyingKey, A); + } + + template bool emplace(PtrKey &PtrKey, std::nullptr_t) { + return emplace(PtrKey.UnderlyingKey, nullptr); + } + + template bool remove(Key &Key) { + auto It = Map.find(&Key); + if (It == Map.end()) + return false; + + Map.erase(It); + return true; + } + + template bool remove(PtrKey &Key) { + return remove(Key.UnderlyingKey); + } + +private: + class AnyStorage { + public: + virtual ~AnyStorage() = default; + virtual void *getValuePtr() = 0; + }; + + template class TypedAnyStorage : public AnyStorage { + static_assert(std::is_same::type, T>::value, + "Argument to TypedAnyStorage must be decayed"); + + public: + template + TypedAnyStorage(Args &&... As) : Value(std::forward(As)...) {} + + void *getValuePtr() override { return &Value; } + + private: + T Value; + }; + + // Map keys are addresses of Key<> objects. + llvm::DenseMap> Map; +}; + +} // 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: 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 IntParam; + Key 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> Param; + + clangd::TypedValueMap Ctx; + Ctx.emplace(Param, llvm::make_unique(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 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 ParentParam; + Key ParentAndChildParam; + Key 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