ilya-biryukov updated this revision to Diff 171067.
ilya-biryukov added a comment.

- Improve traversal of the AST.
- Update the tests.
- Add a fallback to SymbolInformation (flat structure) if the client does not 
support the hierarhical reponse.

Still some work left to do:

- Do not drop the qualifiers for out-of-line declarations, i.e. 'X::foo' is 
presented as 'foo' now, which is a bit confusing.
- See why some names pop up at the global scope (GTest generates macros like 
that).


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D52311

Files:
  clangd/AST.cpp
  clangd/AST.h
  clangd/ClangdLSPServer.cpp
  clangd/ClangdLSPServer.h
  clangd/ClangdServer.cpp
  clangd/ClangdServer.h
  clangd/FindSymbols.cpp
  clangd/FindSymbols.h
  clangd/Protocol.cpp
  clangd/Protocol.h
  clangd/clients/clangd-vscode/package.json
  unittests/clangd/FindSymbolsTests.cpp
  unittests/clangd/SyncAPI.cpp
  unittests/clangd/SyncAPI.h

Index: unittests/clangd/SyncAPI.h
===================================================================
--- unittests/clangd/SyncAPI.h
+++ unittests/clangd/SyncAPI.h
@@ -47,7 +47,7 @@
 llvm::Expected<std::vector<SymbolInformation>>
 runWorkspaceSymbols(ClangdServer &Server, StringRef Query, int Limit);
 
-llvm::Expected<std::vector<SymbolInformation>>
+llvm::Expected<std::vector<DocumentSymbol>>
 runDocumentSymbols(ClangdServer &Server, PathRef File);
 
 SymbolSlab runFuzzyFind(const SymbolIndex &Index, StringRef Query);
Index: unittests/clangd/SyncAPI.cpp
===================================================================
--- unittests/clangd/SyncAPI.cpp
+++ unittests/clangd/SyncAPI.cpp
@@ -119,9 +119,9 @@
   return std::move(*Result);
 }
 
-Expected<std::vector<SymbolInformation>>
+llvm::Expected<std::vector<DocumentSymbol>>
 runDocumentSymbols(ClangdServer &Server, PathRef File) {
-  Optional<Expected<std::vector<SymbolInformation>>> Result;
+  llvm::Optional<llvm::Expected<std::vector<DocumentSymbol>>> Result;
   Server.documentSymbols(File, capture(Result));
   return std::move(*Result);
 }
Index: unittests/clangd/FindSymbolsTests.cpp
===================================================================
--- unittests/clangd/FindSymbolsTests.cpp
+++ unittests/clangd/FindSymbolsTests.cpp
@@ -23,6 +23,7 @@
 using ::testing::AnyOf;
 using ::testing::ElementsAre;
 using ::testing::ElementsAreArray;
+using ::testing::Field;
 using ::testing::IsEmpty;
 using ::testing::UnorderedElementsAre;
 
@@ -37,9 +38,22 @@
     return arg.name == Name;
   return (arg.containerName + "::" + arg.name) == Name;
 }
+MATCHER_P(WithName, N, "") { return arg.name == N; }
 MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; }
 MATCHER_P(SymRange, Range, "") { return arg.location.range == Range; }
 
