Skip to content

Commit 71aa3f7

Browse files
committed
[clangd] Add parameter renaming to define-inline code action
Summary: When moving a function definition to declaration location we also need to handle renaming of the both function and template parameters. This patch achives that by making sure every parameter name and dependent type in destination is renamed to their respective name in the source. Reviewers: ilya-biryukov Subscribers: MaskRay, jkorous, arphaman, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D68937
1 parent 08c7ff9 commit 71aa3f7

File tree

2 files changed

+177
-7
lines changed

2 files changed

+177
-7
lines changed

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

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,107 @@ llvm::Expected<std::string> qualifyAllDecls(const FunctionDecl *FD) {
226226
return QualifiedFunc->substr(BodyBegin, BodyEnd - BodyBegin + 1);
227227
}
228228

229+
/// Generates Replacements for changing template and function parameter names in
230+
/// \p Dest to be the same as in \p Source.
231+
llvm::Expected<tooling::Replacements>
232+
renameParameters(const FunctionDecl *Dest, const FunctionDecl *Source) {
233+
llvm::DenseMap<const Decl *, std::string> ParamToNewName;
234+
llvm::DenseMap<const NamedDecl *, std::vector<SourceLocation>> RefLocs;
235+
auto HandleParam = [&](const NamedDecl *DestParam,
236+
const NamedDecl *SourceParam) {
237+
// No need to rename if parameters already have the same name.
238+
if (DestParam->getName() == SourceParam->getName())
239+
return;
240+
std::string NewName;
241+
// Unnamed parameters won't be visited in findExplicitReferences. So add
242+
// them here.
243+
if (DestParam->getName().empty()) {
244+
RefLocs[DestParam].push_back(DestParam->getLocation());
245+
// If decl is unnamed in destination we pad the new name to avoid gluing
246+
// with previous token, e.g. foo(int^) shouldn't turn into foo(intx).
247+
NewName = " ";
248+
}
249+
NewName.append(SourceParam->getName());
250+
ParamToNewName[DestParam->getCanonicalDecl()] = std::move(NewName);
251+
};
252+
253+
// Populate mapping for template parameters.
254+
auto *DestTempl = Dest->getDescribedFunctionTemplate();
255+
auto *SourceTempl = Source->getDescribedFunctionTemplate();
256+
assert(bool(DestTempl) == bool(SourceTempl));
257+
if (DestTempl) {
258+
const auto *DestTPL = DestTempl->getTemplateParameters();
259+
const auto *SourceTPL = SourceTempl->getTemplateParameters();
260+
assert(DestTPL->size() == SourceTPL->size());
261+
262+
for (size_t I = 0, EP = DestTPL->size(); I != EP; ++I)
263+
HandleParam(DestTPL->getParam(I), SourceTPL->getParam(I));
264+
}
265+
266+
// Populate mapping for function params.
267+
assert(Dest->param_size() == Source->param_size());
268+
for (size_t I = 0, E = Dest->param_size(); I != E; ++I)
269+
HandleParam(Dest->getParamDecl(I), Source->getParamDecl(I));
270+
271+
const SourceManager &SM = Dest->getASTContext().getSourceManager();
272+
const LangOptions &LangOpts = Dest->getASTContext().getLangOpts();
273+
// Collect other references in function signature, i.e parameter types and
274+
// default arguments.
275+
findExplicitReferences(
276+
// Use function template in case of templated functions to visit template
277+
// parameters.
278+
DestTempl ? llvm::dyn_cast<Decl>(DestTempl) : llvm::dyn_cast<Decl>(Dest),
279+
[&](ReferenceLoc Ref) {
280+
if (Ref.Targets.size() != 1)
281+
return;
282+
const auto *Target =
283+
llvm::cast<NamedDecl>(Ref.Targets.front()->getCanonicalDecl());
284+
auto It = ParamToNewName.find(Target);
285+
if (It == ParamToNewName.end())
286+
return;
287+
RefLocs[Target].push_back(Ref.NameLoc);
288+
});
289+
290+
// Now try to generate edits for all the refs.
291+
tooling::Replacements Replacements;
292+
for (auto &Entry : RefLocs) {
293+
const auto *OldDecl = Entry.first;
294+
llvm::StringRef OldName = OldDecl->getName();
295+
llvm::StringRef NewName = ParamToNewName[OldDecl];
296+
for (SourceLocation RefLoc : Entry.second) {
297+
CharSourceRange ReplaceRange;
298+
// In case of unnamed parameters, we have an empty char range, whereas we
299+
// have a tokenrange at RefLoc with named parameters.
300+
if (OldName.empty())
301+
ReplaceRange = CharSourceRange::getCharRange(RefLoc, RefLoc);
302+
else
303+
ReplaceRange = CharSourceRange::getTokenRange(RefLoc, RefLoc);
304+
// If occurence is coming from a macro expansion, try to get back to the
305+
// file range.
306+
if (RefLoc.isMacroID()) {
307+
ReplaceRange = Lexer::makeFileCharRange(ReplaceRange, SM, LangOpts);
308+
// Bail out if we need to replace macro bodies.
309+
if (ReplaceRange.isInvalid()) {
310+
auto Err = llvm::createStringError(
311+
llvm::inconvertibleErrorCode(),
312+
"Cant rename parameter inside macro body.");
313+
elog("define inline: {0}", Err);
314+
return std::move(Err);
315+
}
316+
}
317+
318+
if (auto Err = Replacements.add(
319+
tooling::Replacement(SM, ReplaceRange, NewName))) {
320+
elog("define inline: Couldn't replace parameter name for {0} to {1}: "
321+
"{2}",
322+
OldName, NewName, Err);
323+
return std::move(Err);
324+
}
325+
}
326+
}
327+
return Replacements;
328+
}
329+
229330
// Returns the canonical declaration for the given FunctionDecl. This will
230331
// usually be the first declaration in current translation unit with the
231332
// exception of template specialization.
@@ -332,6 +433,10 @@ class DefineInline : public Tweak {
332433
"Couldn't find semicolon for target declaration.");
333434
}
334435

