Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -8,6 +8,7 @@ ClangdUnit.cpp ClangdUnitStore.cpp CodeComplete.cpp + Context.cpp Compiler.cpp DraftStore.cpp FuzzyMatch.cpp Index: clangd/Context.h =================================================================== --- /dev/null +++ clangd/Context.h @@ -0,0 +1,147 @@ +//===--- 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 { + +class ContextData { +public: + ContextData(std::shared_ptr Parent, TypedValueMap Data); + + /// 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; + } + + /// A helper to get a reference to a \p Key that must exist in the map. + /// Must not be called for keys that are not in the map. + template Type &getExisting(Key &Key) const { + auto Val = get(Key); + if (!Val) + llvm_unreachable("Key does not exist"); + return *Val; + } + + /// A helper to get a string value as StringRef. Returns empty StringRef if \p + /// StrKey does not exist in a map. + llvm::StringRef getString(Key &StrKey) const { + auto Val = get(StrKey); + if (!Val) + return llvm::StringRef(); + return *Val; + } + + /// A helper to get a dereferenced pointer value (i.e. returns Type* as + /// opposed to Type** returned by get()). Returns nullptr if \p Key does not + /// exist in a map. + template Type *getPtr(Key &Key) const { + if (auto Val = get(Key)) + return *Val; + return nullptr; + } + +private: + // We need to make sure Parent is destroyed after Data. The order of members + // is important. + std::shared_ptr Parent; + TypedValueMap Data; +}; + +class ContextBuilder; + +/// 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(std::shared_ptr DataPtr); + + /// Move-only. + Context(Context const &) = delete; + Context &operator=(const Context &) = delete; + + Context(Context &&) = default; + Context &operator=(Context &&) = default; + + const ContextData &operator*() const { return *DataPtr; } + const ContextData *operator->() const { return DataPtr.get(); } + + ContextBuilder derive(); + +private: + std::shared_ptr DataPtr; +}; + +/// 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() = default; + ContextBuilder(std::shared_ptr 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 &&addOpt(Key &Key, llvm::Optional Val) && { + if (!Val) + return std::move(*this); + return std::move(*this).add(Key, std::move(*Val)); + } + + operator Context() &&; + +private: + std::shared_ptr Parent; + TypedValueMap Data; +}; + +/// Returns an empty Context that contains no data. Useful for calling functions +/// that require a Context when no explicit Context is not available. +Context &emptyCtx(); + +/// Creates a ContextBuilder with a null parent. +ContextBuilder buildCtx(); + +} // 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 { + +ContextData::ContextData(std::shared_ptr Parent, + TypedValueMap Data) + : Parent(std::move(Parent)), Data(std::move(Data)) {} + +Context::Context(std::shared_ptr DataPtr) + : DataPtr(std::move(DataPtr)) { + assert(this->DataPtr && "DataPtr must be non-null"); +} + +ContextBuilder Context::derive() { return ContextBuilder(DataPtr); } + +ContextBuilder::ContextBuilder(std::shared_ptr Parent) + : Parent(std::move(Parent)) {} + +ContextBuilder::operator Context() && { + return Context( + std::make_shared(std::move(Parent), std::move(Data))); +} + +Context &emptyCtx() { + static Context Empty = buildCtx(); + return Empty; +} + +ContextBuilder buildCtx() { return ContextBuilder(nullptr); } + +} // namespace clangd +} // namespace clang Index: clangd/TypedValueMap.h =================================================================== --- /dev/null +++ clangd/TypedValueMap.h @@ -0,0 +1,95 @@ +//===--- 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; +}; + +/// 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 bool remove(Key &Key) { + auto It = Map.find(&Key); + if (It == Map.end()) + return false; + + Map.erase(It); + return true; + } + +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 @@ -11,6 +11,7 @@ add_extra_unittest(ClangdTests ClangdTests.cpp CodeCompleteTests.cpp + ContextTests.cpp FuzzyMatchTests.cpp JSONExprTests.cpp TestFS.cpp Index: unittests/clangd/ContextTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/ContextTests.cpp @@ -0,0 +1,71 @@ +//===-- 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(ContextTests, Builders) { + Key ParentParam; + Key ParentAndChildParam; + Key ChildParam; + + Context ParentCtx = + buildCtx().add(ParentParam, 10).add(ParentAndChildParam, 20); + Context ChildCtx = + ParentCtx.derive().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