[clang-tools-extra] [clangd] Add tweak to inline concept requirements (PR #69693)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Nov 27 04:05:42 PST 2023
https://github.com/Venyla updated https://github.com/llvm/llvm-project/pull/69693
>From d42a9f963471d6e78584febdacf4c5e99419a3c2 Mon Sep 17 00:00:00 2001
From: Vina Zahnd <vina.zahnd at gmail.com>
Date: Fri, 20 Oct 2023 10:01:54 +0200
Subject: [PATCH] [clangd] Add tweak to inline concept requirements
Co-authored-by: Jeremy Stucki <dev at jeremystucki.ch>
---
.../clangd/refactor/tweaks/CMakeLists.txt | 1 +
.../tweaks/InlineConceptRequirement.cpp | 265 ++++++++++++++++++
.../clangd/unittests/CMakeLists.txt | 1 +
.../tweaks/InlineConceptRequirementTests.cpp | 94 +++++++
clang-tools-extra/docs/ReleaseNotes.rst | 3 +
5 files changed, 364 insertions(+)
create mode 100644 clang-tools-extra/clangd/refactor/tweaks/InlineConceptRequirement.cpp
create mode 100644 clang-tools-extra/clangd/unittests/tweaks/InlineConceptRequirementTests.cpp
diff --git a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
index 526a073f619ea34..b01053faf738a90 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
+++ b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
@@ -21,6 +21,7 @@ add_clang_library(clangDaemonTweaks OBJECT
ExpandMacro.cpp
ExtractFunction.cpp
ExtractVariable.cpp
+ InlineConceptRequirement.cpp
MemberwiseConstructor.cpp
ObjCLocalizeStringLiteral.cpp
ObjCMemberwiseInitializer.cpp
diff --git a/clang-tools-extra/clangd/refactor/tweaks/InlineConceptRequirement.cpp b/clang-tools-extra/clangd/refactor/tweaks/InlineConceptRequirement.cpp
new file mode 100644
index 000000000000000..201ad8ee15dc5d5
--- /dev/null
+++ b/clang-tools-extra/clangd/refactor/tweaks/InlineConceptRequirement.cpp
@@ -0,0 +1,265 @@
+//===--- 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 &)
+ -> llvm::Expected<tooling::Replacement>;
+ auto generateRequiresTokenReplacement(const syntax::TokenBuffer &)
+ -> tooling::Replacement;
+ auto generateTemplateParameterReplacement(ASTContext &Context)
+ -> llvm::Expected<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{};
+
+ auto TemplateParameterReplacement =
+ generateTemplateParameterReplacement(Context);
+
+ if (auto Err = TemplateParameterReplacement.takeError())
+ return Err;
+
+ if (auto Err = Replacements.add(*TemplateParameterReplacement))
+ return Err;
+
+ auto RequiresReplacement = generateRequiresReplacement(Context);
+
+ if (auto Err = RequiresReplacement.takeError())
+ return Err;
+
+ if (auto Err = Replacements.add(*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)
+ -> llvm::Expected<tooling::Replacement> {
+ 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?");
+
+ return tooling::Replacement(
+ SourceManager, CharSourceRange::getCharRange(*RequiresRange), "");
+}
+
+auto InlineConceptRequirement::generateRequiresTokenReplacement(
+ const syntax::TokenBuffer &TokenBuffer) -> tooling::Replacement {
+ auto &SourceManager = TokenBuffer.sourceManager();
+
+ auto Spelling =
+ TokenBuffer.spelledForExpanded(llvm::ArrayRef(*RequiresToken));
+
+ auto DeletionRange =
+ syntax::Token::range(SourceManager, Spelling->front(), Spelling->back())
+ .toCharRange(SourceManager);
+
+ return tooling::Replacement(SourceManager, DeletionRange, "");
+}
+
+auto InlineConceptRequirement::generateTemplateParameterReplacement(
+ ASTContext &Context) -> llvm::Expected<tooling::Replacement> {
+ auto &SourceManager = Context.getSourceManager();
+
+ auto ConceptName = ConceptSpecializationExpression->getNamedConcept()
+ ->getQualifiedNameAsString();
+
+ auto TemplateParameterName =
+ TemplateTypeParameterDeclaration->getQualifiedNameAsString();
+
+ auto TemplateParameterReplacement = ConceptName + ' ' + TemplateParameterName;
+
+ auto TemplateParameterRange =
+ toHalfOpenFileRange(SourceManager, Context.getLangOpts(),
+ TemplateTypeParameterDeclaration->getSourceRange());
+
+ if (!TemplateParameterRange)
+ return error("Could not obtain range of the template parameter. Macros?");
+
+ return tooling::Replacement(
+ SourceManager, CharSourceRange::getCharRange(*TemplateParameterRange),
+ TemplateParameterReplacement);
+}
+
+auto clang::clangd::InlineConceptRequirement::findToken(
+ const ParsedAST *AST, const SourceRange &SourceRange,
+ const tok::TokenKind TokenKind) -> const syntax::Token * {
+ auto &TokenBuffer = AST->getTokens();
+ const auto &Tokens = TokenBuffer.expandedTokens(SourceRange);
+
+ const auto Predicate = [TokenKind](const auto &Token) {
+ return Token.kind() == TokenKind;
+ };
+
+ auto It = std::find_if(Tokens.begin(), Tokens.end(), Predicate);
+
+ if (It == Tokens.end())
+ return nullptr;
+
+ return It;
+}
+
+template <typename T, typename NodeKind>
+auto InlineConceptRequirement::findNode(const SelectionTree::Node &Root)
+ -> std::tuple<const T *, const SelectionTree::Node *> {
+
+ for (const auto *Node = &Root; Node; Node = Node->Parent) {
+ if (const T *Result = dyn_cast_or_null<T>(Node->ASTNode.get<NodeKind>()))
+ return {Result, Node};
+ }
+
+ return {};
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt
index 8d02b91fdd71669..58773a445545cc3 100644
--- a/clang-tools-extra/clangd/unittests/CMakeLists.txt
+++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt
@@ -125,6 +125,7 @@ add_unittest(ClangdUnitTests ClangdTests
tweaks/ExpandMacroTests.cpp
tweaks/ExtractFunctionTests.cpp
tweaks/ExtractVariableTests.cpp
+ tweaks/InlineConceptRequirementTests.cpp
tweaks/MemberwiseConstructorTests.cpp
tweaks/ObjCLocalizeStringLiteralTests.cpp
tweaks/ObjCMemberwiseInitializerTests.cpp
diff --git a/clang-tools-extra/clangd/unittests/tweaks/InlineConceptRequirementTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/InlineConceptRequirementTests.cpp
new file mode 100644
index 000000000000000..54688318c95a2e9
--- /dev/null
+++ b/clang-tools-extra/clangd/unittests/tweaks/InlineConceptRequirementTests.cpp
@@ -0,0 +1,94 @@
+//===-- InlineConceptRequirementTests.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(InlineConceptRequirement);
+
+TEST_F(InlineConceptRequirementTest, Test) {
+ Header = R"cpp(
+ template <typename T>
+ concept foo = true;
+
+ template <typename T>
+ concept bar = true;
+
+ template <typename T, typename U>
+ concept baz = true;
+ )cpp";
+
+ ExtraArgs = {"-std=c++20"};
+
+ //
+ // Extra spaces are expected and will be stripped by the formatter.
+ //
+
+ EXPECT_EQ(
+ apply("template <typename T, typename U> void f(T) requires f^oo<U> {}"),
+ "template <typename T, foo U> void f(T) {}");
+
+ EXPECT_EQ(
+ apply("template <typename T, typename U> requires foo<^T> void f(T) {}"),
+ "template <foo T, typename U> void f(T) {}");
+
+ EXPECT_EQ(apply("template <template <typename> class FooBar, typename T> "
+ "void f() requires foo<^T> {}"),
+ "template <template <typename> class FooBar, foo T> void f() {}");
+
+ EXPECT_AVAILABLE(R"cpp(
+ template <typename T> void f(T)
+ requires ^f^o^o^<^T^> {}
+ )cpp");
+
+ EXPECT_AVAILABLE(R"cpp(
+ template <typename T> requires ^f^o^o^<^T^>
+ void f(T) {}
+ )cpp");
+
+ EXPECT_AVAILABLE(R"cpp(
+ template <typename T, typename U> void f(T)
+ requires ^f^o^o^<^T^> {}
+ )cpp");
+
+ EXPECT_AVAILABLE(R"cpp(
+ template <template <typename> class FooBar, typename T>
+ void foobar() requires ^f^o^o^<^T^>
+ {}
+ )cpp");
+
+ EXPECT_UNAVAILABLE(R"cpp(
+ template <bar T> void f(T)
+ requires ^f^o^o^<^T^> {}
+ )cpp");
+
+ EXPECT_UNAVAILABLE(R"cpp(
+ template <typename T, typename U> void f(T, U)
+ requires ^b^a^z^<^T^,^ ^U^> {}
+ )cpp");
+
+ EXPECT_UNAVAILABLE(R"cpp(
+ template <typename T> void f(T)
+ requires ^f^o^o^<^T^>^ ^&^&^ ^b^a^r^<^T^> {}
+ )cpp");
+
+ EXPECT_UNAVAILABLE(R"cpp(
+ template <typename T>
+ concept ^f^o^o^b^a^r = requires(^T^ ^x^) {
+ {x} -> ^f^o^o^;
+ };
+ )cpp");
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 366b3abbe1244bf..f2ec0e1bdde838d 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -71,6 +71,9 @@ Code actions
- The extract variable tweak gained support for extracting lambda expressions to a variable.
+- The inline concept requirement tweak was introduced.
+ It allows inlining of simple require clauses into the template declaration.
+
Signature help
^^^^^^^^^^^^^^
More information about the cfe-commits
mailing list