[clang-tools-extra] [clangd] Let DefineOutline tweak create a definition from scratch (PR #71950)

via cfe-commits cfe-commits at lists.llvm.org
Mon Nov 13 04:09:45 PST 2023


https://github.com/ckandeler updated https://github.com/llvm/llvm-project/pull/71950

>From 2d7d6c9b0939bc4b300c53d2d49c20b549a561dd Mon Sep 17 00:00:00 2001
From: Christian Kandeler <christian.kandeler at qt.io>
Date: Fri, 10 Nov 2023 16:01:18 +0100
Subject: [PATCH] [clangd] Let DefineOutline tweak create a definition from
 scratch

---
 .../clangd/refactor/tweaks/DefineOutline.cpp  | 74 ++++++++++++++++---
 .../unittests/tweaks/DefineOutlineTests.cpp   | 32 ++++++--
 2 files changed, 89 insertions(+), 17 deletions(-)

diff --git a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
index b84ae04072f2c19..95d18eca8e5df71 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
@@ -12,6 +12,7 @@
 #include "ParsedAST.h"
 #include "Selection.h"
 #include "SourceCode.h"
+#include "XRefs.h"
 #include "refactor/Tweak.h"
 #include "support/Logger.h"
 #include "support/Path.h"
@@ -128,7 +129,16 @@ getFunctionSourceAfterReplacements(const FunctionDecl *FD,
       SM.getBufferData(SM.getMainFileID()), Replacements);
   if (!QualifiedFunc)
     return QualifiedFunc.takeError();
-  return QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
+  auto QualifiedFuncString =
+      QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
+  if (!FD->hasBody() && !FD->isExplicitlyDefaulted()) {
+    QualifiedFuncString.pop_back(); // The semicolon finishing the declaration.
+    QualifiedFuncString += " {";
+    if (!FD->getReturnType()->isVoidType())
+      QualifiedFuncString += " return {}; ";
+    QualifiedFuncString += "}";
+  }
+  return QualifiedFuncString;
 }
 
 // Returns replacements to delete tokens with kind `Kind` in the range