+// GMock helpers for matching DocumentSymbol.
+MATCHER_P(SymNameRange, Range, "") { return arg.selectionRange == Range; }
+testing::Matcher<DocumentSymbol>
+Children(testing::Matcher<std::vector<DocumentSymbol>> ChildrenM) {
+  return Field(&DocumentSymbol::children, ChildrenM);
+}
+template <class... ChildMatchers>
+testing::Matcher<DocumentSymbol> ChildrenAre(ChildMatchers... ChildrenM) {
+  return Children(ElementsAre(ChildrenM...));
+}
+testing::Matcher<DocumentSymbol> NoChildren() { return Children(IsEmpty()); }
+
 ClangdServer::Options optsForTests() {
   auto ServerOpts = ClangdServer::optsForTest();
   ServerOpts.WorkspaceRoot = testRoot();
@@ -301,7 +315,7 @@
   IgnoreDiagnostics DiagConsumer;
   ClangdServer Server;
 
-  std::vector<SymbolInformation> getSymbols(PathRef File) {
+  std::vector<DocumentSymbol> getSymbols(PathRef File) {
     EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble";
     auto SymbolInfos = runDocumentSymbols(Server, File);
     EXPECT_TRUE(bool(SymbolInfos)) << "documentSymbols returned an error";
@@ -364,31 +378,49 @@
     )");
 
   addFile(FilePath, Main.code());
-  EXPECT_THAT(getSymbols(FilePath),
-              ElementsAreArray(
-                  {AllOf(QName("Foo"), WithKind(SymbolKind::Class)),
-                   AllOf(QName("Foo"), WithKind(SymbolKind::Class)),
-                   AllOf(QName("Foo::Foo"), WithKind(SymbolKind::Method)),
-                   AllOf(QName("Foo::Foo"), WithKind(SymbolKind::Method)),
-                   AllOf(QName("Foo::f"), WithKind(SymbolKind::Method)),
-                   AllOf(QName("f1"), WithKind(SymbolKind::Function)),
-                   AllOf(QName("Foo::operator="), WithKind(SymbolKind::Method)),
-                   AllOf(QName("Foo::~Foo"), WithKind(SymbolKind::Method)),
-                   AllOf(QName("Foo::Nested"), WithKind(SymbolKind::Class)),
-                   AllOf(QName("Foo::Nested::f"), WithKind(SymbolKind::Method)),
-                   AllOf(QName("Friend"), WithKind(SymbolKind::Class)),
-                   AllOf(QName("f1"), WithKind(SymbolKind::Function)),
-                   AllOf(QName("f2"), WithKind(SymbolKind::Function)),
-                   AllOf(QName("KInt"), WithKind(SymbolKind::Variable)),
-                   AllOf(QName("kStr"), WithKind(SymbolKind::Variable)),
-                   AllOf(QName("f1"), WithKind(SymbolKind::Function)),
-                   AllOf(QName("foo"), WithKind(SymbolKind::Namespace)),
-                   AllOf(QName("foo::int32"), WithKind(SymbolKind::Class)),
-                   AllOf(QName("foo::int32_t"), WithKind(SymbolKind::Class)),
-                   AllOf(QName("foo::v1"), WithKind(SymbolKind::Variable)),
-                   AllOf(QName("foo::bar"), WithKind(SymbolKind::Namespace)),
-                   AllOf(QName("foo::bar::v2"), WithKind(SymbolKind::Variable)),
-                   AllOf(QName("foo::baz"), WithKind(SymbolKind::Namespace))}));
+  EXPECT_THAT(
+      getSymbols(FilePath),
+      ElementsAreArray(
+          {AllOf(WithName("Foo"), WithKind(SymbolKind::Class), NoChildren()),
+           AllOf(WithName("Foo"), WithKind(SymbolKind::Class),
+                 ChildrenAre(
+                     AllOf(WithName("Foo"), WithKind(SymbolKind::Method),
+                           NoChildren()),
+                     AllOf(WithName("Foo"), WithKind(SymbolKind::Method),
+                           NoChildren()),
+                     AllOf(WithName("f"), WithKind(SymbolKind::Method),
+                           NoChildren()),
+                     AllOf(WithName("operator="), WithKind(SymbolKind::Method),
+                           NoChildren()),
+                     AllOf(WithName("~Foo"), WithKind(SymbolKind::Method),
+                           NoChildren()),
+                     AllOf(WithName("Nested"), WithKind(SymbolKind::Class),
+                           ChildrenAre(AllOf(WithName("f"),
+                                             WithKind(SymbolKind::Method),
+                                             NoChildren()))))),
+           AllOf(WithName("Friend"), WithKind(SymbolKind::Class), NoChildren()),
+           AllOf(WithName("f1"), WithKind(SymbolKind::Function), NoChildren()),
+           AllOf(WithName("f2"), WithKind(SymbolKind::Function), NoChildren()),
+           AllOf(WithName("KInt"), WithKind(SymbolKind::Variable),
+                 NoChildren()),
+           AllOf(WithName("kStr"), WithKind(SymbolKind::Variable),
+                 NoChildren()),
+           AllOf(WithName("f1"), WithKind(SymbolKind::Function), NoChildren()),
+           AllOf(WithName("foo"), WithKind(SymbolKind::Namespace),
+                 ChildrenAre(
+                     AllOf(WithName("int32"), WithKind(SymbolKind::Class),
+                           NoChildren()),
+                     AllOf(WithName("int32_t"), WithKind(SymbolKind::Class),
+                           NoChildren()),
+                     AllOf(WithName("v1"), WithKind(SymbolKind::Variable),
+                           NoChildren()),
+                     AllOf(WithName("bar"), WithKind(SymbolKind::Namespace),
+                           ChildrenAre(AllOf(WithName("v2"),
+                                             WithKind(SymbolKind::Variable),
+                                             NoChildren()))),
+                     AllOf(WithName("baz"), WithKind(SymbolKind::Namespace),
+                           NoChildren()),
+                     AllOf(WithName("v2"), WithKind(SymbolKind::Variable))))}));
 }
 
 TEST_F(DocumentSymbolsTest, DeclarationDefinition) {
@@ -403,11 +435,12 @@
 
   addFile(FilePath, Main.code());
   EXPECT_THAT(getSymbols(FilePath),
-              ElementsAre(AllOf(QName("Foo"), WithKind(SymbolKind::Class)),
-                          AllOf(QName("Foo::f"), WithKind(SymbolKind::Method),
-                                SymRange(Main.range("decl"))),
-                          AllOf(QName("Foo::f"), WithKind(SymbolKind::Method),
-                                SymRange(Main.range("def")))));
+              ElementsAre(AllOf(WithName("Foo"), WithKind(SymbolKind::Class),
+                                ChildrenAre(AllOf(
+                                    WithName("f"), WithKind(SymbolKind::Method),
+                                    SymNameRange(Main.range("decl"))))),
+                          AllOf(WithName("f"), WithKind(SymbolKind::Method),
+                                SymNameRange(Main.range("def")))));
 }
 
 TEST_F(DocumentSymbolsTest, ExternSymbol) {
@@ -430,7 +463,7 @@
         struct LocalClass {};
         int local_var;
       })cpp");
