kadircet created this revision.
kadircet added reviewers: sammccall, ilya-biryukov.
Herald added subscribers: cfe-commits, arphaman, jkorous, MaskRay.
Herald added a project: clang.
kadircet added a parent revision: D65433: [clangd] DefineInline action 
availability checks.

Initial version of DefineInline action that will fully qualify every
name inside function body. It has some problems while qualyfying dependent
template arguments, see FIXME in the unittests.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D66647

Files:
  clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp
  clang-tools-extra/clangd/unittests/TweakTests.cpp

Index: clang-tools-extra/clangd/unittests/TweakTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/TweakTests.cpp
+++ clang-tools-extra/clangd/unittests/TweakTests.cpp
@@ -15,6 +15,7 @@
 #include "clang/AST/Expr.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Rewrite/Core/Rewriter.h"
+#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
 #include "clang/Tooling/Core/Replacement.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
@@ -75,10 +76,12 @@
     auto T = prepareTweak(ID, Sel);
     if (Available)
       EXPECT_THAT_EXPECTED(T, Succeeded())
-          << "code is " << markRange(Code.code(), Selection);
+          << "code is " << markRange(Code.code(), Selection)
+          << Sel.ASTSelection;
     else
       EXPECT_THAT_EXPECTED(T, Failed())
-          << "code is " << markRange(Code.code(), Selection);
+          << "code is " << markRange(Code.code(), Selection)
+          << Sel.ASTSelection;
   };
   for (auto P : Code.points())
     CheckOver(Range{P, P});
@@ -101,7 +104,9 @@
                         std::move(AdditionalFiles));
 }
 
-llvm::Expected<Tweak::Effect> apply(StringRef ID, llvm::StringRef Input) {
+llvm::Expected<Tweak::Effect>
+apply(StringRef ID, llvm::StringRef Input,
+      const llvm::StringMap<TransformInputFile> &AdditionalFiles = {}) {
   Annotations Code(Input);
   Range SelectionRng;
   if (Code.points().size() != 0) {
@@ -114,6 +119,9 @@
   TestTU TU;
   TU.Filename = "foo.cpp";
   TU.Code = Code.code();
+  for (auto &InpFile : AdditionalFiles) {
+    TU.AdditionalFiles[InpFile.first()] = InpFile.second.InitialText;
+  }
 
   ParsedAST AST = TU.build();
   unsigned Begin = cantFail(positionToOffset(Code.code(), SelectionRng.start));
@@ -126,26 +134,44 @@
   return (*T)->apply(S);
 }
 
-llvm::Expected<std::string> applyEdit(StringRef ID, llvm::StringRef Input) {
-  auto Effect = apply(ID, Input);
+llvm::Expected<llvm::StringMap<std::string>>
+applyEdit(StringRef ID, llvm::StringRef Input,
+          const llvm::StringMap<TransformInputFile> &AdditionalFiles = {}) {
+  auto Effect = apply(ID, Input, AdditionalFiles);
   if (!Effect)
     return Effect.takeError();
   if (!Effect->ApplyEdits)
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "No replacements");
-  auto Edits = *Effect->ApplyEdits;
-  if (Edits.size() > 1)
-    return llvm::createStringError(llvm::inconvertibleErrorCode(),
-                                   "Multi-file edits");
-  Annotations Code(Input);
-  return applyAllReplacements(Code.code(), Edits.begin()->second.Reps);
+  llvm::StringMap<std::string> Output;
+  for (const auto &It : *Effect->ApplyEdits) {
+    llvm::StringRef FilePath = It.first();
+    FilePath.consume_front(testRoot());
+    FilePath.consume_front("/");
+    auto InpIt = AdditionalFiles.find(FilePath);
+    if (InpIt == AdditionalFiles.end())
+      FilePath = "";
+    Annotations Code(InpIt == AdditionalFiles.end()
+                         ? Input
+                         : llvm::StringRef(InpIt->second.InitialText));
+    auto Replaced = applyAllReplacements(Code.code(), It.second.Reps);
+    if (!Replaced)
+      return Replaced.takeError();
+    Output[FilePath] = *Replaced;
+  }
+  return Output;
 }
 
 void checkTransform(llvm::StringRef ID, llvm::StringRef Input,
-                    std::string Output) {
-  auto Result = applyEdit(ID, Input);
+                    std::string Output,
+                    llvm::StringMap<TransformInputFile> AdditionalFiles = {}) {
+  auto Result = applyEdit(ID, Input, AdditionalFiles);
   ASSERT_TRUE(bool(Result)) << llvm::toString(Result.takeError()) << Input;
-  EXPECT_EQ(Output, std::string(*Result)) << Input;
+  EXPECT_EQ(Output, Result->lookup("")) << Input;
+  for (const auto &File : AdditionalFiles) {
+    llvm::errs() << "Looking for: " << File.first() << '\n';
+    EXPECT_EQ(File.second.ModifiedText, Result->lookup(File.first())) << Input;
+  }
 }
 
 TWEAK_TEST(SwapIfBranches);
@@ -154,7 +180,8 @@
   EXPECT_EQ(apply("^if (true) {return 100;} else {continue;}"),
             "if (true) {continue;} else {return 100;}");
   EXPECT_EQ(apply("^if () {return 100;} else {continue;}"),
-            "if () {continue;} else {return 100;}") << "broken condition";
+            "if () {continue;} else {return 100;}")
+      << "broken condition";
   EXPECT_AVAILABLE("^i^f^^(^t^r^u^e^) { return 100; } ^e^l^s^e^ { continue; }");
   EXPECT_UNAVAILABLE("if (true) {^return ^100;^ } else { ^continue^;^ }");
   // Available in subexpressions of the condition;
@@ -185,7 +212,7 @@
   EXPECT_UNAVAILABLE(R"cpp(R"(multi )" ^"token " u8"str\ning")cpp"); // nonascii
   EXPECT_UNAVAILABLE(R"cpp(^R^"^(^multi )" "token " "str\ning")cpp"); // raw
   EXPECT_UNAVAILABLE(R"cpp(^"token\n" __FILE__)cpp"); // chunk is macro
-  EXPECT_UNAVAILABLE(R"cpp(^"a\r\n";)cpp"); // forbidden escape char
+  EXPECT_UNAVAILABLE(R"cpp(^"a\r\n";)cpp");           // forbidden escape char
 
   const char *Input = R"cpp(R"(multi
 token)" "\nst^ring\n" "literal")cpp";
@@ -366,11 +393,11 @@
                  void f(int a) {
                    int y = PLUS([[1+a]]);
                  })cpp",
