Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -220,6 +220,30 @@ return HeaderFile{std::move(*Resolved), /*Verbatim=*/false}; } +size_t overloadToken(const CodeCompletionResult &R) { + if (!R.Declaration || !R.Declaration->isFunctionOrFunctionTemplate()) + return 0; + // XXX avoid string copies. + if (R.Declaration->isCXXClassMember()) + return hash_combine('M', + StringRef(R.Declaration->getDeclName().getAsString())); + return hash_combine('F', + StringRef(R.Declaration->getQualifiedNameAsString())); +} + +size_t overloadToken(const Symbol &S) { + switch (S.SymInfo.Kind) { + case index::SymbolKind::ClassMethod: + case index::SymbolKind::InstanceMethod: + case index::SymbolKind::StaticMethod: + return hash_combine('M', S.Name); + case index::SymbolKind::Function: + return hash_combine('F', StringRef((S.Scope + S.Name).str())); + default: + return 0; + } +} + /// A code completion result, in clang-native form. /// It may be promoted to a CompletionItem if it's among the top-ranked results. struct CompletionCandidate { @@ -228,12 +252,24 @@ const CodeCompletionResult *SemaResult = nullptr; const Symbol *IndexResult = nullptr; + // Returns a token identifying the overload set this is part of. + // 0 indicates it's not part of any overload set. + size_t overload() const { + if (SemaResult && IndexResult) + assert(overloadToken(*SemaResult) == overloadToken(*IndexResult)); + if (IndexResult) + return overloadToken(*IndexResult); + if (SemaResult) + return overloadToken(*SemaResult); + return 0; + } + // Builds an LSP completion item. CompletionItem build(StringRef FileName, const CompletionItemScores &Scores, const CodeCompleteOptions &Opts, CodeCompletionString *SemaCCS, const IncludeInserter *Includes, - llvm::StringRef SemaDocComment) const { + llvm::StringRef SemaDocComment, unsigned Bundle) const { assert(bool(SemaResult) == bool(SemaCCS)); CompletionItem I; bool ShouldInsertInclude = true; @@ -309,10 +345,27 @@ I.sortText = sortText(Scores.finalScore, Name); I.insertTextFormat = Opts.EnableSnippets ? InsertTextFormat::Snippet : InsertTextFormat::PlainText; + if (Bundle) { + I.detail = "[overloaded]"; + I.insertText = Opts.EnableSnippets ? (Name + "(${0})").str() : Name.str(); + I.label = (Name + "(...)").str(); + } return I; } }; -using ScoredCandidate = std::pair; +struct CandidateBundle { + SmallVector Candidates; + // Builds an LSP completion item. + CompletionItem build(StringRef FileName, const CompletionItemScores &Scores, + const CodeCompleteOptions &Opts, + CodeCompletionString *SemaCCS, + const IncludeInserter *Includes, + llvm::StringRef SemaDocComment) const { + return Candidates.front().build(FileName, Scores, Opts, SemaCCS, Includes, + SemaDocComment, Candidates.size() > 1); + } +}; +using ScoredBundle = std::pair; // Determine the symbol ID for a Sema code completion result, if possible. llvm::Optional getSymbolID(const CodeCompletionResult &R) { @@ -589,10 +642,10 @@ }; struct ScoredCandidateGreater { - bool operator()(const ScoredCandidate &L, const ScoredCandidate &R) { + bool operator()(const ScoredBundle &L, const ScoredBundle &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. + return L.first.Candidates.front().Name < R.first.Candidates.front().Name; // Earlier name is better. } }; @@ -984,13 +1037,30 @@ // Merges the Sema and Index results where possible, scores them, and // returns the top results from best to worst. - std::vector> + std::vector> mergeResults(const std::vector &SemaResults, const SymbolSlab &IndexResults) { trace::Span Tracer("Merge and score results"); - // We only keep the best N results at any time, in "native" format. - TopN Top( - Opts.Limit == 0 ? std::numeric_limits::max() : Opts.Limit); + // Candidates are grouped into overload bundles. + std::vector Bundles; + llvm::DenseMap BundleLookup; + auto AddToBundles = [&](const CodeCompletionResult *SemaResult, + const Symbol *IndexResult) { + CompletionCandidate C; + C.SemaResult = SemaResult; + C.IndexResult = IndexResult; + C.Name = IndexResult ? IndexResult->Name : Recorder->getName(*SemaResult); + auto Overload = C.overload(); + if (Overload) { + auto Ret = BundleLookup.try_emplace(C.overload(), Bundles.size()); + if (Ret.second) + Bundles.emplace_back(); + Bundles[Ret.first->second].Candidates.push_back(std::move(C)); + } else { + Bundles.emplace_back(); + Bundles.back().Candidates.push_back(std::move(C)); + } + }; llvm::DenseSet UsedIndexResults; auto CorrespondingIndexResult = [&](const CodeCompletionResult &SemaResult) -> const Symbol * { @@ -1005,13 +1075,18 @@ }; // Emit all Sema results, merging them with Index results if possible. for (auto &SemaResult : Recorder->Results) - addCandidate(Top, &SemaResult, CorrespondingIndexResult(SemaResult)); + AddToBundles(&SemaResult, CorrespondingIndexResult(SemaResult)); // Now emit any Index-only results. for (const auto &IndexResult : IndexResults) { if (UsedIndexResults.count(&IndexResult)) continue; - addCandidate(Top, /*SemaResult=*/nullptr, &IndexResult); + AddToBundles(/*SemaResult=*/nullptr, &IndexResult); } + // We only keep the best N results at any time, in "native" format. + TopN Top( + Opts.Limit == 0 ? std::numeric_limits::max() : Opts.Limit); + for (auto &Bundle : Bundles) + addCandidate(Top, std::move(Bundle)); return std::move(Top).items(); } @@ -1025,28 +1100,28 @@ } // 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); - + void addCandidate(TopN &Candidates, + CandidateBundle Bundle) { SymbolQualitySignals Quality; SymbolRelevanceSignals Relevance; Relevance.Query = SymbolRelevanceSignals::CodeComplete; - if (auto FuzzyScore = fuzzyScore(C)) + auto First = Bundle.Candidates.front(); + if (auto FuzzyScore = fuzzyScore(First)) Relevance.NameMatch = *FuzzyScore; else return; - if (IndexResult) { - Quality.merge(*IndexResult); - Relevance.merge(*IndexResult); - } - if (SemaResult) { - Quality.merge(*SemaResult); - Relevance.merge(*SemaResult); + unsigned SemaResult = 0, IndexResult = 0; + for (const auto &Candidate : Bundle.Candidates) { + if (Candidate.IndexResult) { + Quality.merge(*Candidate.IndexResult); + Relevance.merge(*Candidate.IndexResult); + ++IndexResult; + } + if (Candidate.SemaResult) { + Quality.merge(*Candidate.SemaResult); + Relevance.merge(*Candidate.SemaResult); + ++SemaResult; + } } float QualScore = Quality.evaluate(), RelScore = Relevance.evaluate(); @@ -1059,24 +1134,24 @@ Scores.symbolScore = Scores.filterScore ? Scores.finalScore / Scores.filterScore : QualScore; - LLVM_DEBUG(llvm::dbgs() - << "CodeComplete: " << C.Name << (IndexResult ? " (index)" : "") - << (SemaResult ? " (sema)" : "") << " = " << Scores.finalScore - << "\n" - << Quality << Relevance << "\n"); + LLVM_DEBUG(llvm::dbgs() << "CodeComplete: " << First.Name << "(" + << IndexResult << " index) " + << "(" << SemaResult << " sema)" + << " = " << Scores.finalScore << "\n" + << Quality << Relevance << "\n"); NSema += bool(SemaResult); NIndex += bool(IndexResult); NBoth += SemaResult && IndexResult; - if (Candidates.push({C, Scores})) + if (Candidates.push({std::move(Bundle), Scores})) Incomplete = true; } - CompletionItem toCompletionItem(const CompletionCandidate &Candidate, + CompletionItem toCompletionItem(const CandidateBundle &Candidate, const CompletionItemScores &Scores) { CodeCompletionString *SemaCCS = nullptr; std::string DocComment; - if (auto *SR = Candidate.SemaResult) { + if (auto *SR = Candidate.Candidates.front().SemaResult) { SemaCCS = Recorder->codeCompletionString(*SR); if (Opts.IncludeComments) { assert(Recorder->CCSema);