@@ -348,6 +358,30 @@ llvm::Expected<InsertionPoint> getInsertionPoint(llvm::StringRef Contents,
 // initializers.
 SourceRange getDeletionRange(const FunctionDecl *FD,
                              const syntax::TokenBuffer &TokBuf) {
+  if (!FD->hasBody()) {
+    if (!FD->isExplicitlyDefaulted())
+      return {};
+
+    auto tokens = TokBuf.expandedTokens(FD->getSourceRange());
+    for (auto It = std::rbegin(tokens); It != std::rend(tokens); ++It) {
+      if (It->kind() == tok::kw_default) {
+        const auto nextIt = std::next(It);
+        if (nextIt == std::rend(tokens))
+          return {};
+        const auto &ExpandedEquals = *nextIt;
+        if (ExpandedEquals.kind() != tok::equal)
+          return {};
+        auto SpelledEquals =
+            TokBuf.spelledForExpanded(llvm::ArrayRef(ExpandedEquals));
+        if (!SpelledEquals)
+          return {};
+        return {SpelledEquals->front().location(),
+                FD->getDefaultLoc().getLocWithOffset(1)};
+      }
+    }
+    return {};
+  }
+
   auto DeletionRange = FD->getBody()->getSourceRange();
   if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
     // AST doesn't contain the location for ":" in ctor initializers. Therefore
@@ -405,7 +439,9 @@ class DefineOutline : public Tweak {
     return CodeAction::REFACTOR_KIND;
   }
   std::string title() const override {
-    return "Move function body to out-of-line";
+    if (Source->doesThisDeclarationHaveABody())
+      return "Move function body to out-of-line";
+    return "Create function body out-of-line";
   }
 
   bool prepare(const Selection &Sel) override {
@@ -417,11 +453,22 @@ class DefineOutline : public Tweak {
       return false;
 
     Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
-    // Bail out if the selection is not a in-line function definition.
-    if (!Source || !Source->doesThisDeclarationHaveABody() ||
-        Source->isOutOfLine())
+    // Bail out if the selection is not a function declaration.
+    if (!Source || Source->isDeleted() || Source->isOutOfLine())
       return false;
 
+    // Bail out if a definition exists somewhere else.
+    if (!Source->hasBody() && !Source->isExplicitlyDefaulted() && Sel.Index) {
+      bool HasDefinition = false;
+      Sel.Index->lookup({{getSymbolID(Source)}},
+                        [&HasDefinition](const Symbol &S) {
+                          if (S.Definition)
+                            HasDefinition = true;
+                        });
+      if (HasDefinition)
+        return false;
+    }
+
     // Bail out if this is a function template or specialization, as their
     // definitions need to be visible in all including translation units.
     if (Source->getDescribedFunctionTemplate())
@@ -483,12 +530,17 @@ class DefineOutline : public Tweak {
     if (!Effect)
       return Effect.takeError();
 
-    tooling::Replacements HeaderUpdates(tooling::Replacement(
-        Sel.AST->getSourceManager(),
-        CharSourceRange::getTokenRange(*toHalfOpenFileRange(
-            SM, Sel.AST->getLangOpts(),
-            getDeletionRange(Source, Sel.AST->getTokens()))),
-        ";"));
+    tooling::Replacements HeaderUpdates;
+    auto DeletionRange = getDeletionRange(Source, Sel.AST->getTokens());
+    if (DeletionRange.isValid()) {
+      if (auto Error = HeaderUpdates.add(tooling::Replacement(
+              Sel.AST->getSourceManager(),
+              CharSourceRange::getTokenRange(*toHalfOpenFileRange(
+                  SM, Sel.AST->getLangOpts(), DeletionRange)),
+              ";"))) {
+        return Error;
+      }
+    }
 
     if (Source->isInlineSpecified()) {
       auto DelInline =
diff --git a/clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp
index d1e60b070f20e95..dede5f7c3c25e8b 100644
--- a/clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp
+++ b/clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp
@@ -28,9 +28,6 @@ TEST_F(DefineOutlineTest, TriggersOnFunctionDecl) {
   FileName = "Test.hpp";
   // Not available unless function name or fully body is selected.
   EXPECT_UNAVAILABLE(R"cpp(
-    // Not a definition
-    vo^i[[d^ ^f]]^oo();
-
     [[vo^id ]]foo[[()]] {[[
       [[(void)(5+3);
       return;]]
@@ -63,10 +60,9 @@ TEST_F(DefineOutlineTest, TriggersOnFunctionDecl) {
       return;
     }]]]])cpp");
 
-  // Not available on defaulted/deleted members.
+  // Not available on deleted members.
   EXPECT_UNAVAILABLE(R"cpp(
     class Foo {
-      Fo^o() = default;
       F^oo(const Foo&) = delete;
     };)cpp");
 
@@ -128,12 +124,24 @@ TEST_F(DefineOutlineTest, ApplyTest) {
     llvm::StringRef ExpectedHeader;
     llvm::StringRef ExpectedSource;
   } Cases[] = {
-      // Simple check
+      // Simple check: Move
       {
           "void fo^o() { return; }",
           "void foo() ;",
           "void foo() { return; }",
       },
+      // Simple check: Create
+      {
+          "void fo^o(int i = 5);",
+          "void foo(int i = 5);",
+          "void foo(int i ) {}",
+      },
+      // Simple check: Create with return value
+      {
+          "int fo^o();",
+          "int foo();",
+          "int foo() { return {}; }",
+      },
       // Inline specifier.
       {
           "inline void fo^o() { return; }",
@@ -205,6 +213,18 @@ TEST_F(DefineOutlineTest, ApplyTest) {
               };)cpp",
           "Foo::Foo(int z) __attribute__((weak)) : bar(2){}\n",
       },
+      // Default ctor
+      {
+          R"cpp(
+              class Foo {
+                F^oo() = default;
+              };)cpp",
+          R"cpp(
+              class Foo {
+                Foo() ;
+              };)cpp",
+          "Foo::Foo() = default;",
+      },
       // Virt specifiers.
       {
           R"cpp(



More information about the cfe-commits mailing list