-          /*FIXME: It should be extracted like this.
-           R"cpp(#define PLUS(x) x++
-                 void f(int a) {
-                   auto dummy = 1+a; int y = PLUS(dummy);
-                 })cpp"},*/
+           /*FIXME: It should be extracted like this.
+            R"cpp(#define PLUS(x) x++
+                  void f(int a) {
+                    auto dummy = 1+a; int y = PLUS(dummy);
+                  })cpp"},*/
            R"cpp(#define PLUS(x) x++
                  void f(int a) {
                    auto dummy = PLUS(1+a); int y = dummy;
@@ -381,13 +408,13 @@
                    if(1)
                     LOOP(5 + [[3]])
                  })cpp",
-          /*FIXME: It should be extracted like this. SelectionTree needs to be
-            * fixed for macros.
-           R"cpp(#define LOOP(x) while (1) {a = x;}
-               void f(int a) {
-                 auto dummy = 3; if(1)
-                  LOOP(5 + dummy)
-               })cpp"},*/
+           /*FIXME: It should be extracted like this. SelectionTree needs to be
+             * fixed for macros.
+            R"cpp(#define LOOP(x) while (1) {a = x;}
+                void f(int a) {
+                  auto dummy = 3; if(1)
+                   LOOP(5 + dummy)
+                })cpp"},*/
            R"cpp(#define LOOP(x) while (1) {a = x;}
                  void f(int a) {
                    auto dummy = LOOP(5 + 3); if(1)
@@ -483,8 +510,8 @@
                  void f() {
                    auto dummy = S(2) + S(3) + S(4); S x = S(1) + dummy + S(5);
                  })cpp"},
