Skip to content

Commit fc3a907

Browse files
committed
[clangd] Let DefineOutline tweak handle member functions
... of class templates.
1 parent 0e346ee commit fc3a907

File tree

3 files changed

+89
-20
lines changed

3 files changed

+89
-20
lines changed

clang-tools-extra/clangd/AST.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,13 @@ getQualification(ASTContext &Context, const DeclContext *DestContext,
144144
// since we stored inner-most parent first.
145145
std::string Result;
146146
llvm::raw_string_ostream OS(Result);
147-
for (const auto *Parent : llvm::reverse(Parents))
147+
for (const auto *Parent : llvm::reverse(Parents)) {
148+
if (Parent != *Parents.rbegin() && Parent->isDependent() &&
149+
Parent->getAsRecordDecl() &&
150+
Parent->getAsRecordDecl()->getDescribedClassTemplate())
151+
OS << "template ";
148152
Parent->print(OS, Context.getPrintingPolicy());
153+
}
149154
return OS.str();
150155
}
151156

clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,27 @@ getFunctionSourceAfterReplacements(const FunctionDecl *FD,
128128
SM.getBufferData(SM.getMainFileID()), Replacements);
129129
if (!QualifiedFunc)
130130
return QualifiedFunc.takeError();
131-
return QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
131+
132+
std::string TemplatePrefix;
133+
if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(FD)) {
134+
for (const CXXRecordDecl *Parent = MD->getParent(); Parent;
135+
Parent =
136+
llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {
137+
if (auto Params = Parent->getDescribedTemplateParams()) {
138+
std::string S;
139+
llvm::raw_string_ostream Stream(S);
140+
Params->print(Stream, FD->getASTContext());
141+
if (!S.empty())
142+
*S.rbegin() = '\n'; // Replace space with newline
143+
TemplatePrefix.insert(0, S);
144+
}
145+
}
146+
}
147+
148+
auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
149+
if (!TemplatePrefix.empty())
150+
Source.insert(0, TemplatePrefix);
151+
return Source;
132152
}
133153

134154
// Returns replacements to delete tokens with kind `Kind` in the range
@@ -212,9 +232,13 @@ getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext,
212232
}
213233
}
214234
const NamedDecl *ND = Ref.Targets.front();
215-
const std::string Qualifier =
235+
std::string Qualifier =
216236
getQualification(AST, TargetContext,
217237
SM.getLocForStartOfFile(SM.getMainFileID()), ND);
238+
if (ND->getDeclContext()->isDependentContext()) {
239+
if (llvm::isa<TypeDecl>(ND))
240+
Qualifier.insert(0, "typename ");
241+
}
218242
if (auto Err = DeclarationCleanups.add(
219243
tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))
220244
Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
@@ -407,10 +431,21 @@ class DefineOutline : public Tweak {
407431
return !SameFile;
408432
}
409433

410-
// Bail out in templated classes, as it is hard to spell the class name,
411-
// i.e if the template parameter is unnamed.
412-
if (MD->getParent()->isTemplated())
413-
return false;
434+
for (const CXXRecordDecl *Parent = MD->getParent(); Parent;
435+
Parent =
436+
llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {
437+
if (auto Params = Parent->getDescribedTemplateParams()) {
438+
439+
// Class template member functions must be defined in the
440+
// same file.
441+
SameFile = true;
442+
443+
for (NamedDecl *P : *Params) {
444+
if (!P->getIdentifier())
445+
return false;
446+
}
447+
}
448+
}
414449

415450
// The refactoring is meaningless for unnamed classes and namespaces,
416451
// unless we're outlining in the same file

clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ TEST_F(DefineOutlineTest, TriggersOnFunctionDecl) {
105105
F^oo(const Foo&) = delete;
106106
};)cpp");
107107

108-
// Not available within templated classes, as it is hard to spell class name
109-
// out-of-line in such cases.
108+
// Not available within templated classes with unnamed parameters, as it is
109+
// hard to spell class name out-of-line in such cases.
110110
EXPECT_UNAVAILABLE(R"cpp(
111111
template <typename> struct Foo { void fo^o(){} };
112112
)cpp");
@@ -154,7 +154,6 @@ TEST_F(DefineOutlineTest, FailsWithoutSource) {
154154
}
155155

156156
TEST_F(DefineOutlineTest, ApplyTest) {
157-
llvm::StringMap<std::string> EditedFiles;
158157
ExtraFiles["Test.cpp"] = "";
159158
FileName = "Test.hpp";
160159

@@ -229,17 +228,18 @@ TEST_F(DefineOutlineTest, ApplyTest) {
229228
// Ctor initializer with attribute.
230229
{
231230
R"cpp(
232-
class Foo {
233-
F^oo(int z) __attribute__((weak)) : bar(2){}
231+
template <typename T> class Foo {
232+
F^oo(T z) __attribute__((weak)) : bar(2){}
234233
int bar;
235234
};)cpp",
236235
R"cpp(
237-
class Foo {
238-
Foo(int z) __attribute__((weak)) ;
236+
template <typename T> class Foo {
237+
Foo(T z) __attribute__((weak)) ;
239238
int bar;
240-
};)cpp",
241-
"Foo::Foo(int z) __attribute__((weak)) : bar(2){}\n",
242-
},
239+
};template <typename T>
240+
Foo<T>::Foo(T z) __attribute__((weak)) : bar(2){}
241+
)cpp",
242+
""},
243243
// Virt specifiers.
244244
{
245245
R"cpp(
@@ -369,7 +369,31 @@ TEST_F(DefineOutlineTest, ApplyTest) {
369369
};)cpp",
370370
" void A::foo(int) {}\n",
371371
},
372-
// Destrctors
372+
// Complex class template
373+
{
374+
R"cpp(
375+
template <typename T, typename ...U> struct O1 {
376+
template <class V, int A> struct O2 {
377+
enum E { E1, E2 };
378+
struct I {
379+
E f^oo(T, U..., V, E) { return E1; }
380+
};
381+
};
382+
};)cpp",
383+
R"cpp(
384+
template <typename T, typename ...U> struct O1 {
385+
template <class V, int A> struct O2 {
386+
enum E { E1, E2 };
387+
struct I {
388+
E foo(T, U..., V, E) ;
389+
};
390+
};
391+
};template <typename T, typename ...U>
392+
template <class V, int A>
393+
typename O1<T, U...>::template O2<V, A>::E O1<T, U...>::template O2<V, A>::I::foo(T, U..., V, E) { return E1; }
394+
)cpp",
395+
""},
396+
// Destructors
373397
{
374398
"class A { ~A^(){} };",
375399
"class A { ~A(); };",
@@ -378,9 +402,14 @@ TEST_F(DefineOutlineTest, ApplyTest) {
378402
};
379403
for (const auto &Case : Cases) {
380404
SCOPED_TRACE(Case.Test);
405+
llvm::StringMap<std::string> EditedFiles;
381406
EXPECT_EQ(apply(Case.Test, &EditedFiles), Case.ExpectedHeader);
382-
EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
383-
testPath("Test.cpp"), Case.ExpectedSource)));
407+
if (Case.ExpectedSource.empty()) {
408+
EXPECT_TRUE(EditedFiles.empty());
409+
} else {
410+
EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
411+
testPath("Test.cpp"), Case.ExpectedSource)));
412+
}
384413
}
385414
}
386415

0 commit comments

Comments
 (0)