ilya-biryukov updated this revision to Diff 126319. ilya-biryukov marked 4 inline comments as done. ilya-biryukov added a comment.
- Added a comment about the Parent vs Data lifetimes. Repository: rCTE Clang Tools Extra 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,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<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(ContextTests, Builders) { + Key<int> ParentParam; + Key<int> ParentAndChildParam; + Key<int> 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 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: 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 <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; +}; + +/// A type-safe map from Key<T> to T. +class TypedValueMap { +public: + TypedValueMap() = default; + TypedValueMap(const TypedValueMap &) = delete; + TypedValueMap(TypedValueMap &&) = default; + + template <class Type> const Type *get(const Key<Type> &Key) const { + return const_cast<TypedValueMap *>(this)->get(Key); + } + + template <class Type> Type *get(const Key<Type> &Key) { + 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(const 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> bool remove(Key<Type> &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 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<const void *, std::unique_ptr<AnyStorage>> Map; +}; + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_TYPEDVALUEMAP_H_ Index: clangd/Context.h =================================================================== --- /dev/null +++ clangd/Context.h @@ -0,0 +1,176 @@ +//===--- 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 <type_traits> + +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<Key<T>, 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<const ContextData> 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 <class Type> const Type *get(const Key<Type> &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 <class Type> const Type &getExisting(const Key<Type> &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; + + /// Clone this context object. + Context clone() const; + +private: + friend class ContextBuilder; + + class ContextData { + public: + ContextData(std::shared_ptr<const ContextData> 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 <class Type> const Type *get(const Key<Type> &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 is destroyed after 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. Therefore, we need to run + // destructors for child layer data first. + std::shared_ptr<const ContextData> Parent; + TypedValueMap Data; + }; + + std::shared_ptr<const ContextData> 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; + ContextBuilder(const 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 <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); + } + + /// 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 <class T> + ContextBuilder &&addOpt(Key<T> &Key, llvm::Optional<T> 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<const Context::ContextData> 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,42 @@ +//===--- 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 <cassert> + +namespace clang { +namespace clangd { + +Context::Context(std::shared_ptr<const ContextData> DataPtr) + : DataPtr(std::move(DataPtr)) { + assert(this->DataPtr && "DataPtr must be non-null"); +} + +ContextBuilder Context::derive() const { return ContextBuilder(*this); } + +Context Context::clone() const { return Context(DataPtr); } + +ContextBuilder::ContextBuilder(const Context &Parent) + : Parent(Parent.DataPtr) {} + +ContextBuilder::operator Context() && { + return Context(std::make_shared<Context::ContextData>(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/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
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits