[clangd] Bundle code completion items when the include paths differ, but resolve to the same file.

This can happen when, for example, merging results from an external
index that generates IncludeHeaders with full URI rather than just
literal include.

Differential Revision: https://reviews.llvm.org/D92494
This commit is contained in:
Adam Czachorowski 2020-12-02 18:55:32 +01:00
parent 2ce38b3f03
commit 517828a31b
2 changed files with 41 additions and 5 deletions

View File

@ -173,9 +173,22 @@ struct CompletionCandidate {
// Returns a token identifying the overload set this is part of.
// 0 indicates it's not part of any overload set.
size_t overloadSet(const CodeCompleteOptions &Opts) const {
size_t overloadSet(const CodeCompleteOptions &Opts, llvm::StringRef FileName,
IncludeInserter *Inserter) const {
if (!Opts.BundleOverloads.getValueOr(false))
return 0;
// Depending on the index implementation, we can see different header
// strings (literal or URI) mapping to the same file. We still want to
// bundle those, so we must resolve the header to be included here.
std::string HeaderForHash;
if (Inserter)
if (auto Header = headerToInsertIfAllowed(Opts))
if (auto HeaderFile = toHeaderFile(*Header, FileName))
if (auto Spelled =
Inserter->calculateIncludePath(*HeaderFile, FileName))
HeaderForHash = *Spelled;
llvm::SmallString<256> Scratch;
if (IndexResult) {
switch (IndexResult->SymInfo.Kind) {
@ -192,7 +205,7 @@ struct CompletionCandidate {
// This could break #include insertion.
return llvm::hash_combine(
(IndexResult->Scope + IndexResult->Name).toStringRef(Scratch),
headerToInsertIfAllowed(Opts).getValueOr(""));
HeaderForHash);
default:
return 0;
}
@ -206,8 +219,7 @@ struct CompletionCandidate {
llvm::raw_svector_ostream OS(Scratch);
D->printQualifiedName(OS);
}
return llvm::hash_combine(Scratch,
headerToInsertIfAllowed(Opts).getValueOr(""));
return llvm::hash_combine(Scratch, HeaderForHash);
}
assert(IdentifierResult);
return 0;
@ -1570,7 +1582,8 @@ private:
assert(IdentifierResult);
C.Name = IdentifierResult->Name;
}
if (auto OverloadSet = C.overloadSet(Opts)) {
if (auto OverloadSet = C.overloadSet(
Opts, FileName, Inserter ? Inserter.getPointer() : nullptr)) {
auto Ret = BundleLookup.try_emplace(OverloadSet, Bundles.size());
if (Ret.second)
Bundles.emplace_back();

View File

@ -1628,6 +1628,29 @@ TEST(CompletionTest, OverloadBundling) {
EXPECT_EQ(A.SnippetSuffix, "($0)");
}
TEST(CompletionTest, OverloadBundlingSameFileDifferentURI) {
clangd::CodeCompleteOptions Opts;
Opts.BundleOverloads = true;
Symbol SymX = sym("ns::X", index::SymbolKind::Function, "@F@\\0#");
Symbol SymY = sym("ns::X", index::SymbolKind::Function, "@F@\\0#I#");
std::string BarHeader = testPath("bar.h");
auto BarURI = URI::create(BarHeader).toString();
SymX.CanonicalDeclaration.FileURI = BarURI.c_str();
SymY.CanonicalDeclaration.FileURI = BarURI.c_str();
// The include header is different, but really it's the same file.
SymX.IncludeHeaders.emplace_back("\"bar.h\"", 1);
SymY.IncludeHeaders.emplace_back(BarURI.c_str(), 1);
auto Results = completions("void f() { ::ns::^ }", {SymX, SymY}, Opts);
// Expect both results are bundled, despite the different-but-same
// IncludeHeader.
ASSERT_EQ(1u, Results.Completions.size());
const auto &R = Results.Completions.front();
EXPECT_EQ("X", R.Name);
EXPECT_EQ(2u, R.BundleSize);
}
TEST(CompletionTest, DocumentationFromChangedFileCrash) {
MockFS FS;
auto FooH = testPath("foo.h");