Index: clang-rename/RenamingAction.h =================================================================== --- clang-rename/RenamingAction.h +++ clang-rename/RenamingAction.h @@ -42,6 +42,25 @@ bool PrintLocations; }; +class QualifiedRenamingAction { +public: + QualifiedRenamingAction( + const std::vector &NewNames, + const std::vector &OldNames, + const std::vector> &USRList, + std::map &FileToReplaces) + : NewNames(NewNames), OldNames(OldNames), USRList(USRList), + FileToReplaces(FileToReplaces) {} + + std::unique_ptr newASTConsumer(); + +private: + const std::vector &NewNames; + const std::vector &OldNames; + const std::vector> &USRList; + std::map &FileToReplaces; +}; + } // namespace rename } // namespace clang Index: clang-rename/RenamingAction.cpp =================================================================== --- clang-rename/RenamingAction.cpp +++ clang-rename/RenamingAction.cpp @@ -84,10 +84,54 @@ bool PrintLocations; }; +class QualifiedRenamingASTConsumer : public ASTConsumer { +public: + QualifiedRenamingASTConsumer( + const std::vector &NewNames, + const std::vector &OldNames, + const std::vector> &USRList, + std::map &FileToReplaces) + : NewNames(NewNames), OldNames(OldNames), USRList(USRList), + FileToReplaces(FileToReplaces) {} + + void HandleTranslationUnit(ASTContext &Context) override { + for (unsigned I = 0; I < NewNames.size(); ++I) + HandleOneRename(Context, NewNames[I], OldNames[I], USRList[I]); + } + + void HandleOneRename(ASTContext &Context, llvm::StringRef NewName, + const std::string &OldName, + const std::vector &USRs) { + auto RenameLocs = getQualifiedRenameLocsOfUSRs( + USRs, OldName, NewName, Context.getTranslationUnitDecl()); + for (const auto &Loc : RenameLocs) { + tooling::Replacement Replace( + Context.getSourceManager(), + CharSourceRange::getTokenRange(Loc.Begin, Loc.End), Loc.NewName); + llvm::Error Err = FileToReplaces[Replace.getFilePath()].add(Replace); + if (Err) { + llvm::errs() << "Renaming failed in " << Replace.getFilePath() << "! " + << llvm::toString(std::move(Err)) << "\n"; + } + } + } + +private: + const std::vector &NewNames; + const std::vector &OldNames; + const std::vector> &USRList; + std::map &FileToReplaces; +}; + std::unique_ptr RenamingAction::newASTConsumer() { return llvm::make_unique(NewNames, PrevNames, USRList, FileToReplaces, PrintLocations); } +std::unique_ptr QualifiedRenamingAction::newASTConsumer() { + return llvm::make_unique( + NewNames, OldNames, USRList, FileToReplaces); +} + } // namespace rename } // namespace clang Index: clang-rename/USRFinder.cpp =================================================================== --- clang-rename/USRFinder.cpp +++ clang-rename/USRFinder.cpp @@ -135,8 +135,10 @@ return true; } else { // Fully qualified name is used to find the declaration. - if (Name != Decl->getQualifiedNameAsString()) + if (Name != Decl->getQualifiedNameAsString() && + Name != "::" + Decl->getQualifiedNameAsString()) { return true; + } } Result = Decl; return false; @@ -195,6 +197,11 @@ NamedDeclFindingASTVisitor Visitor(Name, Context); Visitor.TraverseDecl(Context.getTranslationUnitDecl()); + // Also find all USRs of nested declarations. + NestedNameSpecifierLocFinder Finder(const_cast(Context)); + for (const auto &Location : Finder.getNestedNameSpecifierLocations()) + Visitor.handleNestedNameSpecifierLoc(Location); + return Visitor.getNamedDecl(); } Index: clang-rename/USRFindingAction.cpp =================================================================== --- clang-rename/USRFindingAction.cpp +++ clang-rename/USRFindingAction.cpp @@ -104,6 +104,10 @@ void addUSRsOfCtorDtors(const CXXRecordDecl *RecordDecl) { RecordDecl = RecordDecl->getDefinition(); + // Skip if the CXXRecordDecl doesn't have definition. + if (!RecordDecl) + return; + for (const auto *CtorDecl : RecordDecl->ctors()) USRSet.insert(getUSRForDecl(CtorDecl)); Index: clang-rename/USRLocFinder.h =================================================================== --- clang-rename/USRLocFinder.h +++ clang-rename/USRLocFinder.h @@ -24,6 +24,17 @@ namespace clang { namespace rename { +struct NewNameReplacement { + SourceLocation Begin; + SourceLocation End; + std::string NewName; +}; + +std::vector +getQualifiedRenameLocsOfUSRs(llvm::ArrayRef USRs, + llvm::StringRef OldName, llvm::StringRef NewName, + Decl *TranslationUnitDecl); + // FIXME: make this an AST matcher. Wouldn't that be awesome??? I agree! std::vector getLocationsOfUSRs(const std::vector &USRs, Index: clang-rename/USRLocFinder.cpp =================================================================== --- clang-rename/USRLocFinder.cpp +++ clang-rename/USRLocFinder.cpp @@ -22,6 +22,7 @@ #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" +#include "clang/Tooling/Core/Lookup.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include @@ -148,6 +149,297 @@ const ASTContext &Context; }; +clang::SourceLocation StartLocationForType(clang::TypeLoc TL) { + // For elaborated types (e.g. `struct a::A`) we want the portion after the + // `struct` but including the namespace qualifier, `a::`. + if (TL.getTypeLocClass() == clang::TypeLoc::Elaborated) { + clang::NestedNameSpecifierLoc nested_name_specifier = + TL.castAs().getQualifierLoc(); + if (nested_name_specifier.getNestedNameSpecifier()) + return nested_name_specifier.getBeginLoc(); + TL = TL.getNextTypeLoc(); + } + return TL.getLocStart(); +} + +clang::SourceLocation EndLocationForType(clang::TypeLoc TL) { + // Dig past any namespace or keyword qualifications. + while (TL.getTypeLocClass() == clang::TypeLoc::Elaborated || + TL.getTypeLocClass() == clang::TypeLoc::Qualified) { + TL = TL.getNextTypeLoc(); + } + + // The location for template specializations (e.g. Foo) includes the + // templated types in its location range. We want to restrict this to just + // before the `<` character. + if (TL.getTypeLocClass() == clang::TypeLoc::TemplateSpecialization) { + return TL.castAs() + .getLAngleLoc() + .getLocWithOffset(-1); + } + return TL.getEndLoc(); +} + +clang::NestedNameSpecifier *GetNestedNameForType(clang::TypeLoc TL) { + // Dig past any keyword qualifications. + while (TL.getTypeLocClass() == clang::TypeLoc::Qualified) { + TL = TL.getNextTypeLoc(); + } + + // For elaborated types (e.g. `struct a::A`) we want the portion after the + // `struct` but including the namespace qualifier, `a::`. + if (auto ElaboratedTypeLoc = TL.getAs()) + return ElaboratedTypeLoc.getQualifierLoc().getNestedNameSpecifier(); + return nullptr; +} + +class RenameLocFindingASTVisitor + : public clang::RecursiveASTVisitor { +public: + RenameLocFindingASTVisitor(llvm::ArrayRef USRs, + ASTContext &Context) + : USRSet(USRs.begin(), USRs.end()), Context(Context) {} + + // A structure records all information of a symbol reference being renamed. + // We try to add as few prefix qualifiers as possible. + struct RenameInfo { + // The begin location of a symbol being renamed. + SourceLocation Begin; + // The end location of a symbol being renamed. + SourceLocation End; + // The declaration of a symbol being renamed (can be nullptr). + const NamedDecl *FromDecl; + // The declaration in which the nested name is contained (can be nullptr). + const Decl *Context; + // The nested name being replaced (can be nullptr). + const NestedNameSpecifier *Specifier; + }; + + // FIXME: For renaming declarations/definitions, prefix qualifiers should be + // filtered out. + bool VisitNamedDecl(const NamedDecl *Decl) { + // UsingDecl has been handled in other place. + if (llvm::isa(Decl)) + return true; + + // DestructorDecl has been handled in Typeloc. + if (llvm::isa(Decl)) + return true; + + if (Decl->isImplicit()) + return true; + + if (isInUSRSet(Decl)) { + RenameInfo Info = {Decl->getLocation(), Decl->getLocation(), nullptr, + nullptr, nullptr}; + RenameInfos.push_back(Info); + } + return true; + } + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl(); + if (isInUSRSet(Decl)) { + RenameInfo Info = {Expr->getSourceRange().getBegin(), + Expr->getSourceRange().getEnd(), Decl, + getClosestAncestorDecl(*Expr), Expr->getQualifier()}; + RenameInfos.push_back(Info); + } + + return true; + } + + bool VisitUsingDecl(const UsingDecl *Using) { + for (const auto *UsingShadow : Using->shadows()) { + if (isInUSRSet(UsingShadow->getTargetDecl())) { + UsingDecls.push_back(Using); + break; + } + } + return true; + } + + bool VisitNestedNameSpecifierLocations(NestedNameSpecifierLoc NestedLoc) { + if (!NestedLoc.getNestedNameSpecifier()->getAsType()) + return true; + if (IsTypeAliasWhichWillBeRenamedElsewhere(NestedLoc.getTypeLoc())) + return true; + + if (const auto *TargetDecl = + getSupportedDeclFromTypeLoc(NestedLoc.getTypeLoc())) { + if (isInUSRSet(TargetDecl)) { + RenameInfo Info = {NestedLoc.getBeginLoc(), + EndLocationForType(NestedLoc.getTypeLoc()), + TargetDecl, getClosestAncestorDecl(NestedLoc), + NestedLoc.getNestedNameSpecifier()->getPrefix()}; + RenameInfos.push_back(Info); + } + } + return true; + } + + bool VisitTypeLoc(TypeLoc Loc) { + if (IsTypeAliasWhichWillBeRenamedElsewhere(Loc)) + return true; + + auto Parents = Context.getParents(Loc); + TypeLoc ParentTypeLoc; + if (!Parents.empty()) { + // Handle cases of nested name specificier locations. + // + // The VisitNestedNameSpecifierLoc interface is not impelmented in + // RecursiveASTVisitor, we have to handle it explicitly. + if (const auto *NSL = Parents[0].get()) { + VisitNestedNameSpecifierLocations(*NSL); + return true; + } + + if (const auto *TL = Parents[0].get()) { + ParentTypeLoc = *TL; + } + } + + // Handle the outmost TypeLoc which is directly linked to the interesting + // declaration and don't handle nested name specifier locations. + if (const auto *TargetDecl = getSupportedDeclFromTypeLoc(Loc)) { + if (isInUSRSet(TargetDecl)) { + // Only handle the outmost typeLoc. + // + // For a type like "a::Foo", there will be two typeLocs for it. + // One ElaboratedType, the other is RecordType: + // + // ElaboratedType 0x33b9390 'a::Foo' sugar + // `-RecordType 0x338fef0 'class a::Foo' + // `-CXXRecord 0x338fe58 'Foo' + // + // Skip if this is an inner typeLoc. + if (!ParentTypeLoc.isNull() && + isInUSRSet(getSupportedDeclFromTypeLoc(ParentTypeLoc))) { + return true; + } + RenameInfo Info = {StartLocationForType(Loc), EndLocationForType(Loc), + TargetDecl, getClosestAncestorDecl(Loc), + GetNestedNameForType(Loc)}; + RenameInfos.push_back(Info); + return true; + } + } + + // Handle specific template class specialiation cases. + if (const auto *TemplateSpecType = + dyn_cast(Loc.getType())) { + TypeLoc TargetLoc = Loc; + if (!ParentTypeLoc.isNull()) { + if (llvm::isa(ParentTypeLoc.getType())) + TargetLoc = ParentTypeLoc; + } + + if (isInUSRSet(TemplateSpecType->getTemplateName().getAsTemplateDecl())) { + TypeLoc TargetLoc = Loc; + // FIXME: Find a better way to handle this case. + // For the qualified template class specification type like + // "ns::Foo" in "ns::Foo& f();", we want the parent typeLoc + // (ElaboratedType) of the TemplateSpecializationType in order to + // catch the prefix qualifiers "ns::". + if (!ParentTypeLoc.isNull() && + llvm::isa(ParentTypeLoc.getType())) + TargetLoc = ParentTypeLoc; + RenameInfo Info = { + StartLocationForType(TargetLoc), EndLocationForType(TargetLoc), + TemplateSpecType->getTemplateName().getAsTemplateDecl(), + getClosestAncestorDecl( + ast_type_traits::DynTypedNode::create(TargetLoc)), + GetNestedNameForType(TargetLoc)}; + RenameInfos.push_back(Info); + } + } + return true; + } + + // Returns a list of RenameInfo. + const std::vector &getRenameInfos() const { return RenameInfos; } + + // Returns a list of using declarations which are needed to update. + const std::vector &getUsingDecls() const { + return UsingDecls; + } + +private: + bool IsTypeAliasWhichWillBeRenamedElsewhere(TypeLoc TL) const { + while (!TL.isNull()) { + // SubstTemplateTypeParm is the TypeLocation class for a substituted type + // inside a template expansion so we ignore these. For example: + // + // template struct S { + // T t; // <-- this T becomes a TypeLoc(int) with class + // // SubstTemplateTypeParm when S is instantiated + // } + if (TL.getTypeLocClass() == TypeLoc::SubstTemplateTypeParm) { + return true; + } + + // Typedef is the TypeLocation class for a type which is a typedef to the + // type we want to replace. We ignore the use of the typedef as we will + // replace the definition of it. For example: + // + // typedef int T; + // T a; // <--- This T is a TypeLoc(int) with class Typedef. + if (TL.getTypeLocClass() == TypeLoc::Typedef) { + return true; + } + TL = TL.getNextTypeLoc(); + } + return false; + } + + // Get the supported declaration from a given typeLoc. If the declaration type + // is not supported, returns nullptr. + // + // FIXME: support more types, e.g. enum, type alias. + const NamedDecl *getSupportedDeclFromTypeLoc(TypeLoc Loc) { + if (const auto *RD = Loc.getType()->getAsCXXRecordDecl()) + return RD; + return nullptr; + } + + // Get the closest ancester which is a declaration of a given AST node. + template + const Decl *getClosestAncestorDecl(ASTNodeType Node) { + auto Parents = Context.getParents(Node); + // FIXME: figure out how to handle it when there are multiple parents. + if (Parents.size() != 1) + return nullptr; + if (ast_type_traits::ASTNodeKind::getFromNodeKind().isBaseOf( + Parents[0].getNodeKind())) { + return Parents[0].template get(); + } + return getClosestAncestorDecl(Parents[0]); + } + + // Get the parent typeLoc of a given typeLoc. If there is no such parent, + // return nullptr. + const TypeLoc *getParentTypeLoc(TypeLoc Loc) const { + auto Parents = Context.getParents(Loc); + // FIXME: figure out how to handle it when there are multiple parents. + if (Parents.size() != 1) + return nullptr; + return Parents[0].get(); + } + + // Check whether the USR of a given Decl is in the USRSet. + bool isInUSRSet(const Decl *Decl) const { + auto USR = getUSRForDecl(Decl); + if (USR.empty()) + return false; + return llvm::is_contained(USRSet, USR); + } + + const std::set USRSet; + ASTContext &Context; + std::vector RenameInfos; + std::vector UsingDecls; +}; + } // namespace std::vector @@ -163,5 +455,39 @@ return Visitor.getLocationsFound(); } +std::vector +getQualifiedRenameLocsOfUSRs(llvm::ArrayRef USRs, + llvm::StringRef OldName, llvm::StringRef NewName, + Decl *TranslationUnitDecl) { + RenameLocFindingASTVisitor Finder(USRs, TranslationUnitDecl->getASTContext()); + Finder.TraverseDecl(TranslationUnitDecl); + std::vector NewNameReplacements; + + for (const auto &Loc : Finder.getRenameInfos()) { + std::string ReplacedName = NewName.str(); + if (Loc.FromDecl && Loc.Context) { + if (!llvm::isa( + Loc.Context->getDeclContext())) { + ReplacedName = tooling::replaceNestedName( + Loc.Specifier, Loc.Context->getDeclContext(), Loc.FromDecl, + NewName.startswith("::") ? NewName.str() : ("::" + NewName).str()); + } + } + // If the NewName contains leading "::", add it back. + if (NewName.startswith("::") && NewName.substr(2) == ReplacedName) + ReplacedName = NewName.str(); + NewNameReplacements.push_back( + NewNameReplacement{Loc.Begin, Loc.End, ReplacedName}); + } + + // Hanlde using declarations explicitly. + for (const auto *Using : Finder.getUsingDecls()) { + NewNameReplacements.push_back(NewNameReplacement{ + Using->getLocStart(), Using->getLocEnd(), "using " + NewName.str()}); + } + + return NewNameReplacements; +} + } // namespace rename } // namespace clang Index: unittests/clang-rename/CMakeLists.txt =================================================================== --- unittests/clang-rename/CMakeLists.txt +++ unittests/clang-rename/CMakeLists.txt @@ -12,7 +12,7 @@ include_directories(${CLANG_SOURCE_DIR}) add_extra_unittest(ClangRenameTests - ClangRenameTests.cpp + RenameClassTest.cpp ) target_link_libraries(ClangRenameTests Index: unittests/clang-rename/ClangRenameTest.h =================================================================== --- unittests/clang-rename/ClangRenameTest.h +++ unittests/clang-rename/ClangRenameTest.h @@ -30,19 +30,19 @@ namespace clang { namespace clang_rename { -namespace { +namespace test { struct Case { std::string Before; std::string After; + std::string OldName; + std::string NewName; }; class ClangRenameTest : public testing::Test, public testing::WithParamInterface { protected: - void AppendToHeader(StringRef Code) { - HeaderContent += Code.str(); - } + void AppendToHeader(StringRef Code) { HeaderContent += Code.str(); } std::string runClangRenameOnCode(llvm::StringRef Code, llvm::StringRef OldName, @@ -71,8 +71,8 @@ const std::vector &PrevNames = FindingAction.getUSRSpellings(); std::vector NewNames = {NewName}; std::map FileToReplacements; - rename::RenamingAction RenameAction(NewNames, PrevNames, USRList, - FileToReplacements); + rename::QualifiedRenamingAction RenameAction(NewNames, PrevNames, USRList, + FileToReplacements); auto RenameActionFactory = tooling::newFrontendActionFactory(&RenameAction); if (!tooling::runToolOnCodeWithArgs( RenameActionFactory->create(), NewCode, {"-std=c++11"}, CCName, @@ -108,46 +108,6 @@ std::string CCName = "input.cc"; }; -class RenameClassTest : public ClangRenameTest { - public: - RenameClassTest() { - AppendToHeader("\nclass Foo {};\n"); - } -}; - -INSTANTIATE_TEST_CASE_P( - RenameTests, RenameClassTest, - testing::ValuesIn(std::vector({ - {"Foo f;", "Bar f;"}, - {"void f(Foo f) {}", "void f(Bar f) {}"}, - {"void f(Foo *f) {}", "void f(Bar *f) {}"}, - {"Foo f() { return Foo(); }", "Bar f() { return Bar(); }"}, - }))); - -TEST_P(RenameClassTest, RenameClasses) { - auto Param = GetParam(); - std::string OldName = "Foo"; - std::string NewName = "Bar"; - std::string Actual = runClangRenameOnCode(Param.Before, OldName, NewName); - CompareSnippets(Param.After, Actual); -} - -class RenameFunctionTest : public ClangRenameTest {}; - -INSTANTIATE_TEST_CASE_P( - RenameTests, RenameFunctionTest, - testing::ValuesIn(std::vector({ - {"void func1() {}", "void func2() {}"}, - }))); - -TEST_P(RenameFunctionTest, RenameFunctions) { - auto Param = GetParam(); - std::string OldName = "func1"; - std::string NewName = "func2"; - std::string Actual = runClangRenameOnCode(Param.Before, OldName, NewName); - CompareSnippets(Param.After, Actual); -} - -} // anonymous namespace +} // namespace test } // namespace clang_rename } // namesdpace clang Index: unittests/clang-rename/RenameClassTest.cpp =================================================================== --- /dev/null +++ unittests/clang-rename/RenameClassTest.cpp @@ -0,0 +1,670 @@ +//===-- RenameClassTest.cpp - unit tests for renaming classes -------------===// +// +// 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 RenameClassTest : public ClangRenameTest { +public: + RenameClassTest() { + AppendToHeader(R"( + namespace a { + class Foo { + public: + struct Nested { + enum NestedEnum {E1, E2}; + }; + void func() {} + }; + class Goo { + public: + struct Nested { + enum NestedEnum {E1, E2}; + }; + }; + } // namespace a + namespace b { + class Foo {}; + } // namespace b + + #define MACRO(x) x + + template class ptr {}; + )"); + } +}; + +INSTANTIATE_TEST_CASE_P( + RenameClassTests, RenameClassTest, + testing::ValuesIn(std::vector({ + // basic classes + {"a::Foo f;", "b::Bar f;", "a::Foo", "b::Bar"}, + {"void f(a::Foo f) {}", "void f(b::Bar f) {}", "a::Foo", "b::Bar"}, + {"void f(a::Foo *f) {}", "void f(b::Bar *f) {}", "a::Foo", "b::Bar"}, + {"a::Foo f() { return a::Foo(); }", "b::Bar f() { return b::Bar(); }", + "a::Foo", "b::Bar"}, + {"namespace a {a::Foo f() { return Foo(); }}", + "namespace a {b::Bar f() { return b::Bar(); }}", "a::Foo", "b::Bar"}, + {"void f(const a::Foo& a1) {}", "void f(const b::Bar& a1) {}", "a::Foo", + "b::Bar"}, + {"void f(const a::Foo* a1) {}", "void f(const b::Bar* a1) {}", "a::Foo", + "b::Bar"}, + {"namespace a { void f(Foo a1) {} }", + "namespace a { void f(b::Bar a1) {} }", "a::Foo", "b::Bar"}, + {"void f(MACRO(a::Foo) a1) {}", "void f(MACRO(b::Bar) a1) {}", "a::Foo", + "b::Bar"}, + {"void f(MACRO(a::Foo a1)) {}", "void f(MACRO(b::Bar a1)) {}", "a::Foo", + "b::Bar"}, + + // use namespace and typedefs + {"using a::Foo; Foo gA;", "using b::Bar; b::Bar gA;"}, + {"using a::Foo; void f(Foo gA) {}", "using b::Bar; void f(Bar gA) {}"}, + {"using a::Foo; namespace x { Foo gA; }", + "using b::Bar; namespace x { Bar gA; }"}, + {"struct S { using T = a::Foo; T a_; };", + "struct S { using T = b::Bar; T a_; };"}, + {"using T = a::Foo; T gA;", "using T = b::Bar; T gA;"}, + {"typedef a::Foo T; T gA;", "typedef b::Bar T; T gA;"}, + {"typedef MACRO(a::Foo) T; T gA;", "typedef MACRO(b::Bar) T; T gA;"}, + + // struct members and other oddities + {"struct S : public a::Foo {};", "struct S : public b::Bar {};"}, + {"struct F { void f(a::Foo a1) {} };", + "struct F { void f(b::Bar a1) {} };"}, + {"struct F { a::Foo a_; };", "struct F { b::Bar a_; };"}, + {"struct F { ptr a_; };", "struct F { ptr a_; };"}, + + {"void f() { a::Foo::Nested ne; }", "void f() { b::Bar::Nested ne; }", + "a::Foo", "b::Bar"}, + {"void f() { a::Goo::Nested ne; }", "void f() { a::Goo::Nested ne; }", + "a::Foo", "b::Bar"}, + {"void f() { a::Foo::Nested::NestedEnum e; }", + "void f() { b::Bar::Nested::NestedEnum e; }", "a::Foo", "b::Bar"}, + {"void f() { auto e = a::Foo::Nested::NestedEnum::E1; }", + "void f() { auto e = b::Bar::Nested::NestedEnum::E1; }", "a::Foo", + "b::Bar"}, + {"void f() { auto e = a::Foo::Nested::E1; }", + "void f() { auto e = b::Bar::Nested::E1; }", "a::Foo", "b::Bar"}, + + // templates + {"template struct Foo { T t; };\n" + "void f() { Foo foo; }", + "template struct Foo { T t; };\n" + "void f() { Foo foo; }"}, + {"template struct Foo { a::Foo a; };", + "template struct Foo { b::Bar a; };"}, + {"template void f(T t) {}\n" + "void g() { f(a::Foo()); }", + "template void f(T t) {}\n" + "void g() { f(b::Bar()); }"}, + {"template int f() { return 1; }\n" + "template <> int f() { return 2; }\n" + "int g() { return f(); }", + "template int f() { return 1; }\n" + "template <> int f() { return 2; }\n" + "int g() { return f(); }"}, + {"struct Foo { template T foo(); };\n" + "void g() { Foo f; auto a = f.template foo(); }", + "struct Foo { template T foo(); };\n" + "void g() { Foo f; auto a = f.template foo(); }"}, + + // The following two templates are distilled from regressions found in + // unique_ptr<> and type_traits.h + {"template struct outer {\n" + " typedef T type;\n" + " type Baz();\n" + " };\n" + " outer g_A;", + "template struct outer {\n" + " typedef T type;\n" + " type Baz();\n" + " };\n" + " outer g_A;"}, + {"template struct nested { typedef T type; };\n" + "template struct outer { typename nested::type Foo(); " + "};\n" + "outer g_A;", + "template struct nested { typedef T type; };\n" + "template struct outer { typename nested::type Foo(); " + "};\n" + "outer g_A;"}, + + // macros + {"#define FOO(T, t) T t\n" + "void f() { FOO(a::Foo, a1); FOO(a::Foo, a2); }", + "#define FOO(T, t) T t\n" + "void f() { FOO(b::Bar, a1); FOO(b::Bar, a2); }"}, + {"#define FOO(n) a::Foo n\n" + " void f() { FOO(a1); FOO(a2); }", + "#define FOO(n) b::Bar n\n" + " void f() { FOO(a1); FOO(a2); }"}, + + // 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 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;", + "typedef b::Bar T; auto gA = &T::func;"}, + {"auto gA = &MACRO(a::Foo)::func;", "auto gA = &MACRO(b::Bar)::func;"}, + + // Short match inside a namespace + {"namespace a { void f(Foo a1) {} }", + "namespace a { void f(b::Bar a1) {} }"}, + + // Correct match. + {"using a::Foo; struct F { ptr a_; };", + "using b::Bar; struct F { ptr a_; };"}, + + // avoid false positives + {"void f(b::Foo a) {}", "void f(b::Foo a) {}"}, + {"namespace b { void f(Foo a) {} }", + "namespace b { void f(Foo a) {} }"}, + + // friends, everyone needs friends. + {"class Foo { int i; friend class a::Foo; };", + "class Foo { int i; friend class b::Bar; };"}, + }))); + +TEST_P(RenameClassTest, RenameClasses) { + auto Param = GetParam(); + std::string OldName = "a::Foo"; + std::string NewName = "b::Bar"; + std::string Actual = runClangRenameOnCode(Param.Before, OldName, NewName); + CompareSnippets(Param.After, Actual); +} + +class NamespaceDetectionTest : public ClangRenameTest { +protected: + NamespaceDetectionTest() { + AppendToHeader(R"( + class Old {}; + namespace o1 { + class Old {}; + namespace o2 { + class Old {}; + namespace o3 { + class Old {}; + } // namespace o3 + } // namespace o2 + } // namespace o1 + )"); + } +}; + +INSTANTIATE_TEST_CASE_P( + RenameClassTest, NamespaceDetectionTest, + ::testing::ValuesIn(std::vector({ + // Test old and new namespace overlap. + {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", + "namespace o1 { namespace o2 { namespace o3 { New moo; } } }", + "o1::o2::o3::Old", "o1::o2::o3::New"}, + {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", + "namespace o1 { namespace o2 { namespace o3 { n3::New moo; } } }", + "o1::o2::o3::Old", "o1::o2::n3::New"}, + {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", + "namespace o1 { namespace o2 { namespace o3 { n2::n3::New moo; } } }", + "o1::o2::o3::Old", "o1::n2::n3::New"}, + {"namespace o1 { namespace o2 { Old moo; } }", + "namespace o1 { namespace o2 { New moo; } }", "::o1::o2::Old", + "::o1::o2::New"}, + {"namespace o1 { namespace o2 { Old moo; } }", + "namespace o1 { namespace o2 { n2::New moo; } }", "::o1::o2::Old", + "::o1::n2::New"}, + {"namespace o1 { namespace o2 { Old moo; } }", + "namespace o1 { namespace o2 { ::n1::n2::New moo; } }", + "::o1::o2::Old", "::n1::n2::New"}, + {"namespace o1 { namespace o2 { Old moo; } }", + "namespace o1 { namespace o2 { n1::n2::New moo; } }", "::o1::o2::Old", + "n1::n2::New"}, + + // Test old and new namespace with differing depths. + {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", + "namespace o1 { namespace o2 { namespace o3 { New moo; } } }", + "o1::o2::o3::Old", "::o1::New"}, + {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", + "namespace o1 { namespace o2 { namespace o3 { New moo; } } }", + "o1::o2::o3::Old", "::o1::o2::New"}, + {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", + "namespace o1 { namespace o2 { namespace o3 { New moo; } } }", + "o1::o2::o3::Old", "o1::New"}, + {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", + "namespace o1 { namespace o2 { namespace o3 { New moo; } } }", + "o1::o2::o3::Old", "o1::o2::New"}, + {"Old moo;", "o1::New moo;", "::Old", "o1::New"}, + {"Old moo;", "o1::New moo;", "Old", "o1::New"}, + {"namespace o1 { ::Old moo; }", "namespace o1 { New moo; }", "Old", + "o1::New"}, + {"namespace o1 { namespace o2 { Old moo; } }", + "namespace o1 { namespace o2 { ::New moo; } }", "::o1::o2::Old", + "::New"}, + {"namespace o1 { namespace o2 { Old moo; } }", + "namespace o1 { namespace o2 { New moo; } }", "::o1::o2::Old", "New"}, + + // Test moving into the new namespace at different levels. + {"namespace n1 { namespace n2 { o1::o2::Old moo; } }", + "namespace n1 { namespace n2 { New moo; } }", "::o1::o2::Old", + "::n1::n2::New"}, + {"namespace n1 { namespace n2 { o1::o2::Old moo; } }", + "namespace n1 { namespace n2 { New moo; } }", "::o1::o2::Old", + "n1::n2::New"}, + {"namespace n1 { namespace n2 { o1::o2::Old moo; } }", + "namespace n1 { namespace n2 { o2::New moo; } }", "::o1::o2::Old", + "::n1::o2::New"}, + {"namespace n1 { namespace n2 { o1::o2::Old moo; } }", + "namespace n1 { namespace n2 { o2::New moo; } }", "::o1::o2::Old", + "n1::o2::New"}, + {"namespace n1 { namespace n2 { o1::o2::Old moo; } }", + "namespace n1 { namespace n2 { ::o1::o2::New moo; } }", + "::o1::o2::Old", "::o1::o2::New"}, + {"namespace n1 { namespace n2 { o1::o2::Old moo; } }", + "namespace n1 { namespace n2 { o1::o2::New moo; } }", "::o1::o2::Old", + "o1::o2::New"}, + + // Test friends declarations. + {"class Foo { friend class o1::Old; };", + "class Foo { friend class o1::New; };", "o1::Old", "o1::New"}, + {"class Foo { int i; friend class o1::Old; };", + "class Foo { int i; friend class ::o1::New; };", "::o1::Old", + "::o1::New"}, + {"namespace o1 { class Foo { int i; friend class Old; }; }", + "namespace o1 { class Foo { int i; friend class New; }; }", "o1::Old", + "o1::New"}, + {"namespace o1 { class Foo { int i; friend class Old; }; }", + "namespace o1 { class Foo { int i; friend class New; }; }", + "::o1::Old", "::o1::New"}, + }))); + +TEST_P(NamespaceDetectionTest, RenameClasses) { + auto Param = GetParam(); + std::string Actual = + runClangRenameOnCode(Param.Before, Param.OldName, Param.NewName); + CompareSnippets(Param.After, Actual); +} + +class TemplatedClassRenameTest : public ClangRenameTest { +protected: + TemplatedClassRenameTest() { + AppendToHeader(R"( + template struct Old { + T t_; + T f() { return T(); }; + static T s(T t) { return t; } + }; + namespace ns { + template struct Old { + T t_; + T f() { return T(); }; + static T s(T t) { return t; } + }; + } // namespace ns + + namespace o1 { + namespace o2 { + namespace o3 { + template struct Old { + T t_; + T f() { return T(); }; + static T s(T t) { return t; } + }; + } // namespace o3 + } // namespace o2 + } // namespace o1 + )"); + } +}; + +INSTANTIATE_TEST_CASE_P( + RenameClassTests, TemplatedClassRenameTest, + ::testing::ValuesIn(std::vector({ + {"Old gI; Old gB;", "New gI; New gB;", "Old", + "New"}, + {"ns::Old gI; ns::Old gB;", + "ns::New gI; ns::New gB;", "ns::Old", "ns::New"}, + {"auto gI = &Old::f; auto gB = &Old::f;", + "auto gI = &New::f; auto gB = &New::f;", "Old", "New"}, + {"auto gI = &ns::Old::f;", "auto gI = &ns::New::f;", + "ns::Old", "ns::New"}, + + {"int gI = Old::s(0); bool gB = Old::s(false);", + "int gI = New::s(0); bool gB = New::s(false);", "Old", + "New"}, + {"int gI = ns::Old::s(0); bool gB = ns::Old::s(false);", + "int gI = ns::New::s(0); bool gB = ns::New::s(false);", + "ns::Old", "ns::New"}, + + {"struct S { Old o_; };", "struct S { New o_; };", "Old", + "New"}, + {"struct S { ns::Old o_; };", "struct S { ns::New o_; };", + "ns::Old", "ns::New"}, + + {"auto a = reinterpret_cast*>(new Old);", + "auto a = reinterpret_cast*>(new New);", "Old", "New"}, + {"auto a = reinterpret_cast*>(new ns::Old);", + "auto a = reinterpret_cast*>(new ns::New);", + "ns::Old", "ns::New"}, + {"auto a = reinterpret_cast*>(new Old);", + "auto a = reinterpret_cast*>(new New);", "Old", + "New"}, + {"auto a = reinterpret_cast*>(new ns::Old);", + "auto a = reinterpret_cast*>(new ns::New);", + "ns::Old", "ns::New"}, + + {"Old& foo();", "New& foo();", "Old", "New"}, + {"ns::Old& foo();", "ns::New& foo();", "ns::Old", + "ns::New"}, + {"o1::o2::o3::Old& foo();", "o1::o2::o3::New& foo();", + "o1::o2::o3::Old", "o1::o2::o3::New"}, + {"namespace ns { Old& foo(); }", + "namespace ns { New& foo(); }", "ns::Old", "ns::New"}, + {"const Old& foo();", "const New& foo();", "Old", "New"}, + {"const ns::Old& foo();", "const ns::New& foo();", + "ns::Old", "ns::New"}, + + // FIXME: figure out why this only works when Moo gets + // specialized at some point. + {"template struct Moo { Old o_; }; Moo m;", + "template struct Moo { New o_; }; Moo m;", "Old", + "New"}, + {"template struct Moo { ns::Old o_; }; Moo m;", + "template struct Moo { ns::New o_; }; Moo m;", + "ns::Old", "ns::New"}, + }))); + +TEST_P(TemplatedClassRenameTest, RenameTemplateClasses) { + auto Param = GetParam(); + std::string Actual = + runClangRenameOnCode(Param.Before, Param.OldName, Param.NewName); + CompareSnippets(Param.After, Actual); +} + +TEST_F(ClangRenameTest, RenameClassWithOutOfLineMembers) { + std::string Before = R"( + class Old { + public: + Old(); + ~Old(); + + Old* next(); + + private: + Old* next_; + }; + + Old::Old() {} + Old::~Old() {} + Old* Old::next() { return next_; } + )"; + std::string Expected = R"( + class New { + public: + New(); + ~New(); + + New* next(); + + private: + New* next_; + }; + + New::New() {} + New::~New() {} + New* New::next() { return next_; } + )"; + std::string After = runClangRenameOnCode(Before, "Old", "New"); + CompareSnippets(Expected, After); +} + +TEST_F(ClangRenameTest, RenameClassWithInlineMembers) { + std::string Before = R"( + class Old { + public: + Old() {} + ~Old() {} + + Old* next() { return next_; } + + private: + Old* next_; + }; + )"; + std::string Expected = R"( + class New { + public: + New() {} + ~New() {} + + New* next() { return next_; } + + private: + New* next_; + }; + )"; + std::string After = runClangRenameOnCode(Before, "Old", "New"); + CompareSnippets(Expected, After); +} + +// FIXME: no prefix qualifiers being added to the class definition and +// constructor. +TEST_F(ClangRenameTest, RenameClassWithNamespaceWithInlineMembers) { + std::string Before = R"( + namespace ns { + class Old { + public: + Old() {} + ~Old() {} + + Old* next() { return next_; } + + private: + Old* next_; + }; + } // namespace ns + )"; + std::string Expected = R"( + namespace ns { + class ns::New { + public: + ns::New() {} + ~New() {} + + New* next() { return next_; } + + private: + New* next_; + }; + } // namespace ns + )"; + std::string After = runClangRenameOnCode(Before, "ns::Old", "ns::New"); + CompareSnippets(Expected, After); +} + +// FIXME: no prefix qualifiers being added to the class definition and +// constructor. +TEST_F(ClangRenameTest, RenameClassWithNamespaceWithOutOfInlineMembers) { + std::string Before = R"( + namespace ns { + class Old { + public: + Old(); + ~Old(); + + Old* next(); + + private: + Old* next_; + }; + + Old::Old() {} + Old::~Old() {} + Old* Old::next() { return next_; } + } // namespace ns + )"; + std::string Expected = R"( + namespace ns { + class ns::New { + public: + ns::New(); + ~New(); + + New* next(); + + private: + New* next_; + }; + + New::ns::New() {} + New::~New() {} + New* New::next() { return next_; } + } // namespace ns + )"; + std::string After = runClangRenameOnCode(Before, "ns::Old", "ns::New"); + CompareSnippets(Expected, After); +} + +// FIXME: no prefix qualifiers being added to the definition. +TEST_F(ClangRenameTest, RenameClassInInheritedConstructor) { + // `using Base::Base;` will generate an implicit constructor containing usage + // of `::ns::Old` which should not be matched. + std::string Before = R"( + namespace ns { + class Old { + int x; + }; + class Base { + protected: + Old *moo_; + public: + Base(Old *moo) : moo_(moo) {} + }; + class Derived : public Base { + public: + using Base::Base; + }; + } // namespace ns + int main() { + ::ns::Old foo; + ::ns::Derived d(&foo); + return 0; + })"; + std::string Expected = R"( + namespace ns { + class ns::New { + int x; + }; + class Base { + protected: + New *moo_; + public: + Base(New *moo) : moo_(moo) {} + }; + class Derived : public Base { + public: + using Base::Base; + }; + } // namespace ns + int main() { + ::ns::New foo; + ::ns::Derived d(&foo); + return 0; + })"; + std::string After = runClangRenameOnCode(Before, "ns::Old", "ns::New"); + CompareSnippets(Expected, After); +} + +TEST_F(ClangRenameTest, DontRenameReferencesInImplicitFunction) { + std::string Before = R"( + namespace ns { + class Old { + }; + } // namespace ns + struct S { + int y; + ns::Old old; + }; + void f() { + S s1, s2, s3; + // This causes an implicit assignment operator to be created. + s1 = s2 = s3; + } + )"; + std::string Expected = R"( + namespace ns { + class ::new_ns::New { + }; + } // namespace ns + struct S { + int y; + ::new_ns::New old; + }; + void f() { + S s1, s2, s3; + // This causes an implicit assignment operator to be created. + s1 = s2 = s3; + } + )"; + std::string After = runClangRenameOnCode(Before, "ns::Old", "::new_ns::New"); + CompareSnippets(Expected, After); +} + +// FIXME: no prefix qualifiers being adding to the definition. +TEST_F(ClangRenameTest, ReferencesInLambdaFunctionParameters) { + std::string Before = R"( + template + class function; + template + class function { + public: + template + function(Functor f) {} + + function() {} + + R operator()(ArgTypes...) const {} + }; + + namespace ns { + class 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 { + class ::new_ns::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