436+
auto ParamReplacements = renameParameters(Target, Source);
437+
if (!ParamReplacements)
438+
return ParamReplacements.takeError();
439+
335440
auto QualifiedBody = qualifyAllDecls(Source);
336441
if (!QualifiedBody)
337442
return QualifiedBody.takeError();
@@ -354,8 +459,9 @@ class DefineInline : public Tweak {
354459

355460
llvm::SmallVector<std::pair<std::string, Edit>, 2> Edits;
356461
// Edit for Target.
357-
auto FE = Effect::fileEdit(SM, SM.getFileID(*Semicolon),
358-
tooling::Replacements(SemicolonToFuncBody));
462+
auto FE = Effect::fileEdit(
463+
SM, SM.getFileID(*Semicolon),
464+
tooling::Replacements(SemicolonToFuncBody).merge(*ParamReplacements));
359465
if (!FE)
360466
return FE.takeError();
361467
Edits.push_back(std::move(*FE));

clang-tools-extra/clangd/unittests/TweakTests.cpp

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@
4040
#include <vector>
4141

4242
using ::testing::AllOf;
43+
using ::testing::ElementsAre;
4344
using ::testing::HasSubstr;
4445
using ::testing::StartsWith;
45-
using ::testing::ElementsAre;
4646

4747
namespace clang {
4848
namespace clangd {
@@ -1386,7 +1386,7 @@ TEST_F(DefineInlineTest, TransformFunctionTempls) {
13861386
// Template body is not parsed until instantiation time on windows, which
13871387
// results in arbitrary failures as function body becomes NULL.
13881388
ExtraArgs.push_back("-fno-delayed-template-parsing");
1389-
for(const auto &Case : Cases)
1389+
for (const auto &Case : Cases)
13901390
EXPECT_EQ(apply(Case.first), Case.second) << Case.first;
13911391
}
13921392

@@ -1429,7 +1429,7 @@ TEST_F(DefineInlineTest, TransformTypeLocs) {
14291429
}
14301430

14311431
TEST_F(DefineInlineTest, TransformDeclRefs) {
1432-
auto Test =R"cpp(
1432+
auto Test = R"cpp(
14331433
namespace a {
14341434
template <typename T> class Bar {
14351435
public:
@@ -1498,6 +1498,70 @@ TEST_F(DefineInlineTest, StaticMembers) {
14981498
EXPECT_EQ(apply(Test), Expected);
14991499
}
15001500

1501+
TEST_F(DefineInlineTest, TransformParamNames) {
1502+
std::pair<llvm::StringRef, llvm::StringRef> Cases[] = {
1503+
{R"cpp(
1504+
void foo(int, bool b, int T\
1505+
est);
1506+
void ^foo(int f, bool x, int z) {})cpp",
1507+
R"cpp(
1508+
void foo(int f, bool x, int z){}
1509+
)cpp"},
1510+
{R"cpp(
1511+
#define PARAM int Z
1512+
void foo(PARAM);
1513+
1514+
void ^foo(int X) {})cpp",
1515+
"fail: Cant rename parameter inside macro body."},
1516+
{R"cpp(
1517+
#define TYPE int
1518+
#define PARAM TYPE Z
1519+
#define BODY(x) 5 * (x) + 2
1520+
template <int P>
1521+
void foo(PARAM, TYPE Q, TYPE, TYPE W = BODY(P));
1522+
template <int x>
1523+
void ^foo(int Z, int b, int c, int d) {})cpp",
1524+
R"cpp(
1525+
#define TYPE int
1526+
#define PARAM TYPE Z
1527+
#define BODY(x) 5 * (x) + 2
1528+
template <int x>
1529+
void foo(PARAM, TYPE b, TYPE c, TYPE d = BODY(x)){}
1530+
)cpp"},
1531+
};
1532+
for (const auto &Case : Cases)
1533+
EXPECT_EQ(apply(Case.first), Case.second) << Case.first;
1534+
}
1535+
1536+
TEST_F(DefineInlineTest, TransformTemplParamNames) {
1537+
auto Test = R"cpp(
1538+
struct Foo {
1539+
struct Bar {
1540+
template <class, class X,
1541+
template<typename> class, template<typename> class Y,
1542+
int, int Z>
1543+
void foo(X, Y<X>, int W = 5 * Z + 2);
1544+
};
1545+
};
1546+
1547+
template <class T, class U,
1548+
template<typename> class V, template<typename> class W,
1549+
int X, int Y>
1550+
void Foo::Bar::f^oo(U, W<U>, int Q) {})cpp";
1551+
auto Expected = R"cpp(
1552+
struct Foo {
1553+
struct Bar {
1554+
template <class T, class U,
1555+
template<typename> class V, template<typename> class W,
1556+
int X, int Y>
1557+
void foo(U, W<U>, int Q = 5 * Y + 2){}
1558+
};
1559+
};
1560+
1561+
)cpp";
1562+
EXPECT_EQ(apply(Test), Expected);
1563+
}
1564+
15011565
TEST_F(DefineInlineTest, TransformInlineNamespaces) {
15021566
auto Test = R"cpp(
15031567
namespace a { inline namespace b { namespace { struct Foo{}; } } }
@@ -1536,7 +1600,7 @@ TEST_F(DefineInlineTest, TokensBeforeSemicolon) {
15361600
void fo^o() { return ; })cpp",
15371601
"fail: Couldn't find semicolon for target declaration."},
15381602
};
1539-
for(const auto& Case: Cases)
1603+
for (const auto &Case : Cases)
15401604
EXPECT_EQ(apply(Case.first), Case.second) << Case.first;
15411605
}
15421606

@@ -1594,7 +1658,7 @@ TEST_F(DefineInlineTest, HandleMacros) {
15941658
void TARGET(){ return; }
15951659
)cpp"},
15961660
};
1597-
for(const auto& Case: Cases)
1661+
for (const auto &Case : Cases)
15981662
EXPECT_EQ(apply(Case.first), Case.second) << Case.first;
15991663
}
16001664

0 commit comments

Comments
 (0)