From 657159c273d4cc98c2943e790004cacc62555974 Mon Sep 17 00:00:00 2001 From: Ilya Biryukov Date: Tue, 12 Dec 2017 11:16:45 +0000 Subject: [PATCH] [clangd] Introduced a Context that stores implicit data Summary: It will be used to pass around things like Logger and Tracer throughout clangd classes. Reviewers: sammccall, ioeric, hokein, bkramer Reviewed By: sammccall Subscribers: klimek, bkramer, mgorny, cfe-commits Differential Revision: https://reviews.llvm.org/D40485 llvm-svn: 320468 --- clang-tools-extra/clangd/CMakeLists.txt | 1 + clang-tools-extra/clangd/Context.cpp | 24 +++ clang-tools-extra/clangd/Context.h | 186 ++++++++++++++++++ .../unittests/clangd/CMakeLists.txt | 1 + .../unittests/clangd/ContextTests.cpp | 57 ++++++ 5 files changed, 269 insertions(+) create mode 100644 clang-tools-extra/clangd/Context.cpp create mode 100644 clang-tools-extra/clangd/Context.h create mode 100644 clang-tools-extra/unittests/clangd/ContextTests.cpp diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt index 5641fc8cee2c..e8a1fc0e3cfa 100644 --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -8,6 +8,7 @@ add_clang_library(clangDaemon ClangdUnit.cpp ClangdUnitStore.cpp CodeComplete.cpp + Context.cpp Compiler.cpp DraftStore.cpp FuzzyMatch.cpp diff --git a/clang-tools-extra/clangd/Context.cpp b/clang-tools-extra/clangd/Context.cpp new file mode 100644 index 000000000000..d7a1a04b035c --- /dev/null +++ b/clang-tools-extra/clangd/Context.cpp @@ -0,0 +1,24 @@ +//===--- 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 { + +Context Context::empty() { return Context(/*Data=*/nullptr); } + +Context::Context(std::shared_ptr DataPtr) + : DataPtr(std::move(DataPtr)) {} + +Context Context::clone() const { return Context(DataPtr); } + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/Context.h b/clang-tools-extra/clangd/Context.h new file mode 100644 index 000000000000..33e946b6d63f --- /dev/null +++ b/clang-tools-extra/clangd/Context.h @@ -0,0 +1,186 @@ +//===--- 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 "llvm/ADT/STLExtras.h" +#include +#include + +namespace clang { +namespace clangd { + +/// A key for a value of type \p Type, stored inside a context. Keys are +/// non-movable and non-copyable. See documentation of the Context class for +/// more details and usage examples. +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 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, use an explicit clone() method +/// when you need a copy of the context instead. +/// +/// To derive a child context use derive() function, e.g. +/// Context ChildCtx = ParentCtx.derive(RequestIdKey, 123); +/// +/// To create a new root context, derive() from empty Context. +/// e.g.: +/// Context Ctx = Context::empty().derive(RequestIdKey, 123); +/// +/// Values in the context are indexed by typed keys (instances of Key class). +/// Key serves two purposes: +/// - it provides a lookup key for the context (each instance of a key is +/// unique), +/// - it keeps the type information about the value stored in the context map +/// in the template arguments. +/// This provides a type-safe interface to store and access values of multiple +/// types inside a single context. +/// For example, +/// Key RequestID; +/// Key Version; +/// +/// Context Ctx = Context::empty().derive(RequestID, 10).derive(Version, 3); +/// assert(*Ctx.get(RequestID) == 10); +/// assert(*Ctx.get(Version) == 3); +/// +/// Keys are typically used across multiple functions, so most of the time you +/// would want to make them static class members or global variables. +class Context { +public: + /// Returns an empty context that contains no data. Useful for calling + /// functions that require a context when no explicit context is available. + static Context empty(); + +private: + struct Data; + 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 { + for (const Data *DataPtr = this->DataPtr.get(); DataPtr != nullptr; + DataPtr = DataPtr->Parent.get()) { + if (DataPtr->KeyPtr == &Key) + return static_cast(DataPtr->Value->getValuePtr()); + } + 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 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. + template + Context derive(const Key &Key, + typename std::decay::type Value) const & { + return Context(std::make_shared(Data{ + /*Parent=*/DataPtr, &Key, + llvm::make_unique::type>>( + std::move(Value))})); + } + + template + Context + derive(const Key &Key, + typename std::decay::type Value) && /* takes ownership */ { + return Context(std::make_shared(Data{ + /*Parent=*/std::move(DataPtr), &Key, + llvm::make_unique::type>>( + std::move(Value))})); + } + + /// Clone this context object. + Context clone() const; + +private: + class AnyStorage { + public: + virtual ~AnyStorage() = default; + virtual void *getValuePtr() = 0; + }; + + template class TypedAnyStorage : public Context::AnyStorage { + static_assert(std::is_same::type, T>::value, + "Argument to TypedAnyStorage must be decayed"); + + public: + TypedAnyStorage(T &&Value) : Value(std::move(Value)) {} + + void *getValuePtr() override { return &Value; } + + private: + T Value; + }; + + struct Data { + // We need to make sure Parent outlives the Value, 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; + const void *KeyPtr; + std::unique_ptr Value; + }; + + std::shared_ptr DataPtr; +}; // namespace clangd + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_CONTEXT_H_ diff --git a/clang-tools-extra/unittests/clangd/CMakeLists.txt b/clang-tools-extra/unittests/clangd/CMakeLists.txt index 98727da2bb71..5ee5a1ece950 100644 --- a/clang-tools-extra/unittests/clangd/CMakeLists.txt +++ b/clang-tools-extra/unittests/clangd/CMakeLists.txt @@ -11,6 +11,7 @@ include_directories( add_extra_unittest(ClangdTests ClangdTests.cpp CodeCompleteTests.cpp + ContextTests.cpp FuzzyMatchTests.cpp JSONExprTests.cpp TestFS.cpp diff --git a/clang-tools-extra/unittests/clangd/ContextTests.cpp b/clang-tools-extra/unittests/clangd/ContextTests.cpp new file mode 100644 index 000000000000..d5cb3ce6e02e --- /dev/null +++ b/clang-tools-extra/unittests/clangd/ContextTests.cpp @@ -0,0 +1,57 @@ +//===-- 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(ContextTests, Simple) { + Key IntParam; + Key ExtraIntParam; + + Context Ctx = Context::empty().derive(IntParam, 10).derive(ExtraIntParam, 20); + + EXPECT_EQ(*Ctx.get(IntParam), 10); + EXPECT_EQ(*Ctx.get(ExtraIntParam), 20); +} + +TEST(ContextTests, MoveOps) { + Key> Param; + + Context Ctx = Context::empty().derive(Param, llvm::make_unique(10)); + EXPECT_EQ(**Ctx.get(Param), 10); + + Context NewCtx = std::move(Ctx); + EXPECT_EQ(**NewCtx.get(Param), 10); +} + +TEST(ContextTests, Builders) { + Key ParentParam; + Key ParentAndChildParam; + Key ChildParam; + + Context ParentCtx = + Context::empty().derive(ParentParam, 10).derive(ParentAndChildParam, 20); + Context ChildCtx = + ParentCtx.derive(ParentAndChildParam, 30).derive(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