ioeric created this revision.
ioeric added a reviewer: sammccall.
Herald added subscribers: cfe-commits, kadircet, arphaman, jkorous, MaskRay, 
ilya-biryukov.

When no scope qualifier is specified, allow completing index symbols
from any scope and insert proper automatically. This is still experimental and
hidden behind a flag.

Things missing:

- Scope proximity based scoring.
- FuzzyFind supports weighted scopes.


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D52364

Files:
  clangd/CodeComplete.cpp
  clangd/CodeComplete.h
  clangd/index/Index.h
  clangd/index/MemIndex.cpp
  clangd/index/dex/Dex.cpp
  clangd/tool/ClangdMain.cpp
  unittests/clangd/CodeCompleteTests.cpp
  unittests/clangd/DexTests.cpp

Index: unittests/clangd/DexTests.cpp
===================================================================
--- unittests/clangd/DexTests.cpp
+++ unittests/clangd/DexTests.cpp
@@ -565,6 +565,16 @@
   EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1"));
 }
 
+TEST(DexTest, WildcardScope) {
+  auto I =
+      Dex::build(generateSymbols({"a::y1", "a::b::y2", "c::y3"}), URISchemes);
+  FuzzyFindRequest Req;
+  Req.Query = "y";
+  Req.Scopes = {"a::", "*"};
+  EXPECT_THAT(match(*I, Req),
+              UnorderedElementsAre("a::y1", "a::b::y2", "c::y3"));
+}
+
 TEST(DexTest, IgnoreCases) {
   auto I = Dex::build(generateSymbols({"ns::ABC", "ns::abc"}), URISchemes);
   FuzzyFindRequest Req;
Index: unittests/clangd/CodeCompleteTests.cpp
===================================================================
--- unittests/clangd/CodeCompleteTests.cpp
+++ unittests/clangd/CodeCompleteTests.cpp
@@ -2040,6 +2040,41 @@
   }
 }
 
+TEST(CompletionTest, CrossNamespaceCompletion) {
+  clangd::CodeCompleteOptions Opts = {};
+  Opts.IncludeIndexSymbolsFromAllScopes = true;
+
+  auto Results = completions(
+      R"cpp(
+    namespace na {
+    void f() { Clangd^ }
+    }
+  )cpp",
+      {cls("nx::Clangd1"), cls("ny::Clangd2"), cls("Clangd3"),
+       cls("na::nb::Clangd4")},
+      Opts);
+  EXPECT_THAT(Results.Completions,
+              UnorderedElementsAre(AllOf(Qualifier("nx::"), Named("Clangd1")),
+                                   AllOf(Qualifier("ny::"), Named("Clangd2")),
+                                   AllOf(Qualifier(""), Named("Clangd3")),
+                                   AllOf(Qualifier("nb::"), Named("Clangd4"))));
+}
+
+TEST(CompletionTest, NoQualifierIfShadowed) {
+  clangd::CodeCompleteOptions Opts = {};
+  Opts.IncludeIndexSymbolsFromAllScopes = true;
+
+  auto Results = completions(R"cpp(
+    namespace nx { class Clangd1 {}; }
+    using nx::Clangd1;
+    void f() { Clangd^ }
+  )cpp",
+                             {cls("nx::Clangd1"), cls("nx::Clangd2")}, Opts);
+  EXPECT_THAT(Results.Completions,
+              UnorderedElementsAre(AllOf(Qualifier(""), Named("Clangd1")),
+                                   AllOf(Qualifier("nx::"), Named("Clangd2"))));
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clangd/tool/ClangdMain.cpp
===================================================================
--- clangd/tool/ClangdMain.cpp
+++ clangd/tool/ClangdMain.cpp
@@ -136,6 +136,15 @@
         "enabled separatedly."),
     llvm::cl::init(true), llvm::cl::Hidden);
 
+static llvm::cl::opt<bool> CrossNamespaceCompletion(
+    "cross-namespace-completion",
+    llvm::cl::desc(
+        "This is an experimental feature. If set to true, code completion will "
+        "include index symbols that are not defined in the scopes (e.g. "
+        "namespaces) visible from the code completion point. Such completions "
+        "can insert scope qualifiers."),
+    llvm::cl::init(false), llvm::cl::Hidden);
+
 static llvm::cl::opt<bool>
     ShowOrigins("debug-origin",
                 llvm::cl::desc("Show origins of completion items"),
@@ -304,6 +313,7 @@
   }
   CCOpts.SpeculativeIndexRequest = Opts.StaticIndex;
   CCOpts.EnableFunctionArgSnippets = EnableFunctionArgSnippets;
