[clang-tools-extra] r341458 - [clangd] Implement findReferences function

Sam McCall via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 5 03:33:36 PDT 2018


Author: sammccall
Date: Wed Sep  5 03:33:36 2018
New Revision: 341458

URL: http://llvm.org/viewvc/llvm-project?rev=341458&view=rev
Log:
[clangd] Implement findReferences function

clangd will use findReferences to provide LSP's reference feature.

Modified:
    clang-tools-extra/trunk/clangd/XRefs.cpp
    clang-tools-extra/trunk/clangd/XRefs.h
    clang-tools-extra/trunk/unittests/clangd/TestTU.cpp
    clang-tools-extra/trunk/unittests/clangd/XRefsTests.cpp

Modified: clang-tools-extra/trunk/clangd/XRefs.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/XRefs.cpp?rev=341458&r1=341457&r2=341458&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/XRefs.cpp (original)
+++ clang-tools-extra/trunk/clangd/XRefs.cpp Wed Sep  5 03:33:36 2018
@@ -174,30 +174,27 @@ IdentifiedSymbol getSymbolAtPosition(Par
   return {DeclMacrosFinder.takeDecls(), DeclMacrosFinder.takeMacroInfos()};
 }
 
-llvm::Optional<Location>
-makeLocation(ParsedAST &AST, const SourceRange &ValSourceRange) {
+Range getTokenRange(ParsedAST &AST, SourceLocation TokLoc) {
   const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
-  const LangOptions &LangOpts = AST.getASTContext().getLangOpts();
-  SourceLocation LocStart = ValSourceRange.getBegin();
+  SourceLocation LocEnd = Lexer::getLocForEndOfToken(
+      TokLoc, 0, SourceMgr, AST.getASTContext().getLangOpts());
+  return {sourceLocToPosition(SourceMgr, TokLoc),
+          sourceLocToPosition(SourceMgr, LocEnd)};
+}
 
-  const FileEntry *F =
-      SourceMgr.getFileEntryForID(SourceMgr.getFileID(LocStart));
+llvm::Optional<Location> makeLocation(ParsedAST &AST, SourceLocation TokLoc) {
+  const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
+  const FileEntry *F = SourceMgr.getFileEntryForID(SourceMgr.getFileID(TokLoc));
   if (!F)
     return llvm::None;
-  SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), 0,
-                                                     SourceMgr, LangOpts);
-  Position Begin = sourceLocToPosition(SourceMgr, LocStart);
-  Position End = sourceLocToPosition(SourceMgr, LocEnd);
-  Range R = {Begin, End};
-  Location L;
-
   auto FilePath = getRealPath(F, SourceMgr);
   if (!FilePath) {
     log("failed to get path!");
     return llvm::None;
   }
+  Location L;
   L.uri = URIForFile(*FilePath);
-  L.range = R;
+  L.range = getTokenRange(AST, TokLoc);
   return L;
 }
 
