[clang-tools-extra] 7b83837 - [clangd] Bind outgoing calls through LSPBinder too. NFC

Sam McCall via cfe-commits cfe-commits at lists.llvm.org
Wed Feb 17 01:57:27 PST 2021


Author: Sam McCall
Date: 2021-02-17T10:56:06+01:00
New Revision: 7b83837af6f472e9c0ed0b96b78717d4a3c4dbfe

URL: https://github.com/llvm/llvm-project/commit/7b83837af6f472e9c0ed0b96b78717d4a3c4dbfe
DIFF: https://github.com/llvm/llvm-project/commit/7b83837af6f472e9c0ed0b96b78717d4a3c4dbfe.diff

LOG: [clangd] Bind outgoing calls through LSPBinder too. NFC

The redundancy around work-done-progress is annoying but ok for now.

There's a weirdness with context lifetimes around outgoing method calls, which
I've preserved to keep this NFC. We should probably fix it though.

Differential Revision: https://reviews.llvm.org/D96717

Added: 
    

Modified: 
    clang-tools-extra/clangd/ClangdLSPServer.cpp
    clang-tools-extra/clangd/ClangdLSPServer.h
    clang-tools-extra/clangd/LSPBinder.h
    clang-tools-extra/clangd/Module.h
    clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp
    clang-tools-extra/clangd/unittests/LSPBinderTests.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 8bc49b5a5817..d29014f3e7ca 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -410,8 +410,8 @@ class ClangdLSPServer::MessageHandler : public Transport::MessageHandler {
 constexpr int ClangdLSPServer::MessageHandler::MaxReplayCallbacks;
 
 // call(), notify(), and reply() wrap the Transport, adding logging and locking.
-void ClangdLSPServer::callRaw(StringRef Method, llvm::json::Value Params,
-                              Callback<llvm::json::Value> CB) {
+void ClangdLSPServer::callMethod(StringRef Method, llvm::json::Value Params,
+                                 Callback<llvm::json::Value> CB) {
   auto ID = MsgHandler->bindReply(std::move(CB));
   log("--> {0}({1})", Method, ID);
   std::lock_guard<std::mutex> Lock(TranspWriter);
@@ -578,7 +578,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
   };
 
   {
-    LSPBinder Binder(Handlers);
+    LSPBinder Binder(Handlers, *this);
     bindMethods(Binder);
     if (Opts.Modules)
       for (auto &Mod : *Opts.Modules)
@@ -736,7 +736,7 @@ void ClangdLSPServer::onCommandApplyTweak(const TweakArgs &Args,
       ShowMessageParams Msg;
       Msg.message = *R->ShowMessage;
       Msg.type = MessageType::Info;
-      notify("window/showMessage", Msg);
+      ShowMessage(Msg);
     }
     // When no edit is specified, make sure we Reply().
     if (R->ApplyEdits.empty())
@@ -762,10 +762,9 @@ void ClangdLSPServer::applyEdit(WorkspaceEdit WE, llvm::json::Value Success,
                                 Callback<llvm::json::Value> Reply) {
   ApplyWorkspaceEditParams Edit;
   Edit.edit = std::move(WE);
-  call<ApplyWorkspaceEditResponse>(
-      "workspace/applyEdit", std::move(Edit),
-      [Reply = std::move(Reply), SuccessMessage = std::move(Success)](
-          llvm::Expected<ApplyWorkspaceEditResponse> Response) mutable {
+  ApplyWorkspaceEdit(
+      Edit, [Reply = std::move(Reply), SuccessMessage = std::move(Success)](
+                llvm::Expected<ApplyWorkspaceEditResponse> Response) mutable {
         if (!Response)
           return Reply(Response.takeError());
         if (!Response->applied) {
@@ -851,7 +850,7 @@ void ClangdLSPServer::onDocumentDidClose(
   // executed after it returns.
   PublishDiagnosticsParams Notification;
   Notification.uri = URIForFile::canonicalize(File, /*TUPath=*/File);
-  publishDiagnostics(Notification);
+  PublishDiagnostics(Notification);
 }
 
 void ClangdLSPServer::onDocumentOnTypeFormatting(
@@ -1251,11 +1250,6 @@ void ClangdLSPServer::applyConfiguration(
       [&](llvm::StringRef File) { return ModifiedFiles.count(File) != 0; });
 }
 
-void ClangdLSPServer::publishDiagnostics(
-    const PublishDiagnosticsParams &Params) {
-  notify("textDocument/publishDiagnostics", Params);
-}
-
 void ClangdLSPServer::maybeExportMemoryProfile() {
   if (!trace::enabled() || !ShouldProfile())
     return;
@@ -1454,7 +1448,7 @@ ClangdLSPServer::ClangdLSPServer(class Transport &Transp,
     this->Opts.ContextProvider = ClangdServer::createConfiguredContextProvider(
         Opts.ConfigProvider, this);
   }
-  LSPBinder Bind(this->Handlers);
+  LSPBinder Bind(this->Handlers, *this);
   Bind.method("initialize", this, &ClangdLSPServer::onInitialize);
 }
 
@@ -1503,6 +1497,15 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind) {
     Bind.method("textDocument/foldingRange", this, &ClangdLSPServer::onFoldingRange);
   Bind.command(APPLY_FIX_COMMAND, this, &ClangdLSPServer::onCommandApplyEdit);
   Bind.command(APPLY_TWEAK_COMMAND, this, &ClangdLSPServer::onCommandApplyTweak);
+
+  ApplyWorkspaceEdit = Bind.outgoingMethod("workspace/applyEdit");
+  PublishDiagnostics = Bind.outgoingNotification("textDocument/publishDiagnostics");
+  ShowMessage = Bind.outgoingNotification("window/showMessage");
+  NotifyFileStatus = Bind.outgoingNotification("textDocument/clangd.fileStatus");
+  CreateWorkDoneProgress = Bind.outgoingMethod("window/workDoneProgress/create");
+  BeginWorkDoneProgress = Bind.outgoingNotification("$/progress");
+  ReportWorkDoneProgress = Bind.outgoingNotification("$/progress");
+  EndWorkDoneProgress = Bind.outgoingNotification("$/progress");
   // clang-format on
 }
 
@@ -1589,7 +1592,7 @@ void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version,
   }
 
   // Send a notification to the LSP client.
-  publishDiagnostics(Notification);
+  PublishDiagnostics(Notification);
 }
 
 void ClangdLSPServer::onBackgroundIndexProgress(
@@ -1606,7 +1609,7 @@ void ClangdLSPServer::onBackgroundIndexProgress(
       WorkDoneProgressBegin Begin;
       Begin.percentage = true;
       Begin.title = "indexing";
-      progress(ProgressToken, std::move(Begin));
+      BeginWorkDoneProgress({ProgressToken, std::move(Begin)});
       BackgroundIndexProgressState = BackgroundIndexProgress::Live;
     }
 
@@ -1618,10 +1621,10 @@ void ClangdLSPServer::onBackgroundIndexProgress(
       Report.message =
           llvm::formatv("{0}/{1}", Stats.Completed - Stats.LastIdle,
                         Stats.Enqueued - Stats.LastIdle);
-      progress(ProgressToken, std::move(Report));
+      ReportWorkDoneProgress({ProgressToken, std::move(Report)});
     } else {
       assert(Stats.Completed == Stats.Enqueued);
-      progress(ProgressToken, WorkDoneProgressEnd());
+      EndWorkDoneProgress({ProgressToken, WorkDoneProgressEnd()});
       BackgroundIndexProgressState = BackgroundIndexProgress::Empty;
     }
   };
@@ -1643,8 +1646,8 @@ void ClangdLSPServer::onBackgroundIndexProgress(
     BackgroundIndexProgressState = BackgroundIndexProgress::Creating;
     WorkDoneProgressCreateParams CreateRequest;
     CreateRequest.token = ProgressToken;
-    call<std::nullptr_t>(
-        "window/workDoneProgress/create", CreateRequest,
+    CreateWorkDoneProgress(
+        CreateRequest,
         [this, NotifyProgress](llvm::Expected<std::nullptr_t> E) {
           std::lock_guard<std::mutex> Lock(BackgroundIndexProgressMutex);
           if (E) {
@@ -1675,7 +1678,7 @@ void ClangdLSPServer::onFileUpdated(PathRef File, const TUStatus &Status) {
       (Status.ASTActivity.K == ASTAction::Building ||
        Status.ASTActivity.K == ASTAction::RunningAction))
     return;
-  notify("textDocument/clangd.fileStatus", Status.render(File));
+  NotifyFileStatus(Status.render(File));
 }
 
 void ClangdLSPServer::reparseOpenFilesIfNeeded(

diff  --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index 659d7668591b..eeabebbc23cb 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -39,7 +39,8 @@ class SymbolIndex;
 /// MessageHandler binds the implemented LSP methods (e.g. onInitialize) to
 /// corresponding JSON-RPC methods ("initialize").
 /// The server also supports $/cancelRequest (MessageHandler provides this).
-class ClangdLSPServer : private ClangdServer::Callbacks {
+class ClangdLSPServer : private ClangdServer::Callbacks,
+                        private LSPBinder::RawOutgoing {
 public:
   struct Options : ClangdServer::Options {
     /// Supplies configuration (overrides ClangdServer::ContextProvider).
@@ -165,6 +166,22 @@ class ClangdLSPServer : private ClangdServer::Callbacks {
   void onCommandApplyEdit(const WorkspaceEdit &, Callback<llvm::json::Value>);
   void onCommandApplyTweak(const TweakArgs &, Callback<llvm::json::Value>);
 
+  /// Outgoing LSP calls.
+  LSPBinder::OutgoingMethod<ApplyWorkspaceEditParams,
+                            ApplyWorkspaceEditResponse>
+      ApplyWorkspaceEdit;
+  LSPBinder::OutgoingNotification<ShowMessageParams> ShowMessage;
+  LSPBinder::OutgoingNotification<PublishDiagnosticsParams> PublishDiagnostics;
+  LSPBinder::OutgoingNotification<FileStatus> NotifyFileStatus;
+  LSPBinder::OutgoingMethod<WorkDoneProgressCreateParams, std::nullptr_t>
+      CreateWorkDoneProgress;
+  LSPBinder::OutgoingNotification<ProgressParams<WorkDoneProgressBegin>>
+      BeginWorkDoneProgress;
+  LSPBinder::OutgoingNotification<ProgressParams<WorkDoneProgressReport>>
+      ReportWorkDoneProgress;
+  LSPBinder::OutgoingNotification<ProgressParams<WorkDoneProgressEnd>>
+      EndWorkDoneProgress;
+
   void applyEdit(WorkspaceEdit WE, llvm::json::Value Success,
                  Callback<llvm::json::Value> Reply);
 
@@ -185,9 +202,6 @@ class ClangdLSPServer : private ClangdServer::Callbacks {
       llvm::function_ref<bool(llvm::StringRef File)> Filter);
   void applyConfiguration(const ConfigurationSettings &Settings);
 
-  /// Sends a "publishDiagnostics" notification to the LSP client.
-  void publishDiagnostics(const PublishDiagnosticsParams &);
-
   /// Runs profiling and exports memory usage metrics if tracing is enabled and
   /// profiling hasn't happened recently.
   void maybeExportMemoryProfile();
@@ -220,35 +234,16 @@ class ClangdLSPServer : private ClangdServer::Callbacks {
   std::mutex SemanticTokensMutex;
   llvm::StringMap<SemanticTokens> LastSemanticTokens;
 
-  // Most code should not deal with Transport directly.
-  // MessageHandler deals with incoming messages, use call() etc for outgoing.
+  // Most code should not deal with Transport, callMethod, notify directly.
+  // Use LSPBinder to handle incoming and outgoing calls.
   clangd::Transport &Transp;
   class MessageHandler;
   std::unique_ptr<MessageHandler> MsgHandler;
   std::mutex TranspWriter;
 
-  template <typename Response>
-  void call(StringRef Method, llvm::json::Value Params, Callback<Response> CB) {
-    // Wrap the callback with LSP conversion and error-handling.
-    auto HandleReply =
-        [CB = std::move(CB), Ctx = Context::current().clone(),
-         Method = Method.str()](
-            llvm::Expected<llvm::json::Value> RawResponse) mutable {
-          if (!RawResponse)
-            return CB(RawResponse.takeError());
-          CB(LSPBinder::parse<Response>(*RawResponse, Method, "response"));
-        };
-    callRaw(Method, std::move(Params), std::move(HandleReply));
-  }
-  void callRaw(StringRef Method, llvm::json::Value Params,
-               Callback<llvm::json::Value> CB);
-  void notify(StringRef Method, llvm::json::Value Params);
-  template <typename T> void progress(const llvm::json::Value &Token, T Value) {
-    ProgressParams<T> Params;
-    Params.token = Token;
-    Params.value = std::move(Value);
-    notify("$/progress", Params);
-  }
+  void callMethod(StringRef Method, llvm::json::Value Params,
+                  Callback<llvm::json::Value> CB) override;
+  void notify(StringRef Method, llvm::json::Value Params) override;
 
   LSPBinder::RawHandlers Handlers;
 

diff  --git a/clang-tools-extra/clangd/LSPBinder.h b/clang-tools-extra/clangd/LSPBinder.h
index ee2cdfb79b42..854211268137 100644
--- a/clang-tools-extra/clangd/LSPBinder.h
+++ b/clang-tools-extra/clangd/LSPBinder.h
@@ -43,8 +43,15 @@ class LSPBinder {
     HandlerMap<void(JSON, Callback<JSON>)> MethodHandlers;
     HandlerMap<void(JSON, Callback<JSON>)> CommandHandlers;
   };
+  class RawOutgoing {
+  public:
+    virtual ~RawOutgoing() = default;
+    virtual void callMethod(llvm::StringRef Method, JSON Params,
+                            Callback<JSON> Reply) = 0;
+    virtual void notify(llvm::StringRef Method, JSON Params) = 0;
+  };
 
-  LSPBinder(RawHandlers &Raw) : Raw(Raw) {}
+  LSPBinder(RawHandlers &Raw, RawOutgoing &Out) : Raw(Raw), Out(Out) {}
 
   /// Bind a handler for an LSP method.
   /// e.g. Bind.method("peek", this, &ThisModule::peek);
@@ -70,14 +77,33 @@ class LSPBinder {
   void command(llvm::StringLiteral Command, ThisT *This,
                void (ThisT::*Handler)(const Param &, Callback<Result>));
 
+  template <typename P, typename R>
+  using OutgoingMethod = llvm::unique_function<void(const P &, Callback<R>)>;
+  /// UntypedOutgoingMethod is convertible to OutgoingMethod<P, R>.
+  class UntypedOutgoingMethod;
+  /// Bind a function object to be used for outgoing method calls.
+  /// e.g. OutgoingMethod<EParams, EResult> Edit = Bind.outgoingMethod("edit");
+  /// EParams must be JSON-serializable, EResult must be parseable.
+  UntypedOutgoingMethod outgoingMethod(llvm::StringLiteral Method);
+
+  template <typename P>
+  using OutgoingNotification = llvm::unique_function<void(const P &)>;
+  /// UntypedOutgoingNotification is convertible to OutgoingNotification<T>.
+  class UntypedOutgoingNotification;
+  /// Bind a function object to be used for outgoing notifications.
+  /// e.g. OutgoingNotification<LogParams> Log = Bind.outgoingMethod("log");
+  /// LogParams must be JSON-serializable.
+  UntypedOutgoingNotification outgoingNotification(llvm::StringLiteral Method);
+
+private:
   // FIXME: remove usage from ClangdLSPServer and make this private.
   template <typename T>
   static llvm::Expected<T> parse(const llvm::json::Value &Raw,
                                  llvm::StringRef PayloadName,
                                  llvm::StringRef PayloadKind);
 
-private:
   RawHandlers &Raw;
+  RawOutgoing &Out;
 };
 
 template <typename T>
@@ -141,6 +167,56 @@ void LSPBinder::command(llvm::StringLiteral Method, ThisT *This,
   };
 }
 
+class LSPBinder::UntypedOutgoingNotification {
+  llvm::StringLiteral Method;
+  RawOutgoing *Out;
+  UntypedOutgoingNotification(llvm::StringLiteral Method, RawOutgoing *Out)
+      : Method(Method), Out(Out) {}
+  friend UntypedOutgoingNotification
+      LSPBinder::outgoingNotification(llvm::StringLiteral);
+
+public:
+  template <typename Request> operator OutgoingNotification<Request>() && {
+    return
+        [Method(Method), Out(Out)](Request R) { Out->notify(Method, JSON(R)); };
+  }
+};
+
+inline LSPBinder::UntypedOutgoingNotification
+LSPBinder::outgoingNotification(llvm::StringLiteral Method) {
+  return UntypedOutgoingNotification(Method, &Out);
+}
+
+class LSPBinder::UntypedOutgoingMethod {
+  llvm::StringLiteral Method;
+  RawOutgoing *Out;
+  UntypedOutgoingMethod(llvm::StringLiteral Method, RawOutgoing *Out)
+      : Method(Method), Out(Out) {}
+  friend UntypedOutgoingMethod LSPBinder::outgoingMethod(llvm::StringLiteral);
+
+public:
+  template <typename Request, typename Response>
+  operator OutgoingMethod<Request, Response>() && {
+    return [Method(Method), Out(Out)](Request R, Callback<Response> Reply) {
+      Out->callMethod(
+          Method, JSON(R),
+          // FIXME: why keep ctx alive but not restore it for the callback?
+          [Reply(std::move(Reply)), Ctx(Context::current().clone()),
+           Method](llvm::Expected<JSON> RawRsp) mutable {
+            if (!RawRsp)
+              return Reply(RawRsp.takeError());
+            Reply(LSPBinder::parse<Response>(std::move(*RawRsp), Method,
+                                             "reply"));
+          });
+    };
+  }
+};
+
+inline LSPBinder::UntypedOutgoingMethod
+LSPBinder::outgoingMethod(llvm::StringLiteral Method) {
+  return UntypedOutgoingMethod(Method, &Out);
+}
+
 } // namespace clangd
 } // namespace clang
 

diff  --git a/clang-tools-extra/clangd/Module.h b/clang-tools-extra/clangd/Module.h
index 4b184b19dcb3..148da7789aef 100644
--- a/clang-tools-extra/clangd/Module.h
+++ b/clang-tools-extra/clangd/Module.h
@@ -9,6 +9,8 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_MODULE_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_MODULE_H
 
+#include "support/Function.h"
+#include "llvm/ADT/FunctionExtras.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/JSON.h"
@@ -25,8 +27,6 @@ class TUScheduler;
 
 /// A Module contributes a vertical feature to clangd.
 ///
-/// FIXME: Extend this to support outgoing LSP calls.
-///
 /// The lifetime of a module is roughly:
 ///  - modules are created before the LSP server, in ClangdMain.cpp
 ///  - these modules are then passed to ClangdLSPServer in a ModuleSet
@@ -73,6 +73,13 @@ class Module {
   /// The filesystem is used to read source files on disk.
   const ThreadsafeFS &fs() { return facilities().FS; }
 
+  /// Types of function objects that modules use for outgoing calls.
+  /// (Bound throuh LSPBinder, made available here for convenience).
+  template <typename P>
+  using OutgoingNotification = llvm::unique_function<void(const P &)>;
+  template <typename P, typename R>
+  using OutgoingMethod = llvm::unique_function<void(const P &, Callback<R>)>;
+
 private:
   llvm::Optional<Facilities> Fac;
 };

diff  --git a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp
index a6a364d1a686..a079d75dd3af 100644
--- a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp
@@ -23,6 +23,7 @@
 namespace clang {
 namespace clangd {
 namespace {
+using testing::ElementsAre;
 
 MATCHER_P(DiagMessage, M, "") {
   if (const auto *O = arg.getAsObject()) {
@@ -223,16 +224,20 @@ TEST_F(LSPTest, CDBConfigIntegration) {
 
 TEST_F(LSPTest, ModulesTest) {
   class MathModule final : public Module {
+    OutgoingNotification<int> Changed;
     void initializeLSP(LSPBinder &Bind, const llvm::json::Object &ClientCaps,
                        llvm::json::Object &ServerCaps) override {
       Bind.notification("add", this, &MathModule::add);
       Bind.method("get", this, &MathModule::get);
+      Changed = Bind.outgoingNotification("changed");
     }
 
     int Value = 0;
 
-  public:
-    void add(const int &X) { Value += X; }
+    void add(const int &X) {
+      Value += X;
+      Changed(Value);
+    }
     void get(const std::nullptr_t &, Callback<int> Reply) {
       scheduler().runQuick(
           "get", "",
@@ -245,8 +250,10 @@ TEST_F(LSPTest, ModulesTest) {
 
   auto &Client = start();
   Client.notify("add", 2);
-  Mods.get<MathModule>()->add(8);
+  Client.notify("add", 8);
   EXPECT_EQ(10, Client.call("get", nullptr).takeValue());
+  EXPECT_THAT(Client.takeNotifications("changed"),
+              ElementsAre(llvm::json::Value(2), llvm::json::Value(10)));
 }
 
 } // namespace

diff  --git a/clang-tools-extra/clangd/unittests/LSPBinderTests.cpp b/clang-tools-extra/clangd/unittests/LSPBinderTests.cpp
index 55028645e0b4..8b9363ba0865 100644
--- a/clang-tools-extra/clangd/unittests/LSPBinderTests.cpp
+++ b/clang-tools-extra/clangd/unittests/LSPBinderTests.cpp
@@ -15,12 +15,15 @@ namespace clang {
 namespace clangd {
 namespace {
 
+using testing::ElementsAre;
 using testing::HasSubstr;
+using testing::IsEmpty;
 using testing::UnorderedElementsAre;
 
 // JSON-serializable type for testing.
 struct Foo {
   int X;
+  friend bool operator==(Foo A, Foo B) { return A.X == B.X; }
 };
 bool fromJSON(const llvm::json::Value &V, Foo &F, llvm::json::Path P) {
   return fromJSON(V, F.X, P.field("X"));
@@ -35,9 +38,31 @@ capture(llvm::Optional<llvm::Expected<T>> &Out) {
   return [&Out](llvm::Expected<T> V) { Out.emplace(std::move(V)); };
 }
 
+struct OutgoingRecorder : public LSPBinder::RawOutgoing {
+  llvm::StringMap<std::vector<llvm::json::Value>> Received;
+
+  void callMethod(llvm::StringRef Method, llvm::json::Value Params,
+                  Callback<llvm::json::Value> Reply) override {
+    Received[Method].push_back(Params);
+    if (Method == "fail")
+      return Reply(error("Params={0}", Params));
+    Reply(Params); // echo back the request
+  }
+  void notify(llvm::StringRef Method, llvm::json::Value Params) override {
+    Received[Method].push_back(std::move(Params));
+  }
+
+  std::vector<llvm::json::Value> take(llvm::StringRef Method) {
+    std::vector<llvm::json::Value> Result = Received.lookup(Method);
+    Received.erase(Method);
+    return Result;
+  }
+};
+
 TEST(LSPBinderTest, IncomingCalls) {
   LSPBinder::RawHandlers RawHandlers;
-  LSPBinder Binder{RawHandlers};
+  OutgoingRecorder RawOutgoing;
+  LSPBinder Binder{RawHandlers, RawOutgoing};
   struct Handler {
     void plusOne(const Foo &Params, Callback<Foo> Reply) {
       Reply(Foo{Params.X + 1});
@@ -94,7 +119,45 @@ TEST(LSPBinderTest, IncomingCalls) {
   RawCmdPlusOne(1, capture(Reply));
   ASSERT_TRUE(Reply.hasValue());
   EXPECT_THAT_EXPECTED(Reply.getValue(), llvm::HasValue(2));
+
+  // None of this generated any outgoing traffic.
+  EXPECT_THAT(RawOutgoing.Received, IsEmpty());
+}
+
+TEST(LSPBinderTest, OutgoingCalls) {
+  LSPBinder::RawHandlers RawHandlers;
+  OutgoingRecorder RawOutgoing;
+  LSPBinder Binder{RawHandlers, RawOutgoing};
+
+  LSPBinder::OutgoingMethod<Foo, Foo> Echo;
+  Echo = Binder.outgoingMethod("echo");
+  LSPBinder::OutgoingMethod<Foo, std::string> WrongSignature;
+  WrongSignature = Binder.outgoingMethod("wrongSignature");
+  LSPBinder::OutgoingMethod<Foo, Foo> Fail;
+  Fail = Binder.outgoingMethod("fail");
+
+  llvm::Optional<llvm::Expected<Foo>> Reply;
+  Echo(Foo{2}, capture(Reply));
+  EXPECT_THAT(RawOutgoing.take("echo"), ElementsAre(llvm::json::Value(2)));
+  ASSERT_TRUE(Reply.hasValue());
+  EXPECT_THAT_EXPECTED(Reply.getValue(), llvm::HasValue(Foo{2}));
+
+  // JSON response is integer, can't be parsed as string.
+  llvm::Optional<llvm::Expected<std::string>> WrongTypeReply;
+  WrongSignature(Foo{2}, capture(WrongTypeReply));
+  EXPECT_THAT(RawOutgoing.take("wrongSignature"),
+              ElementsAre(llvm::json::Value(2)));
+  ASSERT_TRUE(Reply.hasValue());
+  EXPECT_THAT_EXPECTED(WrongTypeReply.getValue(),
+                       llvm::FailedWithMessage(
+                           HasSubstr("failed to decode wrongSignature reply")));
+
+  Fail(Foo{2}, capture(Reply));
+  EXPECT_THAT(RawOutgoing.take("fail"), ElementsAre(llvm::json::Value(2)));
+  ASSERT_TRUE(Reply.hasValue());
+  EXPECT_THAT_EXPECTED(Reply.getValue(), llvm::FailedWithMessage("Params=2"));
 }
+
 } // namespace
 } // namespace clangd
 } // namespace clang


        


More information about the cfe-commits mailing list