Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -1366,11 +1366,45 @@ auto Top = mergeResults(Recorder->Results, IndexResults); CodeCompleteResult Output; + // Keys are indices into Output vector. + llvm::DenseMap OutputIndex; + LookupRequest DocIndexRequest; // Convert the results to final form, assembling the expensive strings. - for (auto &C : Top) { - Output.Completions.push_back(toCodeCompletion(C.first)); - Output.Completions.back().Score = C.second; + for (size_t I = 0; I < Top.size(); ++I) { + const auto& Bundle = Top[I].first; + const auto& Scope = Top[I].second; + Output.Completions.push_back(toCodeCompletion(Bundle)); + Output.Completions.back().Score = Scope; Output.Completions.back().CompletionTokenRange = TextEditRange; + + if (Opts.IncludeComments && + Output.Completions.back().Documentation.empty()) { + if (Bundle.size() != 1) + continue; + const auto *SemaR = Bundle.front().SemaResult; + if (!SemaR) + continue; + if (auto ID = + getSymbolID(*SemaR, Recorder->CCSema->getSourceManager())) { + OutputIndex[I] = *ID; + DocIndexRequest.IDs.insert(*ID); + } + } + } + // Sema doesn't load docs from the preamble, so we get the docs from the + // index and assemble them for the final results. + if (!DocIndexRequest.IDs.empty() && Opts.Index) { + llvm::DenseMap FetchedDocs; + Opts.Index->lookup(DocIndexRequest, [&](const Symbol &S) { + if (!S.Documentation.empty()) + FetchedDocs[S.ID] = S.Documentation; + }); + for (auto IndexAndID : OutputIndex) { + auto FetchDocsIt = FetchedDocs.find(IndexAndID.second); + if (FetchDocsIt != FetchedDocs.end()) + Output.Completions[IndexAndID.first].Documentation = + FetchDocsIt->second; + } } Output.HasMore = Incomplete; Output.Context = Recorder->CCContext.getKind(); Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -729,6 +729,64 @@ Doc("Doooc"), ReturnType("void")))); } +TEST(CompletionTest, DocumentationFromIndex) { + MockFSProvider FS; + MockCompilationDatabase CDB; + IgnoreDiagnostics DiagConsumer; + FS.Files[testPath("foo.h")] = R"cpp( + class Foo { + public: + // Doc for foo + int foo(); + + // Doc for foo int + int foo2(int); + // Doc for foo bool + int foo2(bool); + }; + )cpp"; + + auto File = testPath("bar.cpp"); + Annotations Test(R"cpp( + #include "foo.h" + void test() { + Foo f; + f.^ + } + )cpp"); + auto Opts = ClangdServer::optsForTest(); + { + // Run code completion without index, verify that we don't get any docs from + // Sema. + Opts.BuildDynamicSymbolIndex = false; + ClangdServer Server(CDB, FS, DiagConsumer, Opts); + runAddDocument(Server, File, Test.code()); + auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {})); + EXPECT_THAT( + Results.Completions, + Contains((Named("foo"), Kind(CompletionItemKind::Method), Doc("")))); + EXPECT_THAT( + Results.Completions, + Contains((Named("foo2"), Kind(CompletionItemKind::Method), Doc("")))); + } + { + Opts.BuildDynamicSymbolIndex = true; + ClangdServer Server(CDB, FS, DiagConsumer, Opts); + runAddDocument(Server, File, Test.code()); + clangd::CodeCompleteOptions CCOpts; + CCOpts.BundleOverloads = true; + auto Results = + cantFail(runCodeComplete(Server, File, Test.point(), CCOpts)); + EXPECT_THAT(Results.Completions, + Contains((Named("foo"), Kind(CompletionItemKind::Method), + Doc("Doc for foo")))); + // No doc for overload bundle. + EXPECT_THAT( + Results.Completions, + Contains((Named("foo2"), Kind(CompletionItemKind::Method), Doc("")))); + } +} + TEST(CompletionTest, Documentation) { auto Results = completions( R"cpp(