hokein created this revision.
Herald added subscribers: mgorny, klimek.

- Add unit tests for renaming enum.
- Support unscoped enum constants in expressions.


https://reviews.llvm.org/D38989

Files:
  lib/Tooling/Refactoring/Rename/USRLocFinder.cpp
  unittests/Rename/CMakeLists.txt
  unittests/Rename/RenameEnumTest.cpp

Index: unittests/Rename/RenameEnumTest.cpp
===================================================================
--- /dev/null
+++ unittests/Rename/RenameEnumTest.cpp
@@ -0,0 +1,185 @@
+#include "ClangRenameTest.h"
+
+namespace clang {
+namespace clang_rename {
+namespace test {
+namespace {
+
+class RenameEnumTest : public ClangRenameTest {
+public:
+  RenameEnumTest() {
+    AppendToHeader(R"(
+        #define MACRO(x) x
+        namespace a {
+        enum A1 { Red };
+        enum class A2 { Blue };
+        struct C {
+         enum NestedEnum { White };
+         enum class NestedScopedEnum { Black };
+        };
+        namespace d {
+        enum A3 { Orange };
+        } // namespace d
+        enum A4 { Pink };
+        } // namespace a
+        enum A5 { Green };)");
+  }
+};
+
+INSTANTIATE_TEST_CASE_P(
+    RenameEnumTests, RenameEnumTest,
+    testing::ValuesIn(std::vector<Case>({
+        {"void f(a::A2 arg) { a::A2 t = a::A2::Blue; }",
+         "void f(b::B2 arg) { b::B2 t = b::B2::Blue; }", "a::A2", "b::B2"},
+        {"void f() { a::A1* t1; }", "void f() { b::B1* t1; }", "a::A1",
+         "b::B1"},
+        {"void f() { a::A2* t1; }", "void f() { b::B2* t1; }", "a::A2",
+         "b::B2"},
+        {"void f() { enum a::A2 t = a::A2::Blue; }",
+         "void f() { enum b::B2 t = b::B2::Blue; }", "a::A2", "b::B2"},
+        {"void f() { enum a::A2 t = a::A2::Blue; }",
+         "void f() { enum b::B2 t = b::B2::Blue; }", "a::A2", "b::B2"},
+
+        {"void f() { a::A1 t = a::Red; }", "void f() { b::B1 t = b::B1::Red; }",
+         "a::A1", "b::B1"},
+        {"void f() { a::A1 t = a::A1::Red; }",
+         "void f() { b::B1 t = b::B1::Red; }", "a::A1", "b::B1"},
+        {"void f() { auto t = a::Red; }", "void f() { auto t = b::B1::Red; }",
+         "a::A1", "b::B1"},
+        {"namespace b { void f() { a::A1 t = a::Red; } }",
+         "namespace b { void f() { B1 t = B1::Red; } }", "a::A1", "b::B1"},
+        {"void f() { a::d::A3 t = a::d::Orange; }",
+         "void f() { a::b::B3 t = a::b::B3::Orange; }", "a::d::A3", "a::b::B3"},
+        {"namespace a { void f() { a::d::A3 t = a::d::Orange; } }",
+         "namespace a { void f() { b::B3 t = b::B3::Orange; } }", "a::d::A3",
+         "a::b::B3"},
+        {"void f() { A5 t = Green; }", "void f() { B5 t = Green; }", "A5",
+         "B5"},
+
+        // namespace qualifiers
+        {"namespace a { void f(A1 a1) {} }",
+         "namespace a { void f(b::B1 a1) {} }", "a::A1", "b::B1"},
+        {"namespace a { void f(A2 a2) {} }",
+         "namespace a { void f(b::B2 a2) {} }", "a::A2", "b::B2"},
+        {"namespace b { void f(a::A1 a1) {} }",
+         "namespace b { void f(B1 a1) {} }", "a::A1", "b::B1"},
+        {"namespace b { void f(a::A2 a2) {} }",
+         "namespace b { void f(B2 a2) {} }", "a::A2", "b::B2"},
+
+        // nested enums
+        {"void f() { a::C::NestedEnum t = a::C::White; }",
+         "void f() { a::C::NewNestedEnum t = a::C::NewNestedEnum::White; }",
+         "a::C::NestedEnum", "a::C::NewNestedEnum"},
+        {"void f() { a::C::NestedScopedEnum t = a::C::NestedScopedEnum::Black; "
+         "}",
+         "void f() { a::C::NewNestedScopedEnum t = "
+         "a::C::NewNestedScopedEnum::Black; }",
+         "a::C::NestedScopedEnum", "a::C::NewNestedScopedEnum"},
+
+        // macros
+        {"void f(MACRO(a::A1) a1) {}", "void f(MACRO(b::B1) a1) {}", "a::A1",
+         "b::B1"},
+        {"void f(MACRO(a::A2) a2) {}", "void f(MACRO(b::B2) a2) {}", "a::A2",
+         "b::B2"},
+        {"#define FOO(T, t) T t\nvoid f() { FOO(a::A1, a1); }",
+         "#define FOO(T, t) T t\nvoid f() { FOO(b::B1, a1); }", "a::A1",
+         "b::B1"},
+        {"#define FOO(T, t) T t\nvoid f() { FOO(a::A2, a2); }",
+         "#define FOO(T, t) T t\nvoid f() { FOO(b::B2, a2); }", "a::A2",
+         "b::B2"},
+        {"#define FOO(n) a::A1 n\nvoid f() { FOO(a1); FOO(a2); }",
+         "#define FOO(n) b::B1 n\nvoid f() { FOO(a1); FOO(a2); }", "a::A1",
+         "b::B1"},
+
+        // using and type alias
+        {"using a::A1; A1 gA;", "using b::B1; b::B1 gA;", "a::A1", "b::B1"},
+        {"using a::A2; A2 gA;", "using b::B2; b::B2 gA;", "a::A2", "b::B2"},
+        {"struct S { using T = a::A1; T a_; };",
+         "struct S { using T = b::B1; T a_; };", "a::A1", "b::B1"},
+        {"using T = a::A1; T gA;", "using T = b::B1; T gA;", "a::A1", "b::B1"},
+        {"using T = a::A2; T gA;", "using T = b::B2; T gA;", "a::A2", "b::B2"},
+        {"typedef a::A1 T; T gA;", "typedef b::B1 T; T gA;", "a::A1", "b::B1"},
+        {"typedef a::A2 T; T gA;", "typedef b::B2 T; T gA;", "a::A2", "b::B2"},
+        {"typedef MACRO(a::A1) T; T gA;", "typedef MACRO(b::B1) T; T gA;",
+         "a::A1", "b::B1"},
+
+        // templates
+        {"template<typename T> struct Foo { T t; }; void f() { Foo<a::A1> "
+         "foo1; }",
+         "template<typename T> struct Foo { T t; }; void f() { Foo<b::B1> "
+         "foo1; }",
+         "a::A1", "b::B1"},
+        {"template<typename T> struct Foo { T t; }; void f() { Foo<a::A2> "
+         "foo2; }",
+         "template<typename T> struct Foo { T t; }; void f() { Foo<b::B2> "
+         "foo2; }",
+         "a::A2", "b::B2"},
+        {"template<typename T> struct Foo { a::A1 a1; };",
+         "template<typename T> struct Foo { b::B1 a1; };", "a::A1", "b::B1"},
+        {"template<typename T> struct Foo { a::A2 a2; };",
+         "template<typename T> struct Foo { b::B2 a2; };", "a::A2", "b::B2"},
+        {"template<typename T> int f() { return 1; } template<> int f<a::A1>() "
+         "{ return 2; } int g() { return f<a::A1>(); }",
+         "template<typename T> int f() { return 1; } template<> int f<b::B1>() "
+         "{ return 2; } int g() { return f<b::B1>(); }",
+         "a::A1", "b::B1"},
+        {"template<typename T> int f() { return 1; } template<> int f<a::A2>() "
+         "{ return 2; } int g() { return f<a::A2>(); }",
+         "template<typename T> int f() { return 1; } template<> int f<b::B2>() "
+         "{ return 2; } int g() { return f<b::B2>(); }",
+         "a::A2", "b::B2"},
+        {"struct Foo { template <typename T> T foo(); }; void g() { Foo f;  "
+         "f.foo<a::A1>(); }",
+         "struct Foo { template <typename T> T foo(); }; void g() { Foo f;  "
+         "f.foo<b::B1>(); }",
+         "a::A1", "b::B1"},
+        {"struct Foo { template <typename T> T foo(); }; void g() { Foo f;  "
+         "f.foo<a::A2>(); }",
+         "struct Foo { template <typename T> T foo(); }; void g() { Foo f;  "
+         "f.foo<b::B2>(); }",
+         "a::A2", "b::B2"},
+    })), );
+
+TEST_P(RenameEnumTest, RenameEnums) {
+  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(RenameEnumTest, RenameEnumDecl) {
+  std::string Before = R"(
+      namespace ns {
+      enum Old1 { Blue };
+      }
+  )";
+  std::string Expected = R"(
+      namespace ns {
+      enum New1 { Blue };
+      }
+  )";
+  std::string After = runClangRenameOnCode(Before, "ns::Old1", "ns::New1");
+  CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameEnumTest, RenameScopedEnumDecl) {
+  std::string Before = R"(
+      namespace ns {
+      enum class Old1 { Blue };
+      }
+  )";
+  std::string Expected = R"(
+      namespace ns {
+      enum class New1 { Blue };
+      }
+  )";
+  std::string After = runClangRenameOnCode(Before, "ns::Old1", "ns::New1");
+  CompareSnippets(Expected, After);
+}
+
+} // anonymous namespace
+} // namespace test
+} // namespace clang_rename
+} // namesdpace clang
Index: unittests/Rename/CMakeLists.txt
===================================================================
--- unittests/Rename/CMakeLists.txt
+++ unittests/Rename/CMakeLists.txt
@@ -7,6 +7,7 @@
 
 add_clang_unittest(ClangRenameTests
   RenameClassTest.cpp
+  RenameEnumTest.cpp
   RenameFunctionTest.cpp
   )
 