+  CCOpts.IncludeIndexSymbolsFromAllScopes = CrossNamespaceCompletion;
 
   // Initialize and run ClangdLSPServer.
   ClangdLSPServer LSPServer(
Index: clangd/index/dex/Dex.cpp
===================================================================
--- clangd/index/dex/Dex.cpp
+++ clangd/index/dex/Dex.cpp
@@ -12,6 +12,7 @@
 #include "FuzzyMatch.h"
 #include "Logger.h"
 #include "Quality.h"
+#include "index/Index.h"
 #include "llvm/ADT/StringSet.h"
 #include <algorithm>
 #include <queue>
@@ -156,6 +157,10 @@
   // Generate scope tokens for search query.
   std::vector<std::unique_ptr<Iterator>> ScopeIterators;
   for (const auto &Scope : Req.Scopes) {
+    if (Scope == "*") {
+      ScopeIterators.push_back(createTrue(Symbols.size()));
+      continue;
+    }
     const auto It = InvertedIndex.find(Token(Token::Kind::Scope, Scope));
     if (It != InvertedIndex.end())
       ScopeIterators.push_back(It->second.iterator());
Index: clangd/index/MemIndex.cpp
===================================================================
--- clangd/index/MemIndex.cpp
+++ clangd/index/MemIndex.cpp
@@ -37,7 +37,8 @@
     const Symbol *Sym = Pair.second;
 
     // Exact match against all possible scopes.
-    if (!Req.Scopes.empty() && !llvm::is_contained(Req.Scopes, Sym->Scope))
+    if (!Req.Scopes.empty() && !llvm::is_contained(Req.Scopes, Sym->Scope) &&
+        !llvm::is_contained(Req.Scopes, "*"))
       continue;
     if (Req.RestrictForCodeCompletion &&
         !(Sym->Flags & Symbol::IndexedForCodeCompletion))
Index: clangd/index/Index.h
===================================================================
--- clangd/index/Index.h
+++ clangd/index/Index.h
@@ -427,7 +427,9 @@
   /// is provided, the matched symbols must be defined in namespace xyz but not
   /// namespace xyz::abc.
   ///
-  /// The global scope is "", a top level scope is "foo::", etc.
+  /// The global scope is "", a top level scope is "foo::", etc. "*" is
+  /// wildcard.
+  /// FIXME: support assigning different weight to each scope.
   std::vector<std::string> Scopes;
   /// \brief The number of top candidates to return. The index may choose to
   /// return more than this, e.g. if it doesn't know which candidates are best.
Index: clangd/CodeComplete.h
===================================================================
--- clangd/CodeComplete.h
+++ clangd/CodeComplete.h
@@ -101,6 +101,11 @@
   /// Whether to generate snippets for function arguments on code-completion.
   /// Needs snippets to be enabled as well.
   bool EnableFunctionArgSnippets = true;
+
+  /// Whether to include index symbols that are not defined in the scopes
+  /// visible from the code completion point (e.g. enclosing namespaces). Such
+  /// completions can insert scope qualifiers.
+  bool IncludeIndexSymbolsFromAllScopes = false;
 };
 
 // Semi-structured representation of a code-complete suggestion for our C++ API.
Index: clangd/CodeComplete.cpp
===================================================================
--- clangd/CodeComplete.cpp
+++ clangd/CodeComplete.cpp
@@ -44,6 +44,7 @@
 #include "clang/Sema/CodeCompleteConsumer.h"
 #include "clang/Sema/Sema.h"
 #include "clang/Tooling/Core/Replacement.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/Optional.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/Error.h"
@@ -330,6 +331,7 @@
 struct CodeCompletionBuilder {
   CodeCompletionBuilder(ASTContext &ASTCtx, const CompletionCandidate &C,
                         CodeCompletionString *SemaCCS,
+                        llvm::ArrayRef<std::string> QueryScopes,
                         const IncludeInserter &Includes, StringRef FileName,
                         CodeCompletionContext::Kind ContextKind,
                         const CodeCompleteOptions &Opts)
@@ -362,13 +364,29 @@
           (C.SemaResult->Availability == CXAvailability_Deprecated);
     }
     if (C.IndexResult) {
-      Completion.Origin |= C.IndexResult->Origin;
+      const auto &Sym = *C.IndexResult;
+      Completion.Origin |= Sym.Origin;
       if (Completion.Scope.empty())
-        Completion.Scope = C.IndexResult->Scope;
+        Completion.Scope = Sym.Scope;
       if (Completion.Kind == CompletionItemKind::Missing)
-        Completion.Kind = toCompletionItemKind(C.IndexResult->SymInfo.Kind);
+        Completion.Kind = toCompletionItemKind(Sym.SymInfo.Kind);
       if (Completion.Name.empty())
-        Completion.Name = C.IndexResult->Name;
+        Completion.Name = Sym.Name;
+      // To avoid inserting unnecessary qualifiers (e.g. no need for qualifier
+      // when symbol is accessed via using shadow like "using ns::X;"), only
+      // insert qualifier if the symbol is not already available form Sema.
+      // FIXME(ioeric): find a better way to avoid inserting redundant
+      // qualifiers.
+      if (Completion.RequiredQualifier.empty() && !C.SemaResult) {
+        StringRef ShortestQualifier = Sym.Scope;
+        for (StringRef Scope: QueryScopes) {
+          StringRef Qualifier = Sym.Scope;
+          if (Qualifier.consume_front(Scope) &&
+              Qualifier.size() < ShortestQualifier.size())
+            ShortestQualifier = Qualifier;
+        }
+        Completion.RequiredQualifier = ShortestQualifier;
+      }
       Completion.Deprecated |= (C.IndexResult->Flags & Symbol::Deprecated);
     }
 
@@ -571,7 +589,8 @@
 
 // Get all scopes that will be queried in indexes.
 std::vector<std::string> getQueryScopes(CodeCompletionContext &CCContext,
-                                        const SourceManager &SM) {
+                                        const SourceManager &SM,
+                                        const CodeCompleteOptions &Opts) {
   auto GetAllAccessibleScopes = [](CodeCompletionContext &CCContext) {
     SpecifiedScope Info;
     for (auto *Context : CCContext.getVisitedContexts()) {
@@ -592,7 +611,10 @@
     // FIXME: Capture scopes and use for scoring, for example,
     //        "using namespace std; namespace foo {v^}" =>
     //        foo::value > std::vector > boost::variant
-    return GetAllAccessibleScopes(CCContext).scopesForIndexQuery();
+    auto Scopes = GetAllAccessibleScopes(CCContext).scopesForIndexQuery();
+    if (Opts.IncludeIndexSymbolsFromAllScopes)
+      Scopes.push_back("*");
+    return Scopes;
   }
 
   // Qualified completion ("std::vec^"), we have two cases depending on whether
@@ -1353,7 +1375,7 @@
     Filter = FuzzyMatcher(
         Recorder->CCSema->getPreprocessor().getCodeCompletionFilter());
     QueryScopes = getQueryScopes(Recorder->CCContext,
-                                 Recorder->CCSema->getSourceManager());
+                                 Recorder->CCSema->getSourceManager(), Opts);
     // Sema provides the needed context to query the index.
     // FIXME: in addition to querying for extra/overlapping symbols, we should
     //        explicitly request symbols corresponding to Sema results.
@@ -1503,6 +1525,8 @@
     Relevance.Context = Recorder->CCContext.getKind();
     Relevance.Query = SymbolRelevanceSignals::CodeComplete;
     Relevance.FileProximityMatch = FileProximity.getPointer();
+    // FIXME: incorparate scope proximity into relevance score.
+
     auto &First = Bundle.front();
     if (auto FuzzyScore = fuzzyScore(First))
       Relevance.NameMatch = *FuzzyScore;
@@ -1552,8 +1576,8 @@
                           : nullptr;
       if (!Builder)
         Builder.emplace(Recorder->CCSema->getASTContext(), Item, SemaCCS,
-                        *Inserter, FileName, Recorder->CCContext.getKind(),
-                        Opts);
+                        QueryScopes, *Inserter, FileName,
+                        Recorder->CCContext.getKind(), Opts);
       else
         Builder->add(Item, SemaCCS);
     }
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to