-  EXPECT_THAT(getSymbols(FilePath), ElementsAre(QName("test")));
+  EXPECT_THAT(getSymbols(FilePath), ElementsAre(WithName("test")));
 }
 
 TEST_F(DocumentSymbolsTest, Unnamed) {
@@ -443,9 +476,12 @@
       )cpp");
   EXPECT_THAT(
       getSymbols(FilePath),
-      ElementsAre(AllOf(QName("UnnamedStruct"), WithKind(SymbolKind::Variable)),
-                  AllOf(QName("(anonymous struct)::InUnnamed"),
-                        WithKind(SymbolKind::Field))));
+      ElementsAre(
+          AllOf(WithName("(anonymous struct)"), WithKind(SymbolKind::Struct),
+                ChildrenAre(AllOf(WithName("InUnnamed"),
+                                  WithKind(SymbolKind::Field), NoChildren()))),
+          AllOf(WithName("UnnamedStruct"), WithKind(SymbolKind::Variable),
+                NoChildren())));
 }
 
 TEST_F(DocumentSymbolsTest, InHeaderFile) {
@@ -462,23 +498,30 @@
   addFile("foo.cpp", R"cpp(
       #include "foo.h"
       )cpp");
-  EXPECT_THAT(getSymbols(FilePath), ElementsAre(QName("test")));
+  EXPECT_THAT(getSymbols(FilePath), ElementsAre(WithName("test")));
 }
 
 TEST_F(DocumentSymbolsTest, Template) {
   std::string FilePath = testPath("foo.cpp");
   addFile(FilePath, R"(
     // Primary templates and specializations are included but instantiations
     // are not.
     template <class T> struct Tmpl {T x = 0;};
-    template <> struct Tmpl<int> {};
+    template <> struct Tmpl<int> {
+      int y = 0;
+    };
     extern template struct Tmpl<float>;
     template struct Tmpl<double>;
   )");
-  EXPECT_THAT(getSymbols(FilePath),
-              ElementsAre(AllOf(QName("Tmpl"), WithKind(SymbolKind::Struct)),
-                          AllOf(QName("Tmpl::x"), WithKind(SymbolKind::Field)),
-                          AllOf(QName("Tmpl"), WithKind(SymbolKind::Struct))));
+  EXPECT_THAT(
+      getSymbols(FilePath),
+      ElementsAre(
+          AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct),
+                ChildrenAre(AllOf(WithName("x"), WithKind(SymbolKind::Field)))),
+          AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct),
+                ChildrenAre(WithName("y"))),
+          AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), NoChildren()),
+          AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), NoChildren())));
 }
 
 TEST_F(DocumentSymbolsTest, Namespaces) {
@@ -506,12 +549,20 @@
       }
       }
       )cpp");
-  EXPECT_THAT(
-      getSymbols(FilePath),
-      ElementsAreArray({QName("ans1"), QName("ans1::ai1"), QName("ans1::ans2"),
-                        QName("ans1::ans2::ai2"), QName("test"), QName("na"),
-                        QName("na::nb"), QName("na::Foo"), QName("na"),
-                        QName("na::nb"), QName("na::Bar")}));
+  EXPECT_THAT(getSymbols(FilePath),
+              ElementsAreArray<testing::Matcher<DocumentSymbol>>(
+                  {AllOf(WithName("ans1"),
+                         ChildrenAre(AllOf(WithName("ai1"), NoChildren()),
+                                     AllOf(WithName("ans2"),
+                                           ChildrenAre(WithName("ai2"))))),
+                   AllOf(WithName("(anonymous namespace)"),
+                         ChildrenAre(WithName("test"))),
+                   AllOf(WithName("na"),
+                         ChildrenAre(AllOf(WithName("nb"),
+                                           ChildrenAre(WithName("Foo"))))),
+                   AllOf(WithName("na"),
+                         ChildrenAre(AllOf(WithName("nb"),
+                                           ChildrenAre(WithName("Bar")))))}));
 }
 
 TEST_F(DocumentSymbolsTest, Enums) {
@@ -532,10 +583,12 @@
       };
       }
     )");
