[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