[clang-tools-extra] 6f1a56d - [clangd] Enable partial namespace matches for workspace symbols

Kadir Cetinkaya via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 9 01:21:46 PDT 2020


Author: Kadir Cetinkaya
Date: 2020-10-09T10:20:41+02:00
New Revision: 6f1a56d37ac6a541f585846ab0734ab0bedc264f

URL: https://github.com/llvm/llvm-project/commit/6f1a56d37ac6a541f585846ab0734ab0bedc264f
DIFF: https://github.com/llvm/llvm-project/commit/6f1a56d37ac6a541f585846ab0734ab0bedc264f.diff

LOG: [clangd] Enable partial namespace matches for workspace symbols

This will enable queries like "clangd::" to find symbols under clangd
namespace, without requiring full "clang::clangd::" qualification.

Since Fuzzyfind performs the search under all scopes and only boosts the symbols
from relevant namespaces, we might get symbols from non-matching namespaces.
This patch chooses to drop those as they clearly do not match the query.

Fixes https://github.com/clangd/clangd/issues/550.

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

Added: 
    

Modified: 
    clang-tools-extra/clangd/FindSymbols.cpp
    clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 8e21ae22dcd9..3169ffd98038 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -18,10 +18,14 @@
 #include "clang/Index/IndexDataConsumer.h"
 #include "clang/Index/IndexSymbol.h"
 #include "clang/Index/IndexingAction.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/ScopedPrinter.h"
+#include <tuple>
 
 #define DEBUG_TYPE "FindSymbols"
 
@@ -38,6 +42,21 @@ struct ScoredSymbolGreater {
   }
 };
 