-  EXPECT_THAT(getSymbols(FilePath),
-              ElementsAre(QName("Red"), QName("Color"), QName("Green"),
-                          QName("Color2"), QName("Color2::Yellow"), QName("ns"),
-                          QName("ns::Black")));
+  EXPECT_THAT(
+      getSymbols(FilePath),
+      ElementsAre(WithName("Red"),
+                  AllOf(WithName("Color"), ChildrenAre(WithName("Green"))),
+                  AllOf(WithName("Color2"), ChildrenAre(WithName("Yellow"))),
+                  AllOf(WithName("ns"), ChildrenAre(WithName("Black")))));
 }
 
 TEST_F(DocumentSymbolsTest, FromMacro) {
@@ -554,8 +607,24 @@
   addFile(FilePath, Main.code());
   EXPECT_THAT(
       getSymbols(FilePath),
-      ElementsAre(AllOf(QName("abc_Test"), SymRange(Main.range("expansion"))),
-                  AllOf(QName("Test"), SymRange(Main.range("spelling")))));
+      ElementsAre(
+          AllOf(WithName("abc_Test"), SymNameRange(Main.range("expansion"))),
+          AllOf(WithName("Test"), SymNameRange(Main.range("spelling")))));
+}
+
+TEST_F(DocumentSymbolsTest, FuncTemplates) {
+  std::string FilePath = testPath("foo.cpp");
+  Annotations Source(R"cpp(
+    template <class T>
+    T foo() {}
+
+    auto x = foo<int>();
+    auto y = foo<double>()
+  )cpp");
+  addFile(FilePath, Source.code());
+  // Make sure we only see the template declaration, not instantiations.
+  EXPECT_THAT(getSymbols(FilePath),
+              ElementsAre(WithName("foo"), WithName("x"), WithName("y")));
 }
 
 } // namespace clangd
Index: clangd/clients/clangd-vscode/package.json
===================================================================
--- clangd/clients/clangd-vscode/package.json
+++ clangd/clients/clangd-vscode/package.json
@@ -6,7 +6,7 @@
     "publisher": "llvm-vs-code-extensions",
     "homepage": "https://clang.llvm.org/extra/clangd.html";,
     "engines": {
-        "vscode": "^1.18.0"
+        "vscode": "^1.27.0"
     },
     "categories": [
         "Programming Languages",
@@ -32,8 +32,8 @@
         "test": "node ./node_modules/vscode/bin/test"
     },
     "dependencies": {
-        "vscode-languageclient": "^4.0.0",
-        "vscode-languageserver": "^4.0.0"
+        "vscode-languageclient": "^5.1.0",
+        "vscode-languageserver": "^5.1.0"
     },
     "devDependencies": {
         "typescript": "^2.0.3",
Index: clangd/Protocol.h
===================================================================
--- clangd/Protocol.h
+++ clangd/Protocol.h
@@ -40,6 +40,7 @@
   InvalidRequest = -32600,
   MethodNotFound = -32601,
   InvalidParams = -32602,
+
   InternalError = -32603,
 
   ServerNotInitialized = -32002,
@@ -150,6 +151,9 @@
   }
 
   bool contains(Position Pos) const { return start <= Pos && Pos < end; }
+  bool containsSubrange(Range Rng) const {
+    return start <= Rng.start && Rng.end <= end;
+  }
 };
 bool fromJSON(const llvm::json::Value &, Range &);
 llvm::json::Value toJSON(const Range &);
@@ -336,6 +340,9 @@
   /// textDocument.completion.completionItem.snippetSupport
   bool CompletionSnippets = false;
 
+  /// Client supports hierarchical document symbols.
+  bool HierarchicalDocumentSymbol = false;
+
   /// The supported set of CompletionItemKinds for textDocument/completion.
   /// textDocument.completion.completionItemKind.valueSet
   llvm::Optional<CompletionItemKindBitset> CompletionItemKinds;
@@ -661,6 +668,39 @@
 };
 llvm::json::Value toJSON(const CodeAction &);
 
+/// Represents programming constructs like variables, classes, interfaces etc.
+/// that appear in a document. Document symbols can be hierarchical and they
+/// have two ranges: one that encloses its definition and one that points to its
+/// most interesting range, e.g. the range of an identifier.
+struct DocumentSymbol {
+  /// The name of this symbol.
+  std::string name;
+
+  /// More detail for this symbol, e.g the signature of a function.
+  std::string detail;
+
+  /// The kind of this symbol.
+  SymbolKind kind;
+
+  /// Indicates if this symbol is deprecated.
+  bool deprecated;
+
+  /// The range enclosing this symbol not including leading/trailing whitespace
+  /// but everything else like comments. This information is typically used to
+  /// determine if the clients cursor is inside the symbol to reveal in the
+  /// symbol in the UI.
+  Range range;
+
+  /// The range that should be selected and revealed when this symbol is being
+  /// picked, e.g the name of a function. Must be contained by the `range`.
+  Range selectionRange;
+
+  /// Children of this symbol, e.g. properties of a class.
+  std::vector<DocumentSymbol> children;
+};
+llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const DocumentSymbol &S);
+llvm::json::Value toJSON(const DocumentSymbol &S);
+
 /// Represents information about programming constructs like variables, classes,
 /// interfaces etc.
 struct SymbolInformation {
Index: clangd/Protocol.cpp
===================================================================
--- clangd/Protocol.cpp
+++ clangd/Protocol.cpp
@@ -18,6 +18,7 @@
 #include "llvm/ADT/SmallString.h"
 #include "llvm/Support/Format.h"
 #include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/JSON.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/raw_ostream.h"
 
@@ -230,6 +231,11 @@
       if (CodeAction->getObject("codeActionLiteralSupport"))
         R.CodeActionStructure = true;
     }
