[clang-tools-extra] a0e4ba4 - [clangd] Add support to extract method for ExtractFunction Tweak

Sam McCall via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 5 10:49:43 PDT 2022


Author: Fabio Rossini Sluzala
Date: 2022-04-05T19:49:17+02:00
New Revision: a0e4ba4b4607267d96c3ab8bc1c38f5a09830692

URL: https://github.com/llvm/llvm-project/commit/a0e4ba4b4607267d96c3ab8bc1c38f5a09830692
DIFF: https://github.com/llvm/llvm-project/commit/a0e4ba4b4607267d96c3ab8bc1c38f5a09830692.diff

LOG: [clangd] Add support to extract method for ExtractFunction Tweak

I miss more automatically refactoring functions when working with already running code, so I am making some small addition that I hope help more people.

This works by checking if the function is a method (CXXMethodDecl), then collecting information about the function that the code is being extracted, looking for the declaration if it is out-of-line, creating the declaration if it is necessary and putting the extracted function as a class-method.

This is my first code review request, sorry if I did something wrong.

Reviewed By: sammccall

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

Added: 
    

Modified: 
    clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp
    clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp
index 7bbe0d12dac9f..355c3183edf60 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp
@@ -56,6 +56,7 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclBase.h"
+#include "clang/AST/NestedNameSpecifier.h"
 #include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/AST/Stmt.h"
 #include "clang/Basic/LangOptions.h"