+// Returns true if \p Query can be found as a sub-sequence inside \p Scope.
+bool approximateScopeMatch(llvm::StringRef Scope, llvm::StringRef Query) {
+  assert(Scope.empty() || Scope.endswith("::"));
+  assert(Query.empty() || Query.endswith("::"));
+  while (!Scope.empty() && !Query.empty()) {
+    auto Colons = Scope.find("::");
+    assert(Colons != llvm::StringRef::npos);
+
+    llvm::StringRef LeadingSpecifier = Scope.slice(0, Colons + 2);
+    Scope = Scope.slice(Colons + 2, llvm::StringRef::npos);
+    Query.consume_front(LeadingSpecifier);
+  }
+  return Query.empty();
+}
+
 } // namespace
 
 llvm::Expected<Location> indexToLSPLocation(const SymbolLocation &Loc,
@@ -71,44 +90,54 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit,
   if (Query.empty() || !Index)
     return Result;
 
+  // Lookup for qualified names are performed as:
+  // - Exact namespaces are boosted by the index.
+  // - Approximate matches are (sub-scope match) included via AnyScope logic.
+  // - Non-matching namespaces (no sub-scope match) are post-filtered.
   auto Names = splitQualifiedName(Query);
 
   FuzzyFindRequest Req;
   Req.Query = std::string(Names.second);
 
-  // FuzzyFind doesn't want leading :: qualifier
-  bool IsGlobalQuery = Names.first.consume_front("::");
-  // Restrict results to the scope in the query string if present (global or
-  // not).
-  if (IsGlobalQuery || !Names.first.empty())
+  // FuzzyFind doesn't want leading :: qualifier.
+  auto HasLeadingColons = Names.first.consume_front("::");
+  // Limit the query to specific namespace if it is fully-qualified.
+  Req.AnyScope = !HasLeadingColons;
+  // Boost symbols from desired namespace.
+  if (HasLeadingColons || !Names.first.empty())
     Req.Scopes = {std::string(Names.first)};
-  else
-    Req.AnyScope = true;
-  if (Limit)
+  if (Limit) {
     Req.Limit = Limit;
+    // If we are boosting a specific scope allow more results to be retrieved,
+    // since some symbols from preferred namespaces might not make the cut.
+    if (Req.AnyScope && !Req.Scopes.empty())
+      *Req.Limit *= 5;
+  }
   TopN<ScoredSymbolInfo, ScoredSymbolGreater> Top(
       Req.Limit ? *Req.Limit : std::numeric_limits<size_t>::max());
   FuzzyMatcher Filter(Req.Query);
-  Index->fuzzyFind(Req, [HintPath, &Top, &Filter](const Symbol &Sym) {
+
+  Index->fuzzyFind(Req, [HintPath, &Top, &Filter, AnyScope = Req.AnyScope,
+                         ReqScope = Names.first](const Symbol &Sym) {
+    llvm::StringRef Scope = Sym.Scope;
+    // Fuzzyfind might return symbols from irrelevant namespaces if query was
+    // not fully-qualified, drop those.
+    if (AnyScope && !approximateScopeMatch(Scope, ReqScope))
+      return;
+
     auto Loc = symbolToLocation(Sym, HintPath);
     if (!Loc) {
       log("Workspace symbols: {0}", Loc.takeError());
       return;
     }
 
-    llvm::StringRef Scope = Sym.Scope;
-    Scope.consume_back("::");
-    SymbolInformation Info;
-    Info.name = (Sym.Name + Sym.TemplateSpecializationArgs).str();
-    Info.kind = indexSymbolKindToSymbolKind(Sym.SymInfo.Kind);
-    Info.location = *Loc;
-    Info.containerName = Scope.str();
-
     SymbolQualitySignals Quality;
     Quality.merge(Sym);
     SymbolRelevanceSignals Relevance;
     Relevance.Name = Sym.Name;
     Relevance.Query = SymbolRelevanceSignals::Generic;
+    // If symbol and request scopes do not match exactly, apply a penalty.
+    Relevance.InBaseClass = AnyScope && Scope != ReqScope;
     if (auto NameMatch = Filter.match(Sym.Name))
       Relevance.NameMatch = *NameMatch;
     else {
@@ -122,6 +151,13 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit,
     dlog("FindSymbols: {0}{1} = {2}\n{3}{4}\n", Sym.Scope, Sym.Name, Score,
          Quality, Relevance);
 
+    SymbolInformation Info;
+    Info.name = (Sym.Name + Sym.TemplateSpecializationArgs).str();
+    Info.kind = indexSymbolKindToSymbolKind(Sym.SymInfo.Kind);
+    Info.location = *Loc;
+    Scope.consume_back("::");
+    Info.containerName = Scope.str();
+
     // Exposed score excludes fuzzy-match component, for client-side re-ranking.
     Info.score = Score / Relevance.NameMatch;
     Top.push({Score, std::move(Info)});

diff  --git a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
index 8576e11a5f21..a7a0188a85fd 100644
--- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
@@ -126,28 +126,43 @@ TEST(WorkspaceSymbols, Namespaces) {
   TU.AdditionalFiles["foo.h"] = R"cpp(
       namespace ans1 {
         int ai1;
-      namespace ans2 {
-        int ai2;
-      }
+        namespace ans2 {
+          int ai2;
+          namespace ans3 {
+            int ai3;
+          }
+        }
       }
       )cpp";
   TU.Code = R"cpp(
       #include "foo.h"
       )cpp";
   EXPECT_THAT(getSymbols(TU, "a"),
-              UnorderedElementsAre(QName("ans1"), QName("ans1::ai1"),
-                                   QName("ans1::ans2"),
-                                   QName("ans1::ans2::ai2")));
+              UnorderedElementsAre(
+                  QName("ans1"), QName("ans1::ai1"), QName("ans1::ans2"),
+                  QName("ans1::ans2::ai2"), QName("ans1::ans2::ans3"),
+                  QName("ans1::ans2::ans3::ai3")));
   EXPECT_THAT(getSymbols(TU, "::"), ElementsAre(QName("ans1")));
   EXPECT_THAT(getSymbols(TU, "::a"), ElementsAre(QName("ans1")));
   EXPECT_THAT(getSymbols(TU, "ans1::"),
-              UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2")));
+              UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2"),
+                                   QName("ans1::ans2::ai2"),
+                                   QName("ans1::ans2::ans3"),
+                                   QName("ans1::ans2::ans3::ai3")));
+  EXPECT_THAT(getSymbols(TU, "ans2::"),
+              UnorderedElementsAre(QName("ans1::ans2::ai2"),
+                                   QName("ans1::ans2::ans3"),
+                                   QName("ans1::ans2::ans3::ai3")));
   EXPECT_THAT(getSymbols(TU, "::ans1"), ElementsAre(QName("ans1")));
   EXPECT_THAT(getSymbols(TU, "::ans1::"),
               UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2")));
   EXPECT_THAT(getSymbols(TU, "::ans1::ans2"), ElementsAre(QName("ans1::ans2")));
   EXPECT_THAT(getSymbols(TU, "::ans1::ans2::"),
-              ElementsAre(QName("ans1::ans2::ai2")));
+              ElementsAre(QName("ans1::ans2::ai2"), QName("ans1::ans2::ans3")));
+
+  // Ensure sub-sequence matching works.
+  EXPECT_THAT(getSymbols(TU, "ans1::ans3::ai"),
+              UnorderedElementsAre(QName("ans1::ans2::ans3::ai3")));
 }
 
 TEST(WorkspaceSymbols, AnonymousNamespace) {
@@ -256,6 +271,17 @@ TEST(WorkspaceSymbols, Ranking) {
   EXPECT_THAT(getSymbols(TU, "::"), ElementsAre(QName("func"), QName("ns")));
 }
 
+TEST(WorkspaceSymbols, RankingPartialNamespace) {
+  TestTU TU;
+  TU.Code = R"cpp(
+    namespace ns1 {
+      namespace ns2 { struct Foo {}; }
+    }
+    namespace ns2 { struct FooB {}; })cpp";
+  EXPECT_THAT(getSymbols(TU, "ns2::f"),
+              ElementsAre(QName("ns2::FooB"), QName("ns1::ns2::Foo")));
+}
+
 TEST(WorkspaceSymbols, WithLimit) {
   TestTU TU;
   TU.AdditionalFiles["foo.h"] = R"cpp(


        


More information about the cfe-commits mailing list