Index: lib/Tooling/Refactoring/Rename/USRLocFinder.cpp =================================================================== --- lib/Tooling/Refactoring/Rename/USRLocFinder.cpp +++ lib/Tooling/Refactoring/Rename/USRLocFinder.cpp @@ -194,6 +194,12 @@ 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(Decl)) { + Decl = UsingShadow->getTargetDecl(); + } + if (isInUSRSet(Decl)) { RenameInfo Info = {Expr->getSourceRange().getBegin(), Expr->getSourceRange().getEnd(), @@ -445,14 +451,11 @@ ReplacedName = NewName.substr(LastColonPos + 1); } else { if (RenameInfo.FromDecl && RenameInfo.Context) { - if (!llvm::isa( - RenameInfo.Context->getDeclContext())) { - ReplacedName = tooling::replaceNestedName( - RenameInfo.Specifier, RenameInfo.Context->getDeclContext(), - RenameInfo.FromDecl, - NewName.startswith("::") ? NewName.str() - : ("::" + NewName).str()); - } + ReplacedName = tooling::replaceNestedName( + RenameInfo.Specifier, RenameInfo.Context->getDeclContext(), + RenameInfo.FromDecl, + NewName.startswith("::") ? NewName.str() + : ("::" + NewName).str()); } // If the NewName contains leading "::", add it back. if (NewName.startswith("::") && NewName.substr(2) == ReplacedName) Index: unittests/Rename/CMakeLists.txt =================================================================== --- unittests/Rename/CMakeLists.txt +++ unittests/Rename/CMakeLists.txt @@ -7,6 +7,7 @@ add_clang_unittest(ClangRenameTests RenameClassTest.cpp + RenameFunctionTest.cpp ) target_link_libraries(ClangRenameTests Index: unittests/Rename/RenameClassTest.cpp =================================================================== --- unittests/Rename/RenameClassTest.cpp +++ unittests/Rename/RenameClassTest.cpp @@ -51,6 +51,7 @@ testing::ValuesIn(std::vector({ // 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(); }", @@ -69,7 +70,7 @@ "a::Foo::Nested2"}, // use namespace and typedefs - {"using a::Foo; Foo gA;", "using b::Bar; b::Bar gA;", "", ""}, + {"using a::Foo; Foo gA;", "using b::Bar; Bar gA;", "", ""}, {"using a::Foo; void f(Foo gA) {}", "using b::Bar; void f(Bar gA) {}", "", ""}, {"using a::Foo; namespace x { Foo gA; }", @@ -165,7 +166,7 @@ // Pointer to member functions {"auto gA = &a::Foo::func;", "auto gA = &b::Bar::func;", "", ""}, {"using a::Foo; auto gA = &Foo::func;", - "using b::Bar; auto gA = &b::Bar::func;", "", ""}, + "using b::Bar; auto gA = &Bar::func;", "", ""}, {"using a::Foo; namespace x { auto gA = &Foo::func; }", "using b::Bar; namespace x { auto gA = &Bar::func; }", "", ""}, {"typedef a::Foo T; auto gA = &T::func;", Index: unittests/Rename/RenameFunctionTest.cpp =================================================================== --- /dev/null +++ unittests/Rename/RenameFunctionTest.cpp @@ -0,0 +1,508 @@ +//===-- 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(); + 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 + class TIterator {}; + + template + class T { + public: + typedef TIterator IterType; + using TI = TIterator; + void g() { + Func(); + auto func = Func; + TIterator iter; + } + }; + + + void f() { T tx; tx.g(); } + } // namespace some_ns + )"; + std::string Expected = R"( + void X(); + + // Assume that the reference is in another file. + namespace some_ns { + template + class TIterator {}; + + template + class T { + public: + typedef TIterator IterType; + using TI = TIterator; + void g() { + Func(); + auto func = Func; + TIterator iter; + } + }; + + + void f() { T 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); +} + +} // anonymous namespace +} // namespace test +} // namespace clang_rename +} // namesdpace clang