Skip to content

Commit 030d48b

Browse files
[clangd] Augment code completion results with documentation from the index. (#120099)
When looking up code completions from Sema, there is no associated documentation. This is due to crash issues with stale preambles. However, this also means that code completion results from other than the main file do not have documentation in certain cases, which is a bad user experience. This patch performs a lookup into the index using the code completion result declarations to find documentation, and attaches it to the results. Fixes clangd/clangd#2252 Fixes clangd/clangd#564
1 parent 02403f4 commit 030d48b

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

clang-tools-extra/clangd/CodeComplete.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,14 +1867,41 @@ class CodeCompleteFlow {
18671867
CodeCompleteResult Output;
18681868

18691869
// Convert the results to final form, assembling the expensive strings.
1870+
// If necessary, search the index for documentation comments.
1871+
LookupRequest Req;
1872+
llvm::DenseMap<SymbolID, uint32_t> SymbolToCompletion;
18701873
for (auto &C : Scored) {
18711874
Output.Completions.push_back(toCodeCompletion(C.first));
18721875
Output.Completions.back().Score = C.second;
18731876
Output.Completions.back().CompletionTokenRange = ReplacedRange;
1877+
if (Opts.Index && !Output.Completions.back().Documentation) {
1878+
for (auto &Cand : C.first) {
1879+
if (Cand.SemaResult &&
1880+
Cand.SemaResult->Kind == CodeCompletionResult::RK_Declaration) {
1881+
auto ID = clangd::getSymbolID(Cand.SemaResult->getDeclaration());
1882+
if (!ID)
1883+
continue;
1884+
Req.IDs.insert(ID);
1885+
SymbolToCompletion[ID] = Output.Completions.size() - 1;
1886+
}
1887+
}
1888+
}
18741889
}
18751890
Output.HasMore = Incomplete;
18761891
Output.Context = CCContextKind;
18771892
Output.CompletionRange = ReplacedRange;
1893+
1894+
// Look up documentation from the index.
1895+
if (Opts.Index) {
1896+
Opts.Index->lookup(Req, [&](const Symbol &S) {
1897+
if (S.Documentation.empty())
1898+
return;
1899+
auto &C = Output.Completions[SymbolToCompletion.at(S.ID)];
1900+
C.Documentation.emplace();
1901+
parseDocumentation(S.Documentation, *C.Documentation);
1902+
});
1903+
}
1904+
18781905
return Output;
18791906
}
18801907

clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,87 @@ int x = foo^
11361136
Contains(AllOf(named("foo"), doc("This comment should be retained!"))));
11371137
}
11381138

1139+
TEST(CompletionTest, CommentsOnMembersFromHeader) {
1140+
MockFS FS;
1141+
MockCompilationDatabase CDB;
1142+
1143+
auto Opts = ClangdServer::optsForTest();
1144+
Opts.BuildDynamicSymbolIndex = true;
1145+
1146+
ClangdServer Server(CDB, FS, Opts);
1147+
1148+
FS.Files[testPath("foo.h")] = R"cpp(
1149+
struct alpha {
1150+
/// This is a member field.
1151+
int gamma;
1152+
1153+
/// This is a member function.
1154+
int delta();
1155+
};
1156+
)cpp";
1157+
1158+
auto File = testPath("foo.cpp");
1159+
Annotations Test(R"cpp(
1160+
#include "foo.h"
1161+
alpha a;
1162+
int x = a.^
1163+
)cpp");
1164+
runAddDocument(Server, File, Test.code());
1165+
auto CompletionList =
1166+
llvm::cantFail(runCodeComplete(Server, File, Test.point(), {}));
1167+
1168+
EXPECT_THAT(CompletionList.Completions,
1169+
Contains(AllOf(named("gamma"), doc("This is a member field."))));
1170+
EXPECT_THAT(
1171+
CompletionList.Completions,
1172+
Contains(AllOf(named("delta"), doc("This is a member function."))));
1173+
}
1174+
1175+
TEST(CompletionTest, CommentsOnMembersFromHeaderOverloadBundling) {
1176+
using testing::AnyOf;
1177+
MockFS FS;
1178+
MockCompilationDatabase CDB;
1179+
1180+
auto Opts = ClangdServer::optsForTest();
1181+
Opts.BuildDynamicSymbolIndex = true;
1182+
1183+
ClangdServer Server(CDB, FS, Opts);
1184+
1185+
FS.Files[testPath("foo.h")] = R"cpp(
1186+
struct alpha {
1187+
/// bool overload.
1188+
int delta(bool b);
1189+
1190+
/// int overload.
1191+
int delta(int i);
1192+
1193+
void epsilon(long l);
1194+
1195+
/// This one has a comment.
1196+
void epsilon(int i);
1197+
};
1198+
)cpp";
1199+
1200+
auto File = testPath("foo.cpp");
1201+
Annotations Test(R"cpp(
1202+
#include "foo.h"
1203+
alpha a;
1204+
int x = a.^
1205+
)cpp");
1206+
runAddDocument(Server, File, Test.code());
1207+
clangd::CodeCompleteOptions CCOpts;
1208+
CCOpts.BundleOverloads = true;
1209+
auto CompletionList =
1210+
llvm::cantFail(runCodeComplete(Server, File, Test.point(), CCOpts));
1211+
1212+
EXPECT_THAT(
1213+
CompletionList.Completions,
1214+
Contains(AllOf(named("epsilon"), doc("This one has a comment."))));
1215+
EXPECT_THAT(CompletionList.Completions,
1216+
Contains(AllOf(named("delta"), AnyOf(doc("bool overload."),
1217+
doc("int overload.")))));
1218+
}
1219+
11391220
TEST(CompletionTest, GlobalCompletionFiltering) {
11401221

11411222
Symbol Class = cls("XYZ");

0 commit comments

Comments
 (0)