-           // Don't try to analyze across macro boundaries
-           // FIXME: it'd be nice to do this someday (in a safe way)
+          // Don't try to analyze across macro boundaries
+          // FIXME: it'd be nice to do this someday (in a safe way)
           {R"cpp(#define ECHO(X) X
                  void f() {
                    int x = 1 + [[ECHO(2 + 3) + 4]] + 5;
@@ -512,26 +539,27 @@
   checkAvailable(ID, "^vo^id^ ^f(^) {^}^"); // available everywhere.
   checkAvailable(ID, "[[int a; int b;]]");
   const char *Input = "void ^f() {}";
-  const char *Output = "/* storage.type.primitive.cpp */void /* entity.name.function.cpp */f() {}";
+  const char *Output = "/* storage.type.primitive.cpp */void /* "
+                       "entity.name.function.cpp */f() {}";
   checkTransform(ID, Input, Output);
 
   checkTransform(ID,
-  R"cpp(
+                 R"cpp(
 [[void f1();
 void f2();]]
 )cpp",
-  R"cpp(
+                 R"cpp(
 /* storage.type.primitive.cpp */void /* entity.name.function.cpp */f1();
 /* storage.type.primitive.cpp */void /* entity.name.function.cpp */f2();
 )cpp");
 
-   checkTransform(ID,
-  R"cpp(
+  checkTransform(ID,
+                 R"cpp(
 void f1();
 void f2() {^};
 )cpp",
 
-  R"cpp(
+                 R"cpp(
 void f1();
 /* storage.type.primitive.cpp */void /* entity.name.function.cpp */f2() {};
 )cpp");
@@ -611,7 +639,7 @@
               StartsWith("fail: Could not expand type of lambda expression"));
   // inline namespaces
   EXPECT_EQ(apply("au^to x = inl_ns::Visible();"),
-              "Visible x = inl_ns::Visible();");
+            "Visible x = inl_ns::Visible();");
   // local class
   EXPECT_EQ(apply("namespace x { void y() { struct S{}; ^auto z = S(); } }"),
             "namespace x { void y() { struct S{}; S z = S(); } }");
@@ -725,6 +753,469 @@
                     {{"b.h", "void foo(int test);"}, {"a.h", "void bar();"}});
 }
 