+    if (auto *DocumentSymbol = TextDocument->getObject("documentSymbol")) {
+      if (auto HierarchicalSupport =
+              DocumentSymbol->getBoolean("hierarchicalDocumentSymbolSupport"))
+        R.HierarchicalDocumentSymbol = *HierarchicalSupport;
+    }
   }
   if (auto *Workspace = O->getObject("workspace")) {
     if (auto *Symbol = Workspace->getObject("symbol")) {
@@ -458,6 +464,25 @@
   return std::move(CodeAction);
 }
 
+llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const DocumentSymbol &S) {
+  return O << S.name << " - " << toJSON(S);
+}
+
+llvm::json::Value toJSON(const DocumentSymbol &S) {
+  json::Object Result{{"name", S.name},
+                      {"kind", static_cast<int>(S.kind)},
+                      {"range", S.range},
+                      {"selectionRange", S.selectionRange}};
+
+  if (!S.detail.empty())
+    Result["detail"] = S.detail;
+  if (!S.children.empty())
+    Result["children"] = S.children;
+  if (S.deprecated)
+    Result["deprecated"] = true;
+  return Result;
+}
+
 json::Value toJSON(const WorkspaceEdit &WE) {
   if (!WE.changes)
     return json::Object{};
Index: clangd/FindSymbols.h
===================================================================
--- clangd/FindSymbols.h
+++ clangd/FindSymbols.h
@@ -36,8 +36,7 @@
 
 /// Retrieves the symbols contained in the "main file" section of an AST in the
 /// same order that they appear.
-llvm::Expected<std::vector<SymbolInformation>>
-getDocumentSymbols(ParsedAST &AST);
+llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST);
 
 } // namespace clangd
 } // namespace clang
Index: clangd/FindSymbols.cpp
===================================================================
--- clangd/FindSymbols.cpp
+++ clangd/FindSymbols.cpp
@@ -15,11 +15,13 @@
 #include "Quality.h"
 #include "SourceCode.h"
 #include "index/Index.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/Index/IndexDataConsumer.h"
 #include "clang/Index/IndexSymbol.h"
 #include "clang/Index/IndexingAction.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/Path.h"
+#include "llvm/Support/ScopedPrinter.h"
 
 #define DEBUG_TYPE "FindSymbols"
 