Index: lib/Tooling/Refactoring/Rename/USRLocFinder.cpp
===================================================================
--- lib/Tooling/Refactoring/Rename/USRLocFinder.cpp
+++ lib/Tooling/Refactoring/Rename/USRLocFinder.cpp
@@ -200,9 +200,42 @@
       Decl = UsingShadow->getTargetDecl();
     }
 
+    auto BeginLoc = Expr->getSourceRange().getBegin();
+    auto EndLoc = Expr->getSourceRange().getEnd();
+    // 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
+    // enum decl "ns1::ns2::Color") as these enum constants cannot be caught by
+    // TypeLoc.
+    if (auto* T = llvm::dyn_cast<EnumConstantDecl>(Decl)) {
+      // Ignore the case where there is no prefix qualifer for the enum constant
+      // expression like `a = Green`.
+      if (!Expr->hasQualifier())
+        return true;
+
+      if (auto *ED =
+              llvm::dyn_cast_or_null<EnumDecl>(getClosestAncestorDecl(*T))) {
+        if (ED->isScoped()) {
+          return true;
+        }
+        Decl = ED;
+      }
+      // The current fix would qualify "ns1::ns2::Green" as
+      // "ns1::ns2::Color::Green".
+      //
+      // Get the EndLoc of the replacement by moving 3 characters backward (
+      // enum constant `Green` should be excluded).
+      //    ns1::ns2::Green;
+      //    ^      ^  ^
+      // BeginLoc  |  EndLoc
+      //           new EndLoc
+      EndLoc = EndLoc.getLocWithOffset(-3);
+      assert(EndLoc.isValid() &&
+             "The enum constant should have prefix qualifers.");
+    }
     if (isInUSRSet(Decl)) {
-      RenameInfo Info = {Expr->getSourceRange().getBegin(),
-                         Expr->getSourceRange().getEnd(),
+      RenameInfo Info = {BeginLoc,
+                         EndLoc,
                          Decl,
                          getClosestAncestorDecl(*Expr),
                          Expr->getQualifier(),
@@ -364,10 +397,13 @@
   // 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.
+  // FIXME: support more types, e.g. type alias.
   const NamedDecl *getSupportedDeclFromTypeLoc(TypeLoc Loc) {
     if (const auto *RD = Loc.getType()->getAsCXXRecordDecl())
       return RD;
+    if (const auto *ED =
+            llvm::dyn_cast_or_null<EnumDecl>(Loc.getType()->getAsTagDecl()))
+      return ED;
     return nullptr;
   }
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to