[clang-rename] Add function unit tests.
authorHaojian Wu <hokein@google.com>
Mon, 16 Oct 2017 10:37:42 +0000 (10:37 +0000)
committerHaojian Wu <hokein@google.com>
Mon, 16 Oct 2017 10:37:42 +0000 (10:37 +0000)
Summary:
Also contain a fix:

* Fix a false positive of renaming a using shadow function declaration.

Reviewers: ioeric

Reviewed By: ioeric

Subscribers: klimek, mgorny, cfe-commits

Differential Revision: https://reviews.llvm.org/D38882

llvm-svn: 315898

clang/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp
clang/unittests/Rename/CMakeLists.txt
clang/unittests/Rename/RenameClassTest.cpp
clang/unittests/Rename/RenameFunctionTest.cpp [new file with mode: 0644]

index 76a173f..7f14dc2 100644 (file)
@@ -194,6 +194,12 @@ public:
 
   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<UsingShadowDecl>(Decl)) {
+      Decl = UsingShadow->getTargetDecl();
+    }
+
     if (isInUSRSet(Decl)) {
       RenameInfo Info = {Expr->getSourceRange().getBegin(),
                          Expr->getSourceRange().getEnd(),
@@ -452,6 +458,23 @@ createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs,
               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<void(T)>`) 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 aa76092..763ba3b 100644 (file)
@@ -7,6 +7,7 @@ include_directories(${CLANG_SOURCE_DIR})
 
 add_clang_unittest(ClangRenameTests
   RenameClassTest.cpp
+  RenameFunctionTest.cpp
   )
 
 target_link_libraries(ClangRenameTests
index f46126d..5845d63 100644 (file)
@@ -51,6 +51,7 @@ INSTANTIATE_TEST_CASE_P(
     testing::ValuesIn(std::vector<Case>({
         // 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(); }",
diff --git a/clang/unittests/Rename/RenameFunctionTest.cpp b/clang/unittests/Rename/RenameFunctionTest.cpp
new file mode 100644 (file)
index 0000000..ef84b4b
--- /dev/null
@@ -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 <void (*Func)(void)>
+      class TIterator {};
+
+      template <void (*Func)(void)>
+      class T {
+      public:
+        typedef TIterator<Func> IterType;
+        using TI = TIterator<Func>;
+        void g() {
+          Func();
+          auto func = Func;
+          TIterator<Func> iter;
+        }
+      };
+
+
+      void f() { T<X> tx; tx.g(); }
+      }  // namespace some_ns
+      )";
+  std::string Expected = R"(
+      void X();
+
+      // Assume that the reference is in another file.
+      namespace some_ns {
+      template <void (*Func)(void)>
+      class TIterator {};
+
+      template <void (*Func)(void)>
+      class T {
+      public:
+        typedef TIterator<Func> IterType;
+        using TI = TIterator<Func>;
+        void g() {
+          Func();
+          auto func = Func;
+          TIterator<Func> iter;
+        }
+      };
+
+
+      void f() { T<ns::X> 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<decltype<renamed_symbol>>`.
+TEST_F(ClangRenameTest, DISABLED_ReferencesInLambdaFunctionParameters) {
+  std::string Before = R"(
+      template <class T>
+      class function;
+      template <class R, class... ArgTypes>
+      class function<R(ArgTypes...)> {
+      public:
+        template <typename Functor>
+        function(Functor f) {}
+
+        function() {}
+
+        R operator()(ArgTypes...) const {}
+      };
+
+      namespace ns {
+      void Old() {}
+      void f() {
+        function<decltype(Old)> func;
+      }
+      }  // namespace ns)";
+  std::string Expected = R"(
+      template <class T>
+      class function;
+      template <class R, class... ArgTypes>
+      class function<R(ArgTypes...)> {
+      public:
+        template <typename Functor>
+        function(Functor f) {}
+
+        function() {}
+
+        R operator()(ArgTypes...) const {}
+      };
+
+      namespace ns {
+      void New() {}
+      void f() {
+        function<decltype(::new_ns::New)> 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