@@ -176,104 +178,134 @@
 }
 
 namespace {
-/// Finds document symbols in the main file of the AST.
-class DocumentSymbolsConsumer : public index::IndexDataConsumer {
-  ASTContext &AST;
-  std::vector<SymbolInformation> Symbols;
-  // We are always list document for the same file, so cache the value.
-  Optional<URIForFile> MainFileUri;
+DocumentSymbol declToSym(ASTContext &Ctx, const NamedDecl &ND) {
+  SourceLocation NameLoc = findNameLoc(&ND);
 
-public:
-  DocumentSymbolsConsumer(ASTContext &AST) : AST(AST) {}
-  std::vector<SymbolInformation> takeSymbols() { return std::move(Symbols); }
+  auto &SM = Ctx.getSourceManager();
+  Position NameBegin = sourceLocToPosition(SM, NameLoc);
+  Position NameEnd = sourceLocToPosition(
+      SM, Lexer::getLocForEndOfToken(NameLoc, 0, SM, Ctx.getLangOpts()));
 
-  void initialize(ASTContext &Ctx) override {
-    // Compute the absolute path of the main file which we will use for all
-    // results.
-    const SourceManager &SM = AST.getSourceManager();
-    const FileEntry *F = SM.getFileEntryForID(SM.getMainFileID());
-    if (!F)
-      return;
-    auto FilePath = getRealPath(F, SM);
-    if (FilePath)
-      MainFileUri = URIForFile(*FilePath);
-  }
+  index::SymbolInfo SymInfo = index::getSymbolInfo(&ND);
+  SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind);
 
-  bool shouldIncludeSymbol(const NamedDecl *ND) {
-    if (!ND || ND->isImplicit())
-      return false;
-    // Skip anonymous declarations, e.g (anonymous enum/class/struct).
-    if (ND->getDeclName().isEmpty())
-      return false;
-    return true;
+  DocumentSymbol SI;
+  SI.name = printName(ND);
+  SI.kind = SK;
+  SI.deprecated = ND.isDeprecated();
+  SI.range = Range{sourceLocToPosition(SM, ND.getBeginLoc()),
+                   sourceLocToPosition(SM, ND.getEndLoc())};
+  SI.selectionRange = Range{NameBegin, NameEnd};
+  if (!SI.range.containsSubrange(SI.selectionRange)) {
+    // 'selectionRange' must be contained in 'range', so in cases where clang
+    // reports unrelated ranges we need to reconcile somehow.
+    SI.range = SI.selectionRange;
   }
+  return SI;
+}
 
-  bool
-  handleDeclOccurence(const Decl *, index::SymbolRoleSet Roles,
-                      ArrayRef<index::SymbolRelation> Relations,
-                      SourceLocation Loc,
-                      index::IndexDataConsumer::ASTNodeInfo ASTNode) override {
-    assert(ASTNode.OrigD);
-    // No point in continuing the index consumer if we could not get the
-    // absolute path of the main file.
-    if (!MainFileUri)
-      return false;
-    // We only want declarations and definitions, i.e. no references.
-    if (!(Roles & static_cast<unsigned>(index::SymbolRole::Declaration) ||
-          Roles & static_cast<unsigned>(index::SymbolRole::Definition)))
-      return true;
-    SourceLocation NameLoc = findNameLoc(ASTNode.OrigD);
-    const SourceManager &SourceMgr = AST.getSourceManager();
-    // We should be only be looking at "local" decls in the main file.
-    if (!SourceMgr.isWrittenInMainFile(NameLoc)) {
-      // Even thought we are visiting only local (non-preamble) decls,
-      // we can get here when in the presence of "extern" decls.
-      return true;
+std::vector<DocumentSymbol> collectDocSymbols(ParsedAST &AST) {
+  struct CollectSymbols {
+    CollectSymbols(ParsedAST &AST, std::vector<DocumentSymbol> &Results)
+        : Ctx(AST.getASTContext()) {
+      for (auto &TopLevel : AST.getLocalTopLevelDecls())
+        traverseDecl(TopLevel, Results);
     }
-    const NamedDecl *ND = dyn_cast<NamedDecl>(ASTNode.OrigD);
-    if (!shouldIncludeSymbol(ND))
-      return true;
 
-    SourceLocation EndLoc =
-        Lexer::getLocForEndOfToken(NameLoc, 0, SourceMgr, AST.getLangOpts());
-    Position Begin = sourceLocToPosition(SourceMgr, NameLoc);
-    Position End = sourceLocToPosition(SourceMgr, EndLoc);
-    Range R = {Begin, End};
-    Location L;
-    L.uri = *MainFileUri;
-    L.range = R;
+  private:
+    enum class VisitKind {
+      No,
+      OnlyDecl,
+      DeclAndChildren
+    };
 
-    std::string QName = printQualifiedName(*ND);
-    StringRef Scope, Name;
-    std::tie(Scope, Name) = splitQualifiedName(QName);
-    Scope.consume_back("::");
+    void traverseDecl(Decl *D, std::vector<DocumentSymbol> &Results) {
+      if (auto *Templ = llvm::dyn_cast<TemplateDecl>(D))
+        D = Templ->getTemplatedDecl();
+      auto *ND = llvm::dyn_cast<NamedDecl>(D);
+      if (!ND)
+        return;
+      VisitKind Visit = shouldVisit(D);
+      if (Visit == VisitKind::No)
+        return;
+      if (auto *Enum = llvm::dyn_cast<EnumDecl>(ND)) {
+        // Do not add anonymous enums, promote their children to the parent
+        // level instead.
+        if (!Enum->getIdentifier()) {
+          assert(Visit == VisitKind::DeclAndChildren);
+          return traverseChildren(D, Results);
+        }
+      }
+      DocumentSymbol Sym = declToSym(Ctx, *ND);
+      if (Visit == VisitKind::DeclAndChildren)
+        traverseChildren(D, Sym.children);
+      Results.push_back(std::move(Sym));
+    }
 
-    index::SymbolInfo SymInfo = index::getSymbolInfo(ND);
-    SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind);
+    void traverseChildren(Decl *D, std::vector<DocumentSymbol> &Results) {
+      auto *Scope = llvm::dyn_cast<DeclContext>(D);
+      if (!Scope)
+        return;
+      for (auto *C : Scope->decls())
+        traverseDecl(C, Results);
+    }
 
-    SymbolInformation SI;
-    SI.name = Name;
-    SI.kind = SK;
-    SI.location = L;
-    SI.containerName = Scope;
-    Symbols.push_back(std::move(SI));
-    return true;
-  }
-};
-} // namespace
+    VisitKind shouldVisit(Decl* D) {
+      if (D->isImplicit())
+        return VisitKind::No;
 
-Expected<std::vector<SymbolInformation>> getDocumentSymbols(ParsedAST &AST) {
-  DocumentSymbolsConsumer DocumentSymbolsCons(AST.getASTContext());
+      auto &SM = Ctx.getSourceManager();
+      if (!SM.isWrittenInMainFile(SM.getFileLoc(D->getLocation())))
+        return VisitKind::No;
+
+      if (auto Func = llvm::dyn_cast<FunctionDecl>(D)) {
+        // Some functions are implicit template instantiations, those should be
+        // ignored.
+        if (auto *Info = Func->getTemplateSpecializationInfo()) {
+          if (!Info->isExplicitInstantiationOrSpecialization())
+            return VisitKind::No;
+        }
+        // Only visit the function itself, do not visit the children (i.e.
+        // function parameters, etc.)
+        return VisitKind::OnlyDecl;
+      }
+      // Handle template instantiations. We have three cases to consider:
+      //   - explicit instantiations, e.g. 'template class std::vector<int>;'
+      //     Visit the decl itself (it's present in the code), but not the
+      //     children.
+      //   - implicit instantiations, i.e. not written by the user.
+      //     Do not visit at all, they are not present in the code.
+      //   - explicit specialization, e.g. 'template <> class vector<bool> {};'
+      //     Visit both the decl and its children, both are written in the code.
+      if (auto *TemplSpec = llvm::dyn_cast<ClassTemplateSpecializationDecl>(D)){
+        if (TemplSpec->isExplicitInstantiationOrSpecialization())
+          return TemplSpec->isExplicitSpecialization()
+                     ? VisitKind::DeclAndChildren
+                     : VisitKind::OnlyDecl;
+        return VisitKind::No;
+      }
+      if (auto *TemplSpec = llvm::dyn_cast<VarTemplateSpecializationDecl>(D)) {
+        if (TemplSpec->isExplicitInstantiationOrSpecialization())
+          return TemplSpec->isExplicitSpecialization()
+                     ? VisitKind::DeclAndChildren
+                     : VisitKind::OnlyDecl;
+        return VisitKind::No;
+      }
+      // For all other cases, visit both the children and the decl.
+      return VisitKind::DeclAndChildren;
+    }
 
-  index::IndexingOptions IndexOpts;
-  IndexOpts.SystemSymbolFilter =
-      index::IndexingOptions::SystemSymbolFilterKind::DeclarationsOnly;
-  IndexOpts.IndexFunctionLocals = false;
-  indexTopLevelDecls(AST.getASTContext(), AST.getPreprocessor(),
-                     AST.getLocalTopLevelDecls(), DocumentSymbolsCons,
-                     IndexOpts);
+    ASTContext &Ctx;
+  };
+
+  std::vector<DocumentSymbol> Results;
+  CollectSymbols(AST, Results);
+  return Results;
+}
+} // namespace
 
