Index: cfe/trunk/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp =================================================================== --- cfe/trunk/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp +++ cfe/trunk/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp @@ -212,6 +212,41 @@ return true; } + bool VisitMemberExpr(const MemberExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl(); + auto StartLoc = Expr->getMemberLoc(); + auto EndLoc = Expr->getMemberLoc(); + if (isInUSRSet(Decl)) { + RenameInfos.push_back({StartLoc, EndLoc, + /*FromDecl=*/nullptr, + /*Context=*/nullptr, + /*Specifier=*/nullptr, + /*IgnorePrefixQualifiers=*/true}); + } + return true; + } + + bool VisitCXXConstructorDecl(const CXXConstructorDecl *CD) { + // Fix the constructor initializer when renaming class members. + for (const auto *Initializer : CD->inits()) { + // Ignore implicit initializers. + if (!Initializer->isWritten()) + continue; + + if (const FieldDecl *FD = Initializer->getMember()) { + if (isInUSRSet(FD)) { + auto Loc = Initializer->getSourceLocation(); + RenameInfos.push_back({Loc, Loc, + /*FromDecl=*/nullptr, + /*Context=*/nullptr, + /*Specifier=*/nullptr, + /*IgnorePrefixQualifiers=*/true}); + } + } + } + return true; + } + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { const NamedDecl *Decl = Expr->getFoundDecl(); // Get the underlying declaration of the shadow declaration introduced by a @@ -227,6 +262,20 @@ ? Expr->getLAngleLoc().getLocWithOffset(-1) : Expr->getLocEnd(); + if (const auto *MD = llvm::dyn_cast(Decl)) { + if (isInUSRSet(MD)) { + // Handle renaming static template class methods, we only rename the + // name without prefix qualifiers and restrict the source range to the + // name. + RenameInfos.push_back({EndLoc, EndLoc, + /*FromDecl=*/nullptr, + /*Context=*/nullptr, + /*Specifier=*/nullptr, + /*IgnorePrefixQualifiers=*/true}); + return true; + } + } + // In case of renaming an enum declaration, we have to explicitly handle // unscoped enum constants referenced in expressions (e.g. // "auto r = ns1::ns2::Green" where Green is an enum constant of an unscoped Index: cfe/trunk/unittests/Rename/CMakeLists.txt =================================================================== --- cfe/trunk/unittests/Rename/CMakeLists.txt +++ cfe/trunk/unittests/Rename/CMakeLists.txt @@ -9,6 +9,7 @@ RenameClassTest.cpp RenameEnumTest.cpp RenameAliasTest.cpp + RenameMemberTest.cpp RenameFunctionTest.cpp ) Index: cfe/trunk/unittests/Rename/RenameMemberTest.cpp =================================================================== --- cfe/trunk/unittests/Rename/RenameMemberTest.cpp +++ cfe/trunk/unittests/Rename/RenameMemberTest.cpp @@ -0,0 +1,229 @@ +//===-- ClangMemberTests.cpp - unit tests for renaming class members ------===// +// +// 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 RenameMemberTest : public ClangRenameTest { +public: + RenameMemberTest() { + AppendToHeader(R"( + struct NA { + void Foo(); + void NotFoo(); + static void SFoo(); + static void SNotFoo(); + int Moo; + }; + struct A { + virtual void Foo(); + void NotFoo(); + static void SFoo(); + static void SNotFoo(); + int Moo; + int NotMoo; + static int SMoo; + }; + struct B : public A { + void Foo() override; + }; + template struct TA { + T* Foo(); + T* NotFoo(); + static T* SFoo(); + static T* NotSFoo(); + }; + template struct TB : public TA {}; + namespace ns { + template struct TA { + T* Foo(); + T* NotFoo(); + static T* SFoo(); + static T* NotSFoo(); + static int SMoo; + }; + template struct TB : public TA {}; + struct A { + void Foo(); + void NotFoo(); + static void SFoo(); + static void SNotFoo(); + }; + struct B : public A {}; + struct C { + template + void SFoo(const T& t) {} + template + void Foo() {} + }; + })"); + } +}; + +INSTANTIATE_TEST_CASE_P( + DISABLED_RenameTemplatedClassStaticVariableTest, RenameMemberTest, + testing::ValuesIn(std::vector({ + // FIXME: support renaming static variables for template classes. + {"void f() { ns::TA::SMoo; }", + "void f() { ns::TA::SMeh; }", "ns::TA::SMoo", "ns::TA::SMeh"}, + })), ); + +INSTANTIATE_TEST_CASE_P( + RenameMemberTest, RenameMemberTest, + testing::ValuesIn(std::vector({ + // Normal methods and fields. + {"void f() { A a; a.Foo(); }", "void f() { A a; a.Bar(); }", "A::Foo", + "A::Bar"}, + {"void f() { ns::A a; a.Foo(); }", "void f() { ns::A a; a.Bar(); }", + "ns::A::Foo", "ns::A::Bar"}, + {"void f() { A a; int x = a.Moo; }", "void f() { A a; int x = a.Meh; }", + "A::Moo", "A::Meh"}, + {"void f() { B b; b.Foo(); }", "void f() { B b; b.Bar(); }", "B::Foo", + "B::Bar"}, + {"void f() { ns::B b; b.Foo(); }", "void f() { ns::B b; b.Bar(); }", + "ns::A::Foo", "ns::A::Bar"}, + {"void f() { B b; int x = b.Moo; }", "void f() { B b; int x = b.Meh; }", + "A::Moo", "A::Meh"}, + + // Static methods. + {"void f() { A::SFoo(); }", "void f() { A::SBar(); }", "A::SFoo", + "A::SBar"}, + {"void f() { ns::A::SFoo(); }", "void f() { ns::A::SBar(); }", + "ns::A::SFoo", "ns::A::SBar"}, + {"void f() { TA::SFoo(); }", "void f() { TA::SBar(); }", + "TA::SFoo", "TA::SBar"}, + {"void f() { ns::TA::SFoo(); }", + "void f() { ns::TA::SBar(); }", "ns::TA::SFoo", "ns::TA::SBar"}, + + // Static variables. + {"void f() { A::SMoo; }", + "void f() { A::SMeh; }", "A::SMoo", "A::SMeh"}, + + // Templated methods. + {"void f() { TA a; a.Foo(); }", "void f() { TA a; a.Bar(); }", + "TA::Foo", "TA::Bar"}, + {"void f() { ns::TA a; a.Foo(); }", + "void f() { ns::TA a; a.Bar(); }", "ns::TA::Foo", "ns::TA::Bar"}, + {"void f() { TB b; b.Foo(); }", "void f() { TB b; b.Bar(); }", + "TA::Foo", "TA::Bar"}, + {"void f() { ns::TB b; b.Foo(); }", + "void f() { ns::TB b; b.Bar(); }", "ns::TA::Foo", "ns::TA::Bar"}, + {"void f() { ns::C c; int x; c.SFoo(x); }", + "void f() { ns::C c; int x; c.SBar(x); }", "ns::C::SFoo", + "ns::C::SBar"}, + {"void f() { ns::C c; c.Foo(); }", + "void f() { ns::C c; c.Bar(); }", "ns::C::Foo", "ns::C::Bar"}, + + // Pointers to methods. + {"void f() { auto p = &A::Foo; }", "void f() { auto p = &A::Bar; }", + "A::Foo", "A::Bar"}, + {"void f() { auto p = &A::SFoo; }", "void f() { auto p = &A::SBar; }", + "A::SFoo", "A::SBar"}, + {"void f() { auto p = &B::Foo; }", "void f() { auto p = &B::Bar; }", + "B::Foo", "B::Bar"}, + {"void f() { auto p = &ns::A::Foo; }", + "void f() { auto p = &ns::A::Bar; }", "ns::A::Foo", "ns::A::Bar"}, + {"void f() { auto p = &ns::A::SFoo; }", + "void f() { auto p = &ns::A::SBar; }", "ns::A::SFoo", "ns::A::SBar"}, + {"void f() { auto p = &ns::C::SFoo; }", + "void f() { auto p = &ns::C::SBar; }", "ns::C::SFoo", + "ns::C::SBar"}, + + // These methods are not declared or overrided in the subclass B, we + // have to use the qualified name with parent class A to identify them. + {"void f() { auto p = &ns::B::Foo; }", + "void f() { auto p = &ns::B::Bar; }", "ns::A::Foo", "ns::B::Bar"}, + {"void f() { B::SFoo(); }", "void f() { B::SBar(); }", "A::SFoo", + "B::SBar"}, + {"void f() { ns::B::SFoo(); }", "void f() { ns::B::SBar(); }", + "ns::A::SFoo", "ns::B::SBar"}, + {"void f() { auto p = &B::SFoo; }", "void f() { auto p = &B::SBar; }", + "A::SFoo", "B::SBar"}, + {"void f() { auto p = &ns::B::SFoo; }", + "void f() { auto p = &ns::B::SBar; }", "ns::A::SFoo", "ns::B::SBar"}, + {"void f() { TB::SFoo(); }", "void f() { TB::SBar(); }", + "TA::SFoo", "TB::SBar"}, + {"void f() { ns::TB::SFoo(); }", + "void f() { ns::TB::SBar(); }", "ns::TA::SFoo", "ns::TB::SBar"}, + })), ); + +TEST_P(RenameMemberTest, RenameMembers) { + auto Param = GetParam(); + assert(!Param.OldName.empty()); + assert(!Param.NewName.empty()); + std::string Actual = + runClangRenameOnCode(Param.Before, Param.OldName, Param.NewName); + CompareSnippets(Param.After, Actual); +} + +TEST_F(RenameMemberTest, RenameMemberInsideClassMethods) { + std::string Before = R"( + struct X { + int Moo; + void Baz() { Moo = 1; } + };)"; + std::string Expected = R"( + struct X { + int Meh; + void Baz() { Meh = 1; } + };)"; + std::string After = runClangRenameOnCode(Before, "X::Moo", "Y::Meh"); + CompareSnippets(Expected, After); +} + +TEST_F(RenameMemberTest, RenameMethodInsideClassMethods) { + std::string Before = R"( + struct X { + void Foo() {} + void Baz() { Foo(); } + };)"; + std::string Expected = R"( + struct X { + void Bar() {} + void Baz() { Bar(); } + };)"; + std::string After = runClangRenameOnCode(Before, "X::Foo", "X::Bar"); + CompareSnippets(Expected, After); +} + +TEST_F(RenameMemberTest, RenameCtorInitializer) { + std::string Before = R"( + class X { + public: + X(); + A a; + A a2; + B b; + }; + + X::X():a(), b() {} + )"; + std::string Expected = R"( + class X { + public: + X(); + A bar; + A a2; + B b; + }; + + X::X():bar(), b() {} + )"; + std::string After = runClangRenameOnCode(Before, "X::a", "X::bar"); + CompareSnippets(Expected, After); +} + +} // anonymous namespace +} // namespace test +} // namespace clang_rename +} // namesdpace clang