@@ -223,7 +220,7 @@ std::vector<Location> findDefinitions(Pa
 
   for (auto Item : Symbols.Macros) {
     auto Loc = Item.Info->getDefinitionLoc();
-    auto L = makeLocation(AST, SourceRange(Loc, Loc));
+    auto L = makeLocation(AST, Loc);
     if (L)
       Result.push_back(*L);
   }
@@ -266,7 +263,7 @@ std::vector<Location> findDefinitions(Pa
 
     auto &Candidate = ResultCandidates[Key];
     auto Loc = findNameLoc(D);
-    auto L = makeLocation(AST, SourceRange(Loc, Loc));
+    auto L = makeLocation(AST, Loc);
     // The declaration in the identified symbols is a definition if possible
     // otherwise it is declaration.
     bool IsDef = getDefinition(D) == D;
@@ -316,24 +313,36 @@ std::vector<Location> findDefinitions(Pa
 
 namespace {
 
-/// Finds document highlights that a given list of declarations refers to.
-class DocumentHighlightsFinder : public index::IndexDataConsumer {
-  std::vector<const Decl *> &Decls;
-  std::vector<DocumentHighlight> DocumentHighlights;
-  const ASTContext &AST;
-
+/// Collects references to symbols within the main file.
+class ReferenceFinder : public index::IndexDataConsumer {
 public:
-  DocumentHighlightsFinder(ASTContext &AST, Preprocessor &PP,
-                           std::vector<const Decl *> &Decls)
-      : Decls(Decls), AST(AST) {}
-  std::vector<DocumentHighlight> takeHighlights() {
-    // Don't keep the same highlight multiple times.
-    // This can happen when nodes in the AST are visited twice.
-    std::sort(DocumentHighlights.begin(), DocumentHighlights.end());
-    auto Last =
-        std::unique(DocumentHighlights.begin(), DocumentHighlights.end());
-    DocumentHighlights.erase(Last, DocumentHighlights.end());
-    return std::move(DocumentHighlights);
+  struct Reference {
+    const Decl *Target;
+    SourceLocation Loc;
+    index::SymbolRoleSet Role;
+  };
+
+  ReferenceFinder(ASTContext &AST, Preprocessor &PP,
+                  const std::vector<const Decl *> &TargetDecls)
+      : AST(AST) {
+    for (const Decl *D : TargetDecls)
+      Targets.insert(D);
+  }
+
+  std::vector<Reference> take() && {
+    std::sort(References.begin(), References.end(),
+              [](const Reference &L, const Reference &R) {
+                return std::tie(L.Loc, L.Target, L.Role) <
+                       std::tie(R.Loc, R.Target, R.Role);
+              });
+    // We sometimes see duplicates when parts of the AST get traversed twice.
+    References.erase(std::unique(References.begin(), References.end(),
+                                 [](const Reference &L, const Reference &R) {
+                                   return std::tie(L.Target, L.Loc, L.Role) ==
+                                          std::tie(R.Target, R.Loc, R.Role);
+                                 }),
+                     References.end());
+    return std::move(References);
   }
 
   bool
@@ -341,63 +350,53 @@ public:
                       ArrayRef<index::SymbolRelation> Relations,
                       SourceLocation Loc,
                       index::IndexDataConsumer::ASTNodeInfo ASTNode) override {
-    const SourceManager &SourceMgr = AST.getSourceManager();
-    SourceLocation HighlightStartLoc = SourceMgr.getFileLoc(Loc);
-    if (SourceMgr.getMainFileID() != SourceMgr.getFileID(HighlightStartLoc) ||
-        std::find(Decls.begin(), Decls.end(), D) == Decls.end()) {
-      return true;
-    }
-    SourceLocation End;
-    const LangOptions &LangOpts = AST.getLangOpts();
-    End = Lexer::getLocForEndOfToken(HighlightStartLoc, 0, SourceMgr, LangOpts);
-    SourceRange SR(HighlightStartLoc, End);
-
-    DocumentHighlightKind Kind = DocumentHighlightKind::Text;
-    if (static_cast<index::SymbolRoleSet>(index::SymbolRole::Write) & Roles)
-      Kind = DocumentHighlightKind::Write;
-    else if (static_cast<index::SymbolRoleSet>(index::SymbolRole::Read) & Roles)
-      Kind = DocumentHighlightKind::Read;
-
-    DocumentHighlights.push_back(getDocumentHighlight(SR, Kind));
+    const SourceManager &SM = AST.getSourceManager();
+    Loc = SM.getFileLoc(Loc);
+    if (SM.isWrittenInMainFile(Loc) && Targets.count(D))
+      References.push_back({D, Loc, Roles});
     return true;
   }
 
 private:
-  DocumentHighlight getDocumentHighlight(SourceRange SR,
-                                         DocumentHighlightKind Kind) {
-    const SourceManager &SourceMgr = AST.getSourceManager();
-    Position Begin = sourceLocToPosition(SourceMgr, SR.getBegin());
-    Position End = sourceLocToPosition(SourceMgr, SR.getEnd());
-    Range R = {Begin, End};
-    DocumentHighlight DH;
-    DH.range = R;
-    DH.kind = Kind;
-    return DH;
-  }
+  llvm::SmallSet<const Decl *, 4> Targets;
+  std::vector<Reference> References;
+  const ASTContext &AST;
 };
 
-} // namespace
-
-std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
-                                                      Position Pos) {
-  const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
-  SourceLocation SourceLocationBeg =
-      getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID());
-
-  auto Symbols = getSymbolAtPosition(AST, SourceLocationBeg);
-  std::vector<const Decl *> SelectedDecls = Symbols.Decls;
-
-  DocumentHighlightsFinder DocHighlightsFinder(
-      AST.getASTContext(), AST.getPreprocessor(), SelectedDecls);
-
+std::vector<ReferenceFinder::Reference>
+findRefs(const std::vector<const Decl *> &Decls, ParsedAST &AST) {
+  ReferenceFinder RefFinder(AST.getASTContext(), AST.getPreprocessor(), Decls);
   index::IndexingOptions IndexOpts;
   IndexOpts.SystemSymbolFilter =
       index::IndexingOptions::SystemSymbolFilterKind::All;
   IndexOpts.IndexFunctionLocals = true;
   indexTopLevelDecls(AST.getASTContext(), AST.getLocalTopLevelDecls(),
-                     DocHighlightsFinder, IndexOpts);
+                     RefFinder, IndexOpts);
+  return std::move(RefFinder).take();
+}
+
+} // namespace
+
+std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
+                                                      Position Pos) {
+  const SourceManager &SM = AST.getASTContext().getSourceManager();
+  auto Symbols = getSymbolAtPosition(
+      AST, getBeginningOfIdentifier(AST, Pos, SM.getMainFileID()));
+  auto References = findRefs(Symbols.Decls, AST);
 
-  return DocHighlightsFinder.takeHighlights();
+  std::vector<DocumentHighlight> Result;
+  for (const auto &Ref : References) {
+    DocumentHighlight DH;
+    DH.range = getTokenRange(AST, Ref.Loc);
+    if (Ref.Role & index::SymbolRoleSet(index::SymbolRole::Write))
+      DH.kind = DocumentHighlightKind::Write;
+    else if (Ref.Role & index::SymbolRoleSet(index::SymbolRole::Read))
+      DH.kind = DocumentHighlightKind::Read;
+    else
+      DH.kind = DocumentHighlightKind::Text;
+    Result.push_back(std::move(DH));
+  }
+  return Result;
 }
 
 static PrintingPolicy printingPolicyForDecls(PrintingPolicy Base) {
@@ -659,5 +658,51 @@ Optional<Hover> getHover(ParsedAST &AST,
   return None;
 }
 
+std::vector<Location> findReferences(ParsedAST &AST, Position Pos,
+                                     const SymbolIndex *Index) {
+  std::vector<Location> Results;
+  const SourceManager &SM = AST.getASTContext().getSourceManager();
+  auto MainFilePath = getRealPath(SM.getFileEntryForID(SM.getMainFileID()), SM);
+  if (!MainFilePath) {
+    elog("Failed to get a path for the main file, so no references");
+    return Results;
+  }
+  auto Loc = getBeginningOfIdentifier(AST, Pos, SM.getMainFileID());
+  auto Symbols = getSymbolAtPosition(AST, Loc);
+
+  // We traverse the AST to find references in the main file.
+  // TODO: should we handle macros, too?
+  auto MainFileRefs = findRefs(Symbols.Decls, AST);
+  for (const auto &Ref : MainFileRefs) {
+    Location Result;
+    Result.range = getTokenRange(AST, Ref.Loc);
+    Result.uri = URIForFile(*MainFilePath);
+    Results.push_back(std::move(Result));
+  }
+
+  // Now query the index for references from other files.
+  if (!Index)
+    return Results;
+  RefsRequest Req;
+  for (const Decl *D : Symbols.Decls) {
+    // Not all symbols can be referenced from outside (e.g. function-locals).
+    // TODO: we could skip TU-scoped symbols here (e.g. static functions) if
+    // we know this file isn't a header. The details might be tricky.
+    if (D->getParentFunctionOrMethod())
+      continue;
+    if (auto ID = getSymbolID(D))
+      Req.IDs.insert(*ID);
+  }
+  if (Req.IDs.empty())
+    return Results;
+  Index->refs(Req, [&](const Ref &R) {
+    auto LSPLoc = toLSPLocation(R.Location, /*HintPath=*/*MainFilePath);
+    // Avoid indexed results for the main file - the AST is authoritative.
+    if (LSPLoc && LSPLoc->uri.file() != *MainFilePath)
+      Results.push_back(std::move(*LSPLoc));
+  });
+  return Results;
+}
+
 } // namespace clangd
 } // namespace clang

