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(), @@ -452,6 +458,23 @@ 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`) 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. 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(); }", Index: unittests/Rename/RenameFunctionTest.cpp =================================================================== --- /dev/null +++ unittests/Rename/RenameFunctionTest.cpp @@ -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 + 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); +} + +// FIXME: Fix the complex the case where the symbol being renamed is located in +// `std::function>`. +TEST_F(ClangRenameTest, DISABLED_ReferencesInLambdaFunctionParameters) { + std::string Before = R"( + template + class function; + template + class function { + public: + template + function(Functor f) {} + + function() {} + + R operator()(ArgTypes...) const {} + }; + + namespace ns { + void Old() {} + void f() { + function func; + } + } // namespace ns)"; + std::string Expected = R"( + template + class function; + template + class function { + public: + template + function(Functor f) {} + + function() {} + + R operator()(ArgTypes...) const {} + }; + + namespace ns { + void New() {} + void f() { + function 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