[clang-tools-extra] [clangd] Add tweak to abbreviate function templates (PR #74710)

Jeremy Stucki via cfe-commits cfe-commits at lists.llvm.org
Thu Dec 7 03:15:44 PST 2023


https://github.com/jeremystucki updated https://github.com/llvm/llvm-project/pull/74710

>From 64573476024103cf94f6cfc690e5396a368fc3df Mon Sep 17 00:00:00 2001
From: Jeremy Stucki <dev at jeremystucki.ch>
Date: Fri, 27 Oct 2023 16:33:13 +0200
Subject: [PATCH] [clangd] Add tweak to abbreviate function templates

Co-authored-by: Vina Zahnd <vina.zahnd at gmail.com>
---
 .../tweaks/AbbreviateFunctionTemplate.cpp     | 352 ++++++++++++++++++
 .../clangd/refactor/tweaks/CMakeLists.txt     |   1 +
 .../clangd/unittests/CMakeLists.txt           |   1 +
 .../AbbreviateFunctionTemplateTests.cpp       |  76 ++++
 clang-tools-extra/docs/ReleaseNotes.rst       |   3 +
 5 files changed, 433 insertions(+)
 create mode 100644 clang-tools-extra/clangd/refactor/tweaks/AbbreviateFunctionTemplate.cpp
 create mode 100644 clang-tools-extra/clangd/unittests/tweaks/AbbreviateFunctionTemplateTests.cpp

diff --git a/clang-tools-extra/clangd/refactor/tweaks/AbbreviateFunctionTemplate.cpp b/clang-tools-extra/clangd/refactor/tweaks/AbbreviateFunctionTemplate.cpp
new file mode 100644
index 0000000000000..a2cfc6e13b01f
--- /dev/null
+++ b/clang-tools-extra/clangd/refactor/tweaks/AbbreviateFunctionTemplate.cpp
@@ -0,0 +1,352 @@
+//===-- AbbreviateFunctionTemplate.cpp ---------------------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#include "FindTarget.h"
+#include "SourceCode.h"
+#include "XRefs.h"
+#include "refactor/Tweak.h"
+#include "support/Logger.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ExprConcepts.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/Support/Error.h"
+#include <numeric>
+
+namespace clang {
+namespace clangd {
+namespace {
+/// Converts a function template to its abbreviated form using auto parameters.
+/// Before:
+///     template <std::integral T>
+///     auto foo(T param) { }
+///          ^^^^^^^^^^^
+/// After:
+///     auto foo(std::integral auto param) { }
+class AbbreviateFunctionTemplate : public Tweak {
+public:
+  const char *id() const final;
+
+  auto prepare(const Selection &Inputs) -> bool override;
+  auto apply(const Selection &Inputs) -> Expected<Effect> override;
+
+  auto title() const -> std::string override {
+    return llvm::formatv("Abbreviate function template");
+  }
+
+  auto kind() const -> llvm::StringLiteral override {
+    return CodeAction::REFACTOR_KIND;
+  }
+
+private:
+  static const char *AutoKeywordSpelling;
+  const FunctionTemplateDecl *FunctionTemplateDeclaration;
+
+  struct TemplateParameterInfo {
+    const TypeConstraint *Constraint;
+    unsigned int FunctionParameterIndex;
+    std::vector<tok::TokenKind> FunctionParameterQualifiers;
+    std::vector<tok::TokenKind> FunctionParameterTypeQualifiers;
+  };
+
+  std::vector<TemplateParameterInfo> TemplateParameterInfoList;
+
+  auto traverseFunctionParameters(size_t NumberOfTemplateParameters) -> bool;
+
+  auto generateFunctionParameterReplacements(const ASTContext &Context)
+      -> llvm::Expected<tooling::Replacements>;
+
+  auto generateFunctionParameterReplacement(
+      const TemplateParameterInfo &TemplateParameterInfo,
+      const ASTContext &Context) -> llvm::Expected<tooling::Replacement>;
+
+  auto generateTemplateDeclarationReplacement(const ASTContext &Context)
+      -> llvm::Expected<tooling::Replacement>;
+
+  static auto deconstructType(QualType Type)
+      -> std::tuple<QualType, std::vector<tok::TokenKind>,
+                    std::vector<tok::TokenKind>>;
+};
+
+REGISTER_TWEAK(AbbreviateFunctionTemplate)
+
+const char *AbbreviateFunctionTemplate::AutoKeywordSpelling =
+    getKeywordSpelling(tok::kw_auto);
+
+template <typename T>
+auto findDeclaration(const SelectionTree::Node &Root) -> const T * {
+  for (const auto *Node = &Root; Node; Node = Node->Parent) {
+    if (const T *Result = dyn_cast_or_null<T>(Node->ASTNode.get<Decl>()))
+      return Result;
+  }
+
+  return nullptr;
+}
+
+auto getSpellingForQualifier(tok::TokenKind const &Qualifier) -> const char * {
+  if (const auto *Spelling = getKeywordSpelling(Qualifier))
+    return Spelling;
+
+  if (const auto *Spelling = getPunctuatorSpelling(Qualifier))
+    return Spelling;
+
+  return nullptr;
+}
+
+bool AbbreviateFunctionTemplate::prepare(const Selection &Inputs) {
+  const auto *CommonAncestor = Inputs.ASTSelection.commonAncestor();
+  if (!CommonAncestor)
+    return false;
+
+  FunctionTemplateDeclaration =
+      findDeclaration<FunctionTemplateDecl>(*CommonAncestor);
+
+  if (!FunctionTemplateDeclaration)
+    return false;
+
+  auto *TemplateParameters =
+      FunctionTemplateDeclaration->getTemplateParameters();
+
+  auto NumberOfTemplateParameters = TemplateParameters->size();
+  TemplateParameterInfoList =
+      std::vector<TemplateParameterInfo>(NumberOfTemplateParameters);
+
+  // Check how many times each template parameter is referenced.
+  // Depending on the number of references it can be checked
+  // if the refactoring is possible:
+  // - exactly one: The template parameter was declared but never used, which
+  //                means we know for sure it doesn't appear as a parameter.
+  // - exactly two: The template parameter was used exactly once, either as a
+  //                parameter or somewhere else. This is the case we are
+  //                interested in.
+  // - more than two: The template parameter was either used for multiple
+  //                  parameters or somewhere else in the function.
+  for (unsigned TemplateParameterIndex = 0;
+       TemplateParameterIndex < NumberOfTemplateParameters;
+       TemplateParameterIndex++) {
+    auto *TemplateParameter =
+        TemplateParameters->getParam(TemplateParameterIndex);
+    auto *TemplateParameterInfo =
+        &TemplateParameterInfoList[TemplateParameterIndex];
+
+    auto *TemplateParameterDeclaration =
+        dyn_cast_or_null<TemplateTypeParmDecl>(TemplateParameter);
+    if (!TemplateParameterDeclaration)
+      return false;
+
+    TemplateParameterInfo->Constraint =
+        TemplateParameterDeclaration->getTypeConstraint();
+
+    auto TemplateParameterPosition = sourceLocToPosition(
+        Inputs.AST->getSourceManager(), TemplateParameter->getEndLoc());
+
+    auto FindReferencesLimit = 3;
+    auto ReferencesResult =
+        findReferences(*Inputs.AST, TemplateParameterPosition,
+                       FindReferencesLimit, Inputs.Index);
+
+    if (ReferencesResult.References.size() != 2)
+      return false;
+  }
+
+  return traverseFunctionParameters(NumberOfTemplateParameters);
+}
+
+auto AbbreviateFunctionTemplate::apply(const Selection &Inputs)
+    -> Expected<Tweak::Effect> {
+  auto &Context = Inputs.AST->getASTContext();
+  auto FunctionParameterReplacements =
+      generateFunctionParameterReplacements(Context);
+
+  if (auto Err = FunctionParameterReplacements.takeError())
+    return Err;
+
+  auto Replacements = *FunctionParameterReplacements;
+  auto TemplateDeclarationReplacement =
+      generateTemplateDeclarationReplacement(Context);
+
+  if (auto Err = TemplateDeclarationReplacement.takeError())
+    return Err;
+
+  if (auto Err = Replacements.add(*TemplateDeclarationReplacement))
+    return Err;
+
+  return Effect::mainFileEdit(Context.getSourceManager(), Replacements);
+}
+
+auto AbbreviateFunctionTemplate::traverseFunctionParameters(
+    size_t NumberOfTemplateParameters) -> bool {
+  auto CurrentTemplateParameterBeingChecked = 0u;
+  auto FunctionParameters =
+      FunctionTemplateDeclaration->getAsFunction()->parameters();
+
+  for (auto ParameterIndex = 0u; ParameterIndex < FunctionParameters.size();
+       ParameterIndex++) {
+    auto [RawType, ParameterTypeQualifiers, ParameterQualifiers] =
+        deconstructType(FunctionParameters[ParameterIndex]->getOriginalType());
+
+    if (!RawType->isTemplateTypeParmType())
+      continue;
+
+    auto TemplateParameterIndex =
+        dyn_cast<TemplateTypeParmType>(RawType)->getIndex();
+
+    if (TemplateParameterIndex != CurrentTemplateParameterBeingChecked)
+      return false;
+
+    auto *TemplateParameterInfo =
+        &TemplateParameterInfoList[TemplateParameterIndex];
+    TemplateParameterInfo->FunctionParameterIndex = ParameterIndex;
+    TemplateParameterInfo->FunctionParameterTypeQualifiers =
+        ParameterTypeQualifiers;
+    TemplateParameterInfo->FunctionParameterQualifiers = ParameterQualifiers;
+
+    CurrentTemplateParameterBeingChecked++;
+  }
+
+  // All defined template parameters need to be used as function parameters
+  return CurrentTemplateParameterBeingChecked == NumberOfTemplateParameters;
+}
+
+auto AbbreviateFunctionTemplate::generateFunctionParameterReplacements(
+    const ASTContext &Context) -> llvm::Expected<tooling::Replacements> {
+  tooling::Replacements Replacements;
+  for (const auto &TemplateParameterInfo : TemplateParameterInfoList) {
+    auto FunctionParameterReplacement =
+        generateFunctionParameterReplacement(TemplateParameterInfo, Context);
+
+    if (auto Err = FunctionParameterReplacement.takeError())
+      return Err;
+
+    if (auto Err = Replacements.add(*FunctionParameterReplacement))
+      return Err;
+  }
+
+  return Replacements;
+}
+
+auto AbbreviateFunctionTemplate::generateFunctionParameterReplacement(
+    const TemplateParameterInfo &TemplateParameterInfo,
+    const ASTContext &Context) -> llvm::Expected<tooling::Replacement> {
+  auto &SourceManager = Context.getSourceManager();
+
+  const auto *Function = FunctionTemplateDeclaration->getAsFunction();
+  auto *Parameter =
+      Function->getParamDecl(TemplateParameterInfo.FunctionParameterIndex);
+  auto ParameterName = Parameter->getDeclName().getAsString();
+
+  std::vector<std::string> ParameterTokens{};
+
+  if (const auto *TypeConstraint = TemplateParameterInfo.Constraint) {
+    auto *ConceptReference = TypeConstraint->getConceptReference();
+    auto *NamedConcept = ConceptReference->getNamedConcept();
+
+    ParameterTokens.push_back(NamedConcept->getQualifiedNameAsString());
+
+    if (const auto *TemplateArgs = TypeConstraint->getTemplateArgsAsWritten()) {
+      auto TemplateArgsRange = SourceRange(TemplateArgs->getLAngleLoc(),
+                                           TemplateArgs->getRAngleLoc());
+      auto TemplateArgsSource = toSourceCode(SourceManager, TemplateArgsRange);
+      ParameterTokens.push_back(TemplateArgsSource.str() + '>');
+    }
+  }
+
+  ParameterTokens.push_back(AutoKeywordSpelling);
+
+  for (const auto &Qualifier :
+       TemplateParameterInfo.FunctionParameterTypeQualifiers) {
+    ParameterTokens.push_back(getSpellingForQualifier(Qualifier));
+  }
+
+  ParameterTokens.push_back(ParameterName);
+
+  for (const auto &Qualifier :
+       TemplateParameterInfo.FunctionParameterQualifiers) {
+    ParameterTokens.push_back(getSpellingForQualifier(Qualifier));
+  }
+
+  auto FunctionTypeReplacementText = std::accumulate(
+      ParameterTokens.begin(), ParameterTokens.end(), std::string{},
+      [](auto Result, auto Token) { return std::move(Result) + " " + Token; });
+
+  auto FunctionParameterRange = toHalfOpenFileRange(
+      SourceManager, Context.getLangOpts(), Parameter->getSourceRange());
+
+  if (!FunctionParameterRange)
+    return error("Could not obtain range of the template parameter. Macros?");
+
+  return tooling::Replacement(
+      SourceManager, CharSourceRange::getCharRange(*FunctionParameterRange),
+      FunctionTypeReplacementText);
+}
+
+auto AbbreviateFunctionTemplate::generateTemplateDeclarationReplacement(
+    const ASTContext &Context) -> llvm::Expected<tooling::Replacement> {
+  auto &SourceManager = Context.getSourceManager();
+  auto *TemplateParameters =
+      FunctionTemplateDeclaration->getTemplateParameters();
+
+  auto TemplateDeclarationRange =
+      toHalfOpenFileRange(SourceManager, Context.getLangOpts(),
+                          TemplateParameters->getSourceRange());
+
+  if (!TemplateDeclarationRange)
+    return error("Could not obtain range of the template parameter. Macros?");
+
+  auto CharRange = CharSourceRange::getCharRange(*TemplateDeclarationRange);
+  return tooling::Replacement(SourceManager, CharRange, "");
+}
+
+auto AbbreviateFunctionTemplate::deconstructType(QualType Type)
+    -> std::tuple<QualType, std::vector<tok::TokenKind>,
+                  std::vector<tok::TokenKind>> {
+  std::vector<tok::TokenKind> ParameterTypeQualifiers{};
+  std::vector<tok::TokenKind> ParameterQualifiers{};
+
+  if (Type->isIncompleteArrayType()) {
+    ParameterQualifiers.push_back(tok::l_square);
+    ParameterQualifiers.push_back(tok::r_square);
+    Type = Type->castAsArrayTypeUnsafe()->getElementType();
+  }
+
+  if (isa<PackExpansionType>(Type))
+    ParameterTypeQualifiers.push_back(tok::ellipsis);
+
+  Type = Type.getNonPackExpansionType();
+
+  if (Type->isRValueReferenceType()) {
+    ParameterTypeQualifiers.push_back(tok::ampamp);
+    Type = Type.getNonReferenceType();
+  }
+
+  if (Type->isLValueReferenceType()) {
+    ParameterTypeQualifiers.push_back(tok::amp);
+    Type = Type.getNonReferenceType();
+  }
+
+  if (Type.isConstQualified()) {
+    ParameterTypeQualifiers.push_back(tok::kw_const);
+  }
+
+  while (Type->isPointerType()) {
+    ParameterTypeQualifiers.push_back(tok::star);
+    Type = Type->getPointeeType();
+
+    if (Type.isConstQualified()) {
+      ParameterTypeQualifiers.push_back(tok::kw_const);
+    }
+  }
+
+  std::reverse(ParameterTypeQualifiers.begin(), ParameterTypeQualifiers.end());
+
+  return {Type, ParameterTypeQualifiers, ParameterQualifiers};
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
index 2e948c23569f6..f53a2e47d58a8 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
+++ b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
@@ -12,6 +12,7 @@ set(LLVM_LINK_COMPONENTS
 # $<TARGET_OBJECTS:obj.clangDaemonTweaks> to a list of sources, see
 # clangd/tool/CMakeLists.txt for an example.
 add_clang_library(clangDaemonTweaks OBJECT
+  AbbreviateFunctionTemplate.cpp
   AddUsing.cpp
   AnnotateHighlightings.cpp
   DumpAST.cpp
diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt
index 9cd195eaf164f..302e11d2019ca 100644
--- a/clang-tools-extra/clangd/unittests/CMakeLists.txt
+++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt
@@ -114,6 +114,7 @@ add_unittest(ClangdUnitTests ClangdTests
   support/ThreadingTests.cpp
   support/TraceTests.cpp
 
+  tweaks/AbbreviateFunctionTemplateTests.cpp
   tweaks/AddUsingTests.cpp
   tweaks/AnnotateHighlightingsTests.cpp
   tweaks/DefineInlineTests.cpp
diff --git a/clang-tools-extra/clangd/unittests/tweaks/AbbreviateFunctionTemplateTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/AbbreviateFunctionTemplateTests.cpp
new file mode 100644
index 0000000000000..d88fae4080433
--- /dev/null
+++ b/clang-tools-extra/clangd/unittests/tweaks/AbbreviateFunctionTemplateTests.cpp
@@ -0,0 +1,76 @@
+//===-- AbbreviateFunctionTemplateTests.cpp ---------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "TweakTesting.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+
+TWEAK_TEST(AbbreviateFunctionTemplate);
+
+TEST_F(AbbreviateFunctionTemplateTest, Test) {
+  Header = R"cpp(
+      template <typename T>
+      concept foo = true;
+
+      template <typename T>
+      concept bar = true;
+
+      template <typename T, typename U>
+      concept baz = true;
+
+      template <typename T>
+      class list;
+  )cpp";
+
+  ExtraArgs = {"-std=c++20"};
+
+  EXPECT_EQ(apply("template <typename T> auto ^fun(T param) {}"),
+            " auto fun( auto param) {}");
+  EXPECT_EQ(apply("template <foo T> auto ^fun(T param) {}"),
+            " auto fun( foo auto param) {}");
+  EXPECT_EQ(apply("template <foo T> auto ^fun(T) {}"),
+            " auto fun( foo auto ) {}");
+  EXPECT_EQ(apply("template <foo T> auto ^fun(T[]) {}"),
+            " auto fun( foo auto  [ ]) {}");
+  EXPECT_EQ(apply("template <foo T> auto ^fun(T const * param[]) {}"),
+            " auto fun( foo auto const * param [ ]) {}");
+  EXPECT_EQ(apply("template <baz<int> T> auto ^fun(T param) {}"),
+            " auto fun( baz <int> auto param) {}");
+  EXPECT_EQ(apply("template <foo T, bar U> auto ^fun(T param1, U param2) {}"),
+            " auto fun( foo auto param1,  bar auto param2) {}");
+  EXPECT_EQ(apply("template <foo T> auto ^fun(T const ** param) {}"),
+            " auto fun( foo auto const * * param) {}");
+  EXPECT_EQ(apply("template <typename...ArgTypes> auto ^fun(ArgTypes...params) "
+                  "-> void{}"),
+            " auto fun( auto ... params) -> void{}");
+
+  EXPECT_AVAILABLE("temp^l^ate <type^name ^T> au^to fu^n^(^T par^am) {}");
+  EXPECT_AVAILABLE("t^emplat^e <fo^o ^T> aut^o fu^n^(^T ^para^m) -> void {}");
+  EXPECT_AVAILABLE(
+      "^templa^te <f^oo T^> a^uto ^fun(^T const ** para^m) -> void {}");
+  EXPECT_AVAILABLE("templa^te <type^name...ArgTypes> auto "
+                   "fu^n(ArgTy^pes...^para^ms) -> void{}");
+
+  EXPECT_UNAVAILABLE(
+      "templ^ate<typenam^e T> auto f^u^n(list<T> pa^ram) -> void {}");
+
+  // Template parameters need to be in the same order as the function parameters
+  EXPECT_UNAVAILABLE(
+      "tem^plate<type^name ^T, typen^ame ^U> auto f^un(^U, ^T) -> void {}");
+
+  // Template parameter type can't be used within the function body
+  EXPECT_UNAVAILABLE("templ^ate<cl^ass T>"
+                     "aut^o fu^n(T param) -> v^oid { T bar; }");
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 9262f9bbfe62a..37f8309754958 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -72,6 +72,9 @@ Code actions
 - The extract variable tweak gained support for extracting lambda expressions to a variable.
 - A new tweak was added for turning unscoped into scoped enums.
 
+- The abbreviate function template tweak was introduced.
+  It allows converting function templates to their abbreviated form using auto parameters.
+
 Signature help
 ^^^^^^^^^^^^^^
 



More information about the cfe-commits mailing list