@@ -71,6 +72,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Error.h"
+#include "llvm/Support/raw_os_ostream.h"
 
 namespace clang {
 namespace clangd {
@@ -88,6 +90,12 @@ enum class ZoneRelative {
   OutsideFunc // Outside EnclosingFunction.
 };
 
+enum FunctionDeclKind {
+  InlineDefinition,
+  ForwardDeclaration,
+  OutOfLineDefinition
+};
+
 // A RootStmt is a statement that's fully selected including all it's children
 // and it's parent is unselected.
 // Check if a node is a root statement.
@@ -237,9 +245,6 @@ const FunctionDecl *findEnclosingFunction(const Node *CommonAnc) {
     if (CurNode->ASTNode.get<LambdaExpr>())
       return nullptr;
     if (const FunctionDecl *Func = CurNode->ASTNode.get<FunctionDecl>()) {
-      // FIXME: Support extraction from methods.
-      if (isa<CXXMethodDecl>(Func))
-        return nullptr;
       // FIXME: Support extraction from templated functions.
       if (Func->isTemplated())
         return nullptr;
@@ -343,34 +348,53 @@ struct NewFunction {
   QualType ReturnType;
   std::vector<Parameter> Parameters;
   SourceRange BodyRange;
-  SourceLocation InsertionPoint;
-  const DeclContext *EnclosingFuncContext;
+  SourceLocation DefinitionPoint;
+  llvm::Optional<SourceLocation> ForwardDeclarationPoint;
+  const CXXRecordDecl *EnclosingClass = nullptr;
+  const NestedNameSpecifier *DefinitionQualifier = nullptr;
+  const DeclContext *SemanticDC = nullptr;
+  const DeclContext *SyntacticDC = nullptr;
+  const DeclContext *ForwardDeclarationSyntacticDC = nullptr;
   bool CallerReturnsValue = false;
+  bool Static = false;
+  ConstexprSpecKind Constexpr = ConstexprSpecKind::Unspecified;
+  bool Const = false;
+
   // Decides whether the extracted function body and the function call need a
   // semicolon after extraction.
   tooling::ExtractionSemicolonPolicy SemicolonPolicy;
-  NewFunction(tooling::ExtractionSemicolonPolicy SemicolonPolicy)
-      : SemicolonPolicy(SemicolonPolicy) {}
+  const LangOptions *LangOpts;
+  NewFunction(tooling::ExtractionSemicolonPolicy SemicolonPolicy,
+              const LangOptions *LangOpts)
+      : SemicolonPolicy(SemicolonPolicy), LangOpts(LangOpts) {}
   // Render the call for this function.
   std::string renderCall() const;
   // Render the definition for this function.
-  std::string renderDefinition(const SourceManager &SM) const;
+  std::string renderDeclaration(FunctionDeclKind K,
+                                const DeclContext &SemanticDC,
+                                const DeclContext &SyntacticDC,
+                                const SourceManager &SM) const;
 
 private:
-  std::string renderParametersForDefinition() const;
+  std::string
+  renderParametersForDeclaration(const DeclContext &Enclosing) const;
   std::string renderParametersForCall() const;
+  std::string renderSpecifiers(FunctionDeclKind K) const;
+  std::string renderQualifiers() const;
+  std::string renderDeclarationName(FunctionDeclKind K) const;
   // Generate the function body.
   std::string getFuncBody(const SourceManager &SM) const;
 };
 
-std::string NewFunction::renderParametersForDefinition() const {
+std::string NewFunction::renderParametersForDeclaration(
+    const DeclContext &Enclosing) const {
   std::string Result;
   bool NeedCommaBefore = false;
   for (const Parameter &P : Parameters) {
     if (NeedCommaBefore)
       Result += ", ";
     NeedCommaBefore = true;
-    Result += P.render(EnclosingFuncContext);
+    Result += P.render(&Enclosing);
   }
   return Result;
 }
@@ -387,6 +411,49 @@ std::string NewFunction::renderParametersForCall() const {
   return Result;
 }
 
+std::string NewFunction::renderSpecifiers(FunctionDeclKind K) const {
+  std::string Attributes;
+
+  if (Static && K != FunctionDeclKind::OutOfLineDefinition) {
+    Attributes += "static ";
+  }
+
+  switch (Constexpr) {
+  case ConstexprSpecKind::Unspecified:
+  case ConstexprSpecKind::Constinit:
+    break;
+  case ConstexprSpecKind::Constexpr:
+    Attributes += "constexpr ";
+    break;
+  case ConstexprSpecKind::Consteval:
+    Attributes += "consteval ";
+    break;
+  }
+
+  return Attributes;
+}
+
+std::string NewFunction::renderQualifiers() const {
+  std::string Attributes;
+
+  if (Const) {
+    Attributes += " const";
+  }
+
+  return Attributes;
+}
+
+std::string NewFunction::renderDeclarationName(FunctionDeclKind K) const {
+  if (DefinitionQualifier == nullptr || K != OutOfLineDefinition) {
+    return Name;
+  }
+
+  std::string QualifierName;
+  llvm::raw_string_ostream Oss(QualifierName);
+  DefinitionQualifier->print(Oss, *LangOpts);
+  return llvm::formatv("{0}{1}", QualifierName, Name);
+}
+
 std::string NewFunction::renderCall() const {
   return std::string(
       llvm::formatv("{0}{1}({2}){3}", CallerReturnsValue ? "return " : "", Name,
@@ -394,10 +461,24 @@ std::string NewFunction::renderCall() const {
                     (SemicolonPolicy.isNeededInOriginalFunction() ? ";" : "")));
 }
 
-std::string NewFunction::renderDefinition(const SourceManager &SM) const {
-  return std::string(llvm::formatv(
-      "{0} {1}({2}) {\n{3}\n}\n", printType(ReturnType, *EnclosingFuncContext),
-      Name, renderParametersForDefinition(), getFuncBody(SM)));
+std::string NewFunction::renderDeclaration(FunctionDeclKind K,
+                                           const DeclContext &SemanticDC,
+                                           const DeclContext &SyntacticDC,
+                                           const SourceManager &SM) const {
+  std::string Declaration = std::string(llvm::formatv(
+      "{0}{1} {2}({3}){4}", renderSpecifiers(K),
+      printType(ReturnType, SyntacticDC), renderDeclarationName(K),
+      renderParametersForDeclaration(SemanticDC), renderQualifiers()));
+
+  switch (K) {
+  case ForwardDeclaration:
+    return std::string(llvm::formatv("{0};\n", Declaration));
+  case OutOfLineDefinition:
+  case InlineDefinition:
+    return std::string(
+        llvm::formatv("{0} {\n{1}\n}\n", Declaration, getFuncBody(SM)));
+    break;
+  }
 }
 
 std::string NewFunction::getFuncBody(const SourceManager &SM) const {
@@ -658,6 +739,13 @@ bool generateReturnProperties(NewFunction &ExtractedFunc,
   return true;
 }
 
+void captureMethodInfo(NewFunction &ExtractedFunc,
+                       const CXXMethodDecl *Method) {
+  ExtractedFunc.Static = Method->isStatic();
+  ExtractedFunc.Const = Method->isConst();
+  ExtractedFunc.EnclosingClass = Method->getParent();
+}
+
 // FIXME: add support for adding other function return types besides void.
 // FIXME: assign the value returned by non void extracted function.
 llvm::Expected<NewFunction> getExtractedFunction(ExtractionZone &ExtZone,
@@ -668,11 +756,35 @@ llvm::Expected<NewFunction> getExtractedFunction(ExtractionZone &ExtZone,
   if (CapturedInfo.BrokenControlFlow)
     return error("Cannot extract break/continue without corresponding "
                  "loop/switch statement.");
-  NewFunction ExtractedFunc(getSemicolonPolicy(ExtZone, SM, LangOpts));
+  NewFunction ExtractedFunc(getSemicolonPolicy(ExtZone, SM, LangOpts),
+                            &LangOpts);
+
+  ExtractedFunc.SyntacticDC =
+      ExtZone.EnclosingFunction->getLexicalDeclContext();
+  ExtractedFunc.SemanticDC = ExtZone.EnclosingFunction->getDeclContext();
+  ExtractedFunc.DefinitionQualifier = ExtZone.EnclosingFunction->getQualifier();
+  ExtractedFunc.Constexpr = ExtZone.EnclosingFunction->getConstexprKind();
+
+  if (const auto *Method =
+          llvm::dyn_cast<CXXMethodDecl>(ExtZone.EnclosingFunction))
+    captureMethodInfo(ExtractedFunc, Method);
+
+  if (ExtZone.EnclosingFunction->isOutOfLine()) {
+    // FIXME: Put the extracted method in a private section if it's a class or
+    // maybe in an anonymous namespace
+    const auto *FirstOriginalDecl =
+        ExtZone.EnclosingFunction->getCanonicalDecl();
+    auto DeclPos =
+        toHalfOpenFileRange(SM, LangOpts, FirstOriginalDecl->getSourceRange());
+    if (!DeclPos)
+      return error("Declaration is inside a macro");
+    ExtractedFunc.ForwardDeclarationPoint = DeclPos.getValue().getBegin();
+    ExtractedFunc.ForwardDeclarationSyntacticDC = ExtractedFunc.SemanticDC;
+  }
+
   ExtractedFunc.BodyRange = ExtZone.ZoneRange;
-  ExtractedFunc.InsertionPoint = ExtZone.getInsertionPoint();
-  ExtractedFunc.EnclosingFuncContext =
-      ExtZone.EnclosingFunction->getDeclContext();
+  ExtractedFunc.DefinitionPoint = ExtZone.getInsertionPoint();
+
   ExtractedFunc.CallerReturnsValue = CapturedInfo.AlwaysReturns;
   if (!createParameters(ExtractedFunc, CapturedInfo) ||
       !generateReturnProperties(ExtractedFunc, *ExtZone.EnclosingFunction,
@@ -706,8 +818,24 @@ tooling::Replacement replaceWithFuncCall(const NewFunction &ExtractedFunc,
 
 tooling::Replacement createFunctionDefinition(const NewFunction &ExtractedFunc,
                                               const SourceManager &SM) {
-  std::string FunctionDef = ExtractedFunc.renderDefinition(SM);
-  return tooling::Replacement(SM, ExtractedFunc.InsertionPoint, 0, FunctionDef);
+  FunctionDeclKind DeclKind = InlineDefinition;
+  if (ExtractedFunc.ForwardDeclarationPoint.hasValue())
+    DeclKind = OutOfLineDefinition;
+  std::string FunctionDef = ExtractedFunc.renderDeclaration(
+      DeclKind, *ExtractedFunc.SemanticDC, *ExtractedFunc.SyntacticDC, SM);
+
+  return tooling::Replacement(SM, ExtractedFunc.DefinitionPoint, 0,
+                              FunctionDef);
+}
+
+tooling::Replacement createForwardDeclaration(const NewFunction &ExtractedFunc,
+                                              const SourceManager &SM) {
+  std::string FunctionDecl = ExtractedFunc.renderDeclaration(
+      ForwardDeclaration, *ExtractedFunc.SemanticDC,
+      *ExtractedFunc.ForwardDeclarationSyntacticDC, SM);
+  SourceLocation DeclPoint = ExtractedFunc.ForwardDeclarationPoint.getValue();
+
+  return tooling::Replacement(SM, DeclPoint, 0, FunctionDecl);
 }
 
 // Returns true if ExtZone contains any ReturnStmts.
@@ -757,12 +885,35 @@ Expected<Tweak::Effect> ExtractFunction::apply(const Selection &Inputs) {
   // FIXME: Add more types of errors.
   if (!ExtractedFunc)
     return ExtractedFunc.takeError();
-  tooling::Replacements Result;
-  if (auto Err = Result.add(createFunctionDefinition(*ExtractedFunc, SM)))
+  tooling::Replacements Edit;
+  if (auto Err = Edit.add(createFunctionDefinition(*ExtractedFunc, SM)))
     return std::move(Err);
-  if (auto Err = Result.add(replaceWithFuncCall(*ExtractedFunc, SM, LangOpts)))
+  if (auto Err = Edit.add(replaceWithFuncCall(*ExtractedFunc, SM, LangOpts)))
     return std::move(Err);
-  return Effect::mainFileEdit(SM, std::move(Result));
+
+  if (auto FwdLoc = ExtractedFunc->ForwardDeclarationPoint) {
+    // If the fwd-declaration goes in the same file, merge into Replacements.
+    // Otherwise it needs to be a separate file edit.
+    if (SM.isWrittenInSameFile(ExtractedFunc->DefinitionPoint, *FwdLoc)) {
+      if (auto Err = Edit.add(createForwardDeclaration(*ExtractedFunc, SM)))
+        return std::move(Err);
+    } else {
+      auto MultiFileEffect = Effect::mainFileEdit(SM, std::move(Edit));
+      if (!MultiFileEffect)
+        return MultiFileEffect.takeError();
+
+      tooling::Replacements OtherEdit(
+          createForwardDeclaration(*ExtractedFunc, SM));
+      if (auto PathAndEdit = Tweak::Effect::fileEdit(SM, SM.getFileID(*FwdLoc),
+                                                 OtherEdit))
+        MultiFileEffect->ApplyEdits.try_emplace(PathAndEdit->first,
+                                                PathAndEdit->second);
+      else
+        return PathAndEdit.takeError();
+      return MultiFileEffect;
+    }
+  }
+  return Effect::mainFileEdit(SM, std::move(Edit));
 }
 
 } // namespace

diff  --git a/clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp
index 79450cb4f4c70..8e1da884bbd7d 100644
--- a/clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp
+++ b/clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp
@@ -114,16 +114,48 @@ int f(const X::Y &y) {
   // Don't extract when we need to make a function as a parameter.
   EXPECT_THAT(apply("void f() { [[int a; f();]] }"), StartsWith("fail"));
 
-  // We don't extract from methods for now since they may involve multi-file
-  // edits
-  std::string MethodFailInput = R"cpp(
+  std::string MethodInput = R"cpp(
     class T {
       void f() {
         [[int x;]]
       }
     };
   )cpp";
-  EXPECT_EQ(apply(MethodFailInput), "unavailable");
+  std::string MethodCheckOutput = R"cpp(
+    class T {
+      void extracted() {
+int x;
+}
+void f() {
+        extracted();
+      }
+    };
+  )cpp";
+  EXPECT_EQ(apply(MethodInput), MethodCheckOutput);
+
+  std::string OutOfLineMethodInput = R"cpp(
+    class T {
+      void f();
+    };
+
+    void T::f() {
+      [[int x;]]
+    }
+  )cpp";
+  std::string OutOfLineMethodCheckOutput = R"cpp(
+    class T {
+      void extracted();
+void f();
+    };
+
+    void T::extracted() {
+int x;
+}
+void T::f() {
+      extracted();
+    }
+  )cpp";
+  EXPECT_EQ(apply(OutOfLineMethodInput), OutOfLineMethodCheckOutput);
 
   // We don't extract from templated functions for now as templates are hard
   // to deal with.
@@ -159,6 +191,333 @@ F (extracted();)
   EXPECT_EQ(apply(CompoundFailInput), "unavailable");
 }
 
+TEST_F(ExtractFunctionTest, DifferentHeaderSourceTest) {
+  Header = R"cpp(
+    class SomeClass {
+      void f();
+    };
+  )cpp";
+
+  std::string OutOfLineSource = R"cpp(
+    void SomeClass::f() {
+      [[int x;]]
+    }
+  )cpp";
+
+  std::string OutOfLineSourceOutputCheck = R"cpp(
+    void SomeClass::extracted() {
+int x;
+}
+void SomeClass::f() {
+      extracted();
+    }
+  )cpp";
+
+  std::string HeaderOutputCheck = R"cpp(
+    class SomeClass {
+      void extracted();
+void f();
+    };
+  )cpp";
+
+  llvm::StringMap<std::string> EditedFiles;
+
+  EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
+  EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
+}
+
+TEST_F(ExtractFunctionTest, DifferentFilesNestedTest) {
+  Header = R"cpp(
+    class T {
+    class SomeClass {
+      void f();
+    };
+    };
+  )cpp";
+
+  std::string NestedOutOfLineSource = R"cpp(
+    void T::SomeClass::f() {
+      [[int x;]]
+    }
+  )cpp";
+
+  std::string NestedOutOfLineSourceOutputCheck = R"cpp(
+    void T::SomeClass::extracted() {
+int x;
+}
+void T::SomeClass::f() {
+      extracted();
+    }
+  )cpp";
+
+  std::string NestedHeaderOutputCheck = R"cpp(
+    class T {
+    class SomeClass {
+      void extracted();
+void f();
+    };
+    };
+  )cpp";
+
+  llvm::StringMap<std::string> EditedFiles;
+
+  EXPECT_EQ(apply(NestedOutOfLineSource, &EditedFiles),
+            NestedOutOfLineSourceOutputCheck);
+  EXPECT_EQ(EditedFiles.begin()->second, NestedHeaderOutputCheck);
+}
+
+TEST_F(ExtractFunctionTest, ConstexprDifferentHeaderSourceTest) {
+  Header = R"cpp(
+    class SomeClass {
+      constexpr void f() const;
+    };
+  )cpp";
+
+  std::string OutOfLineSource = R"cpp(
+    constexpr void SomeClass::f() const {
+      [[int x;]]
+    }
+  )cpp";
+
+  std::string OutOfLineSourceOutputCheck = R"cpp(
+    constexpr void SomeClass::extracted() const {
+int x;
+}
+constexpr void SomeClass::f() const {
+      extracted();
+    }
+  )cpp";
+
+  std::string HeaderOutputCheck = R"cpp(
+    class SomeClass {
+      constexpr void extracted() const;
+constexpr void f() const;
+    };
+  )cpp";
+
+  llvm::StringMap<std::string> EditedFiles;
+
+  EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
+  EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
+      << "The header should be edited and receives the declaration of the new "
+         "function";
+
+  if (EditedFiles.begin() != EditedFiles.end()) {
+    EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
+  }
+}
+
+TEST_F(ExtractFunctionTest, ConstevalDifferentHeaderSourceTest) {
+  ExtraArgs.push_back("--std=c++20");
+  Header = R"cpp(
+    class SomeClass {
+      consteval void f() const;
+    };
+  )cpp";
+
+  std::string OutOfLineSource = R"cpp(
+    consteval void SomeClass::f() const {
+      [[int x;]]
+    }
+  )cpp";
+
+  std::string OutOfLineSourceOutputCheck = R"cpp(
+    consteval void SomeClass::extracted() const {
+int x;
+}
+consteval void SomeClass::f() const {
+      extracted();
+    }
+  )cpp";
+
+  std::string HeaderOutputCheck = R"cpp(
+    class SomeClass {
+      consteval void extracted() const;
+consteval void f() const;
+    };
+  )cpp";
+
+  llvm::StringMap<std::string> EditedFiles;
+
+  EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
+  EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
+      << "The header should be edited and receives the declaration of the new "
+         "function";
+
+  if (EditedFiles.begin() != EditedFiles.end()) {
+    EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
+  }
+}
+
+TEST_F(ExtractFunctionTest, ConstDifferentHeaderSourceTest) {
+  Header = R"cpp(
+    class SomeClass {
+      void f() const;
+    };
+  )cpp";
+
+  std::string OutOfLineSource = R"cpp(
+    void SomeClass::f() const {
+      [[int x;]]
+    }
+  )cpp";
+
+  std::string OutOfLineSourceOutputCheck = R"cpp(
+    void SomeClass::extracted() const {
+int x;
+}
+void SomeClass::f() const {
+      extracted();
+    }
+  )cpp";
+
+  std::string HeaderOutputCheck = R"cpp(
+    class SomeClass {
+      void extracted() const;
+void f() const;
+    };
+  )cpp";
+
+  llvm::StringMap<std::string> EditedFiles;
+
+  EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
+  EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
+      << "The header should be edited and receives the declaration of the new "
+         "function";
+
+  if (EditedFiles.begin() != EditedFiles.end()) {
+    EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
+  }
+}
+
+TEST_F(ExtractFunctionTest, StaticDifferentHeaderSourceTest) {
+  Header = R"cpp(
+    class SomeClass {
+      static void f();
+    };
+  )cpp";
+
+  std::string OutOfLineSource = R"cpp(
+    void SomeClass::f() {
+      [[int x;]]
+    }
+  )cpp";
+
+  std::string OutOfLineSourceOutputCheck = R"cpp(
+    void SomeClass::extracted() {
+int x;
+}
+void SomeClass::f() {
+      extracted();
+    }
+  )cpp";
+
+  std::string HeaderOutputCheck = R"cpp(
+    class SomeClass {
+      static void extracted();
+static void f();
+    };
+  )cpp";
+
+  llvm::StringMap<std::string> EditedFiles;
+
+  EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
+  EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
+      << "The header should be edited and receives the declaration of the new "
+         "function";
+
+  if (EditedFiles.begin() != EditedFiles.end()) {
+    EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
+  }
+}
+
+TEST_F(ExtractFunctionTest, DifferentContextHeaderSourceTest) {
+  Header = R"cpp(
+    namespace ns{
+    class A {
+      class C {
+      public:
+        class RType {};
+      };
+
+      class T {
+        class SomeClass {
+          static C::RType f();
+        };
+      };
+    };
+    } // ns
+  )cpp";
+
+  std::string OutOfLineSource = R"cpp(
+    ns::A::C::RType ns::A::T::SomeClass::f() {
+      [[A::C::RType x;
+      return x;]]
+    }
+  )cpp";
+
+  std::string OutOfLineSourceOutputCheck = R"cpp(
+    ns::A::C::RType ns::A::T::SomeClass::extracted() {
+A::C::RType x;
+      return x;
+}
+ns::A::C::RType ns::A::T::SomeClass::f() {
+      return extracted();
+    }
+  )cpp";
+
+  std::string HeaderOutputCheck = R"cpp(
+    namespace ns{
+    class A {
+      class C {
+      public:
+        class RType {};
+      };
+
+      class T {
+        class SomeClass {
+          static ns::A::C::RType extracted();
+static C::RType f();
+        };
+      };
+    };
+    } // ns
+  )cpp";
+
+  llvm::StringMap<std::string> EditedFiles;
+
+  EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
+  EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
+}
+
+TEST_F(ExtractFunctionTest, DifferentSyntacticContextNamespace) {
+  std::string OutOfLineSource = R"cpp(
+    namespace ns {
+      void f();
+    }
+
+    void ns::f() {
+      [[int x;]]
+    }
+  )cpp";
+
+  std::string OutOfLineSourceOutputCheck = R"cpp(
+    namespace ns {
+      void extracted();
+void f();
+    }
+
+    void ns::extracted() {
+int x;
+}
+void ns::f() {
+      extracted();
+    }
+  )cpp";
+
+  EXPECT_EQ(apply(OutOfLineSource), OutOfLineSourceOutputCheck);
+}
+
 TEST_F(ExtractFunctionTest, ControlFlow) {
   Context = Function;
   // We should be able to extract break/continue with a parent loop/switch.


        


More information about the cfe-commits mailing list