[clang-tools-extra] r322945 - [clangd] Merge index-provided completions with those from Sema.

Sam McCall via cfe-commits cfe-commits at lists.llvm.org
Fri Jan 19 06:34:02 PST 2018


Author: sammccall
Date: Fri Jan 19 06:34:02 2018
New Revision: 322945

URL: http://llvm.org/viewvc/llvm-project?rev=322945&view=rev
Log:
[clangd] Merge index-provided completions with those from Sema.

Summary:
 - we match on USR, and do a field-by-field merge if both have results
 - scoring is post-merge, with both sets of information available
   (for now, sema priority is used if available, static score for index results)
 - limit is applied to the complete result set (previously index ignored limit)
 - CompletionItem is only produces for the returned results
 - If the user doesn't type a scope, we send the global scope for completion
   (we can improve this after D42073)

Reviewers: ioeric

Subscribers: klimek, ilya-biryukov, mgrang, cfe-commits

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

Modified:
    clang-tools-extra/trunk/clangd/CodeComplete.cpp
    clang-tools-extra/trunk/clangd/FuzzyMatch.h
    clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp

Modified: clang-tools-extra/trunk/clangd/CodeComplete.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/CodeComplete.cpp?rev=322945&r1=322944&r2=322945&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/CodeComplete.cpp (original)
+++ clang-tools-extra/trunk/clangd/CodeComplete.cpp Fri Jan 19 06:34:02 2018
@@ -22,6 +22,7 @@
 #include "index/Index.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Frontend/FrontendActions.h"
+#include "clang/Index/USRGeneration.h"
 #include "clang/Sema/CodeCompleteConsumer.h"
 #include "clang/Sema/Sema.h"
 #include "llvm/Support/Format.h"
@@ -185,48 +186,53 @@ getOptionalParameters(const CodeCompleti
   return Result;
 }
 
-/// A scored code completion result.
+// Produces an integer that sorts in the same order as F.
+// That is: a < b <==> encodeFloat(a) < encodeFloat(b).
+uint32_t encodeFloat(float F) {
+  static_assert(std::numeric_limits<float>::is_iec559, "");
+  static_assert(sizeof(float) == sizeof(uint32_t), "");
+  constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1);
+
+  // Get the bits of the float. Endianness is the same as for integers.
+  uint32_t U;
+  memcpy(&U, &F, sizeof(float));
+  // IEEE 754 floats compare like sign-magnitude integers.
+  if (U & TopBit)    // Negative float.
+    return 0 - U;    // Map onto the low half of integers, order reversed.
+  return U + TopBit; // Positive floats map onto the high half of integers.
+}
+
+// Returns a string that sorts in the same order as (-Score, Name), for LSP.
+std::string sortText(float Score, llvm::StringRef Name) {
+  // We convert -Score to an integer, and hex-encode for readability.
+  // Example: [0.5, "foo"] -> "41000000foo"
+  std::string S;
+  llvm::raw_string_ostream OS(S);
+  write_hex(OS, encodeFloat(-Score), llvm::HexPrintStyle::Lower,
+            /*Width=*/2 * sizeof(Score));
+  OS << Name;
+  OS.flush();
+  return S;
+}
+
+/// A code completion result, in clang-native form.
 /// It may be promoted to a CompletionItem if it's among the top-ranked results.
