diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -173,9 +173,23 @@ // 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 +206,7 @@ // This could break #include insertion. return llvm::hash_combine( (IndexResult->Scope + IndexResult->Name).toStringRef(Scratch), - headerToInsertIfAllowed(Opts).getValueOr("")); + HeaderForHash); default: return 0; } @@ -206,8 +220,7 @@ 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 +1583,8 @@ 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(); diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -1628,6 +1628,29 @@ 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");