[clangd] Use the index-based API to do the header-source switch.

Summary:
If the file heuristic fails, we try to use the index&AST to do the
header/source inference.

Reviewers: kadircet

Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, usaxena95, cfe-commits

Tags: #clang

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

git-svn-id: https://llvm.org/svn/llvm-project/clang-tools-extra/trunk@373320 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/clangd/ClangdLSPServer.cpp b/clangd/ClangdLSPServer.cpp
index fd5b344..4ac4d95 100644
--- a/clangd/ClangdLSPServer.cpp
+++ b/clangd/ClangdLSPServer.cpp
@@ -1038,10 +1038,16 @@
 void ClangdLSPServer::onSwitchSourceHeader(
     const TextDocumentIdentifier &Params,
     Callback<llvm::Optional<URIForFile>> Reply) {
-  if (auto Result = Server->switchSourceHeader(Params.uri.file()))
-    Reply(URIForFile::canonicalize(*Result, Params.uri.file()));
-  else
-    Reply(llvm::None);
+  Server->switchSourceHeader(
+      Params.uri.file(),
+      [Reply = std::move(Reply),
+       Params](llvm::Expected<llvm::Optional<clangd::Path>> Path) mutable {
+        if (!Path)
+          return Reply(Path.takeError());
+        if (*Path)
+          Reply(URIForFile::canonicalize(**Path, Params.uri.file()));
+        return Reply(llvm::None);
+      });
 }
 
 void ClangdLSPServer::onDocumentHighlight(
diff --git a/clangd/ClangdServer.cpp b/clangd/ClangdServer.cpp
index 86d9439..b59bb41 100644
--- a/clangd/ClangdServer.cpp
+++ b/clangd/ClangdServer.cpp
@@ -449,8 +449,24 @@
   WorkScheduler.runWithAST("Definitions", File, std::move(Action));
 }
 
-llvm::Optional<Path> ClangdServer::switchSourceHeader(PathRef Path) {
-  return getCorrespondingHeaderOrSource(Path, FSProvider.getFileSystem());
+void ClangdServer::switchSourceHeader(
+    PathRef Path, Callback<llvm::Optional<clangd::Path>> CB) {
+  // We want to return the result as fast as possible, stragety is:
+  //  1) use the file-only heuristic, it requires some IO but it is much
+  //     faster than building AST, but it only works when .h/.cc files are in
+  //     the same directory.
+  //  2) if 1) fails, we use the AST&Index approach, it is slower but supports
+  //     different code layout.
+  if (auto CorrespondingFile =
+          getCorrespondingHeaderOrSource(Path, FSProvider.getFileSystem()))
+    return CB(std::move(CorrespondingFile));
+  auto Action = [Path, CB = std::move(CB),
+                 this](llvm::Expected<InputsAndAST> InpAST) mutable {
+    if (!InpAST)
+      return CB(InpAST.takeError());
+    CB(getCorrespondingHeaderOrSource(Path, InpAST->AST, Index));
+  };
+  WorkScheduler.runWithAST("SwitchHeaderSource", Path, std::move(Action));
 }
 
 llvm::Expected<tooling::Replacements>
diff --git a/clangd/ClangdServer.h b/clangd/ClangdServer.h
index 4d268bf..c04dc50 100644
--- a/clangd/ClangdServer.h
+++ b/clangd/ClangdServer.h
@@ -192,9 +192,10 @@
   void locateSymbolAt(PathRef File, Position Pos,
                       Callback<std::vector<LocatedSymbol>> CB);
 
-  /// Helper function that returns a path to the corresponding source file when
-  /// given a header file and vice versa.
-  llvm::Optional<Path> switchSourceHeader(PathRef Path);
+  /// Switch to a corresponding source file when given a header file, and vice
+  /// versa.
+  void switchSourceHeader(PathRef Path,
+                          Callback<llvm::Optional<clangd::Path>> CB);
 
   /// Get document highlights for a given position.
   void findDocumentHighlights(PathRef File, Position Pos,
diff --git a/clangd/unittests/HeaderSourceSwitchTests.cpp b/clangd/unittests/HeaderSourceSwitchTests.cpp
index 1ca543d..3b5fe86 100644
--- a/clangd/unittests/HeaderSourceSwitchTests.cpp
+++ b/clangd/unittests/HeaderSourceSwitchTests.cpp
@@ -8,6 +8,7 @@
 
 #include "HeaderSourceSwitch.h"
 
+#include "SyncAPI.h"
 #include "TestFS.h"
 #include "TestTU.h"
 #include "index/MemIndex.h"
@@ -240,6 +241,32 @@
   }
 }
 
+TEST(HeaderSourceSwitchTest, ClangdServerIntegration) {
+  class IgnoreDiagnostics : public DiagnosticsConsumer {
+    void onDiagnosticsReady(PathRef File,
+                            std::vector<Diag> Diagnostics) override {}
+  } DiagConsumer;
+  MockCompilationDatabase CDB;
+  CDB.ExtraClangFlags = {"-I" +
+                         testPath("src/include")}; // add search directory.
+  MockFSProvider FS;
+  // File heuristic fails here, we rely on the index to find the .h file.
+  std::string CppPath = testPath("src/lib/test.cpp");
+  std::string HeaderPath = testPath("src/include/test.h");
+  FS.Files[HeaderPath] = "void foo();";
+  const std::string FileContent = R"cpp(
+    #include "test.h"
+    void foo() {};
+  )cpp";
+  FS.Files[CppPath] = FileContent;
+  auto Options = ClangdServer::optsForTest();
+  Options.BuildDynamicSymbolIndex = true;
+  ClangdServer Server(CDB, FS, DiagConsumer, Options);
+  runAddDocument(Server, CppPath, FileContent);
+  EXPECT_EQ(HeaderPath,
+            *llvm::cantFail(runSwitchHeaderSource(Server, CppPath)));
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
diff --git a/clangd/unittests/SyncAPI.cpp b/clangd/unittests/SyncAPI.cpp
index ac7c2dc..812fa7a 100644
--- a/clangd/unittests/SyncAPI.cpp
+++ b/clangd/unittests/SyncAPI.cpp
@@ -152,5 +152,12 @@
   return std::move(*Result);
 }
 
+llvm::Expected<llvm::Optional<clangd::Path>>
+runSwitchHeaderSource(ClangdServer &Server, PathRef File) {
+  llvm::Optional<llvm::Expected<llvm::Optional<clangd::Path>>> Result;
+  Server.switchSourceHeader(File, capture(Result));
+  return std::move(*Result);
+}
+
 } // namespace clangd
 } // namespace clang
diff --git a/clangd/unittests/SyncAPI.h b/clangd/unittests/SyncAPI.h
index 1ba9c0b..5ffed1f 100644
--- a/clangd/unittests/SyncAPI.h
+++ b/clangd/unittests/SyncAPI.h
@@ -56,6 +56,9 @@
 llvm::Expected<std::vector<Range>>
 runSemanticRanges(ClangdServer &Server, PathRef File, Position Pos);
 
+llvm::Expected<llvm::Optional<clangd::Path>>
+runSwitchHeaderSource(ClangdServer &Server, PathRef File);
+
 } // namespace clangd
 } // namespace clang