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

2024-04-18 Thread Tom Praschan via cfe-commits

tom-anders wrote:

> We should probably rekindle the discussion on the associated bug report; I 
> think users (rightly) expect this feature.

Agreed, [maybe a protocol extension would also be an 
option](https://github.com/clangd/clangd/issues/445#issuecomment-1250355799)

https://github.com/llvm/llvm-project/pull/71950
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-04-18 Thread Christian Kandeler via cfe-commits

ckandeler wrote:

We should probably rekindle the discussion on the associated bug report; I 
think users (rightly) expect this feature.

https://github.com/llvm/llvm-project/pull/71950
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-04-18 Thread Tom Praschan via cfe-commits

tom-anders wrote:

> The new patch set properly considers "external" definitions (using the 
> frowned-upon index lookup).

I've been using this patch for a few weeks now, and it works very well. Haven't 
encountered any issues/crashes yet. Performance also seems reasonable (working 
in a medium-sized project)

https://github.com/llvm/llvm-project/pull/71950
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2023-11-13 Thread via cfe-commits

ckandeler wrote:

The new patch set properly considers "external" definitions (using the 
frowned-upon index lookup).

https://github.com/llvm/llvm-project/pull/71950
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2023-11-13 Thread via cfe-commits

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 
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 
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(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

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

2023-11-13 Thread via cfe-commits

ckandeler wrote:

> This implements 
> [clangd/clangd#445](https://github.com/clangd/clangd/issues/445)

Aha, so this implementation does not work, because it relies on the AST only 
for figuring out whether an implementation exists. And querying the index is 
considered too expensive during the prepare() step. That's a pity then.

https://github.com/llvm/llvm-project/pull/71950
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2023-11-10 Thread Julian Schmidt via cfe-commits

5chmidti wrote:

This implements clangd/clangd#445

https://github.com/llvm/llvm-project/pull/71950
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2023-11-10 Thread via cfe-commits

llvmbot wrote:




@llvm/pr-subscribers-clangd

Author: None (ckandeler)


Changes



---
Full diff: https://github.com/llvm/llvm-project/pull/71950.diff


2 Files Affected:

- (modified) clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp 
(+50-11) 
- (modified) clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp 
(+26-6) 


``diff
diff --git a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp 
b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
index b84ae04072f2c19..9e715b7bf21175a 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
@@ -128,7 +128,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 +357,30 @@ llvm::Expected 
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(FD)) {
 // AST doesn't contain the location for ":" in ctor initializers. Therefore
@@ -405,7 +438,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,9 +452,8 @@ 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 this is a function template or specialization, as their
@@ -483,12 +517,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.

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

2023-11-10 Thread via cfe-commits

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

None

>From b32023796e29ad02080519f7f2cfa98307f7d1d9 Mon Sep 17 00:00:00 2001
From: Christian Kandeler 
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  | 61 +++
 .../unittests/tweaks/DefineOutlineTests.cpp   | 32 --
 2 files changed, 76 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..9e715b7bf21175a 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
@@ -128,7 +128,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 +357,30 @@ llvm::Expected 
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(FD)) {
 // AST doesn't contain the location for ":" in ctor initializers. Therefore
@@ -405,7 +438,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,9 +452,8 @@ 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 this is a function template or specialization, as their
@@ -483,12 +517,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 @@ TE