Modified: clang-tools-extra/trunk/clangd/XRefs.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/XRefs.h?rev=341458&r1=341457&r2=341458&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/XRefs.h (original)
+++ clang-tools-extra/trunk/clangd/XRefs.h Wed Sep  5 03:33:36 2018
@@ -34,6 +34,10 @@ std::vector<DocumentHighlight> findDocum
 /// Get the hover information when hovering at \p Pos.
 llvm::Optional<Hover> getHover(ParsedAST &AST, Position Pos);
 
+/// Returns reference locations of the symbol at a specified \p Pos.
+std::vector<Location> findReferences(ParsedAST &AST, Position Pos,
+                                     const SymbolIndex *Index = nullptr);
+
 } // namespace clangd
 } // namespace clang
 

Modified: clang-tools-extra/trunk/unittests/clangd/TestTU.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/TestTU.cpp?rev=341458&r1=341457&r2=341458&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/TestTU.cpp (original)
+++ clang-tools-extra/trunk/unittests/clangd/TestTU.cpp Wed Sep  5 03:33:36 2018
@@ -49,8 +49,10 @@ SymbolSlab TestTU::headerSymbols() const
 }
 
 std::unique_ptr<SymbolIndex> TestTU::index() const {
-  // FIXME: we should generate proper refs for TestTU.
-  return MemIndex::build(headerSymbols(), RefSlab());
+  auto AST = build();
+  auto Content = indexAST(AST.getASTContext(), AST.getPreprocessorPtr(),
+                          AST.getLocalTopLevelDecls());
+  return MemIndex::build(std::move(Content.first), std::move(Content.second));
 }
 
 const Symbol &findSymbol(const SymbolSlab &Slab, llvm::StringRef QName) {

Modified: clang-tools-extra/trunk/unittests/clangd/XRefsTests.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/XRefsTests.cpp?rev=341458&r1=341457&r2=341458&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/XRefsTests.cpp (original)
+++ clang-tools-extra/trunk/unittests/clangd/XRefsTests.cpp Wed Sep  5 03:33:36 2018
@@ -1068,6 +1068,143 @@ TEST(GoToDefinition, WithPreamble) {
       ElementsAre(Location{FooCppUri, FooWithoutHeader.range()}));
 }
 
