hokein updated this revision to Diff 130618.
hokein marked 4 inline comments as done.
hokein added a comment.

- Rebase the patch to latest
- Update the patch based on the offline discussion

Updating D42073: [clangd] Query all visible scopes based on all visible 
using-namespace declarations
====================================================================================================

and containing namespace for global qualified code completion.


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D42073

Files:
  clangd/CodeComplete.cpp
  unittests/clangd/CodeCompleteTests.cpp

Index: unittests/clangd/CodeCompleteTests.cpp
===================================================================
--- unittests/clangd/CodeCompleteTests.cpp
+++ unittests/clangd/CodeCompleteTests.cpp
@@ -58,6 +58,7 @@
 using ::testing::ElementsAre;
 using ::testing::Not;
 using ::testing::UnorderedElementsAre;
+using ::testing::Field;
 
 class IgnoreDiagnostics : public DiagnosticsConsumer {
   void
@@ -676,6 +677,123 @@
   EXPECT_EQ(1, Results.activeParameter);
 }
 
+class IndexRequestCollector : public SymbolIndex {
+public:
+  bool
+  fuzzyFind(const Context &Ctx, const FuzzyFindRequest &Req,
+            llvm::function_ref<void(const Symbol &)> Callback) const override {
+    Requests.push_back(Req);
+    return false;
+  }
+
+  const std::vector<FuzzyFindRequest> allRequests() const { return Requests; }
+
+private:
+  mutable std::vector<FuzzyFindRequest> Requests;
+};
+
+TEST(CompletionTest, UnqualifiedIdQuery) {
+  clangd::CodeCompleteOptions Opts;
+  IndexRequestCollector Requests;
+  Opts.Index = &Requests;
+
+  auto Results = completions(R"cpp(
+     namespace std {}
+     using namespace std;
+     namespace ns {
+     void f() {
+       vec^
+     }
+     }
+  )cpp", {}, Opts);
+
+  EXPECT_THAT(Requests.allRequests(),
+              ElementsAre(Field(&FuzzyFindRequest::Scopes,
+                                UnorderedElementsAre("", "ns", "std"))));
+}
+
+TEST(CompletionTest, ResolvedQualifiedIdQuery) {
+  clangd::CodeCompleteOptions Opts;
+  IndexRequestCollector Requests;
+  Opts.Index = &Requests;
+
+  auto Results = completions(R"cpp(
+     namespace ns1 {}
+     namespace ns2 {} // ignore
+     namespace ns3 { namespace nns3 {} }
+     namespace foo {
+     using namespace ns1;
+     using namespace ns3::nns3;
+     }
+     namespace ns {
+     void f() {
+       foo::^
+     }
+     }
+  )cpp", {}, Opts);
+
+  EXPECT_THAT(
+      Requests.allRequests(),
+      ElementsAre(Field(&FuzzyFindRequest::Scopes,
+                        UnorderedElementsAre("foo", "ns1", "ns3::nns3"))));
+}
+
+TEST(CompletionTest, UnresolvedQualifierIdQuery) {
+  clangd::CodeCompleteOptions Opts;
+  IndexRequestCollector Requests;
+  Opts.Index = &Requests;
+
+  auto Results = completions(R"cpp(
+  namespace a {}
+  using namespace a;
+  namespace ns {
+  void f() {
+  bar::^
+  }
+  } // namespace ns
+  )cpp", {}, Opts);
+
+  EXPECT_THAT(Requests.allRequests(),
+              ElementsAre(Field(&FuzzyFindRequest::Scopes,
+                                UnorderedElementsAre("bar"))));
+}
+
+TEST(CompletionTest, EmptyQualifiedQuery) {
+  clangd::CodeCompleteOptions Opts;
+  IndexRequestCollector Requests;
+  Opts.Index = &Requests;
+
+  auto Results = completions(R"cpp(
+  namespace ns {
+  void f() {
+  ^
+  }
+  } // namespace ns
+  )cpp", {}, Opts);
+
+  EXPECT_THAT(Requests.allRequests(),
+              ElementsAre(Field(&FuzzyFindRequest::Scopes,
+                                UnorderedElementsAre("", "ns"))));
+}
+
+TEST(CompletionTest, GlobalQualifiedQuery) {
+  clangd::CodeCompleteOptions Opts;
+  IndexRequestCollector Requests;
+  Opts.Index = &Requests;
+
+  auto Results = completions(R"cpp(
+  namespace ns {
+  void f() {
+  ::^
+  }
+  } // namespace ns
+  )cpp", {}, Opts);
+
+  EXPECT_THAT(
+      Requests.allRequests(),
+      ElementsAre(Field(&FuzzyFindRequest::Scopes, UnorderedElementsAre(""))));
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clangd/CodeComplete.cpp
===================================================================
--- clangd/CodeComplete.cpp
+++ clangd/CodeComplete.cpp
@@ -309,20 +309,52 @@
   llvm_unreachable("unknown CodeCompletionResult kind");
 }
 