+TEST(DefineInline, TransformUsings) {
+  llvm::StringLiteral ID = "DefineInline";
+
+  checkTransform(ID, R"cpp(
+  namespace a {
+  void bar();
+  namespace b {
+  void baz();
+  namespace c {
+  void aux();
+  }
+  }
+  }
+
+  void foo();
+  void f^oo() {
+    using namespace a;
+
+    using namespace b;
+    using namespace a::b;
+
+    using namespace c;
+    using namespace b::c;
+    using namespace a::b::c;
+
+    using a::bar;
+
+    using b::baz;
+    using a::b::baz;
+
+    using c::aux;
+    using b::c::aux;
+    using a::b::c::aux;
+
+    namespace d = c;
+    namespace d = b::c;
+  }
+  )cpp",
+                 R"cpp(
+  namespace a {
+  void bar();
+  namespace b {
+  void baz();
+  namespace c {
+  void aux();
+  }
+  }
+  }
+
+  void foo(){
+    using namespace a;
+
+    using namespace a::b;
+    using namespace a::b;
+
+    using namespace a::b::c;
+    using namespace a::b::c;
+    using namespace a::b::c;
+
+    using a::bar;
+
+    using a::b::baz;
+    using a::b::baz;
+
+    using a::b::c::aux;
+    using a::b::c::aux;
+    using a::b::c::aux;
+
+    namespace d = a::b::c;
+    namespace d = a::b::c;
+  }
+  
+  )cpp");
+}
+
+TEST(DefineInline, TransformDecls) {
+  llvm::StringLiteral ID = "DefineInline";
+
+  checkTransform(ID, R"cpp(
+  void foo()    /*Comment -_-*/  ;
+
+  void f^oo() {
+    class Foo {
+    public:
+      void foo();
+      int x;
+      static int y;
+    };
+    Foo::y = 0;
+
+    enum En { Zero, One };
+    En x = Zero;
+
+    enum class EnClass { Zero, One };
+    EnClass y = EnClass::Zero;
+
+    template <typename T> class Bar {};
+    Bar<int> z;
+  }
+  )cpp",
+                 R"cpp(
+  void foo()    /*Comment -_-*/  {
+    class Foo {
+    public:
+      void foo();
+      int x;
+      static int y;
+    };
+    Foo::y = 0;
+
+    enum En { Zero, One };
+    En x = Zero;
+
+    enum class EnClass { Zero, One };
+    EnClass y = EnClass::Zero;
+
+    template <typename T> class Bar {};
+    Bar<int> z;
+  }
+
+  
+  )cpp");
+}
+
+TEST(DefineInline, TransformTemplDecls) {
+  llvm::StringLiteral ID = "DefineInline";
+
+  checkTransform(ID, R"cpp(
+  namespace a {
+    template <typename T> class Bar {
+    public:
+      void bar();
+    };
+    template <typename T> T bar;
+    template <typename T> void aux() {}
+  }
+
+  void foo()    /*Comment -_-*/  ;
+
+  void f^oo() {
+    using namespace a;
+    bar<Bar<int>>.bar();
+    aux<Bar<int>>();
+  }
+  )cpp",
+                 R"cpp(
+  namespace a {
+    template <typename T> class Bar {
+    public:
+      void bar();
+    };
+    template <typename T> T bar;
+    template <typename T> void aux() {}
+  }
+
+  void foo()    /*Comment -_-*/  {
+    using namespace a;
+    a::bar<a::Bar<int> >.bar();
+    a::aux<a::Bar<int> >();
+  }
+
+  
+  )cpp");
+}
+
+TEST(DefineInline, TransformMembers) {
+  llvm::StringLiteral ID = "DefineInline";
+
+  checkTransform(ID, R"cpp(
+  class Foo {
+    void foo()    /*Comment -_-*/  ;
+  };
+
+  void Foo::f^oo() {
+    return;
+  }
+  )cpp",
+                 R"cpp(
+  class Foo {
+    void foo()    /*Comment -_-*/  {
+    return;
+  }
+  };
+
+  
+  )cpp");
+
+  checkTransform(ID, R"cpp(
+  #include "a.h"
+  void Foo::f^oo() {
+    return;
+  }
+  )cpp",
+                 R"cpp(
+  #include "a.h"
+  
+  )cpp",
+                 {{"a.h",
+                   {R"cpp(
+  class Foo {
+    void foo()    /*Comment -_-*/  ;
+  };
+
+  
+  )cpp",
+                    R"cpp(
+  class Foo {
+    void foo()    /*Comment -_-*/  {
+    return;
+  }
+  };
+
+  
+  )cpp"}}});
+}
+
+TEST(DefineInline, TransformDependentTypes) {
+  llvm::StringLiteral ID = "DefineInline";
+
+  checkTransform(ID, R"cpp(
+  namespace a {
+    template <typename T> class Bar {};
+  }
+  using namespace a;
+
+  template <typename T>
+  void foo()    /*Comment -_-*/  ;
+
+  template <typename T>
+  void f^oo() {
+    Bar<T> B;
+    // FIXME: This should be a::Bar<a::Bar<T> >
+    Bar<Bar<T>> q;
+  }
+  )cpp",
+                 R"cpp(
+  namespace a {
+    template <typename T> class Bar {};
+  }
+  using namespace a;
+
+  template <typename T>
+  void foo()    /*Comment -_-*/  {
+    a::Bar<T> B;
+    // FIXME: This should be a::Bar<a::Bar<T> >
+    a::Bar<Bar<T> > q;
+  }
+
+  
+  )cpp");
+}
+
+TEST(DefineInline, TransformFunctionTempls) {
+  llvm::StringLiteral ID = "DefineInline";
+
+  checkTransform(ID, R"cpp(
+  template <typename T>
+  void foo(T p);
+
+  template <>
+  void foo<int>(int p);
+
+  template <>
+  void foo<char>(char p);
+
+  template <>
+  void fo^o<int>(int p) {
+    return;
+  }
+  )cpp",
+                 R"cpp(
+  template <typename T>
+  void foo(T p);
+
+  template <>
+  void foo<int>(int p){
+    return;
+  }
+
+  template <>
+  void foo<char>(char p);
+
+  
+  )cpp");
+
+  checkTransform(ID, R"cpp(
+  template <typename T>
+  void foo(T p);
+
+  template <>
+  void foo<int>(int p);
+
+  template <>
+  void foo<char>(char p);
+
+  template <>
+  void fo^o<char>(char p) {
+    return;
+  }
+  )cpp",
+                 R"cpp(
+  template <typename T>
+  void foo(T p);
+
+  template <>
+  void foo<int>(int p);
+
+  template <>
+  void foo<char>(char p){
+    return;
+  }
+
+  
+  )cpp");
+
+  checkTransform(ID, R"cpp(
+  template <typename T>
+  void foo(T p);
+
+  template <>
+  void foo<int>(int p);
+
+  template <typename T>
+  void fo^o(T p) {
+    return;
+  }
+  )cpp",
+                 R"cpp(
+  template <typename T>
+  void foo(T p){
+    return;
+  }
+
+  template <>
+  void foo<int>(int p);
+
+  
+  )cpp");
+}
+
+TEST(DefineInline, TransformTypeLocs) {
+  llvm::StringLiteral ID = "DefineInline";
+
+  checkTransform(ID, R"cpp(
+  namespace a {
+    template <typename T> class Bar {
+    public:
+      template <typename Q> class Baz {};
+    };
+    namespace b{
+    class Foo{};
+    };
+  }
+  using namespace a;
+  using namespace b;
+  using namespace c;
+
+  void foo()    /*Comment -_-*/  ;
+
+  void f^oo() {
+    Bar<int> B;
+    b::Foo foo;
+    a::Bar<Bar<int>>::Baz<Bar<int>> q;
+  }
+  )cpp",
+                 R"cpp(
+  namespace a {
+    template <typename T> class Bar {
+    public:
+      template <typename Q> class Baz {};
+    };
+    namespace b{
+    class Foo{};
+    };
+  }
+  using namespace a;
+  using namespace b;
+  using namespace c;
+
+  void foo()    /*Comment -_-*/  {
+    a::Bar<int> B;
+    a::b::Foo foo;
+    a::Bar<a::Bar<int> >::Baz<a::Bar<int> > q;
+  }
+
+  
+  )cpp");
+}
+
+TEST(DefineInline, TransformDeclRefs) {
+  llvm::StringLiteral ID = "DefineInline";
+  checkTransform(ID, R"cpp(
+  namespace a {
+    template <typename T> class Bar {
+    public:
+      void foo();
+      static void bar();
+      int x;
+      static int y;
+    };
+    void bar();
+    namespace b {
+    class Foo{};
+    namespace c {
+    void test();
+    }
+    }
+  }
+  using namespace a;
+  using namespace b;
+  using namespace c;
+
+  void foo()    /*Comment -_-*/  ;
+
+  void f^oo() {
+    a::Bar<int> B;
+    B.foo();
+    a::bar();
+    Bar<Bar<int>>::bar();
+    a::Bar<int>::bar();
+    B.x = Bar<int>::y;
+    Bar<int>::y = 3;
+    bar();
+    c::test();
+  }
+  )cpp",
+                 R"cpp(
+  namespace a {
+    template <typename T> class Bar {
+    public:
+      void foo();
+      static void bar();
+      int x;
+      static int y;
+    };
+    void bar();
+    namespace b {
+    class Foo{};
+    namespace c {
+    void test();
+    }
+    }
+  }
+  using namespace a;
+  using namespace b;
+  using namespace c;
+
+  void foo()    /*Comment -_-*/  {
+    a::Bar<int> B;
+    B.foo();
+    a::bar();
+    a::Bar<a::Bar<int> >::bar();
+    a::Bar<int>::bar();
+    B.x = a::Bar<int>::y;
+    a::Bar<int>::y = 3;
+    a::bar();
+    a::b::c::test();
+  }
+
+  
+  )cpp");
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp
===================================================================
--- clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp
+++ clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp
@@ -59,6 +59,26 @@
 namespace clangd {
 namespace {
 
+// Lexes from end of \p FD until it finds a semicolon.
+llvm::Optional<SourceLocation> getSemiColonForDecl(const FunctionDecl *FD) {
+  const SourceManager &SM = FD->getASTContext().getSourceManager();
+  const LangOptions &LangOpts = FD->getASTContext().getLangOpts();
+
+  SourceLocation SemiColon =
+      Lexer::getLocForEndOfToken(FD->getEndLoc(), 0, SM, LangOpts);
+  llvm::StringRef BufData = SM.getBufferData(SM.getFileID(SemiColon));
+  Lexer RawLexer(SM.getLocForStartOfFile(SM.getFileID(SemiColon)), LangOpts,
+                 BufData.begin(), SM.getCharacterData(SemiColon),
+                 BufData.end());
+  Token CurToken;
+  while (!CurToken.is(tok::semi)) {
+    if (RawLexer.LexFromRawLexer(CurToken))
+      return llvm::None;
+  }
+  SemiColon = CurToken.getLocation();
+  return SemiColon;
+}
+
 // Deduces the FunctionDecl from a selection. Requires either the function body
 // or the function decl to be selected. Returns null if none of the above
 // criteria is met.
@@ -74,6 +94,251 @@
   return nullptr;
 }
 
+// There are three types of spellings that needs to be qualified in a function
+// body:
+// - Types:       Foo                 -> ns::Foo
+// - DeclRefExpr: ns2::foo()          -> ns1::ns2::foo();
+// - UsingDecls:
+//    using ns2::foo      -> using ns1::ns2::foo
+//    using namespace ns2 -> using namespace ns1::ns2
+//    using ns3 = ns2     -> using ns3 = ns1::ns2
+//
+// Goes over all DeclRefExprs, TypeLocs, and Using{,Directive}Decls inside a
+// function body to generate replacements that will fully qualify those. So that
+// body can be moved into an arbitrary file.
+// We perform the qualification by qualyfying the last type/decl in a
+// (un)qualified name. e.g:
+//    namespace a { namespace b { class Bar{}; void foo(); } }
+//    b::Bar x; -> a::b::Bar x;
+//    foo(); -> a::b::foo();
+// Currently there is no way to know whether a given TypeLoc is the last one or
+// not, therefore we generate replacements for all the TypeLocs we see in a
+// given name and pick only the longest one.
+// FIXME: Instead of fully qualyfying we should try deducing visible scopes at
+// target location and generate minimal edits.
+class QualifyingVisitor : public RecursiveASTVisitor<QualifyingVisitor> {
+public:
+  QualifyingVisitor(const FunctionDecl *FD)
+      : LangOpts(FD->getASTContext().getLangOpts()),
+        SM(FD->getASTContext().getSourceManager()),
+        BodyBegin(SM.getFileOffset(FD->getBody()->getBeginLoc())) {
+    TraverseStmt(FD->getBody());
+  }
+
+  // We override traversals for DeclRefExprs and TypeLocs to generate an edit
+  // only for the last Type/Decl in a written name. Also it enables us to catch
+  // current range of the name as written, since the last Type/Decl doesn't
+  // contain that information.
+  bool TraverseDeclRefExpr(DeclRefExpr *DRE) {
+    maybeUpdateCurrentRange(DRE->getSourceRange());
+    return RecursiveASTVisitor<QualifyingVisitor>::TraverseDeclRefExpr(DRE);
+  }
+
+  bool TraverseTypeLoc(TypeLoc TL) {
+    maybeUpdateCurrentRange(TL.getSourceRange());
+    return RecursiveASTVisitor<QualifyingVisitor>::TraverseTypeLoc(TL);
+  }
+
+  // Generates a replacement that will qualify templated name and arguments.
+  bool VisitTemplateSpecializationTypeLoc(TemplateSpecializationTypeLoc TSTL) {
+    std::string Qualified;
+    llvm::raw_string_ostream OS(Qualified);
+
+    QualType Ty = TSTL.getType();
+    if (Ty->isDependentType()) {
+      // We don't have a decl if type is dependent, use the TemplateDecl
+      // instead.
+      const TemplateDecl *TD =
+          TSTL.getTypePtr()->getTemplateName().getAsTemplateDecl();
+
+      TD->printQualifiedName(OS);
+      // FIXME: For some reason this prints types as written in source code,
+      // instead of fully qualified version,
+      //  i.e: a::Bar<Bar<T>> instead of a::Bar<a::Bar<T>>
+      printTemplateArgumentList(OS, TSTL.getTypePtr()->template_arguments(),
+                                TD->getASTContext().getPrintingPolicy());
+    } else if (const auto *CTSD =
+                   llvm::dyn_cast<ClassTemplateSpecializationDecl>(
+                       TSTL.getType()->getAsTagDecl())) {
+
+      CTSD->printQualifiedName(OS);
+      printTemplateArgumentList(OS, CTSD->getTemplateArgs().asArray(),
+                                CTSD->getASTContext().getPrintingPolicy());
+    }
+    OS.flush();
+
+    maybeUpdateReplacement(Qualified);
+    return true;
+  }
+
+  // Qualifies TagTypes, we are not interested in other TypeLocs since they
+  // can't have nested name specifiers.
+  bool VisitTagTypeLoc(TagTypeLoc TTL) {
+    const TagDecl *TD = TTL.getDecl();
+    if (index::isFunctionLocalSymbol(TD))
+      return true;
+
+    // FIXME: Instead of fully qualifying, try to drop visible scopes.
+    maybeUpdateReplacement(TD->getQualifiedNameAsString());
+    return true;
+  }
+
+  bool VisitDeclRefExpr(DeclRefExpr *DRE) {
+    NamedDecl *ND = DRE->getFoundDecl();
+    // UsingShadowDecls are considered local, but we might need to qualify them
+    // if the UsingDecl is not inside function body.
+    if (auto USD = llvm::dyn_cast<UsingShadowDecl>(ND))
+      ND = USD->getTargetDecl();
+    // No need to re-write local symbols.
+    if (index::isFunctionLocalSymbol(ND))
+      return true;
+
+    std::string Qualified;
+    // For templates, in addition to decl name, also print the argument list.
+    if (auto *VTSD = llvm::dyn_cast<VarTemplateSpecializationDecl>(ND)) {
+      llvm::raw_string_ostream OS(Qualified);
+      VTSD->printQualifiedName(OS);
+      printTemplateArgumentList(OS, VTSD->getTemplateArgs().asArray(),
+                                ND->getASTContext().getPrintingPolicy());
+    } else if (auto *FTD = llvm::dyn_cast<FunctionTemplateDecl>(ND)) {
+      llvm::raw_string_ostream OS(Qualified);
+      FTD->printQualifiedName(OS);
+      printTemplateArgumentList(OS,
+                                llvm::dyn_cast<FunctionDecl>(DRE->getDecl())
+                                    ->getTemplateSpecializationArgs()
+                                    ->asArray(),
+                                ND->getASTContext().getPrintingPolicy());
+    } else {
+      Qualified = ND->getQualifiedNameAsString();
+    }
+    maybeUpdateReplacement(Qualified);
+    return true;
+  }
+
+  // For using decls, we chose the first shadow decls. This should not make
+  // any difference, since all of the shadow decls should have the same
+  // fully-qualified name.
+  bool VisitUsingDecl(UsingDecl *UD) {
+    flushCurrentReplacement();
+    maybeUpdateCurrentRange(
+        SourceRange(UD->getQualifierLoc().getBeginLoc(), UD->getEndLoc()));
+    maybeUpdateReplacement(
+        UD->shadow_begin()->getTargetDecl()->getQualifiedNameAsString());
+    return true;
+  }
+
+  // Using-directives are "special" NamedDecls, they span the whole
+  // declaration and their names are not directly useful, we need to peek
+  // into underlying NamespaceDecl for qualified name and start location.
+  bool VisitUsingDirectiveDecl(UsingDirectiveDecl *UDD) {
+    flushCurrentReplacement();
+
+    SourceRange RepRange;
+    if (auto NNSL = UDD->getQualifierLoc())
+      RepRange.setBegin(NNSL.getBeginLoc());
+    else
+      RepRange.setBegin(UDD->getIdentLocation());
+    RepRange.setEnd(UDD->getEndLoc());
+
+    maybeUpdateCurrentRange(RepRange);
+    maybeUpdateReplacement(
+        UDD->getNominatedNamespaceAsWritten()->getQualifiedNameAsString());
+    return true;
+  }
+
+  // Qualifies namespace alias decls of the form:
+  //  using newns = ns2 -> using newns = ns1::ns2
+  bool VisitNamespaceAliasDecl(NamespaceAliasDecl *NAD) {
+    flushCurrentReplacement();
+
+    SourceRange RepRange;
+    if (auto NNSL = NAD->getQualifierLoc())
+      RepRange.setBegin(NNSL.getBeginLoc());
+    else
+      RepRange.setBegin(NAD->getTargetNameLoc());
+    RepRange.setEnd(NAD->getEndLoc());
+
+    maybeUpdateCurrentRange(RepRange);
+    maybeUpdateReplacement(
+        NAD->getAliasedNamespace()->getQualifiedNameAsString());
+    return true;
+  }
+
+  tooling::Replacements getReplacements() {
+    // flush the last replacement.
+    flushCurrentReplacement();
+    return Reps;
+  }
+
+private:
+  // Updates the CurrentRange to NewRange if it is not overlapping with
+  // CurrentRange. Also flushes the replacement for CurrentRange before updating
+  // it.
+  void maybeUpdateCurrentRange(SourceRange NewRange) {
+    if (NewRange.isInvalid())
+      return;
+
+    if (CurrentChange &&
+        SM.isBeforeInSLocAddrSpace(
+            NewRange.getBegin(),
+            // We use EndLoc+1 since CurrentChange also owns the token
+            // starting at EndLoc.
+            CurrentChange->Range.getEnd().getLocWithOffset(1))) {
+      return;
+    }
+
+    flushCurrentReplacement();
+    CurrentChange.emplace();
+    CurrentChange->Range = NewRange;
+  }
+
+  // Changes CurReplacement to contain RepText if it is longer.
+  void maybeUpdateReplacement(llvm::StringRef RepText) {
+    assert(CurrentChange && "Updating non-existing replacement");
+    if (CurrentChange->Text.size() > RepText.size())
+      return;
+    CurrentChange->Text = RepText;
+  }
+
+  // Adds current replacement, the longest one for the current range, to the
+  // Reps.
+  void flushCurrentReplacement() {
+    if (!CurrentChange || CurrentChange->Text.empty())
+      return;
+    SourceLocation Begin = CurrentChange->Range.getBegin();
+    SourceLocation End = Lexer::getLocForEndOfToken(
+        CurrentChange->Range.getEnd(), 0, SM, LangOpts);
+
+    unsigned int BeginOff = SM.getFileOffset(Begin) - BodyBegin;
+    unsigned int EndOff = SM.getFileOffset(End) - BodyBegin;
+    const tooling::Replacement Replacement("", BeginOff, EndOff - BeginOff,
+                                           CurrentChange->Text);
+    CurrentChange.reset();
+
+    if (auto Err = Reps.add(Replacement)) {
+      handleAllErrors(std::move(Err), [&](const llvm::ErrorInfoBase &EI) {
+        std::string ErrMsg;
+        llvm::raw_string_ostream OS(ErrMsg);
+        EI.log(OS);
+        elog("Failed to add replacement:{0}", ErrMsg);
+      });
+    }
+  }
+
+  const LangOptions LangOpts;
+  const SourceManager &SM;
+  // Used as reference points for replacements.
+  const unsigned int BodyBegin;
+  tooling::Replacements Reps;
+
+  struct Change {
+    SourceRange Range; // to be replaced in the original source.
+    std::string Text;  // to replace the range.
+  };
+  // Change to apply for ast node currently being visited.
+  llvm::Optional<Change> CurrentChange;
+};
+
 // Runs clang indexing api to get a list of declarations referenced inside
 // function decl. Skips local symbols.
 llvm::DenseSet<const Decl *> getNonLocalDeclRefs(const FunctionDecl *FD,
@@ -156,6 +421,20 @@
   return true;
 }
 
+// Rewrites body of FD to fully-qualify all of the decls inside.
+llvm::Expected<std::string> qualifyAllDecls(const FunctionDecl *FD) {
+  SourceRange OrigFuncRange = FD->getBody()->getSourceRange();
+  // toSourceCode expects an halfOpenRange, but FuncBody is closed.
+  OrigFuncRange.setEnd(OrigFuncRange.getEnd().getLocWithOffset(1));
+  // applyAllReplacements expects a null terminated string, therefore we make a
+  // copy here.
+  std::string OrigFuncBody =
+      toSourceCode(FD->getASTContext().getSourceManager(), OrigFuncRange);
+
+  return tooling::applyAllReplacements(OrigFuncBody,
+                                       QualifyingVisitor(FD).getReplacements());
+}
+
 // Returns the canonical declaration for the given FunctionDecl. This will
 // usually be the first declaration in current translation unit with the
 // exception of template specialization. For those we return the previous
@@ -170,6 +449,15 @@
   return FD->getCanonicalDecl();
 }
 
