[clang-tools-extra] [clangd] Add tweak to inline concept requirements (PR #69693)

via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 20 05:37:01 PDT 2023


================
@@ -0,0 +1,262 @@
+//===--- InlineConceptRequirement.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 "ParsedAST.h"
+#include "SourceCode.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"
+
+namespace clang {
+namespace clangd {
+namespace {
+/// Inlines a concept requirement.
+///
+/// Before:
+///   template <typename T> void f(T) requires foo<T> {}
+///                                            ^^^^^^
+/// After:
+///   template <foo T> void f(T) {}
+class InlineConceptRequirement : 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 "Inline concept requirement";
+  }
+  auto kind() const -> llvm::StringLiteral override {
+    return CodeAction::REFACTOR_KIND;
+  }
+
+private:
+  const ConceptSpecializationExpr *ConceptSpecializationExpression;
+  const TemplateTypeParmDecl *TemplateTypeParameterDeclaration;
+  const syntax::Token *RequiresToken;
+
+  static auto getTemplateParameterIndexOfTemplateArgument(
+      const TemplateArgument &TemplateArgument) -> std::optional<int>;
+  auto generateRequiresReplacement(ASTContext &)
+      -> std::variant<tooling::Replacement, llvm::Error>;
+  auto generateRequiresTokenReplacement(const syntax::TokenBuffer &)
+      -> tooling::Replacement;
+  auto generateTemplateParameterReplacement(ASTContext &Context)
+      -> tooling::Replacement;
+
+  static auto findToken(const ParsedAST *, const SourceRange &,
+                        const tok::TokenKind) -> const syntax::Token *;
+
+  template <typename T, typename NodeKind>
+  static auto findNode(const SelectionTree::Node &Root)
+      -> std::tuple<const T *, const SelectionTree::Node *>;
+
+  template <typename T>
+  static auto findExpression(const SelectionTree::Node &Root)
+      -> std::tuple<const T *, const SelectionTree::Node *> {
+    return findNode<T, Expr>(Root);
+  }
+
+  template <typename T>
+  static auto findDeclaration(const SelectionTree::Node &Root)
+      -> std::tuple<const T *, const SelectionTree::Node *> {
+    return findNode<T, Decl>(Root);
+  }
+};
+
+REGISTER_TWEAK(InlineConceptRequirement)
+
+auto InlineConceptRequirement::prepare(const Selection &Inputs) -> bool {
+  // Check if C++ version is 20 or higher
+  if (!Inputs.AST->getLangOpts().CPlusPlus20)
+    return false;
+
+  const auto *Root = Inputs.ASTSelection.commonAncestor();
+  if (!Root)
+    return false;
+
+  const SelectionTree::Node *ConceptSpecializationExpressionTreeNode;
+  std::tie(ConceptSpecializationExpression,
+           ConceptSpecializationExpressionTreeNode) =
+      findExpression<ConceptSpecializationExpr>(*Root);
+  if (!ConceptSpecializationExpression)
+    return false;
+
+  // Only allow concepts that are direct children of function template
+  // declarations or function declarations. This excludes conjunctions of
+  // concepts which are not handled.
+  const auto *ParentDeclaration =
+      ConceptSpecializationExpressionTreeNode->Parent->ASTNode.get<Decl>();
+  if (!isa_and_nonnull<FunctionTemplateDecl>(ParentDeclaration) &&
+      !isa_and_nonnull<FunctionDecl>(ParentDeclaration))
+    return false;
+
+  const FunctionTemplateDecl *FunctionTemplateDeclaration =
+      std::get<0>(findDeclaration<FunctionTemplateDecl>(*Root));
+  if (!FunctionTemplateDeclaration)
+    return false;
+
+  auto TemplateArguments =
+      ConceptSpecializationExpression->getTemplateArguments();
+  if (TemplateArguments.size() != 1)
+    return false;
+
+  auto TemplateParameterIndex =
+      getTemplateParameterIndexOfTemplateArgument(TemplateArguments[0]);
+  if (!TemplateParameterIndex)
+    return false;
+
+  TemplateTypeParameterDeclaration = dyn_cast_or_null<TemplateTypeParmDecl>(
+      FunctionTemplateDeclaration->getTemplateParameters()->getParam(
+          *TemplateParameterIndex));
+  if (!TemplateTypeParameterDeclaration->wasDeclaredWithTypename())
+    return false;
+
+  RequiresToken =
+      findToken(Inputs.AST, FunctionTemplateDeclaration->getSourceRange(),
+                tok::kw_requires);
+  if (!RequiresToken)
+    return false;
+
+  return true;
+}
+
+auto InlineConceptRequirement::apply(const Selection &Inputs)
+    -> Expected<Tweak::Effect> {
+  auto &Context = Inputs.AST->getASTContext();
+  auto &TokenBuffer = Inputs.AST->getTokens();
+
+  tooling::Replacements Replacements{};
+
+  if (auto Err =
+          Replacements.add(generateTemplateParameterReplacement(Context)))
+    return Err;
+
+  auto RequiresReplacement = generateRequiresReplacement(Context);
+
+  if (std::holds_alternative<llvm::Error>(RequiresReplacement))
+    return std::move(std::get<llvm::Error>(RequiresReplacement));
+
+  if (auto Err =
+          Replacements.add(std::get<tooling::Replacement>(RequiresReplacement)))
+    return Err;
+
+  if (auto Err =
+          Replacements.add(generateRequiresTokenReplacement(TokenBuffer)))
+    return Err;
+
+  return Effect::mainFileEdit(Context.getSourceManager(), Replacements);
+}
+
+auto InlineConceptRequirement::getTemplateParameterIndexOfTemplateArgument(
+    const TemplateArgument &TemplateArgument) -> std::optional<int> {
+  if (TemplateArgument.getKind() != TemplateArgument.Type)
+    return {};
+
+  auto TemplateArgumentType = TemplateArgument.getAsType();
+  if (!TemplateArgumentType->isTemplateTypeParmType())
+    return {};
+
+  const auto *TemplateTypeParameterType =
+      TemplateArgumentType->getAs<TemplateTypeParmType>();
+  if (!TemplateTypeParameterType)
+    return {};
+
+  return TemplateTypeParameterType->getIndex();
+}
+
+auto InlineConceptRequirement::generateRequiresReplacement(ASTContext &Context)
+    -> std::variant<tooling::Replacement, llvm::Error> {
+  auto &SourceManager = Context.getSourceManager();
+
+  auto RequiresRange =
+      toHalfOpenFileRange(SourceManager, Context.getLangOpts(),
+                          ConceptSpecializationExpression->getSourceRange());
+  if (!RequiresRange)
+    return error("Could not obtain range of the 'requires' branch. Macros?");
+
+  auto RequiresCode = toSourceCode(SourceManager, *RequiresRange);
+
+  return tooling::Replacement(SourceManager, RequiresRange->getBegin(),
----------------
5chmidti wrote:

Instead of needing to call `toSourceCode` to get the size/length of the `RequiresRange`, you could create a replacement with this constructor: `Replacement (const SourceManager &Sources, const CharSourceRange &Range, StringRef ReplacementText, const LangOptions &LangOpts=LangOptions())`.
You can get a `CharSourceRange` with `CharSourceRange::getCharRange`, resulting in
`return tooling::Replacement(SourceManager, CharSourceRange::getCharRange(RequiresRange), "");`. You used that constructor in line 204, or is there a specific reason why you could not use it here?

https://github.com/llvm/llvm-project/pull/69693


More information about the cfe-commits mailing list