-/// \brief Information about the scope specifier in the qualified-id code
-/// completion (e.g. "ns::ab?").
-struct SpecifiedScope {
-  /// The scope specifier as written. For example, for completion "ns::ab?", the
-  /// written scope specifier is "ns".
-  std::string Written;
-  // If this scope specifier is recognized in Sema (e.g. as a namespace
-  // context), this will be set to the fully qualfied name of the corresponding
-  // context.
-  std::string Resolved;
-
-  llvm::StringRef forIndex() {
-    llvm::StringRef Chosen = Resolved.empty() ? Written : Resolved;
-    return Chosen.trim(':');
+StringRef normalizeScope(StringRef Q) {
+  return Q.trim(':');
+}
+
+/// \brief Scopes being queried in indexes for the qualified-id code completion
+/// (e.g. "ns::ab^").
+//
+// FIXME: we might want to make Sema code completion smarter on handling
+// unresolved qualified-id completion.
+//
+// E.g. foo::ns1::ns2::
+//      ^    ^ unresolved qualifier
+//      ^~~ resolved qualifier
+// Instead of getting the whole unresolved qualifier "foo::ns1::ns2::", a better
+// way is to return an unresolved qualifier "ns1::ns2" with all scopes that are
+// accessible in "foo".
+struct QualifiedScopes {
+  // All scopes that are accessible in the completion scope qualifier.
+  //
+  // If the completion scope qualifier is:
+  //   * resolved by Sema, it contains fully qualified names of all accessible
+  //   scopes (namespace) in the resolved qualifier;
+  //   * unresolved by Sema, global namespace "::" is the only accessible scope.
+  //
+  // See examples:
+  //   "::vec" => {"::"}
+  //   "using namespace std; ::vec^" => {"::", "std"}
+  //   "namespace ns {using namespace std;} ns::^" => {"ns", "std"}
+  //   "std::vec^" => {"::"}  // unresolved "std::"
+  std::vector<std::string> AccessibleScopes;
+  // The scope qualifier that is not resolved in Sema, it is the user-written
+  // qualifiers.
+  llvm::Optional<std::string> UnresolvedQualifier;
+
+  std::vector<std::string> forIndex() {
+    std::vector<std::string> Results;
+    for (llvm::StringRef VS : AccessibleScopes) {
+      std::string QueryScope =
+          (normalizeScope(VS) +
+           (UnresolvedQualifier
+                ? "::" + normalizeScope(*UnresolvedQualifier)
+                : ""))
+              .str();
+      Results.push_back(normalizeScope(QueryScope));
+    }
+    return Results;
   }
 };
 
@@ -625,22 +657,27 @@
   return true;
 }
 
-SpecifiedScope getSpecifiedScope(Sema &S, const CXXScopeSpec &SS) {
-  SpecifiedScope Info;
-  auto &SM = S.getSourceManager();
-  auto SpecifierRange = SS.getRange();
-  Info.Written = Lexer::getSourceText(
-      CharSourceRange::getCharRange(SpecifierRange), SM, clang::LangOptions());
-  if (SS.isValid()) {
-    DeclContext *DC = S.computeDeclContext(SS);
-    if (auto *NS = llvm::dyn_cast<NamespaceDecl>(DC)) {
-      Info.Resolved = NS->getQualifiedNameAsString();
-    } else if (llvm::dyn_cast<TranslationUnitDecl>(DC) != nullptr) {
-      Info.Resolved = "::";
-      // Sema does not include the suffix "::" in the range of SS, so we add
-      // it back here.
-      Info.Written = "::";
+QualifiedScopes getQualifiedScopes(Sema &S, const CXXScopeSpec &SS,
+                                   const CodeCompletionContext &CCContext) {
+  QualifiedScopes Info;
+  if (SS.isValid()) { // Resolved qualifier.
+    // FIXME: Disable Sema typo correction dureing code completion.
+    // The resolved qualifier might not perfectly match the written qualifier.
+    // e.g. "namespace clang { clangd::^ }", we will get "clang" declaration
+    // for completion "clangd::".
+    //
+    // Add all namespaces that are accessible from `DC` (including `DC` itself).
+    for (const auto* Ctx : CCContext.getVisitedContexts()) {
+      if (isa<TranslationUnitDecl>(Ctx))
+        Info.AccessibleScopes.push_back("::"); // global namespace
+      else if (const auto* VNS = dyn_cast<NamespaceDecl>(Ctx))
+        Info.AccessibleScopes.push_back(VNS->getQualifiedNameAsString());
     }
+  } else { // Unresolved qualifier.
+    Info.AccessibleScopes.push_back("::"); // global namespace
+    Info.UnresolvedQualifier =
+        Lexer::getSourceText(CharSourceRange::getCharRange(
+            SS.getRange()), S.getSourceManager(), clang::LangOptions());
   }
   return Info;
 }
@@ -805,15 +842,33 @@
     FuzzyFindRequest Req;
     Req.Query = Filter->pattern();
     // If the user typed a scope, e.g. a::b::xxx(), restrict to that scope.
-    // FIXME(ioeric): add scopes based on using directives and enclosing ns.
-    if (auto SS = Recorder.CCContext.getCXXScopeSpecifier())
-      Req.Scopes = {getSpecifiedScope(*Recorder.CCSema, **SS).forIndex()};
-    else
-      // Unless the user typed a ns qualifier, complete in global scope only.
-      // FIXME: once we know what namespaces are in scope (D42073), use those.
+    if (auto SS = Recorder.CCContext.getCXXScopeSpecifier()) {
+      Req.Scopes =
+          getQualifiedScopes(*Recorder.CCSema, **SS, Recorder.CCContext)
+              .forIndex();
+    } else {
+      // Unqualified code completion. Use the containing namespace and all
+      // accessible namespaces to query symbols from index, for examples:
+      //   "vec^" => {"::"}
+      //   "using namespace std; vec^" => {"::", "std"}
+      //   "using namespace std; namespace ns { vec^ }" => {"ns", "std", "::"}
+      //
       // FIXME: once we can insert namespace qualifiers and use the in-scope
       //        namespaces for scoring, search in all namespaces.
-      Req.Scopes = {""};
+      // FIXME: Capture scopes and use for scoring, for example,
+      //        "using namespace std; namespace foo {v^}" =>
+      //        foo::value > std::vector > boost::variant
+      for (auto* Context : Recorder.CCContext.getVisitedContexts()) {
+        if (isa<TranslationUnitDecl>(Context))
+          Req.Scopes.push_back(normalizeScope("::")); // global namespace
+        else if (const auto*NS = dyn_cast<NamespaceDecl>(Context))
+          Req.Scopes.push_back(normalizeScope(NS->getQualifiedNameAsString()));
+      }
+    }
+    log(Ctx, "Query scopes: [");
+    for (auto &R : Req.Scopes)
+      log(Ctx, R);
+    log(Ctx, "]");
     // Run the query against the index.
     Incomplete |= !Opts.Index->fuzzyFind(
         Ctx, Req, [&](const Symbol &Sym) { ResultsBuilder.insert(Sym); });
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to