-///
-/// We score candidates by multiplying the symbolScore ("quality" of the result)
-/// with the filterScore (how well it matched the query).
-/// This is sensitive to the distribution of both component scores!
 struct CompletionCandidate {
-  CompletionCandidate(CodeCompletionResult &Result, float FilterScore)
-      : Result(&Result) {
-    Scores.symbolScore = score(Result);  // Higher is better.
-    Scores.filterScore = FilterScore;    // 0-1, higher is better.
-    Scores.finalScore = Scores.symbolScore * Scores.filterScore;
-  }
-
-  CodeCompletionResult *Result;
-  CompletionItemScores Scores;
-
-  // Comparison reflects rank: better candidates are smaller.
-  bool operator<(const CompletionCandidate &C) const {
-    if (Scores.finalScore != C.Scores.finalScore)
-      return Scores.finalScore > C.Scores.finalScore;
-    return *Result < *C.Result;
-  }
-
-  // Returns a string that sorts in the same order as operator<, for LSP.
-  // Conceptually, this is [-Score, Name]. We convert -Score to an integer, and
-  // hex-encode it for readability. Example: [0.5, "foo"] -> "41000000foo"
-  std::string sortText() const {
-    std::string S, NameStorage;
-    llvm::raw_string_ostream OS(S);
-    write_hex(OS, encodeFloat(-Scores.finalScore), llvm::HexPrintStyle::Lower,
-              /*Width=*/2 * sizeof(Scores.finalScore));
-    OS << Result->getOrderedName(NameStorage);
-    return OS.str();
-  }
+  llvm::StringRef Name; // Used for filtering and sorting.
+  // We may have a result from Sema, from the index, or both.
+  const CodeCompletionResult *SemaResult = nullptr;
+  const Symbol *IndexResult = nullptr;
+
+  // Computes the "symbol quality" score for this completion. Higher is better.
+  float score() const {
+    // For now we just use the Sema priority, mapping it onto a 0-1 interval.
+    if (!SemaResult) // FIXME(sammccall): better scoring for index results.
+      return 0.3;    // fixed mediocre score for index-only results.
 
-private:
-  static float score(const CodeCompletionResult &Result) {
     // Priority 80 is a really bad score.
-    float Score = 1 - std::min<float>(80, Result.Priority) / 80;
+    float Score = 1 - std::min<float>(80, SemaResult->Priority) / 80;
 
-    switch (static_cast<CXAvailabilityKind>(Result.Availability)) {
+    switch (static_cast<CXAvailabilityKind>(SemaResult->Availability)) {
     case CXAvailability_Available:
       // No penalty.
       break;
@@ -241,23 +247,68 @@ private:
     return Score;
   }
 
-  // Produces an integer that sorts in the same order as F.
-  // That is: a < b <==> encodeFloat(a) < encodeFloat(b).
-  static uint32_t encodeFloat(float F) {
-    static_assert(std::numeric_limits<float>::is_iec559, "");
-    static_assert(sizeof(float) == sizeof(uint32_t), "");
-    constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1);
-
-    // Get the bits of the float. Endianness is the same as for integers.
-    uint32_t U;
-    memcpy(&U, &F, sizeof(float));
-    // IEEE 754 floats compare like sign-magnitude integers.
-    if (U & TopBit)    // Negative float.
-      return 0 - U;    // Map onto the low half of integers, order reversed.
-    return U + TopBit; // Positive floats map onto the high half of integers.
+  // Builds an LSP completion item.
+  CompletionItem build(const CompletionItemScores &Scores,
+                       const CodeCompleteOptions &Opts,
+                       CodeCompletionString *SemaCCS) const {
+    assert(bool(SemaResult) == bool(SemaCCS));
+    CompletionItem I;
+    if (SemaResult) {
+      I.kind = toCompletionItemKind(SemaResult->Kind, SemaResult->CursorKind);
+      getLabelAndInsertText(*SemaCCS, &I.label, &I.insertText,
+                            Opts.EnableSnippets);
+      I.filterText = getFilterText(*SemaCCS);
+      I.documentation = getDocumentation(*SemaCCS);
+      I.detail = getDetail(*SemaCCS);
+    }
+    if (IndexResult) {
+      if (I.kind == CompletionItemKind::Missing)
+        I.kind = toCompletionItemKind(IndexResult->SymInfo.Kind);
+      // FIXME: reintroduce a way to show the index source for debugging.
+      if (I.label.empty())
+        I.label = IndexResult->CompletionLabel;
+      if (I.filterText.empty())
+        I.filterText = IndexResult->Name;
+
+      // FIXME(ioeric): support inserting/replacing scope qualifiers.
+      if (I.insertText.empty())
+        I.insertText = Opts.EnableSnippets
+                           ? IndexResult->CompletionSnippetInsertText
+                           : IndexResult->CompletionPlainInsertText;
+
+      if (auto *D = IndexResult->Detail) {
+        if (I.documentation.empty())
+          I.documentation = D->Documentation;
+        if (I.detail.empty())
+          I.detail = D->CompletionDetail;
+      }
+    }
+    I.scoreInfo = Scores;
+    I.sortText = sortText(Scores.finalScore, Name);
+    I.insertTextFormat = Opts.EnableSnippets ? InsertTextFormat::Snippet
+                                             : InsertTextFormat::PlainText;
+    return I;
   }
 };
 
+// Determine the symbol ID for a Sema code completion result, if possible.
+llvm::Optional<SymbolID> getSymbolID(const CodeCompletionResult &R) {
+  switch (R.Kind) {
+  case CodeCompletionResult::RK_Declaration:
+  case CodeCompletionResult::RK_Pattern: {
+    llvm::SmallString<128> USR;
+    if (/*Ignore=*/clang::index::generateUSRForDecl(R.Declaration, USR))
+      return None;
+    return SymbolID(USR);
+  }
+  case CodeCompletionResult::RK_Macro:
+    // FIXME: Macros do have USRs, but the CCR doesn't contain enough info.
+  case CodeCompletionResult::RK_Keyword:
+    return None;
+  }
+  llvm_unreachable("unknown CodeCompletionResult kind");
+}
+
 /// \brief Information about the scope specifier in the qualified-id code
 /// completion (e.g. "ns::ab?").
 struct SpecifiedScope {
@@ -268,133 +319,134 @@ struct SpecifiedScope {
   // context), this will be set to the fully qualfied name of the corresponding
   // context.
   std::string Resolved;
-};
-
-/// \brief Information from sema about (parital) symbol names to be completed.
-/// For example, for completion "ns::ab^", this stores the scope specifier
-/// "ns::" and the completion filter text "ab".
-struct NameToComplete {
-  // The partial identifier being completed, without qualifier.
-  std::string Filter;
 
-  /// This is set if the completion is for qualified IDs, e.g. "abc::x^".
-  llvm::Optional<SpecifiedScope> SSInfo;
+  llvm::StringRef forIndex() {
+    llvm::StringRef Chosen = Resolved.empty() ? Written : Resolved;
+    return Chosen.trim(':');
+  }
 };
 
-SpecifiedScope extraCompletionScope(Sema &S, const CXXScopeSpec &SS);
-
-class CompletionItemsCollector : public CodeCompleteConsumer {
-public:
-  CompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts,
-                           CompletionList &Items, NameToComplete &CompletedName)
-      : CodeCompleteConsumer(CodeCompleteOpts.getClangCompleteOpts(),
+// The CompletionRecorder captures Sema code-complete output, including context.
+// It filters out ignored results (but doesn't apply fuzzy-filtering yet).
+// It doesn't do scoring or conversion to CompletionItem yet, as we want to
+// merge with index results first.
+struct CompletionRecorder : public CodeCompleteConsumer {
+  CompletionRecorder(const CodeCompleteOptions &Opts)
+      : CodeCompleteConsumer(Opts.getClangCompleteOpts(),
                              /*OutputIsBinary=*/false),
-        ClangdOpts(CodeCompleteOpts), Items(Items),
-        Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
-        CCTUInfo(Allocator), CompletedName(CompletedName),
-        EnableSnippets(CodeCompleteOpts.EnableSnippets) {}
+        CCContext(CodeCompletionContext::CCC_Other), Opts(Opts),
+        CCAllocator(std::make_shared<GlobalCodeCompletionAllocator>()),
+        CCTUInfo(CCAllocator) {}
+  std::vector<CodeCompletionResult> Results;
+  CodeCompletionContext CCContext;
+  Sema *CCSema = nullptr; // Sema that created the results.
+  // FIXME: Sema is scary. Can we store ASTContext and Preprocessor, instead?
 
-  void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
-                                  CodeCompletionResult *Results,
+  void ProcessCodeCompleteResults(class Sema &S, CodeCompletionContext Context,
+                                  CodeCompletionResult *InResults,
                                   unsigned NumResults) override final {
-    FuzzyMatcher Filter(S.getPreprocessor().getCodeCompletionFilter());
-    if (auto SS = Context.getCXXScopeSpecifier())
-      CompletedName.SSInfo = extraCompletionScope(S, **SS);
+    // Record the completion context.
+    assert(!CCSema && "ProcessCodeCompleteResults called multiple times!");
+    CCSema = &S;
+    CCContext = Context;
 
-    CompletedName.Filter = S.getPreprocessor().getCodeCompletionFilter();
-    std::priority_queue<CompletionCandidate> Candidates;
+    // Retain the results we might want.
     for (unsigned I = 0; I < NumResults; ++I) {
-      auto &Result = Results[I];
-      // We drop hidden items, as they cannot be found by the lookup after
-      // inserting the corresponding completion item and only produce noise and
-      // duplicates in the completion list. However, there is one exception. If
-      // Result has a Qualifier which is non-informative, we can refer to an
-      // item by adding that qualifier, so we don't filter out this item.
+      auto &Result = InResults[I];
+      // Drop hidden items which cannot be found by lookup after completion.
+      // Exception: some items can be named by using a qualifier.
       if (Result.Hidden && (!Result.Qualifier || Result.QualifierIsInformative))
         continue;
-      if (!ClangdOpts.IncludeIneligibleResults &&
+      if (!Opts.IncludeIneligibleResults &&
           (Result.Availability == CXAvailability_NotAvailable ||
            Result.Availability == CXAvailability_NotAccessible))
         continue;
-      auto FilterScore = fuzzyMatch(S, Context, Filter, Result);
-      if (!FilterScore)
-        continue;
-      Candidates.emplace(Result, *FilterScore);
-      if (ClangdOpts.Limit && Candidates.size() > ClangdOpts.Limit) {
-        Candidates.pop();
-        Items.isIncomplete = true;
-      }
-    }
-    while (!Candidates.empty()) {
-      auto &Candidate = Candidates.top();
-      const auto *CCS = Candidate.Result->CreateCodeCompletionString(
-          S, Context, *Allocator, CCTUInfo,
-          CodeCompleteOpts.IncludeBriefComments);
-      assert(CCS && "Expected the CodeCompletionString to be non-null");
-      Items.items.push_back(ProcessCodeCompleteResult(Candidate, *CCS));
-      Candidates.pop();
+      Results.push_back(Result);
     }
-    std::reverse(Items.items.begin(), Items.items.end());
   }
 
-  GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; }
-
+  CodeCompletionAllocator &getAllocator() override { return *CCAllocator; }
   CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
 
-private:
-  llvm::Optional<float> fuzzyMatch(Sema &S, const CodeCompletionContext &CCCtx,
-                                   FuzzyMatcher &Filter,
-                                   CodeCompletionResult Result) {
+  // Returns the filtering/sorting name for Result, which must be from Results.
+  // Returned string is owned by this recorder (or the AST).
+  llvm::StringRef getName(const CodeCompletionResult &Result) {
     switch (Result.Kind) {
     case CodeCompletionResult::RK_Declaration:
       if (auto *ID = Result.Declaration->getIdentifier())
-        return Filter.match(ID->getName());
+        return ID->getName();
       break;
     case CodeCompletionResult::RK_Keyword:
-      return Filter.match(Result.Keyword);
+      return Result.Keyword;
     case CodeCompletionResult::RK_Macro:
-      return Filter.match(Result.Macro->getName());
+      return Result.Macro->getName();
     case CodeCompletionResult::RK_Pattern:
-      return Filter.match(Result.Pattern->getTypedText());
+      return Result.Pattern->getTypedText();
     }
-    auto *CCS = Result.CreateCodeCompletionString(
-        S, CCCtx, *Allocator, CCTUInfo, /*IncludeBriefComments=*/false);
-    return Filter.match(CCS->getTypedText());
+    auto *CCS = codeCompletionString(Result, /*IncludeBriefComments=*/false);
+    return CCS->getTypedText();
   }
 
-  CompletionItem
-  ProcessCodeCompleteResult(const CompletionCandidate &Candidate,
-                            const CodeCompletionString &CCS) const {
+  // Build a CodeCompletion string for R, which must be from Results.
+  // The CCS will be owned by this recorder.
+  CodeCompletionString *codeCompletionString(const CodeCompletionResult &R,
+                                             bool IncludeBriefComments) {
+    // CodeCompletionResult doesn't seem to be const-correct. We own it, anyway.
+    return const_cast<CodeCompletionResult &>(R).CreateCodeCompletionString(
+        *CCSema, CCContext, *CCAllocator, CCTUInfo, IncludeBriefComments);
+  }
 
-    // Adjust this to InsertTextFormat::Snippet iff we encounter a
-    // CK_Placeholder chunk in SnippetCompletionItemsCollector.
-    CompletionItem Item;
+private:
+  CodeCompleteOptions Opts;
+  std::shared_ptr<GlobalCodeCompletionAllocator> CCAllocator;
+  CodeCompletionTUInfo CCTUInfo;
+};
 
-    Item.documentation = getDocumentation(CCS);
-    Item.sortText = Candidate.sortText();
-    Item.scoreInfo = Candidate.Scores;
+// Tracks a bounded number of candidates with the best scores.
+class TopN {
+public:
+  using value_type = std::pair<CompletionCandidate, CompletionItemScores>;
+  static constexpr size_t Unbounded = std::numeric_limits<size_t>::max();
 
-    Item.detail = getDetail(CCS);
-    Item.filterText = getFilterText(CCS);
-    getLabelAndInsertText(CCS, &Item.label, &Item.insertText, EnableSnippets);
+  TopN(size_t N) : N(N) {}
 
-    Item.insertTextFormat = EnableSnippets ? InsertTextFormat::Snippet
-                                           : InsertTextFormat::PlainText;
+  // Adds a candidate to the set.
+  // Returns true if a candidate was dropped to get back under N.
+  bool push(value_type &&V) {
+    bool Dropped = false;
+    if (Heap.size() >= N) {
+      Dropped = true;
+      if (N > 0 && greater(V, Heap.front())) {
+        std::pop_heap(Heap.begin(), Heap.end(), greater);
+        Heap.back() = std::move(V);
+        std::push_heap(Heap.begin(), Heap.end(), greater);
+      }
+    } else {
+      Heap.push_back(std::move(V));
+      std::push_heap(Heap.begin(), Heap.end(), greater);
+    }
+    assert(Heap.size() <= N);
+    assert(std::is_heap(Heap.begin(), Heap.end(), greater));
+    return Dropped;
+  }
 
-    // Fill in the kind field of the CompletionItem.
-    Item.kind = toCompletionItemKind(Candidate.Result->Kind,
-                                     Candidate.Result->CursorKind);
+  // Returns candidates from best to worst.
+  std::vector<value_type> items() && {
+    std::sort_heap(Heap.begin(), Heap.end(), greater);
+    assert(Heap.size() <= N);
+    return std::move(Heap);
+  }
 
-    return Item;
+private:
+  static bool greater(const value_type &L, const value_type &R) {
+    if (L.second.finalScore != R.second.finalScore)
+      return L.second.finalScore > R.second.finalScore;
+    return L.first.Name < R.first.Name; // Earlier name is better.
   }
 
-  CodeCompleteOptions ClangdOpts;
-  CompletionList &Items;
-  std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
-  CodeCompletionTUInfo CCTUInfo;
-  NameToComplete &CompletedName;
-  bool EnableSnippets;
-}; // CompletionItemsCollector
+  const size_t N;
+  std::vector<value_type> Heap; // Min-heap, comparator is greater().
+};
 
 class SignatureHelpCollector final : public CodeCompleteConsumer {
 
@@ -491,133 +543,89 @@ private:
 
 }; // SignatureHelpCollector
 
-bool invokeCodeComplete(const Context &Ctx,
-                        std::unique_ptr<CodeCompleteConsumer> Consumer,
-                        const clang::CodeCompleteOptions &Options,
-                        PathRef FileName,
-                        const tooling::CompileCommand &Command,
-                        PrecompiledPreamble const *Preamble, StringRef Contents,
-                        Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS,
-                        std::shared_ptr<PCHContainerOperations> PCHs) {
+struct SemaCompleteInput {
+  PathRef FileName;
+  const tooling::CompileCommand &Command;
+  PrecompiledPreamble const *Preamble;
+  StringRef Contents;
+  Position Pos;
+  IntrusiveRefCntPtr<vfs::FileSystem> VFS;
+  std::shared_ptr<PCHContainerOperations> PCHs;
+};
+
+// Invokes Sema code completion on a file.
+// Callback will be invoked once completion is done, but before cleaning up.
+bool semaCodeComplete(const Context &Ctx,
+                      std::unique_ptr<CodeCompleteConsumer> Consumer,
+                      const clang::CodeCompleteOptions &Options,
+                      const SemaCompleteInput &Input,
+                      llvm::function_ref<void()> Callback = nullptr) {
   std::vector<const char *> ArgStrs;
-  for (const auto &S : Command.CommandLine)
+  for (const auto &S : Input.Command.CommandLine)
     ArgStrs.push_back(S.c_str());
 
-  VFS->setCurrentWorkingDirectory(Command.Directory);
+  Input.VFS->setCurrentWorkingDirectory(Input.Command.Directory);
 
   IgnoreDiagnostics DummyDiagsConsumer;
   auto CI = createInvocationFromCommandLine(
       ArgStrs,
       CompilerInstance::createDiagnostics(new DiagnosticOptions,
                                           &DummyDiagsConsumer, false),
-      VFS);
+      Input.VFS);
   assert(CI && "Couldn't create CompilerInvocation");
   CI->getFrontendOpts().DisableFree = false;
 
   std::unique_ptr<llvm::MemoryBuffer> ContentsBuffer =
-      llvm::MemoryBuffer::getMemBufferCopy(Contents, FileName);
+      llvm::MemoryBuffer::getMemBufferCopy(Input.Contents, Input.FileName);
 
   // We reuse the preamble whether it's valid or not. This is a
   // correctness/performance tradeoff: building without a preamble is slow, and
   // completion is latency-sensitive.
-  if (Preamble) {
+  if (Input.Preamble) {
     auto Bounds =
         ComputePreambleBounds(*CI->getLangOpts(), ContentsBuffer.get(), 0);
     // FIXME(ibiryukov): Remove this call to CanReuse() after we'll fix
     // clients relying on getting stats for preamble files during code
     // completion.
     // Note that results of CanReuse() are ignored, see the comment above.
-    Preamble->CanReuse(*CI, ContentsBuffer.get(), Bounds, VFS.get());
+    Input.Preamble->CanReuse(*CI, ContentsBuffer.get(), Bounds,
+                             Input.VFS.get());
   }
   auto Clang = prepareCompilerInstance(
-      std::move(CI), Preamble, std::move(ContentsBuffer), std::move(PCHs),
-      std::move(VFS), DummyDiagsConsumer);
+      std::move(CI), Input.Preamble, std::move(ContentsBuffer),
+      std::move(Input.PCHs), std::move(Input.VFS), DummyDiagsConsumer);
   auto &DiagOpts = Clang->getDiagnosticOpts();
   DiagOpts.IgnoreWarnings = true;
 
   auto &FrontendOpts = Clang->getFrontendOpts();
   FrontendOpts.SkipFunctionBodies = true;
   FrontendOpts.CodeCompleteOpts = Options;
-  FrontendOpts.CodeCompletionAt.FileName = FileName;
-  FrontendOpts.CodeCompletionAt.Line = Pos.line + 1;
-  FrontendOpts.CodeCompletionAt.Column = Pos.character + 1;
+  FrontendOpts.CodeCompletionAt.FileName = Input.FileName;
+  FrontendOpts.CodeCompletionAt.Line = Input.Pos.line + 1;
+  FrontendOpts.CodeCompletionAt.Column = Input.Pos.character + 1;
 
   Clang->setCodeCompletionConsumer(Consumer.release());
 
   SyntaxOnlyAction Action;
   if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {
-    log(Ctx,
-        "BeginSourceFile() failed when running codeComplete for " + FileName);
+    log(Ctx, "BeginSourceFile() failed when running codeComplete for " +
+                 Input.FileName);
     return false;
   }
   if (!Action.Execute()) {
-    log(Ctx, "Execute() failed when running codeComplete for " + FileName);
+    log(Ctx,
+        "Execute() failed when running codeComplete for " + Input.FileName);
     return false;
   }
 
+  if (Callback)
+    Callback();
   Action.EndSourceFile();
 
   return true;
 }
 
-CompletionItem indexCompletionItem(const Symbol &Sym, llvm::StringRef Filter,
-                                   const SpecifiedScope &SSInfo,
-                                   llvm::StringRef DebuggingLabel = "") {
-  CompletionItem Item;
-  Item.kind = toCompletionItemKind(Sym.SymInfo.Kind);
-  // Add DebuggingLabel to the completion results if DebuggingLabel is not
-  // empty.
-  //
-  // For symbols from static index, there are prefix "[G]" in the
-  // results (which is used for debugging purpose).
-  // So completion list will be like:
-  //   clang::symbol_from_dynamic_index
-  //   [G]clang::symbol_from_static_index
-  //
-  // FIXME: Find out a better way to show the index source.
-  if (!DebuggingLabel.empty()) {
-    llvm::raw_string_ostream Label(Item.label);
-    Label << llvm::format("[%s]%s", DebuggingLabel.str().c_str(),
-                          Sym.Name.str().c_str());
-  } else {
-    Item.label = Sym.Name;
-  }
-  // FIXME(ioeric): support inserting/replacing scope qualifiers.
-
-  // FIXME(ioeric): support snippets.
-  Item.insertText = Sym.CompletionPlainInsertText;
-  Item.insertTextFormat = InsertTextFormat::PlainText;
-  Item.filterText = Sym.Name;
-
-  // FIXME(ioeric): sort symbols appropriately.
-  Item.sortText = "";
-
-  if (Sym.Detail) {
-    Item.documentation = Sym.Detail->Documentation;
-    Item.detail = Sym.Detail->CompletionDetail;
-  }
-
-  return Item;
-}
-
-void completeWithIndex(const Context &Ctx, const SymbolIndex &Index,
-                       llvm::StringRef Code, const SpecifiedScope &SSInfo,
-                       llvm::StringRef Filter, CompletionList *Items,
-                       llvm::StringRef DebuggingLabel = "") {
-  FuzzyFindRequest Req;
-  Req.Query = Filter;
-  // FIXME(ioeric): add more possible scopes based on using namespaces and
-  // containing namespaces.
-  StringRef Scope = SSInfo.Resolved.empty() ? SSInfo.Written : SSInfo.Resolved;
-  Req.Scopes = {Scope.trim(':').str()};
-
-  Items->isIncomplete |= !Index.fuzzyFind(Ctx, Req, [&](const Symbol &Sym) {
-    Items->items.push_back(
-        indexCompletionItem(Sym, Filter, SSInfo, DebuggingLabel));
-  });
-}
-
-SpecifiedScope extraCompletionScope(Sema &S, const CXXScopeSpec &SS) {
+SpecifiedScope getSpecifiedScope(Sema &S, const CXXScopeSpec &SS) {
   SpecifiedScope Info;
   auto &SM = S.getSourceManager();
   auto SpecifierRange = SS.getRange();
@@ -637,6 +645,50 @@ SpecifiedScope extraCompletionScope(Sema
   return Info;
 }
 
+// Should we perform index-based completion in this context?
+// FIXME: consider allowing completion, but restricting the result types.
+bool allowIndex(enum CodeCompletionContext::Kind K) {
+  switch (K) {
+  case CodeCompletionContext::CCC_TopLevel:
+  case CodeCompletionContext::CCC_ObjCInterface:
+  case CodeCompletionContext::CCC_ObjCImplementation:
+  case CodeCompletionContext::CCC_ObjCIvarList:
+  case CodeCompletionContext::CCC_ClassStructUnion:
+  case CodeCompletionContext::CCC_Statement:
+  case CodeCompletionContext::CCC_Expression:
+  case CodeCompletionContext::CCC_ObjCMessageReceiver:
+  case CodeCompletionContext::CCC_EnumTag:
+  case CodeCompletionContext::CCC_UnionTag:
+  case CodeCompletionContext::CCC_ClassOrStructTag:
+  case CodeCompletionContext::CCC_ObjCProtocolName:
+  case CodeCompletionContext::CCC_Namespace:
+  case CodeCompletionContext::CCC_Type:
+  case CodeCompletionContext::CCC_Name: // FIXME: why does ns::^ give this?
+  case CodeCompletionContext::CCC_PotentiallyQualifiedName:
+  case CodeCompletionContext::CCC_ParenthesizedExpression:
+  case CodeCompletionContext::CCC_ObjCInterfaceName:
+  case CodeCompletionContext::CCC_ObjCCategoryName:
+    return true;
+  case CodeCompletionContext::CCC_Other: // Be conservative.
+  case CodeCompletionContext::CCC_OtherWithMacros:
+  case CodeCompletionContext::CCC_DotMemberAccess:
+  case CodeCompletionContext::CCC_ArrowMemberAccess:
+  case CodeCompletionContext::CCC_ObjCPropertyAccess:
+  case CodeCompletionContext::CCC_MacroName:
+  case CodeCompletionContext::CCC_MacroNameUse:
+  case CodeCompletionContext::CCC_PreprocessorExpression:
+  case CodeCompletionContext::CCC_PreprocessorDirective:
+  case CodeCompletionContext::CCC_NaturalLanguage:
+  case CodeCompletionContext::CCC_SelectorName:
+  case CodeCompletionContext::CCC_TypeQualifiers:
+  case CodeCompletionContext::CCC_ObjCInstanceMessage:
+  case CodeCompletionContext::CCC_ObjCClassMessage:
+  case CodeCompletionContext::CCC_Recovery:
+    return false;
+  }
+  llvm_unreachable("unknown code completion context");
+}
+
 } // namespace
 
 clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
@@ -654,6 +706,185 @@ clang::CodeCompleteOptions CodeCompleteO
   return Result;
 }
 
+// Runs Sema-based (AST) and Index-based completion, returns merged results.
+//
+// There are a few tricky considerations:
+//   - the AST provides information needed for the index query (e.g. which
+//     namespaces to search in). So Sema must start first.
+//   - we only want to return the top results (Opts.Limit).
+//     Building CompletionItems for everything else is wasteful, so we want to
+//     preserve the "native" format until we're done with scoring.
+//   - the data underlying Sema completion items is owned by the AST and various
+//     other arenas, which must stay alive for us to build CompletionItems.
+//   - we may get duplicate results from Sema and the Index, we need to merge.
+//
+// So we start Sema completion first, but defer its cleanup until we're done.
+// We use the Sema context information to query the index.
+// Then we merge the two result sets, producing items that are Sema/Index/Both.
+// These items are scored, and the top N are synthesized into the LSP response.
+// Finally, we can clean up the data structures created by Sema completion.
+//
+// Main collaborators are:
+//   - semaCodeComplete sets up the compiler machinery to run code completion.
+//   - CompletionRecorder captures Sema completion results, including context.
+//   - SymbolIndex (Opts.Index) provides index completion results as Symbols
+//   - CompletionCandidates are the result of merging Sema and Index results.
+//     Each candidate points to an underlying CodeCompletionResult (Sema), a
+//     Symbol (Index), or both. It computes the result quality score.
+//     CompletionCandidate also does conversion to CompletionItem (at the end).
+//   - FuzzyMatcher scores how the candidate matches the partial identifier.
+//     This score is combined with the result quality score for the final score.
+//   - TopN determines the results with the best score.
+class CodeCompleteFlow {
+  const Context &Ctx;
+  const CodeCompleteOptions &Opts;
+  // Sema takes ownership of Recorder. Recorder is valid until Sema cleanup.
+  std::unique_ptr<CompletionRecorder> RecorderOwner;
+  CompletionRecorder &Recorder;
+  int NSema = 0, NIndex = 0, NBoth = 0; // Counters for logging.
+  bool Incomplete = false; // Would more be available with a higher limit?
+  llvm::Optional<FuzzyMatcher> Filter; // Initialized once Sema runs.
+
+public:
+  // A CodeCompleteFlow object is only useful for calling run() exactly once.
+  CodeCompleteFlow(const Context &Ctx, const CodeCompleteOptions &Opts)
+      : Ctx(Ctx), Opts(Opts), RecorderOwner(new CompletionRecorder(Opts)),
+        Recorder(*RecorderOwner) {}
+
+  CompletionList run(const SemaCompleteInput &SemaCCInput) && {
+    // We run Sema code completion first. It builds an AST and calculates:
+    //   - completion results based on the AST. These are saved for merging.
+    //   - partial identifier and context. We need these for the index query.
+    CompletionList Output;
+    semaCodeComplete(Ctx, std::move(RecorderOwner), Opts.getClangCompleteOpts(),
+                     SemaCCInput, [&] {
+                       if (Recorder.CCSema)
+                         Output = runWithSema();
+                       else
+                         log(Ctx, "Code complete: no Sema callback, 0 results");
+                     });
+
+    log(Ctx,
+        llvm::formatv("Code complete: {0} results from Sema, {1} from Index, "
+                      "{2} matched, {3} returned{4}.",
+                      NSema, NIndex, NBoth, Output.items.size(),
+                      Output.isIncomplete ? " (incomplete)" : ""));
+    assert(!Opts.Limit || Output.items.size() <= Opts.Limit);
+    // We don't assert that isIncomplete means we hit a limit.
+    // Indexes may choose to impose their own limits even if we don't have one.
+    return Output;
+  }
+
+private:
+  // This is called by run() once Sema code completion is done, but before the
+  // Sema data structures are torn down. It does all the real work.
+  CompletionList runWithSema() {
+    Filter = FuzzyMatcher(
+        Recorder.CCSema->getPreprocessor().getCodeCompletionFilter());
+    // 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.
+    //        We can use their signals even if the index can't suggest them.
+    // We must copy index results to preserve them, but there are at most Limit.
+    auto IndexResults = queryIndex();
+    // Merge Sema and Index results, score them, and pick the winners.
+    auto Top = mergeResults(Recorder.Results, IndexResults);
+    // Convert the results to the desired LSP structs.
+    CompletionList Output;
+    for (auto &C : Top)
+      Output.items.push_back(toCompletionItem(C.first, C.second));
+    Output.isIncomplete = Incomplete;
+    return Output;
+  }
+
+  SymbolSlab queryIndex() {
+    if (!Opts.Index || !allowIndex(Recorder.CCContext.getKind()))
+      return SymbolSlab();
+    SymbolSlab::Builder ResultsBuilder;
+    // Build the query.
+    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.
+      // FIXME: once we can insert namespace qualifiers and use the in-scope
+      //        namespaces for scoring, search in all namespaces.
+      Req.Scopes = {""};
+    // Run the query against the index.
+    Incomplete |= !Opts.Index->fuzzyFind(
+        Ctx, Req, [&](const Symbol &Sym) { ResultsBuilder.insert(Sym); });
+    return std::move(ResultsBuilder).build();
+  }
+
+  // Merges the Sema and Index results where possible, scores them, and
+  // returns the top results from best to worst.
+  std::vector<std::pair<CompletionCandidate, CompletionItemScores>>
+  mergeResults(const std::vector<CodeCompletionResult> &SemaResults,
+               const SymbolSlab &IndexResults) {
+    // We only keep the best N results at any time, in "native" format.
+    TopN Top(Opts.Limit == 0 ? TopN::Unbounded : Opts.Limit);
+    llvm::DenseSet<const Symbol *> UsedIndexResults;
+    auto CorrespondingIndexResult =
+        [&](const CodeCompletionResult &SemaResult) -> const Symbol * {
+      if (auto SymID = getSymbolID(SemaResult)) {
+        auto I = IndexResults.find(*SymID);
+        if (I != IndexResults.end()) {
+          UsedIndexResults.insert(&*I);
+          return &*I;
+        }
+      }
+      return nullptr;
+    };
+    // Emit all Sema results, merging them with Index results if possible.
+    for (auto &SemaResult : Recorder.Results)
+      addCandidate(Top, &SemaResult, CorrespondingIndexResult(SemaResult));
+    // Now emit any Index-only results.
+    for (const auto &IndexResult : IndexResults) {
+      if (UsedIndexResults.count(&IndexResult))
+        continue;
+      addCandidate(Top, /*SemaResult=*/nullptr, &IndexResult);
+    }
+    return std::move(Top).items();
+  }
+
+  // Scores a candidate and adds it to the TopN structure.
+  void addCandidate(TopN &Candidates, const CodeCompletionResult *SemaResult,
+                    const Symbol *IndexResult) {
+    CompletionCandidate C;
+    C.SemaResult = SemaResult;
+    C.IndexResult = IndexResult;
+    C.Name = IndexResult ? IndexResult->Name : Recorder.getName(*SemaResult);
+
+    CompletionItemScores Scores;
+    if (auto FuzzyScore = Filter->match(C.Name))
+      Scores.filterScore = *FuzzyScore;
+    else
+      return;
+    Scores.symbolScore = C.score();
+    // We score candidates by multiplying symbolScore ("quality" of the result)
+    // with filterScore (how well it matched the query).
+    // This is sensitive to the distribution of both component scores!
+    Scores.finalScore = Scores.filterScore * Scores.symbolScore;
+
+    NSema += bool(SemaResult);
+    NIndex += bool(IndexResult);
+    NBoth += SemaResult && IndexResult;
+    Incomplete |= Candidates.push({C, Scores});
+  }
+
+  CompletionItem toCompletionItem(const CompletionCandidate &Candidate,
+                                  const CompletionItemScores &Scores) {
+    CodeCompletionString *SemaCCS = nullptr;
+    if (auto *SR = Candidate.SemaResult)
+      SemaCCS = Recorder.codeCompletionString(*SR, Opts.IncludeBriefComments);
+    return Candidate.build(Scores, Opts, SemaCCS);
+  }
+};
+
 CompletionList codeComplete(const Context &Ctx, PathRef FileName,
                             const tooling::CompileCommand &Command,
                             PrecompiledPreamble const *Preamble,
@@ -661,21 +892,8 @@ CompletionList codeComplete(const Contex
                             IntrusiveRefCntPtr<vfs::FileSystem> VFS,
                             std::shared_ptr<PCHContainerOperations> PCHs,
                             CodeCompleteOptions Opts) {
-  CompletionList Results;
-  NameToComplete CompletedName;
-  auto Consumer =
-      llvm::make_unique<CompletionItemsCollector>(Opts, Results, CompletedName);
-  invokeCodeComplete(Ctx, std::move(Consumer), Opts.getClangCompleteOpts(),
-                     FileName, Command, Preamble, Contents, Pos, std::move(VFS),
-                     std::move(PCHs));
-
-  // Got scope specifier (ns::f^) for code completion from sema, try to query
-  // global symbols from indexes.
-  // FIXME: merge with Sema results, and respect limits.
-  if (CompletedName.SSInfo && Opts.Index)
-    completeWithIndex(Ctx, *Opts.Index, Contents, *CompletedName.SSInfo,
-                      CompletedName.Filter, &Results, /*DebuggingLabel=*/"I");
-  return Results;
+  return CodeCompleteFlow(Ctx, Opts).run(
+      {FileName, Command, Preamble, Contents, Pos, VFS, PCHs});
 }
 
 SignatureHelp signatureHelp(const Context &Ctx, PathRef FileName,
@@ -690,10 +908,10 @@ SignatureHelp signatureHelp(const Contex
   Options.IncludeMacros = false;
   Options.IncludeCodePatterns = false;
   Options.IncludeBriefComments = true;
-  invokeCodeComplete(Ctx,
-                     llvm::make_unique<SignatureHelpCollector>(Options, Result),
-                     Options, FileName, Command, Preamble, Contents, Pos,
-                     std::move(VFS), std::move(PCHs));
+  semaCodeComplete(
+      Ctx, llvm::make_unique<SignatureHelpCollector>(Options, Result), Options,
+      {FileName, Command, Preamble, Contents, Pos, std::move(VFS),
+       std::move(PCHs)});
   return Result;
 }
 

Modified: clang-tools-extra/trunk/clangd/FuzzyMatch.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/FuzzyMatch.h?rev=322945&r1=322944&r2=322945&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/FuzzyMatch.h (original)
+++ clang-tools-extra/trunk/clangd/FuzzyMatch.h Fri Jan 19 06:34:02 2018
@@ -35,7 +35,8 @@ public:
   // Characters beyond MaxWord are ignored.
   llvm::Optional<float> match(llvm::StringRef Word);
 
-  bool empty() { return PatN == 0; }
+  llvm::StringRef pattern() const { return llvm::StringRef(Pat, PatN); }
+  bool empty() const { return PatN == 0; }
 
   // Dump internal state from the last match() to the stream, for debugging.
   // Returns the pattern with [] around matched characters, e.g.

Modified: clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp?rev=322945&r1=322944&r2=322945&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp (original)
+++ clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp Fri Jan 19 06:34:02 2018
@@ -17,7 +17,6 @@
 #include "SourceCode.h"
 #include "TestFS.h"
 #include "index/MemIndex.h"
-#include "index/Merge.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
@@ -82,7 +81,7 @@ MATCHER_P(Snippet, Text, "") {
   return arg.insertTextFormat == clangd::InsertTextFormat::Snippet &&
          arg.insertText == Text;
 }
-MATCHER(FilterContainsName, "") {
+MATCHER(NameContainsFilter, "") {
   if (arg.filterText.empty())
     return true;
   return llvm::StringRef(arg.insertText).contains(arg.filterText);
@@ -129,14 +128,27 @@ CompletionList completions(StringRef Tex
           .get()
           .second.Value;
   // Sanity-check that filterText is valid.
-  EXPECT_THAT(CompletionList.items, Each(FilterContainsName()));
+  EXPECT_THAT(CompletionList.items, Each(NameContainsFilter()));
   return CompletionList;
 }
 
+std::string replace(StringRef Haystack, StringRef Needle, StringRef Repl) {
+  std::string Result;
+  raw_string_ostream OS(Result);
+  std::pair<StringRef, StringRef> Split;
+  for (Split = Haystack.split(Needle); !Split.second.empty();
+       Split = Split.first.split(Needle))
+    OS << Split.first << Repl;
+  Result += Split.first;
+  OS.flush();
+  return Result;
+}
+
 // Helpers to produce fake index symbols for memIndex() or completions().
-Symbol sym(StringRef QName, index::SymbolKind Kind) {
+// USRFormat is a regex replacement string for the unqualified part of the USR.
+Symbol sym(StringRef QName, index::SymbolKind Kind, StringRef USRFormat) {
   Symbol Sym;
-  Sym.ID = SymbolID(QName);
+  std::string USR = "c:"; // We synthesize a few simple cases of USRs by hand!
   size_t Pos = QName.rfind("::");
   if (Pos == llvm::StringRef::npos) {
     Sym.Name = QName;
@@ -144,15 +156,25 @@ Symbol sym(StringRef QName, index::Symbo
   } else {
     Sym.Name = QName.substr(Pos + 2);
     Sym.Scope = QName.substr(0, Pos);
+    USR += "@N@" + replace(Sym.Scope, "::", "@N@"); // ns:: -> @N at ns
   }
+  USR += Regex("^.*$").sub(USRFormat, Sym.Name); // e.g. func -> @F at func#
+  Sym.ID = SymbolID(USR);
   Sym.CompletionPlainInsertText = Sym.Name;
+  Sym.CompletionSnippetInsertText = Sym.Name;
   Sym.CompletionLabel = Sym.Name;
   Sym.SymInfo.Kind = Kind;
   return Sym;
 }
-Symbol func(StringRef Name) { return sym(Name, index::SymbolKind::Function); }
-Symbol cls(StringRef Name) { return sym(Name, index::SymbolKind::Class); }
-Symbol var(StringRef Name) { return sym(Name, index::SymbolKind::Variable); }
+Symbol func(StringRef Name) { // Assumes the function has no args.
+  return sym(Name, index::SymbolKind::Function, "@F@\\0#"); // no args
+}
+Symbol cls(StringRef Name) {
+  return sym(Name, index::SymbolKind::Class, "@S@\\0 at S@\\0");
+}
+Symbol var(StringRef Name) {
+  return sym(Name, index::SymbolKind::Variable, "@\\0");
+}
 
 TEST(CompletionTest, Limit) {
   clangd::CodeCompleteOptions Opts;
@@ -226,7 +248,7 @@ void TestAfterDotCompletion(clangd::Code
         ClassWithMembers().^
       }
       )cpp",
-      /*IndexSymbols=*/{}, Opts);
+      {cls("IndexClass"), var("index_var"), func("index_func")}, Opts);
 
   // Class members. The only items that must be present in after-dot
   // completion.
@@ -236,9 +258,11 @@ void TestAfterDotCompletion(clangd::Code
   EXPECT_IFF(Opts.IncludeIneligibleResults, Results.items,
              Has("private_field"));
   // Global items.
-  EXPECT_THAT(Results.items, Not(AnyOf(Has("global_var"), Has("global_func"),
-                                       Has("global_func()"), Has("GlobalClass"),
-                                       Has("MACRO"), Has("LocalClass"))));
+  EXPECT_THAT(
+      Results.items,
+      Not(AnyOf(Has("global_var"), Has("index_var"), Has("global_func"),
+                Has("global_func()"), Has("index_func"), Has("GlobalClass"),
+                Has("IndexClass"), Has("MACRO"), Has("LocalClass"))));
   // There should be no code patterns (aka snippets) in after-dot
   // completion. At least there aren't any we're aware of.
   EXPECT_THAT(Results.items, Not(Contains(Kind(CompletionItemKind::Snippet))));
@@ -271,16 +295,17 @@ void TestGlobalScopeCompletion(clangd::C
         ^
       }
       )cpp",
-      /*IndexSymbols=*/{}, Opts);
+      {cls("IndexClass"), var("index_var"), func("index_func")}, Opts);
 
   // Class members. Should never be present in global completions.
   EXPECT_THAT(Results.items,
               Not(AnyOf(Has("method"), Has("method()"), Has("field"))));
   // Global items.
   EXPECT_THAT(Results.items,
-              AllOf(Has("global_var"),
+              AllOf(Has("global_var"), Has("index_var"),
                     Has(Opts.EnableSnippets ? "global_func()" : "global_func"),
-                    Has("GlobalClass")));
+                    Has("index_func" /* our fake symbol doesn't include () */),
+                    Has("GlobalClass"), Has("IndexClass")));
   // A macro.
   EXPECT_IFF(Opts.IncludeMacros, Results.items, Has("MACRO"));
   // Local items. Must be present always.
@@ -399,83 +424,67 @@ TEST(CompletionTest, Snippets) {
 }
 
 TEST(CompletionTest, Kinds) {
-  auto Results = completions(R"cpp(
-      #define MACRO X
-      int variable;
-      struct Struct {};
-      int function();
-      int X = ^
-  )cpp");
-  EXPECT_THAT(Results.items, Has("function", CompletionItemKind::Function));
-  EXPECT_THAT(Results.items, Has("variable", CompletionItemKind::Variable));
-  EXPECT_THAT(Results.items, Has("int", CompletionItemKind::Keyword));
-  EXPECT_THAT(Results.items, Has("Struct", CompletionItemKind::Class));
-  EXPECT_THAT(Results.items, Has("MACRO", CompletionItemKind::Text));
+  auto Results = completions(
+      R"cpp(
+          #define MACRO X
+          int variable;
+          struct Struct {};
+          int function();
+          int X = ^
+      )cpp",
+      {func("indexFunction"), var("indexVariable"), cls("indexClass")});
+  EXPECT_THAT(Results.items,
+              AllOf(Has("function", CompletionItemKind::Function),
+                    Has("variable", CompletionItemKind::Variable),
+                    Has("int", CompletionItemKind::Keyword),
+                    Has("Struct", CompletionItemKind::Class),
+                    Has("MACRO", CompletionItemKind::Text),
+                    Has("indexFunction", CompletionItemKind::Function),
+                    Has("indexVariable", CompletionItemKind::Variable),
+                    Has("indexClass", CompletionItemKind::Class)));
 
   Results = completions("nam^");
   EXPECT_THAT(Results.items, Has("namespace", CompletionItemKind::Snippet));
 }
 
 TEST(CompletionTest, NoDuplicates) {
-  auto Items = completions(R"cpp(
-struct Adapter {
-  void method();
-};
-
-void Adapter::method() {
-  Adapter^
-}
-  )cpp")
-                   .items;
+  auto Results = completions(
+      R"cpp(
+          class Adapter {
+            void method();
+          };
+
+          void Adapter::method() {
+            Adapter^
+          }
+      )cpp",
+      {cls("Adapter")});
 
   // Make sure there are no duplicate entries of 'Adapter'.
-  EXPECT_THAT(Items, ElementsAre(Named("Adapter"), Named("~Adapter")));
-}
-
-TEST(CompletionTest, FuzzyRanking) {
-  auto Items = completions(R"cpp(
-      struct fake { int BigBang, Babble, Ball; };
-      int main() { fake().bb^ }")cpp").items;
-  // BigBang is a better match than Babble. Ball doesn't match at all.
-  EXPECT_THAT(Items, ElementsAre(Named("BigBang"), Named("Babble")));
-}
-
-TEST(CompletionTest, NoIndex) {
-  auto Results = completions(R"cpp(
-      namespace ns { class Local {}; }
-      void f() { ns::^ }
-  )cpp");
-  EXPECT_THAT(Results.items, Has("Local"));
+  EXPECT_THAT(Results.items, ElementsAre(Named("Adapter"), Named("~Adapter")));
 }
 
-TEST(CompletionTest, StaticAndDynamicIndex) {
-  clangd::CodeCompleteOptions Opts;
-  auto StaticIdx = memIndex({cls("ns::XYZ")});
-  auto DynamicIdx = memIndex({func("ns::foo")});
-  auto Merge = mergeIndex(DynamicIdx.get(), StaticIdx.get());
-  Opts.Index = Merge.get();
-
+TEST(CompletionTest, ScopedNoIndex) {
   auto Results = completions(
       R"cpp(
-          void f() { ::ns::^ }
-      )cpp",
-      /*IndexSymbols=*/{}, Opts);
-  EXPECT_THAT(Results.items, Contains(Labeled("[I]XYZ")));
-  EXPECT_THAT(Results.items, Contains(Labeled("[I]foo")));
+          namespace fake { int BigBang, Babble, Ball; };
+          int main() { fake::bb^ }
+      ")cpp");
+  // BigBang is a better match than Babble. Ball doesn't match at all.
+  EXPECT_THAT(Results.items, ElementsAre(Named("BigBang"), Named("Babble")));
 }
 
-TEST(CompletionTest, IndexScope) {
+TEST(CompletionTest, Scoped) {
   auto Results = completions(
       R"cpp(
-          namespace ns { int local; }
-          void f() { ns::^ }
-      )cpp",
-      {cls("ns::XYZ"), cls("nx::XYZ"), func("ns::foo")});
-  EXPECT_THAT(Results.items,
-              UnorderedElementsAre(Named("XYZ"), Named("foo"), Named("local")));
+          namespace fake { int Babble, Ball; };
+          int main() { fake::bb^ }
+      ")cpp",
+      {var("fake::BigBang")});
+  EXPECT_THAT(Results.items, ElementsAre(Named("BigBang"), Named("Babble")));
 }
 
-TEST(CompletionTest, IndexBasedWithFilter) {
+TEST(CompletionTest, ScopedWithFilter) {
   auto Results = completions(
       R"cpp(
           void f() { ns::x^ }
@@ -485,7 +494,7 @@ TEST(CompletionTest, IndexBasedWithFilte
               UnorderedElementsAre(AllOf(Named("XYZ"), Filter("XYZ"))));
 }
 
-TEST(CompletionTest, IndexGlobalQualified) {
+TEST(CompletionTest, GlobalQualified) {
   auto Results = completions(
       R"cpp(
           void f() { ::^ }
@@ -495,13 +504,28 @@ TEST(CompletionTest, IndexGlobalQualifie
                                    Has("f", CompletionItemKind::Function)));
 }
 
-TEST(CompletionTest, IndexFullyQualifiedScope) {
+TEST(CompletionTest, FullyQualified) {
   auto Results = completions(
       R"cpp(
+          namespace ns { void bar(); }
           void f() { ::ns::^ }
       )cpp",
       {cls("ns::XYZ")});
-  EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class));
+  EXPECT_THAT(Results.items, AllOf(Has("XYZ", CompletionItemKind::Class),
+                                   Has("bar", CompletionItemKind::Function)));
+}
+
+TEST(CompletionTest, SemaIndexMerge) {
+  auto Results = completions(
+      R"cpp(
+          namespace ns { int local; void both(); }
+          void f() { ::ns::^ }
+      )cpp",
+      {func("ns::both"), cls("ns::Index")});
+  // We get results from both index and sema, with no duplicates.
+  EXPECT_THAT(
+      Results.items,
+      UnorderedElementsAre(Named("local"), Named("Index"), Named("both")));
 }
 
 TEST(CompletionTest, IndexSuppressesPreambleCompletions) {




More information about the cfe-commits mailing list