-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Fixes clangd/clangd#1797 #158461
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Fixes clangd/clangd#1797 #158461
Conversation
Do not restrict HeaderSourceSwitch to switching between .h and .cpp. For templates, the relevant 'source' is the header containing the implementation of the templated functions. Instead of considering every symbol in the current file as relevant, we restrict it to those that either have the declaration or definition in that file.
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be notified. If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers. If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
@llvm/pr-subscribers-clang-tools-extra @llvm/pr-subscribers-clangd Author: None (JVApen) ChangesDo not restrict HeaderSourceSwitch to switching between .h and .cpp. For templates, the relevant 'source' is the header containing the implementation of the templated functions. Instead of considering every symbol in the current file as relevant, we restrict it to those that either have the declaration or definition in that file. Full diff: https://github.com/llvm/llvm-project/pull/158461.diff 2 Files Affected:
diff --git a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
index ee4bea1401490..494760d6ffdd3 100644
--- a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
+++ b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
@@ -82,26 +82,36 @@ std::optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
Request.IDs.insert(ID);
}
llvm::StringMap<int> Candidates; // Target path => score.
- auto AwardTarget = [&](const char *TargetURI) {
- if (auto TargetPath = URI::resolve(TargetURI, OriginalFile)) {
- if (!pathEqual(*TargetPath, OriginalFile)) // exclude the original file.
- ++Candidates[*TargetPath];
- } else {
- elog("Failed to resolve URI {0}: {1}", TargetURI, TargetPath.takeError());
- }
- };
- // If we switch from a header, we are looking for the implementation
- // file, so we use the definition loc; otherwise we look for the header file,
- // we use the decl loc;
+ // When in the implementation file, we always search for the header file,
+ // using the decl loc. When we are in a header, this usually implies searching
+ // for implementation, for which we use the definition loc. For templates, we
+ // can have separate implementation headers, which behave as an implementation
+ // file. As such, we always have to add the decl loc and conditionally
+ // definition loc.
//
// For each symbol in the original file, we get its target location (decl or
// def) from the index, then award that target file.
- const bool IsHeader = isHeaderFile(OriginalFile, AST.getLangOpts());
+#ifdef CLANGD_PATH_CASE_INSENSITIVE
+ auto pathEqual = [](const llvm::StringRef &l, const llvm::StringRef &r) {
+ return l.equals_insensitive(r);
+ };
+#else
+ auto pathEqual = [](const llvm::StringRef &l, const llvm::StringRef &r) {
+ return l.equals(r);
+ };
+#endif
Index->lookup(Request, [&](const Symbol &Sym) {
- if (IsHeader)
- AwardTarget(Sym.Definition.FileURI);
- else
- AwardTarget(Sym.CanonicalDeclaration.FileURI);
+ auto TargetPathDefinition =
+ URI::resolve(Sym.Definition.FileURI, OriginalFile);
+ auto TargetPathDeclaration =
+ URI::resolve(Sym.CanonicalDeclaration.FileURI, OriginalFile);
+ if (!TargetPathDefinition || !TargetPathDeclaration)
+ return;
+ if (pathEqual(*TargetPathDefinition, OriginalFile)) {
+ if (!pathEqual(*TargetPathDeclaration, OriginalFile))
+ ++Candidates[*TargetPathDeclaration];
+ } else if (pathEqual(*TargetPathDeclaration, OriginalFile))
+ ++Candidates[*TargetPathDefinition];
});
// FIXME: our index doesn't have any interesting information (this could be
// that the background-index is not finished), we should use the decl/def
diff --git a/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp b/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp
index e600207de458a..8dd480968fdc6 100644
--- a/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp
@@ -223,6 +223,162 @@ TEST(HeaderSourceSwitchTest, FromHeaderToSource) {
}
}
+TEST(HeaderSourceSwitchTest, FromHeaderToImplHeader) {
+ // build a proper index, which contains symbols:
+ // A_Sym1, declared in TestTU.h, defined in a_ext.h
+ // B_Sym[1-2], declared in TestTU.h, defined in b_ext.h
+ SymbolSlab::Builder AllSymbols;
+ auto addHeader = [&](auto implName, auto declarationContent,
+ auto implContent) {
+ TestTU Testing;
+ Testing.ExtraArgs.push_back(
+ "-xc++-header"); // inform clang this is a header.
+
+ Testing.Filename = implName;
+ Testing.Code = implContent;
+ Testing.HeaderFilename = "TestTU.h";
+ Testing.HeaderCode = declarationContent;
+
+ for (auto &Sym : Testing.headerSymbols())
+ AllSymbols.insert(Sym);
+ };
+
+ addHeader("a_ext.h", "template<typename T> void A_Sym1();",
+ "template<typename T> void A_Sym1() {};");
+ addHeader("b_ext.h", R"cpp(
+ template<typename T> void B_Sym1();
+ template<typename T> void B_Sym2();
+ template<typename T> void B_Sym3_NoDef();
+ )cpp",
+ R"cpp(
+ template<typename T> void B_Sym1() {}
+ template<typename T> void B_Sym2() {}
+ )cpp");
+ auto Index = MemIndex::build(std::move(AllSymbols).build(), {}, {});
+
+ // Test for switch from .h header to .cc source
+ struct {
+ llvm::StringRef HeaderCode;
+ std::optional<std::string> ExpectedSource;
+ } TestCases[] = {
+ {"// empty, no header found", std::nullopt},
+ {R"cpp(
+ // no definition found in the index.
+ template<typename T> void NonDefinition();
+ )cpp",
+ std::nullopt},
+ {R"cpp(
+ template<typename T> void A_Sym1();
+ )cpp",
+ testPath("a_ext.h")},
+ {R"cpp(
+ // b_ext.h wins.
+ template<typename T> void A_Sym1();
+ template<typename T> void B_Sym1();
+ template<typename T> void B_Sym2();
+ )cpp",
+ testPath("b_ext.h")},
+ {R"cpp(
+ // a_ext.h and b_ext.h have same scope, but a_ext.h because "a_ext.h" < "b_ext.h".
+ template<typename T> void A_Sym1();
+ template<typename T> void B_Sym1();
+ )cpp",
+ testPath("a_ext.h")},
+
+ {R"cpp(
+ // We don't have definition in the index, so stay in the header.
+ template<typename T> void B_Sym3_NoDef();
+ )cpp",
+ std::nullopt},
+ };
+ for (const auto &Case : TestCases) {
+ TestTU TU = TestTU::withCode(Case.HeaderCode);
+ TU.Filename = "TestTU.h";
+ TU.ExtraArgs.push_back("-xc++-header"); // inform clang this is a header.
+ auto HeaderAST = TU.build();
+ EXPECT_EQ(Case.ExpectedSource,
+ getCorrespondingHeaderOrSource(testPath(TU.Filename), HeaderAST,
+ Index.get()));
+ }
+}
+
+TEST(HeaderSourceSwitchTest, FromImplHeaderToHeader) {
+ // build a proper index, which contains symbols:
+ // A_Sym1, declared in TestTU.h, defined in a_ext.h
+ // B_Sym[1-2], declared in TestTU.h, defined in b_ext.h
+ SymbolSlab::Builder AllSymbols;
+ auto addHeader = [&](auto declName, auto declContent, auto implContent) {
+ TestTU Testing;
+ Testing.ExtraArgs.push_back(
+ "-xc++-header"); // inform clang this is a header.
+
+ Testing.Filename = "TestTU.h";
+ Testing.Code = implContent;
+ Testing.HeaderFilename = declName;
+ Testing.HeaderCode = declContent;
+
+ for (auto &Sym : Testing.headerSymbols())
+ AllSymbols.insert(Sym);
+ };
+
+ addHeader("a.h", "template<typename T> void A_Sym1();",
+ "template<typename T> void A_Sym1() {};");
+ addHeader("b.h", R"cpp(
+ template<typename T> void B_Sym1();
+ template<typename T> void B_Sym2();
+ template<typename T> void B_Sym3_NoDef();
+ )cpp",
+ R"cpp(
+ template<typename T> void B_Sym1() {}
+ template<typename T> void B_Sym2() {}
+ )cpp");
+ auto Index = MemIndex::build(std::move(AllSymbols).build(), {}, {});
+
+ // Test for switch from .h header to .cc source
+ struct {
+ llvm::StringRef HeaderCode;
+ std::optional<std::string> ExpectedSource;
+ } TestCases[] = {
+ {"// empty, no header found", std::nullopt},
+ {R"cpp(
+ // no definition found in the index.
+ template<typename T> void NonDefinition(){}
+ )cpp",
+ std::nullopt},
+ {R"cpp(
+ template<typename T> void A_Sym1(){}
+ )cpp",
+ testPath("a.h")},
+ {R"cpp(
+ // b.h wins.
+ template<typename T> void A_Sym1(){}
+ template<typename T> void B_Sym1(){}
+ template<typename T> void B_Sym2(){}
+ )cpp",
+ testPath("b.h")},
+ {R"cpp(
+ // a.h and b.h have same scope, but a.h because "a.h" < "b.h".
+ template<typename T> void A_Sym1(){}
+ template<typename T> void B_Sym1(){}
+ )cpp",
+ testPath("a.h")},
+
+ {R"cpp(
+ // We don't have definition in the index, so stay in the header.
+ template<typename T> void B_Sym3_NoDef();
+ )cpp",
+ std::nullopt},
+ };
+ for (const auto &Case : TestCases) {
+ TestTU TU = TestTU::withCode(Case.HeaderCode);
+ TU.Filename = "TestTU.h";
+ TU.ExtraArgs.push_back("-xc++-header"); // inform clang this is a header.
+ auto HeaderAST = TU.build();
+ EXPECT_EQ(Case.ExpectedSource,
+ getCorrespondingHeaderOrSource(testPath(TU.Filename), HeaderAST,
+ Index.get()));
+ }
+}
TEST(HeaderSourceSwitchTest, FromSourceToHeader) {
// build a proper index, which contains symbols:
// A_Sym1, declared in a.h, defined in TestTU.cpp
@@ -281,7 +437,7 @@ TEST(HeaderSourceSwitchTest, FromSourceToHeader) {
};
for (const auto &Case : TestCases) {
TestTU TU = TestTU::withCode(Case.SourceCode);
- TU.Filename = "Test.cpp";
+ TU.Filename = "TestTU.cpp";
auto AST = TU.build();
EXPECT_EQ(Case.ExpectedResult,
getCorrespondingHeaderOrSource(testPath(TU.Filename), AST,
|
Can @kadircet and/or @llvm-beanz be added as reviewer? (https://discord.com/channels/636084430946959380/649134148723802113/1416928414241259670) |
Do not restrict HeaderSourceSwitch to switching between .h and .cpp. For templates, the relevant 'source' is the header containing the implementation of the templated functions.
Instead of considering every symbol in the current file as relevant, we restrict it to those that either have the declaration or definition in that file.