[clang-tools-extra] Fixes clangd/clangd#1797 (PR #158461)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Sep 14 07:27:49 PDT 2025
https://github.com/JVApen updated https://github.com/llvm/llvm-project/pull/158461
>From d828397b1ece13b9617b54e5bd75f00c2c69fd64 Mon Sep 17 00:00:00 2001
From: JVApen <vanantwerpenjeroen at gmail.com>
Date: Sat, 13 Sep 2025 10:22:28 +0200
Subject: [PATCH 1/6] Fixes clangd/clangd#1797
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.
---
.../clangd/HeaderSourceSwitch.cpp | 42 +++--
.../unittests/HeaderSourceSwitchTests.cpp | 158 +++++++++++++++++-
2 files changed, 183 insertions(+), 17 deletions(-)
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,
>From 3ea26896e248ee68798d7df3dc26fab7a4f51d88 Mon Sep 17 00:00:00 2001
From: JVApen <vanantwerpenjeroen at gmail.com>
Date: Sun, 14 Sep 2025 08:57:09 +0200
Subject: [PATCH 2/6] Fix linux build
---
clang-tools-extra/clangd/HeaderSourceSwitch.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
index 494760d6ffdd3..e8a44528d6297 100644
--- a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
+++ b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
@@ -97,7 +97,7 @@ std::optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
};
#else
auto pathEqual = [](const llvm::StringRef &l, const llvm::StringRef &r) {
- return l.equals(r);
+ return l == r;
};
#endif
Index->lookup(Request, [&](const Symbol &Sym) {
>From cce4abeb315e7407d068dff990330133740d57ad Mon Sep 17 00:00:00 2001
From: JVApen <vanantwerpenjeroen at gmail.com>
Date: Sun, 14 Sep 2025 08:58:46 +0200
Subject: [PATCH 3/6] StringRef by value
---
clang-tools-extra/clangd/HeaderSourceSwitch.cpp | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
index e8a44528d6297..9a110b986c6ed 100644
--- a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
+++ b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
@@ -92,13 +92,11 @@ std::optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
// For each symbol in the original file, we get its target location (decl or
// def) from the index, then award that target file.
#ifdef CLANGD_PATH_CASE_INSENSITIVE
- auto pathEqual = [](const llvm::StringRef &l, const llvm::StringRef &r) {
+ auto pathEqual = [](llvm::StringRef l, llvm::StringRef r) {
return l.equals_insensitive(r);
};
#else
- auto pathEqual = [](const llvm::StringRef &l, const llvm::StringRef &r) {
- return l == r;
- };
+ auto pathEqual = [](llvm::StringRef l, llvm::StringRef r) { return l == r; };
#endif
Index->lookup(Request, [&](const Symbol &Sym) {
auto TargetPathDefinition =
>From 4b88198baa91824b56925f578f176b9611a03866 Mon Sep 17 00:00:00 2001
From: JVApen <vanantwerpenjeroen at gmail.com>
Date: Sun, 14 Sep 2025 09:51:42 +0200
Subject: [PATCH 4/6] Ensure class templates are handled correctly
---
.../clangd/HeaderSourceSwitch.cpp | 5 +++
.../unittests/HeaderSourceSwitchTests.cpp | 42 +++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
index 9a110b986c6ed..f8bc425fac7e5 100644
--- a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
+++ b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
@@ -147,6 +147,11 @@ std::vector<const Decl *> getIndexableLocalDecls(ParsedAST &AST) {
for (auto *D : Scope->decls())
TraverseDecl(D);
}
+ // ClassTemplateDecl does not inherit from DeclContext
+ if (auto *Scope = llvm::dyn_cast<ClassTemplateDecl>(ND)) {
+ for (auto *D : Scope->getTemplatedDecl()->decls())
+ TraverseDecl(D);
+ }
}
if (llvm::isa<NamespaceDecl>(D))
return; // namespace is indexable, but we're not interested.
diff --git a/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp b/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp
index 8dd480968fdc6..237a174c4f9c6 100644
--- a/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp
@@ -379,6 +379,48 @@ TEST(HeaderSourceSwitchTest, FromImplHeaderToHeader) {
Index.get()));
}
}
+
+TEST(HeaderSourceSwitchTest, FromHeaderToImplHeaderClassTemplate) {
+ // 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;
+ TestTU Testing;
+ Testing.Filename = "a.cpp";
+ Testing.Code = R"cpp(void f() {}
+ void g(){})cpp";
+ Testing.HeaderFilename = "TestTU.h";
+ Testing.HeaderCode = R"cpp(void f();
+ void g();
+ template<typename T> struct S
+ {
+ void a();
+ void b();
+ void c();
+ };)cpp";
+ for (auto &Sym : Testing.headerSymbols())
+ AllSymbols.insert(Sym);
+
+ Testing.Filename = "b_ext.h";
+ Testing.Code =
+ R"cpp(template<typename T> void S<T>::a(){}
+ template<typename T> void S<T>::b(){}
+ template<typename T> void S<T>::c(){})cpp";
+ Testing.ExtraArgs.push_back("-xc++-header"); // inform clang this is a header.
+
+ for (auto &Sym : Testing.headerSymbols())
+ AllSymbols.insert(Sym);
+ auto Index = MemIndex::build(std::move(AllSymbols).build(), {}, {});
+
+ TestTU TU = TestTU::withCode(Testing.HeaderCode);
+ TU.Filename = "TestTU.h";
+ TU.ExtraArgs.push_back("-xc++-header"); // inform clang this is a header.
+ auto HeaderAST = TU.build();
+ EXPECT_EQ(testPath("b_ext.h"),
+ 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
>From db2f4000183fc565cccd869037885714f7967db0 Mon Sep 17 00:00:00 2001
From: JVApen <vanantwerpenjeroen at gmail.com>
Date: Sun, 14 Sep 2025 09:53:50 +0200
Subject: [PATCH 5/6] Ensure expected is always handled
---
clang-tools-extra/clangd/HeaderSourceSwitch.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
index f8bc425fac7e5..37a41c48e836e 100644
--- a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
+++ b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
@@ -101,9 +101,11 @@ std::optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
Index->lookup(Request, [&](const Symbol &Sym) {
auto TargetPathDefinition =
URI::resolve(Sym.Definition.FileURI, OriginalFile);
+ if (!TargetPathDefinition)
+ return;
auto TargetPathDeclaration =
URI::resolve(Sym.CanonicalDeclaration.FileURI, OriginalFile);
- if (!TargetPathDefinition || !TargetPathDeclaration)
+ if (!TargetPathDeclaration)
return;
if (pathEqual(*TargetPathDefinition, OriginalFile)) {
if (!pathEqual(*TargetPathDeclaration, OriginalFile))
>From 4ca5cf6e7219c19ce216a1b60c964c6c9e6aaebc Mon Sep 17 00:00:00 2001
From: JVApen <vanantwerpenjeroen at gmail.com>
Date: Sun, 14 Sep 2025 16:27:31 +0200
Subject: [PATCH 6/6] Resolve issue unhandled Expected
---
clang-tools-extra/clangd/HeaderSourceSwitch.cpp | 3 +++
1 file changed, 3 insertions(+)
diff --git a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
index 37a41c48e836e..f3477047c2508 100644
--- a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
+++ b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
@@ -99,6 +99,9 @@ std::optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
auto pathEqual = [](llvm::StringRef l, llvm::StringRef r) { return l == r; };
#endif
Index->lookup(Request, [&](const Symbol &Sym) {
+ if (llvm::StringRef{Sym.Definition.FileURI}.empty() ||
+ llvm::StringRef{Sym.CanonicalDeclaration.FileURI}.empty())
+ return;
auto TargetPathDefinition =
URI::resolve(Sym.Definition.FileURI, OriginalFile);
if (!TargetPathDefinition)
More information about the cfe-commits
mailing list