+// Returns the begining location for a FunctionDecl. Returns location of
+// template keyword for templated functions.
+const SourceLocation getBeginLoc(const FunctionDecl *FD) {
+  // Include template parameter list.
+  if (auto *FTD = FD->getDescribedFunctionTemplate())
+    return FTD->getBeginLoc();
+  return FD->getBeginLoc();
+}
+
 /// Moves definition of a function to its declaration location.
 /// Before:
 /// a.h:
@@ -223,8 +511,47 @@
   }
 
   Expected<Effect> apply(const Selection &Sel) override {
-    return llvm::createStringError(llvm::inconvertibleErrorCode(),
-                                   "Not implemented yet");
+    const ASTContext &AST = Sel.AST.getASTContext();
+    const SourceManager &SM = AST.getSourceManager();
+
+    auto QualifiedBody = qualifyAllDecls(Source);
+    if (!QualifiedBody)
+      return QualifiedBody.takeError();
+
+    auto SemiColon = getSemiColonForDecl(Target);
+    if (!SemiColon) {
+      return llvm::createStringError(
+          llvm::inconvertibleErrorCode(),
+          "Couldn't find semicolon for target declaration");
+    }
+
+    tooling::Replacements Replacements;
+    auto Err = Replacements.add(
+        tooling::Replacement(SM, *SemiColon, 1, *QualifiedBody));
+    if (Err)
+      return std::move(Err);
+    Effect E;
+    E.ApplyEdits.emplace();
+
+    // We need to generate two edits if the Source and Target are in different
+    // files.
+    if (!SM.isWrittenInSameFile(Source->getBeginLoc(), Target->getBeginLoc())) {
+      E.addEdit(SM.getFilename(*SemiColon),
+                Edit::generateEdit(SM.getBufferData(SM.getFileID(*SemiColon)),
+                                   std::move(Replacements)));
+      Replacements.clear();
+    }
+
+    SourceLocation BeginLoc = getBeginLoc(Source);
+    unsigned int SourceLen =
+        SM.getFileOffset(Source->getEndLoc()) - SM.getFileOffset(BeginLoc) + 1;
+    Err = Replacements.add(tooling::Replacement(SM, BeginLoc, SourceLen, ""));
+    if (Err)
+      return std::move(Err);
+
+    E.addEdit(SM.getFilename(Sel.Cursor),
+              Edit::generateEdit(Sel.Code, std::move(Replacements)));
+    return E;
   }
 
 private:
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to