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 + +namespace clang { +namespace clangd { +namespace { +/// Converts a function template to its abbreviated form using auto parameters. +/// Before: +/// template +/// 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 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 FunctionParameterQualifiers; + std::vector FunctionParameterTypeQualifiers; + }; + + std::vector TemplateParameterInfoList; + + auto traverseFunctionParameters(size_t NumberOfTemplateParameters) -> bool; + + auto generateFunctionParameterReplacements(const ASTContext &Context) + -> llvm::Expected; + + auto generateFunctionParameterReplacement( + const TemplateParameterInfo &TemplateParameterInfo, + const ASTContext &Context) -> llvm::Expected; + + auto generateTemplateDeclarationReplacement(const ASTContext &Context) + -> llvm::Expected; + + static auto deconstructType(QualType Type) + -> std::tuple, + std::vector>; +}; + +REGISTER_TWEAK(AbbreviateFunctionTemplate) + +const char *AbbreviateFunctionTemplate::AutoKeywordSpelling = + getKeywordSpelling(tok::kw_auto); + +template +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(Node->ASTNode.get())) + 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(*CommonAncestor); + + if (!FunctionTemplateDeclaration) + return false; + + auto *TemplateParameters = + FunctionTemplateDeclaration->getTemplateParameters(); + + auto NumberOfTemplateParameters = TemplateParameters->size(); + TemplateParameterInfoList = + std::vector(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(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 { + 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(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 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 { + auto &SourceManager = Context.getSourceManager(); + + const auto *Function = FunctionTemplateDeclaration->getAsFunction(); + auto *Parameter = + Function->getParamDecl(TemplateParameterInfo.FunctionParameterIndex); + auto ParameterName = Parameter->getDeclName().getAsString(); + + std::vector 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 { + 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, + std::vector> { + std::vector ParameterTypeQualifiers{}; + std::vector ParameterQualifiers{}; + + if (Type->isIncompleteArrayType()) { + ParameterQualifiers.push_back(tok::l_square); + ParameterQualifiers.push_back(tok::r_square); + Type = Type->castAsArrayTypeUnsafe()->getElementType(); + } + + if (isa(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 # $ 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 + concept foo = true; + + template + concept bar = true; + + template + concept baz = true; + + template + class list; + )cpp"; + + ExtraArgs = {"-std=c++20"}; + + EXPECT_EQ(apply("template auto ^fun(T param) {}"), + " auto fun( auto param) {}"); + EXPECT_EQ(apply("template auto ^fun(T param) {}"), + " auto fun( foo auto param) {}"); + EXPECT_EQ(apply("template auto ^fun(T) {}"), + " auto fun( foo auto ) {}"); + EXPECT_EQ(apply("template auto ^fun(T[]) {}"), + " auto fun( foo auto [ ]) {}"); + EXPECT_EQ(apply("template auto ^fun(T const * param[]) {}"), + " auto fun( foo auto const * param [ ]) {}"); + EXPECT_EQ(apply("template T> auto ^fun(T param) {}"), + " auto fun( baz auto param) {}"); + EXPECT_EQ(apply("template auto ^fun(T param1, U param2) {}"), + " auto fun( foo auto param1, bar auto param2) {}"); + EXPECT_EQ(apply("template auto ^fun(T const ** param) {}"), + " auto fun( foo auto const * * param) {}"); + EXPECT_EQ(apply("template auto ^fun(ArgTypes...params) " + "-> void{}"), + " auto fun( auto ... params) -> void{}"); + + EXPECT_AVAILABLE("temp^l^ate au^to fu^n^(^T par^am) {}"); + EXPECT_AVAILABLE("t^emplat^e aut^o fu^n^(^T ^para^m) -> void {}"); + EXPECT_AVAILABLE( + "^templa^te a^uto ^fun(^T const ** para^m) -> void {}"); + EXPECT_AVAILABLE("templa^te auto " + "fu^n(ArgTy^pes...^para^ms) -> void{}"); + + EXPECT_UNAVAILABLE( + "templ^ate auto f^u^n(list pa^ram) -> void {}"); + + // Template parameters need to be in the same order as the function parameters + EXPECT_UNAVAILABLE( + "tem^plate auto f^un(^U, ^T) -> void {}"); + + // Template parameter type can't be used within the function body + EXPECT_UNAVAILABLE("templ^ate" + "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 ^^^^^^^^^^^^^^