forked from OSchip/llvm-project
[clang-rename] Add function unit tests.
Summary: Also contain a fix: * Fix a false positive of renaming a using shadow function declaration. Reviewers: ioeric Reviewed By: ioeric Subscribers: klimek, mgorny, cfe-commits Differential Revision: https://reviews.llvm.org/D38882 llvm-svn: 315898
This commit is contained in:
parent
68b285f69e
commit
6f60ff84cd
|
@ -194,6 +194,12 @@ public:
|
|||
|
||||
bool VisitDeclRefExpr(const DeclRefExpr *Expr) {
|
||||
const NamedDecl *Decl = Expr->getFoundDecl();
|
||||
// Get the underlying declaration of the shadow declaration introduced by a
|
||||
// using declaration.
|
||||
if (auto* UsingShadow = llvm::dyn_cast<UsingShadowDecl>(Decl)) {
|
||||
Decl = UsingShadow->getTargetDecl();
|
||||
}
|
||||
|
||||
if (isInUSRSet(Decl)) {
|
||||
RenameInfo Info = {Expr->getSourceRange().getBegin(),
|
||||
Expr->getSourceRange().getEnd(),
|
||||
|
@ -452,6 +458,23 @@ createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs,
|
|||
RenameInfo.FromDecl,
|
||||
NewName.startswith("::") ? NewName.str()
|
||||
: ("::" + NewName).str());
|
||||
} else {
|
||||
// This fixes the case where type `T` is a parameter inside a function
|
||||
// type (e.g. `std::function<void(T)>`) and the DeclContext of `T`
|
||||
// becomes the translation unit. As a workaround, we simply use
|
||||
// fully-qualified name here for all references whose `DeclContext` is
|
||||
// the translation unit and ignore the possible existence of
|
||||
// using-decls (in the global scope) that can shorten the replaced
|
||||
// name.
|
||||
llvm::StringRef ActualName = Lexer::getSourceText(
|
||||
CharSourceRange::getTokenRange(
|
||||
SourceRange(RenameInfo.Begin, RenameInfo.End)),
|
||||
SM, TranslationUnitDecl->getASTContext().getLangOpts());
|
||||
// Add the leading "::" back if the name written in the code contains
|
||||
// it.
|
||||
if (ActualName.startswith("::") && !NewName.startswith("::")) {
|
||||
ReplacedName = "::" + NewName.str();
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the NewName contains leading "::", add it back.
|
||||
|
|
|
@ -7,6 +7,7 @@ include_directories(${CLANG_SOURCE_DIR})
|
|||
|
||||
add_clang_unittest(ClangRenameTests
|
||||
RenameClassTest.cpp
|
||||
RenameFunctionTest.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(ClangRenameTests
|
||||
|
|
|
@ -51,6 +51,7 @@ INSTANTIATE_TEST_CASE_P(
|
|||
testing::ValuesIn(std::vector<Case>({
|
||||
// basic classes
|
||||
{"a::Foo f;", "b::Bar f;", "", ""},
|
||||
{"::a::Foo f;", "::b::Bar f;", "", ""},
|
||||
{"void f(a::Foo f) {}", "void f(b::Bar f) {}", "", ""},
|
||||
{"void f(a::Foo *f) {}", "void f(b::Bar *f) {}", "", ""},
|
||||
{"a::Foo f() { return a::Foo(); }", "b::Bar f() { return b::Bar(); }",
|
||||
|
|
|
@ -0,0 +1,555 @@
|
|||
//===-- RenameFunctionTest.cpp - unit tests for renaming functions --------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "ClangRenameTest.h"
|
||||
|
||||
namespace clang {
|
||||
namespace clang_rename {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
class RenameFunctionTest : public ClangRenameTest {
|
||||
public:
|
||||
RenameFunctionTest() {
|
||||
AppendToHeader(R"(
|
||||
struct A {
|
||||
static bool Foo();
|
||||
static bool Spam();
|
||||
};
|
||||
struct B {
|
||||
static void Same();
|
||||
static bool Foo();
|
||||
static int Eric(int x);
|
||||
};
|
||||
void Same(int x);
|
||||
int Eric(int x);
|
||||
namespace base {
|
||||
void Same();
|
||||
void ToNanoSeconds();
|
||||
void ToInt64NanoSeconds();
|
||||
})");
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(RenameFunctionTest, RefactorsAFoo) {
|
||||
std::string Before = R"(
|
||||
void f() {
|
||||
A::Foo();
|
||||
::A::Foo();
|
||||
})";
|
||||
std::string Expected = R"(
|
||||
void f() {
|
||||
A::Bar();
|
||||
::A::Bar();
|
||||
})";
|
||||
|
||||
std::string After = runClangRenameOnCode(Before, "A::Foo", "A::Bar");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, RefactorsNonCallingAFoo) {
|
||||
std::string Before = R"(
|
||||
bool g(bool (*func)()) {
|
||||
return func();
|
||||
}
|
||||
void f() {
|
||||
auto *ref1 = A::Foo;
|
||||
auto *ref2 = ::A::Foo;
|
||||
g(A::Foo);
|
||||
})";
|
||||
std::string Expected = R"(
|
||||
bool g(bool (*func)()) {
|
||||
return func();
|
||||
}
|
||||
void f() {
|
||||
auto *ref1 = A::Bar;
|
||||
auto *ref2 = ::A::Bar;
|
||||
g(A::Bar);
|
||||
})";
|
||||
std::string After = runClangRenameOnCode(Before, "A::Foo", "A::Bar");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, RefactorsEric) {
|
||||
std::string Before = R"(
|
||||
void f() {
|
||||
if (Eric(3)==4) ::Eric(2);
|
||||
})";
|
||||
std::string Expected = R"(
|
||||
void f() {
|
||||
if (Larry(3)==4) ::Larry(2);
|
||||
})";
|
||||
std::string After = runClangRenameOnCode(Before, "Eric", "Larry");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, RefactorsNonCallingEric) {
|
||||
std::string Before = R"(
|
||||
int g(int (*func)(int)) {
|
||||
return func(1);
|
||||
}
|
||||
void f() {
|
||||
auto *ref = ::Eric;
|
||||
g(Eric);
|
||||
})";
|
||||
std::string Expected = R"(
|
||||
int g(int (*func)(int)) {
|
||||
return func(1);
|
||||
}
|
||||
void f() {
|
||||
auto *ref = ::Larry;
|
||||
g(Larry);
|
||||
})";
|
||||
std::string After = runClangRenameOnCode(Before, "Eric", "Larry");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, DoesNotRefactorBFoo) {
|
||||
std::string Before = R"(
|
||||
void f() {
|
||||
B::Foo();
|
||||
})";
|
||||
std::string After = runClangRenameOnCode(Before, "A::Foo", "A::Bar");
|
||||
CompareSnippets(Before, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, DoesNotRefactorBEric) {
|
||||
std::string Before = R"(
|
||||
void f() {
|
||||
B::Eric(2);
|
||||
})";
|
||||
std::string After = runClangRenameOnCode(Before, "Eric", "Larry");
|
||||
CompareSnippets(Before, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, DoesNotRefactorCEric) {
|
||||
std::string Before = R"(
|
||||
namespace C { int Eric(int x); }
|
||||
void f() {
|
||||
if (C::Eric(3)==4) ::C::Eric(2);
|
||||
})";
|
||||
std::string Expected = R"(
|
||||
namespace C { int Eric(int x); }
|
||||
void f() {
|
||||
if (C::Eric(3)==4) ::C::Eric(2);
|
||||
})";
|
||||
std::string After = runClangRenameOnCode(Before, "Eric", "Larry");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, DoesNotRefactorEricInNamespaceC) {
|
||||
std::string Before = R"(
|
||||
namespace C {
|
||||
int Eric(int x);
|
||||
void f() {
|
||||
if (Eric(3)==4) Eric(2);
|
||||
}
|
||||
} // namespace C)";
|
||||
std::string After = runClangRenameOnCode(Before, "Eric", "Larry");
|
||||
CompareSnippets(Before, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, NamespaceQualified) {
|
||||
std::string Before = R"(
|
||||
void f() {
|
||||
base::ToNanoSeconds();
|
||||
::base::ToNanoSeconds();
|
||||
}
|
||||
void g() {
|
||||
using base::ToNanoSeconds;
|
||||
base::ToNanoSeconds();
|
||||
::base::ToNanoSeconds();
|
||||
ToNanoSeconds();
|
||||
}
|
||||
namespace foo {
|
||||
namespace base {
|
||||
void ToNanoSeconds();
|
||||
void f() {
|
||||
base::ToNanoSeconds();
|
||||
}
|
||||
}
|
||||
void f() {
|
||||
::base::ToNanoSeconds();
|
||||
}
|
||||
})";
|
||||
std::string Expected = R"(
|
||||
void f() {
|
||||
base::ToInt64NanoSeconds();
|
||||
::base::ToInt64NanoSeconds();
|
||||
}
|
||||
void g() {
|
||||
using base::ToInt64NanoSeconds;
|
||||
base::ToInt64NanoSeconds();
|
||||
::base::ToInt64NanoSeconds();
|
||||
base::ToInt64NanoSeconds();
|
||||
}
|
||||
namespace foo {
|
||||
namespace base {
|
||||
void ToNanoSeconds();
|
||||
void f() {
|
||||
base::ToNanoSeconds();
|
||||
}
|
||||
}
|
||||
void f() {
|
||||
::base::ToInt64NanoSeconds();
|
||||
}
|
||||
})";
|
||||
std::string After = runClangRenameOnCode(Before, "base::ToNanoSeconds",
|
||||
"base::ToInt64NanoSeconds");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, RenameFunctionDecls) {
|
||||
std::string Before = R"(
|
||||
namespace na {
|
||||
void X();
|
||||
void X() {}
|
||||
})";
|
||||
std::string Expected = R"(
|
||||
namespace na {
|
||||
void Y();
|
||||
void Y() {}
|
||||
})";
|
||||
std::string After = runClangRenameOnCode(Before, "na::X", "na::Y");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, RenameOutOfLineFunctionDecls) {
|
||||
std::string Before = R"(
|
||||
namespace na {
|
||||
void X();
|
||||
}
|
||||
void na::X() {}
|
||||
)";
|
||||
std::string Expected = R"(
|
||||
namespace na {
|
||||
void Y();
|
||||
}
|
||||
void na::Y() {}
|
||||
)";
|
||||
std::string After = runClangRenameOnCode(Before, "na::X", "na::Y");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, NewNamespaceWithoutLeadingDotDot) {
|
||||
std::string Before = R"(
|
||||
namespace old_ns {
|
||||
void X();
|
||||
void X() {}
|
||||
}
|
||||
// Assume that the reference is in another file.
|
||||
void f() { old_ns::X(); }
|
||||
namespace old_ns { void g() { X(); } }
|
||||
namespace new_ns { void h() { ::old_ns::X(); } }
|
||||
)";
|
||||
std::string Expected = R"(
|
||||
namespace old_ns {
|
||||
void Y();
|
||||
void Y() {}
|
||||
}
|
||||
// Assume that the reference is in another file.
|
||||
void f() { new_ns::Y(); }
|
||||
namespace old_ns { void g() { new_ns::Y(); } }
|
||||
namespace new_ns { void h() { Y(); } }
|
||||
)";
|
||||
std::string After = runClangRenameOnCode(Before, "::old_ns::X", "new_ns::Y");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, NewNamespaceWithLeadingDotDot) {
|
||||
std::string Before = R"(
|
||||
namespace old_ns {
|
||||
void X();
|
||||
void X() {}
|
||||
}
|
||||
// Assume that the reference is in another file.
|
||||
void f() { old_ns::X(); }
|
||||
namespace old_ns { void g() { X(); } }
|
||||
namespace new_ns { void h() { ::old_ns::X(); } }
|
||||
)";
|
||||
std::string Expected = R"(
|
||||
namespace old_ns {
|
||||
void Y();
|
||||
void Y() {}
|
||||
}
|
||||
// Assume that the reference is in another file.
|
||||
void f() { ::new_ns::Y(); }
|
||||
namespace old_ns { void g() { ::new_ns::Y(); } }
|
||||
namespace new_ns { void h() { Y(); } }
|
||||
)";
|
||||
std::string After =
|
||||
runClangRenameOnCode(Before, "::old_ns::X", "::new_ns::Y");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, DontRenameSymbolsDefinedInAnonymousNamespace) {
|
||||
std::string Before = R"(
|
||||
namespace old_ns {
|
||||
class X {};
|
||||
namespace {
|
||||
void X();
|
||||
void X() {}
|
||||
void f() { X(); }
|
||||
}
|
||||
}
|
||||
)";
|
||||
std::string Expected = R"(
|
||||
namespace old_ns {
|
||||
class Y {};
|
||||
namespace {
|
||||
void X();
|
||||
void X() {}
|
||||
void f() { X(); }
|
||||
}
|
||||
}
|
||||
)";
|
||||
std::string After =
|
||||
runClangRenameOnCode(Before, "::old_ns::X", "::old_ns::Y");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, NewNestedNamespace) {
|
||||
std::string Before = R"(
|
||||
namespace old_ns {
|
||||
void X();
|
||||
void X() {}
|
||||
}
|
||||
// Assume that the reference is in another file.
|
||||
namespace old_ns {
|
||||
void f() { X(); }
|
||||
}
|
||||
)";
|
||||
std::string Expected = R"(
|
||||
namespace old_ns {
|
||||
void X();
|
||||
void X() {}
|
||||
}
|
||||
// Assume that the reference is in another file.
|
||||
namespace old_ns {
|
||||
void f() { older_ns::X(); }
|
||||
}
|
||||
)";
|
||||
std::string After =
|
||||
runClangRenameOnCode(Before, "::old_ns::X", "::old_ns::older_ns::X");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, MoveFromGlobalToNamespaceWithoutLeadingDotDot) {
|
||||
std::string Before = R"(
|
||||
void X();
|
||||
void X() {}
|
||||
|
||||
// Assume that the reference is in another file.
|
||||
namespace some_ns {
|
||||
void f() { X(); }
|
||||
}
|
||||
)";
|
||||
std::string Expected = R"(
|
||||
void X();
|
||||
void X() {}
|
||||
|
||||
// Assume that the reference is in another file.
|
||||
namespace some_ns {
|
||||
void f() { ns::X(); }
|
||||
}
|
||||
)";
|
||||
std::string After =
|
||||
runClangRenameOnCode(Before, "::X", "ns::X");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, MoveFromGlobalToNamespaceWithLeadingDotDot) {
|
||||
std::string Before = R"(
|
||||
void Y() {}
|
||||
|
||||
// Assume that the reference is in another file.
|
||||
namespace some_ns {
|
||||
void f() { Y(); }
|
||||
}
|
||||
)";
|
||||
std::string Expected = R"(
|
||||
void Y() {}
|
||||
|
||||
// Assume that the reference is in another file.
|
||||
namespace some_ns {
|
||||
void f() { ::ns::Y(); }
|
||||
}
|
||||
)";
|
||||
std::string After =
|
||||
runClangRenameOnCode(Before, "::Y", "::ns::Y");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
// FIXME: the rename of overloaded operator is not fully supported yet.
|
||||
TEST_F(RenameFunctionTest, DISABLED_DoNotRenameOverloadedOperatorCalls) {
|
||||
std::string Before = R"(
|
||||
namespace old_ns {
|
||||
class T { public: int x; };
|
||||
bool operator==(const T& lhs, const T& rhs) {
|
||||
return lhs.x == rhs.x;
|
||||
}
|
||||
} // namespace old_ns
|
||||
|
||||
// Assume that the reference is in another file.
|
||||
bool f() {
|
||||
auto eq = old_ns::operator==;
|
||||
old_ns::T t1, t2;
|
||||
old_ns::operator==(t1, t2);
|
||||
return t1 == t2;
|
||||
}
|
||||
)";
|
||||
std::string Expected = R"(
|
||||
namespace old_ns {
|
||||
class T { public: int x; };
|
||||
bool operator==(const T& lhs, const T& rhs) {
|
||||
return lhs.x == rhs.x;
|
||||
}
|
||||
} // namespace old_ns
|
||||
|
||||
// Assume that the reference is in another file.
|
||||
bool f() {
|
||||
auto eq = new_ns::operator==;
|
||||
old_ns::T t1, t2;
|
||||
new_ns::operator==(t1, t2);
|
||||
return t1 == t2;
|
||||
}
|
||||
)";
|
||||
std::string After =
|
||||
runClangRenameOnCode(Before, "old_ns::operator==", "new_ns::operator==");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, FunctionRefAsTemplate) {
|
||||
std::string Before = R"(
|
||||
void X();
|
||||
|
||||
// Assume that the reference is in another file.
|
||||
namespace some_ns {
|
||||
template <void (*Func)(void)>
|
||||
class TIterator {};
|
||||
|
||||
template <void (*Func)(void)>
|
||||
class T {
|
||||
public:
|
||||
typedef TIterator<Func> IterType;
|
||||
using TI = TIterator<Func>;
|
||||
void g() {
|
||||
Func();
|
||||
auto func = Func;
|
||||
TIterator<Func> iter;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void f() { T<X> tx; tx.g(); }
|
||||
} // namespace some_ns
|
||||
)";
|
||||
std::string Expected = R"(
|
||||
void X();
|
||||
|
||||
// Assume that the reference is in another file.
|
||||
namespace some_ns {
|
||||
template <void (*Func)(void)>
|
||||
class TIterator {};
|
||||
|
||||
template <void (*Func)(void)>
|
||||
class T {
|
||||
public:
|
||||
typedef TIterator<Func> IterType;
|
||||
using TI = TIterator<Func>;
|
||||
void g() {
|
||||
Func();
|
||||
auto func = Func;
|
||||
TIterator<Func> iter;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void f() { T<ns::X> tx; tx.g(); }
|
||||
} // namespace some_ns
|
||||
)";
|
||||
std::string After = runClangRenameOnCode(Before, "::X", "ns::X");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
TEST_F(RenameFunctionTest, RenameFunctionInUsingDecl) {
|
||||
std::string Before = R"(
|
||||
using base::ToNanoSeconds;
|
||||
namespace old_ns {
|
||||
using base::ToNanoSeconds;
|
||||
void f() {
|
||||
using base::ToNanoSeconds;
|
||||
}
|
||||
}
|
||||
)";
|
||||
std::string Expected = R"(
|
||||
using base::ToInt64NanoSeconds;
|
||||
namespace old_ns {
|
||||
using base::ToInt64NanoSeconds;
|
||||
void f() {
|
||||
using base::ToInt64NanoSeconds;
|
||||
}
|
||||
}
|
||||
)";
|
||||
std::string After = runClangRenameOnCode(Before, "base::ToNanoSeconds",
|
||||
"base::ToInt64NanoSeconds");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
// FIXME: Fix the complex the case where the symbol being renamed is located in
|
||||
// `std::function<decltype<renamed_symbol>>`.
|
||||
TEST_F(ClangRenameTest, DISABLED_ReferencesInLambdaFunctionParameters) {
|
||||
std::string Before = R"(
|
||||
template <class T>
|
||||
class function;
|
||||
template <class R, class... ArgTypes>
|
||||
class function<R(ArgTypes...)> {
|
||||
public:
|
||||
template <typename Functor>
|
||||
function(Functor f) {}
|
||||
|
||||
function() {}
|
||||
|
||||
R operator()(ArgTypes...) const {}
|
||||
};
|
||||
|
||||
namespace ns {
|
||||
void Old() {}
|
||||
void f() {
|
||||
function<decltype(Old)> func;
|
||||
}
|
||||
} // namespace ns)";
|
||||
std::string Expected = R"(
|
||||
template <class T>
|
||||
class function;
|
||||
template <class R, class... ArgTypes>
|
||||
class function<R(ArgTypes...)> {
|
||||
public:
|
||||
template <typename Functor>
|
||||
function(Functor f) {}
|
||||
|
||||
function() {}
|
||||
|
||||
R operator()(ArgTypes...) const {}
|
||||
};
|
||||
|
||||
namespace ns {
|
||||
void New() {}
|
||||
void f() {
|
||||
function<decltype(::new_ns::New)> func;
|
||||
}
|
||||
} // namespace ns)";
|
||||
std::string After = runClangRenameOnCode(Before, "ns::Old", "::new_ns::New");
|
||||
CompareSnippets(Expected, After);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
} // namespace test
|
||||
} // namespace clang_rename
|
||||
} // namesdpace clang
|
Loading…
Reference in New Issue