[clang-tools-extra] [clangd] Let DefineOutline tweak handle member functions (PR #95235)
Christian Kandeler via cfe-commits
cfe-commits at lists.llvm.org
Wed Jun 12 05:25:21 PDT 2024
https://github.com/ckandeler created https://github.com/llvm/llvm-project/pull/95235
... of class templates.
>From fc3a907ab2550a999801d37400268fdc31df054d Mon Sep 17 00:00:00 2001
From: Christian Kandeler <christian.kandeler at qt.io>
Date: Tue, 14 Nov 2023 16:35:46 +0100
Subject: [PATCH] [clangd] Let DefineOutline tweak handle member functions
... of class templates.
---
clang-tools-extra/clangd/AST.cpp | 7 ++-
.../clangd/refactor/tweaks/DefineOutline.cpp | 47 ++++++++++++++--
.../unittests/tweaks/DefineOutlineTests.cpp | 55 ++++++++++++++-----
3 files changed, 89 insertions(+), 20 deletions(-)
diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index fda1e5fdf8d82..2f2f8437d6ed9 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -144,8 +144,13 @@ getQualification(ASTContext &Context, const DeclContext *DestContext,
// since we stored inner-most parent first.
std::string Result;
llvm::raw_string_ostream OS(Result);
- for (const auto *Parent : llvm::reverse(Parents))
+ for (const auto *Parent : llvm::reverse(Parents)) {
+ if (Parent != *Parents.rbegin() && Parent->isDependent() &&
+ Parent->getAsRecordDecl() &&
+ Parent->getAsRecordDecl()->getDescribedClassTemplate())
+ OS << "template ";
Parent->print(OS, Context.getPrintingPolicy());
+ }
return OS.str();
}
diff --git a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
index f43f2417df8fc..15cb586e6c21e 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
@@ -128,7 +128,27 @@ getFunctionSourceAfterReplacements(const FunctionDecl *FD,
SM.getBufferData(SM.getMainFileID()), Replacements);
if (!QualifiedFunc)
return QualifiedFunc.takeError();
- return QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
+
+ std::string TemplatePrefix;
+ if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(FD)) {
+ for (const CXXRecordDecl *Parent = MD->getParent(); Parent;
+ Parent =
+ llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {
+ if (auto Params = Parent->getDescribedTemplateParams()) {
+ std::string S;
+ llvm::raw_string_ostream Stream(S);
+ Params->print(Stream, FD->getASTContext());
+ if (!S.empty())
+ *S.rbegin() = '\n'; // Replace space with newline
+ TemplatePrefix.insert(0, S);
+ }
+ }
+ }
+
+ auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
+ if (!TemplatePrefix.empty())
+ Source.insert(0, TemplatePrefix);
+ return Source;
}
// Returns replacements to delete tokens with kind `Kind` in the range
@@ -212,9 +232,13 @@ getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext,
}
}
const NamedDecl *ND = Ref.Targets.front();
- const std::string Qualifier =
+ std::string Qualifier =
getQualification(AST, TargetContext,
SM.getLocForStartOfFile(SM.getMainFileID()), ND);
+ if (ND->getDeclContext()->isDependentContext()) {
+ if (llvm::isa<TypeDecl>(ND))
+ Qualifier.insert(0, "typename ");
+ }
if (auto Err = DeclarationCleanups.add(
tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))
Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
@@ -407,10 +431,21 @@ class DefineOutline : public Tweak {
return !SameFile;
}
- // Bail out in templated classes, as it is hard to spell the class name,
- // i.e if the template parameter is unnamed.
- if (MD->getParent()->isTemplated())
- return false;
+ for (const CXXRecordDecl *Parent = MD->getParent(); Parent;
+ Parent =
+ llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {
+ if (auto Params = Parent->getDescribedTemplateParams()) {
+
+ // Class template member functions must be defined in the
+ // same file.
+ SameFile = true;
+
+ for (NamedDecl *P : *Params) {
+ if (!P->getIdentifier())
+ return false;
+ }
+ }
+ }
// The refactoring is meaningless for unnamed classes and namespaces,
// unless we're outlining in the same file
diff --git a/clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp
index 906ff33db8734..6a9e90c3bfa70 100644
--- a/clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp
+++ b/clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp
@@ -105,8 +105,8 @@ TEST_F(DefineOutlineTest, TriggersOnFunctionDecl) {
F^oo(const Foo&) = delete;
};)cpp");
- // Not available within templated classes, as it is hard to spell class name
- // out-of-line in such cases.
+ // Not available within templated classes with unnamed parameters, as it is
+ // hard to spell class name out-of-line in such cases.
EXPECT_UNAVAILABLE(R"cpp(
template <typename> struct Foo { void fo^o(){} };
)cpp");
@@ -154,7 +154,6 @@ TEST_F(DefineOutlineTest, FailsWithoutSource) {
}
TEST_F(DefineOutlineTest, ApplyTest) {
- llvm::StringMap<std::string> EditedFiles;
ExtraFiles["Test.cpp"] = "";
FileName = "Test.hpp";
@@ -229,17 +228,18 @@ TEST_F(DefineOutlineTest, ApplyTest) {
// Ctor initializer with attribute.
{
R"cpp(
- class Foo {
- F^oo(int z) __attribute__((weak)) : bar(2){}
+ template <typename T> class Foo {
+ F^oo(T z) __attribute__((weak)) : bar(2){}
int bar;
};)cpp",
R"cpp(
- class Foo {
- Foo(int z) __attribute__((weak)) ;
+ template <typename T> class Foo {
+ Foo(T z) __attribute__((weak)) ;
int bar;
- };)cpp",
- "Foo::Foo(int z) __attribute__((weak)) : bar(2){}\n",
- },
+ };template <typename T>
+Foo<T>::Foo(T z) __attribute__((weak)) : bar(2){}
+)cpp",
+ ""},
// Virt specifiers.
{
R"cpp(
@@ -369,7 +369,31 @@ TEST_F(DefineOutlineTest, ApplyTest) {
};)cpp",
" void A::foo(int) {}\n",
},
- // Destrctors
+ // Complex class template
+ {
+ R"cpp(
+ template <typename T, typename ...U> struct O1 {
+ template <class V, int A> struct O2 {
+ enum E { E1, E2 };
+ struct I {
+ E f^oo(T, U..., V, E) { return E1; }
+ };
+ };
+ };)cpp",
+ R"cpp(
+ template <typename T, typename ...U> struct O1 {
+ template <class V, int A> struct O2 {
+ enum E { E1, E2 };
+ struct I {
+ E foo(T, U..., V, E) ;
+ };
+ };
+ };template <typename T, typename ...U>
+template <class V, int A>
+typename O1<T, U...>::template O2<V, A>::E O1<T, U...>::template O2<V, A>::I::foo(T, U..., V, E) { return E1; }
+)cpp",
+ ""},
+ // Destructors
{
"class A { ~A^(){} };",
"class A { ~A(); };",
@@ -378,9 +402,14 @@ TEST_F(DefineOutlineTest, ApplyTest) {
};
for (const auto &Case : Cases) {
SCOPED_TRACE(Case.Test);
+ llvm::StringMap<std::string> EditedFiles;
EXPECT_EQ(apply(Case.Test, &EditedFiles), Case.ExpectedHeader);
- EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
- testPath("Test.cpp"), Case.ExpectedSource)));
+ if (Case.ExpectedSource.empty()) {
+ EXPECT_TRUE(EditedFiles.empty());
+ } else {
+ EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
+ testPath("Test.cpp"), Case.ExpectedSource)));
+ }
}
}
More information about the cfe-commits
mailing list