+TEST(FindReferences, WithinAST) {
+  const char *Tests[] = {
+      R"cpp(// Local variable
+        int main() {
+          int $foo[[foo]];
+          $foo[[^foo]] = 2;
+          int test1 = $foo[[foo]];
+        }
+      )cpp",
+
+      R"cpp(// Struct
+        namespace ns1 {
+        struct $foo[[Foo]] {};
+        } // namespace ns1
+        int main() {
+          ns1::$foo[[Fo^o]]* Params;
+        }
+      )cpp",
+
+      R"cpp(// Function
+        int $foo[[foo]](int) {}
+        int main() {
+          auto *X = &$foo[[^foo]];
+          $foo[[foo]](42)
+        }
+      )cpp",
+
+      R"cpp(// Field
+        struct Foo {
+          int $foo[[foo]];
+          Foo() : $foo[[foo]](0) {}
+        };
+        int main() {
+          Foo f;
+          f.$foo[[f^oo]] = 1;
+        }
+      )cpp",
+
+      R"cpp(// Method call
+        struct Foo { int [[foo]](); };
+        int Foo::[[foo]]() {}
+        int main() {
+          Foo f;
+          f.^foo();
+        }
+      )cpp",
+
+      R"cpp(// Typedef
+        typedef int $foo[[Foo]];
+        int main() {
+          $foo[[^Foo]] bar;
+        }
+      )cpp",
+
+      R"cpp(// Namespace
+        namespace $foo[[ns]] {
+        struct Foo {};
+        } // namespace ns
+        int main() { $foo[[^ns]]::Foo foo; }
+      )cpp",
+  };
+  for (const char *Test : Tests) {
+    Annotations T(Test);
+    auto AST = TestTU::withCode(T.code()).build();
+    std::vector<Matcher<Location>> ExpectedLocations;
+    for (const auto &R : T.ranges("foo"))
+      ExpectedLocations.push_back(RangeIs(R));
+    EXPECT_THAT(findReferences(AST, T.point()),
+                ElementsAreArray(ExpectedLocations))
+        << Test;
+  }
+}
+
+TEST(FindReferences, NeedsIndex) {
+  const char *Header = "int foo();";
+  Annotations Main("int main() { [[f^oo]](); }");
+  TestTU TU;
+  TU.Code = Main.code();
+  TU.HeaderCode = Header;
+  auto AST = TU.build();
+
+  // References in main file are returned without index.
+  EXPECT_THAT(findReferences(AST, Main.point(), /*Index=*/nullptr),
+              ElementsAre(RangeIs(Main.range())));
+  Annotations IndexedMain(R"cpp(
+    int main() { [[f^oo]](); }
+  )cpp");
+
+  // References from indexed files are included.
+  TestTU IndexedTU;
+  IndexedTU.Code = IndexedMain.code();
+  IndexedTU.Filename = "Indexed.cpp";
+  IndexedTU.HeaderCode = Header;
+  EXPECT_THAT(findReferences(AST, Main.point(), IndexedTU.index().get()),
+              ElementsAre(RangeIs(Main.range()), RangeIs(IndexedMain.range())));
+
+  // If the main file is in the index, we don't return duplicates.
+  // (even if the references are in a different location)
+  TU.Code = ("\n\n" + Main.code()).str();
+  EXPECT_THAT(findReferences(AST, Main.point(), TU.index().get()),
+              ElementsAre(RangeIs(Main.range())));
+};
+
+TEST(FindReferences, NoQueryForLocalSymbols) {
+  struct RecordingIndex : public MemIndex {
+    mutable Optional<DenseSet<SymbolID>> RefIDs;
+    void refs(const RefsRequest &Req,
+              llvm::function_ref<void(const Ref &)>) const override {
+      RefIDs = Req.IDs;
+    }
+  };
+
+  struct Test {
+    StringRef AnnotatedCode;
+    bool WantQuery;
+  } Tests[] = {
+      {"int ^x;", true},
+      // For now we don't assume header structure which would allow skipping.
+      {"namespace { int ^x; }", true},
+      {"static int ^x;", true},
+      // Anything in a function certainly can't be referenced though.
+      {"void foo() { int ^x; }", false},
+      {"void foo() { struct ^x{}; }", false},
+      {"auto lambda = []{ int ^x; };", false},
+  };
+  for (Test T : Tests) {
+    Annotations File(T.AnnotatedCode);
+    RecordingIndex Rec;
+    auto AST = TestTU::withCode(File.code()).build();
+    findReferences(AST, File.point(), &Rec);
+    if (T.WantQuery)
+      EXPECT_NE(Rec.RefIDs, llvm::None) << T.AnnotatedCode;
+    else
+      EXPECT_EQ(Rec.RefIDs, llvm::None) << T.AnnotatedCode;
+  }
+};
+
 } // namespace
 } // namespace clangd
 } // namespace clang




More information about the cfe-commits mailing list