-  return DocumentSymbolsCons.takeSymbols();
+llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST) {
+  return collectDocSymbols(AST);
 }
 
 } // namespace clangd
Index: clangd/ClangdServer.h
===================================================================
--- clangd/ClangdServer.h
+++ clangd/ClangdServer.h
@@ -170,7 +170,7 @@
 
   /// Retrieve the symbols within the specified file.
   void documentSymbols(StringRef File,
-                       Callback<std::vector<SymbolInformation>> CB);
+                       Callback<std::vector<DocumentSymbol>> CB);
 
   /// Retrieve locations for symbol references.
   void findReferences(PathRef File, Position Pos,
Index: clangd/ClangdServer.cpp
===================================================================
--- clangd/ClangdServer.cpp
+++ clangd/ClangdServer.cpp
@@ -486,10 +486,10 @@
                                  WorkspaceRoot.getValueOr("")));
 }
 
-void ClangdServer::documentSymbols(
-    StringRef File, Callback<std::vector<SymbolInformation>> CB) {
-  auto Action = [](Callback<std::vector<SymbolInformation>> CB,
-                   Expected<InputsAndAST> InpAST) {
+void ClangdServer::documentSymbols(StringRef File,
+                                   Callback<std::vector<DocumentSymbol>> CB) {
+  auto Action = [](Callback<std::vector<DocumentSymbol>> CB,
+                   llvm::Expected<InputsAndAST> InpAST) {
     if (!InpAST)
       return CB(InpAST.takeError());
     CB(clangd::getDocumentSymbols(InpAST->AST));
Index: clangd/ClangdLSPServer.h
===================================================================
--- clangd/ClangdLSPServer.h
+++ clangd/ClangdLSPServer.h
@@ -64,8 +64,11 @@
                                  Callback<std::vector<TextEdit>>);
   void onDocumentFormatting(const DocumentFormattingParams &,
                             Callback<std::vector<TextEdit>>);
+  // The results are serialized 'vector<DocumentSymbol>' if
+  // SupportsHierarchicalDocumentSymbol is true and 'vector<SymbolInformation>'
+  // otherwise.
   void onDocumentSymbol(const DocumentSymbolParams &,
-                        Callback<std::vector<SymbolInformation>>);
+                        Callback<llvm::json::Value>);
   void onCodeAction(const CodeActionParams &, Callback<llvm::json::Value>);
   void onCompletion(const TextDocumentPositionParams &,
                     Callback<CompletionList>);
@@ -166,6 +169,8 @@
   CompletionItemKindBitset SupportedCompletionItemKinds;
   // Whether the client supports CodeAction response objects.
   bool SupportsCodeAction = false;
+  /// From capabilities of textDocument/documentSymbol.
+  bool SupportsHierarchicalDocumentSymbol = false;
 
   // Store of the current versions of the open documents.
   DraftStore DraftMgr;
Index: clangd/ClangdLSPServer.cpp
===================================================================
--- clangd/ClangdLSPServer.cpp
+++ clangd/ClangdLSPServer.cpp
@@ -23,6 +23,53 @@
 namespace clangd {
 namespace {
 
+/// The functions constructs a flattened view of the DocumentSymbol hierarchy.
+/// Used by the clients that do not support the hierarchical view.
+static std::vector<SymbolInformation>
+flattenDocumentSymbols(llvm::ArrayRef<DocumentSymbol> Symbols,
+                       const URIForFile &FileURI) {
+  // A helper struct to keep the state for recursive processing, computes
+  // results on construction.
+  struct FlattenRec {
+    FlattenRec(const URIForFile &FileURI,
+               llvm::ArrayRef<DocumentSymbol> Symbols)
+        : FileURI(FileURI) {
+      for (auto &S : Symbols)
+        process(S, /*ParentName=*/"");
+    }
+
+    std::vector<SymbolInformation> results() && { return std::move(Results); }
+
+  private:
+    void process(const DocumentSymbol &S, llvm::StringRef ParentName) {
+      SymbolInformation SI;
+      SI.containerName = ParentName;
+      SI.name = S.name;
+      SI.kind = S.kind;
+      SI.location.range = S.range;
+      SI.location.uri = FileURI;
+
+      Results.push_back(std::move(SI));
+      std::string FullName =
+          ParentName.empty() ? S.name : (ParentName.str() + "::" + S.name);
+      for (auto &C : S.children)
+        process(C, /*ParentName=*/FullName);
+    }
+
+    std::vector<SymbolInformation> Results;
+    const URIForFile &FileURI;
+  };
+  return FlattenRec(FileURI, Symbols).results();
+}
+
+static void adjustSymbolKinds(llvm::MutableArrayRef<DocumentSymbol> Syms,
+                              SymbolKindBitset Kinds) {
+  for (auto &S : Syms) {
+    S.kind = adjustKindToCapability(S.kind, Kinds);
+    adjustSymbolKinds(S.children, Kinds);
+  }
+}
+
 /// \brief Supports a test URI scheme with relaxed constraints for lit tests.
 /// The path in a test URI will be combined with a platform-specific fake
 /// directory to form an absolute path. For example, test:///a.cpp is resolved
@@ -283,6 +330,8 @@
   if (Params.capabilities.CompletionItemKinds)
     SupportedCompletionItemKinds |= *Params.capabilities.CompletionItemKinds;
   SupportsCodeAction = Params.capabilities.CodeActionStructure;
+  SupportsHierarchicalDocumentSymbol =
+      Params.capabilities.HierarchicalDocumentSymbol;
 
   Reply(json::Object{
       {{"capabilities",
@@ -504,19 +553,21 @@
     Reply(ReplacementsOrError.takeError());
 }
 
-void ClangdLSPServer::onDocumentSymbol(
-    const DocumentSymbolParams &Params,
-    Callback<std::vector<SymbolInformation>> Reply) {
+void ClangdLSPServer::onDocumentSymbol(const DocumentSymbolParams &Params,
+                                       Callback<json::Value> Reply) {
+  URIForFile FileURI = Params.textDocument.uri;
   Server->documentSymbols(
       Params.textDocument.uri.file(),
       Bind(
-          [this](decltype(Reply) Reply,
-                 Expected<std::vector<SymbolInformation>> Items) {
+          [this, FileURI](decltype(Reply) Reply,
+                          Expected<std::vector<DocumentSymbol>> Items) {
             if (!Items)
               return Reply(Items.takeError());
-            for (auto &Sym : *Items)
-              Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
-            Reply(std::move(*Items));
+            adjustSymbolKinds(*Items, SupportedSymbolKinds);
+            if (SupportsHierarchicalDocumentSymbol)
+              return Reply(std::move(*Items));
+            else
+              return Reply(flattenDocumentSymbols(*Items, FileURI));
           },
           std::move(Reply)));
 }
Index: clangd/AST.h
===================================================================
--- clangd/AST.h
+++ clangd/AST.h
@@ -42,6 +42,11 @@
 /// Returns the first enclosing namespace scope starting from \p DC.
 std::string printNamespaceScope(const DeclContext &DC);
 
+/// Prints unqualified name of the decl for the purpose of displaying it to the
+/// user. Anonymous decls return names of the form "(anonymous {kind})", e.g.
+/// "(anonymous struct)" or "(anonymous namespace)".
+std::string printName(const NamedDecl &ND);
+
 /// Gets the symbol ID for a declaration, if possible.
 llvm::Optional<SymbolID> getSymbolID(const Decl *D);
 
Index: clangd/AST.cpp
===================================================================
--- clangd/AST.cpp
+++ clangd/AST.cpp
@@ -14,6 +14,7 @@
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Index/USRGeneration.h"
+#include "llvm/Support/ScopedPrinter.h"
 
 using namespace llvm;
 namespace clang {
@@ -64,6 +65,18 @@
   return QName;
 }
 
+std::string printName(const NamedDecl &ND) {
+  const NamedDecl *NameSource = &ND;
+  std::string Name = llvm::to_string(NameSource->getDeclName());
+  if (!Name.empty())
+    return Name;
+  if (auto NS = llvm::dyn_cast<NamespaceDecl>(NameSource))
+    return "(anonymous namespace)";
+  if (auto Cls = llvm::dyn_cast<RecordDecl>(NameSource))
+    return ("(anonymous " + Cls->getKindName() + ")").str();
+  return "(anonymous)";
+}
+
 std::string printNamespaceScope(const DeclContext &DC) {
   for (const auto *Ctx = &DC; Ctx != nullptr; Ctx = Ctx->getParent())
     if (const auto *NS = dyn_cast<NamespaceDecl>(Ctx))
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
  • [PATCH] D52311: [clangd] Add... Ilya Biryukov via Phabricator via cfe-commits

Reply via email to