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,175 @@ +//===--- 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 + +namespace clang { +namespace clangd { + +class ContextBuilder; + +/// A context is an immutable container for per-request data that must be +/// propagated through layers that don't care about it. An example is a request +/// ID that we may want to use when logging. +/// +/// Conceptually, a context is a heterogeneous map, T>. Each key has +/// an associated value type, which allows the map to be typesafe. +/// +/// You can't add data to an existing context, instead you create a new +/// immutable context derived from it with extra data added. When you retrieve +/// data, the context will walk up the parent chain until the key is found. +/// +/// Contexts should be: +/// - passed by reference when calling synchronous functions +/// - passed by value (move) when calling asynchronous functions. The result +/// callback of async operations will receive the context again. +/// - cloned only when 'forking' an asynchronous computation that we don't wait +/// for. +/// +/// Copy operations for this class are deleted to avoid implicit copies, use an +/// explicit clone() method instead. +/// +/// Contexts are created using a fluent-builder API. +/// To create a new context without any parent use the buildCtx() function, +/// e.g.: +/// Context Ctx = buildCtx().add(RequestIdKey, 123); +/// +/// To derive a child context use derive() function, e.g. +/// Context ChildCtx = ParentCtx.derive().add(RequestIdKey, 123); +class Context { +private: + class ContextData; + Context(std::shared_ptr DataPtr); + +public: + /// 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 const Type *get(const Key &Key) const { + return DataPtr->get(Key); + } + + /// 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 const Type &getExisting(const Key &Key) const { + auto Val = get(Key); + assert(Val && "Key does not exist"); + return *Val; + } + + /// Derives a child context + /// It is safe to move or destroy a parent context after calling derive() from + /// it. The child context will continue to have access to the data stored in + /// the parent context. + ContextBuilder derive() const &; + ContextBuilder derive() const &&; + + /// Clone this context object. + Context clone() const; + +private: + friend class ContextBuilder; + + class ContextData { + public: + ContextData(std::shared_ptr Parent, TypedValueMap Data) + : Parent(std::move(Parent)), Data(std::move(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 const Type *get(const Key &Key) const { + if (auto Val = Data.get(Key)) + return Val; + if (Parent) + return Parent->get(Key); + return nullptr; + } + + private: + // We need to make sure Parent outlives Data. So the order of members is + // important. We do that to allow classes stored in Context's child layers + // to store references to the data in the parent layers. + std::shared_ptr Parent; + TypedValueMap Data; + }; + + std::shared_ptr DataPtr; +}; // namespace clangd + +/// 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(RequestIdKey, 123); +class ContextBuilder { +public: + ContextBuilder() = default; + explicit ContextBuilder(const Context &Parent); + explicit ContextBuilder(Context &&Parent); + + ContextBuilder(ContextBuilder &&) = default; + ContextBuilder &operator=(ContextBuilder &&) = default; + + ContextBuilder(const ContextBuilder &) = delete; + ContextBuilder &operator=(const ContextBuilder &) = default; + + /// Add a value to the context we are building. Same \p Key cannot be + /// specified twice. + 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); + } + + /// If \p Val has a value, add it to the context we are building. + /// If \p Val does not have a value, do nothing. + template + ContextBuilder &&addOpt(Key &Key, llvm::Optional Val) && { + if (!Val) + return std::move(*this); + return std::move(*this).add(Key, std::move(*Val)); + } + + /// Finish building the resulting context and return it. + 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. +const 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,46 @@ +//===--- 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 "TypedValueMap.h" +#include + +namespace clang { +namespace clangd { + +Context::Context(std::shared_ptr DataPtr) + : DataPtr(std::move(DataPtr)) { + assert(this->DataPtr && "DataPtr must be non-null"); +} + +ContextBuilder Context::derive() const & { return ContextBuilder(*this); } +ContextBuilder Context::derive() const && { return ContextBuilder(std::move(*this)); } + +Context Context::clone() const { return Context(DataPtr); } + +ContextBuilder::ContextBuilder(const Context &Parent) + : Parent(Parent.DataPtr) {} + +ContextBuilder::ContextBuilder(Context &&Parent) + : Parent(std::move(Parent.DataPtr)) {} + +ContextBuilder::operator Context() && { + return Context(std::make_shared(std::move(Parent), + std::move(Data))); +} + +const Context &emptyCtx() { + static Context Empty = buildCtx(); + return Empty; +} + +ContextBuilder buildCtx() { return ContextBuilder(); } + +} // namespace clangd +} // namespace clang Index: clangd/TypedValueMap.h =================================================================== --- /dev/null +++ clangd/TypedValueMap.h @@ -0,0 +1,104 @@ +//===--- 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. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_TYPEDVALUEMAP_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_TYPEDVALUEMAP_H_ + +#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 const Type *get(const Key &Key) const { + return const_cast(this)->get(Key); + } + + template Type *get(const Key &Key) { + auto It = Map.find(&Key); + if (It == Map.end()) + return nullptr; + return static_cast(It->second->getValuePtr()); + } + + template + bool emplace(const 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 + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_TYPEDVALUEMAP_H_ 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