[clang-tools-extra] [clangd] Add tweak to abbreviate function templates (PR #74710)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 13 04:44:58 PDT 2026
================
@@ -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 "XRefs.h"
+#include "refactor/Tweak.h"
+#include "support/Logger.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.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;
+
+ bool prepare(const Selection &Inputs) override;
+ Expected<Effect> apply(const Selection &Inputs) override;
+
+ std::string title() const override {
+ return llvm::formatv("Abbreviate function template");
+ }
+
+ llvm::StringLiteral kind() const override {
+ return CodeAction::REFACTOR_KIND;
+ }
+
+private:
+ static const char *AutoKeywordSpelling;
+ const FunctionTemplateDecl *FunctionTemplateDeclaration;
+
+ struct TemplateParameterInfo {
+ const TypeConstraint *Constraint;
+ unsigned int FunctionParameterIndex;
+ llvm::SmallVector<tok::TokenKind> FunctionParameterQualifiers;
+ llvm::SmallVector<tok::TokenKind> FunctionParameterTypeQualifiers;
+ };
+
+ llvm::SmallVector<TemplateParameterInfo> TemplateParameterInfoList;
+
+ bool traverseFunctionParameters(size_t NumberOfTemplateParameters);
+
+ llvm::Expected<tooling::Replacements>
+ generateFunctionParameterReplacements(const ASTContext &Context);
+
+ llvm::Expected<tooling::Replacement> generateFunctionParameterReplacement(
+ const TemplateParameterInfo &TemplateParameterInfo,
+ const ASTContext &Context);
+
+ llvm::Expected<tooling::Replacement>
+ generateTemplateDeclarationReplacement(const ASTContext &Context);
+
+ static std::tuple<QualType, llvm::SmallVector<tok::TokenKind>,
+ llvm::SmallVector<tok::TokenKind>>
+ deconstructType(QualType Type);
+};
+
+REGISTER_TWEAK(AbbreviateFunctionTemplate)
+
+const char *AbbreviateFunctionTemplate::AutoKeywordSpelling =
+ getKeywordSpelling(tok::kw_auto);
+
+template <typename T>
+const T *findDeclaration(const SelectionTree::Node &Root) {
+ 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;
+}
+
+const char *getSpellingForQualifier(tok::TokenKind const &Qualifier) {
+ 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 =
+ llvm::SmallVector<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);
+}
+
+Expected<Tweak::Effect>
+AbbreviateFunctionTemplate::apply(const Selection &Inputs) {
+ 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);
+}
+
+bool AbbreviateFunctionTemplate::traverseFunctionParameters(
+ size_t NumberOfTemplateParameters) {
+ 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;
+}
+
+llvm::Expected<tooling::Replacements>
+AbbreviateFunctionTemplate::generateFunctionParameterReplacements(
+ const ASTContext &Context) {
+ 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;
+}
+
+llvm::Expected<tooling::Replacement>
+AbbreviateFunctionTemplate::generateFunctionParameterReplacement(
+ const TemplateParameterInfo &TemplateParameterInfo,
+ const ASTContext &Context) {
+ auto &SourceManager = Context.getSourceManager();
+
+ const auto *Function = FunctionTemplateDeclaration->getAsFunction();
+ auto *Parameter =
+ Function->getParamDecl(TemplateParameterInfo.FunctionParameterIndex);
+ auto ParameterName = Parameter->getDeclName().getAsString();
+
+ llvm::SmallVector<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() + '>');
----------------
timon-ul wrote:
Honestly I am a bit confused of this codeblock, is there a case in which we actually generate angular brackets as output? What case would that be? I don't think you have a test for this.
Also why do we have to manually add the closing angular bracket? Shouldn't that already be optimally be part of the `SourceRange` or is that not present in the code for this case? An example for this would really help me understand.
https://github.com/llvm/llvm-project/pull/74710
More information about the cfe-commits
mailing list