[clang-tools-extra] [clang-tidy] Add modernize-make-direct check (PR #118120)

Helmut Januschka via cfe-commits cfe-commits at lists.llvm.org
Fri Nov 29 10:18:29 PST 2024


https://github.com/hjanuschka created https://github.com/llvm/llvm-project/pull/118120

Adds a new check that converts std::make_* function calls to direct constructor calls using CTAD. Transforms make_optional, make_unique, make_shared and make_pair into their equivalent direct constructor calls, leveraging C++17's class template argument deduction.

>From 2bcf868d023b5be2f3c88f1d68dd2084b7db4604 Mon Sep 17 00:00:00 2001
From: Helmut Januschka <helmut at januschka.com>
Date: Fri, 29 Nov 2024 19:17:36 +0100
Subject: [PATCH] [clang-tidy] Add modernize-make-direct check

Adds a new check that converts std::make_* function calls to direct constructor
calls using CTAD. Transforms make_optional, make_unique, make_shared and make_pair
into their equivalent direct constructor calls, leveraging C++17's class template
argument deduction.
---
 .../clang-tidy/modernize/CMakeLists.txt       |   1 +
 .../modernize/MakeFunctionToDirectCheck.cpp   | 108 ++++++++++++++++++
 .../modernize/MakeFunctionToDirectCheck.h     |  20 ++++
 .../modernize/ModernizeTidyModule.cpp         |   3 +
 clang-tools-extra/docs/ReleaseNotes.rst       |   6 +
 .../modernize/modernize-make-direct.rst       |  17 +++
 .../checkers/modernize/make-direct-check.cpp  |  66 +++++++++++
 7 files changed, 221 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/modernize/MakeFunctionToDirectCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/modernize/MakeFunctionToDirectCheck.h
 create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/modernize-make-direct.rst
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/make-direct-check.cpp

diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index c919d49b42873a..7e8d9296c6a64c 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -49,6 +49,7 @@ add_clang_library(clangTidyModernizeModule STATIC
   UseTransparentFunctorsCheck.cpp
   UseUncaughtExceptionsCheck.cpp
   UseUsingCheck.cpp
+  MakeFunctionToDirectCheck.cpp
 
   LINK_LIBS
   clangTidy
diff --git a/clang-tools-extra/clang-tidy/modernize/MakeFunctionToDirectCheck.cpp b/clang-tools-extra/clang-tidy/modernize/MakeFunctionToDirectCheck.cpp
new file mode 100644
index 00000000000000..0262f77f4992cb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/MakeFunctionToDirectCheck.cpp
@@ -0,0 +1,108 @@
+#include "MakeFunctionToDirectCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Type.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+void MakeFunctionToDirectCheck::registerMatchers(MatchFinder *Finder) {
+  if (!getLangOpts().CPlusPlus17)
+    return;
+  // Match make_xxx function calls
+  Finder->addMatcher(callExpr(callee(functionDecl(hasAnyName(
+                                  "std::make_optional", "std::make_unique",
+                                  "std::make_shared", "std::make_pair"))))
+                         .bind("make_call"),
+                     this);
+}
+
+bool MakeFunctionToDirectCheck::isMakeFunction(
+    const std::string &FuncName) const {
+  static const std::array<std::string_view, 4> MakeFuncs = {
+      "make_optional", "make_unique", "make_shared", "make_pair"};
+
+  return std::any_of(MakeFuncs.begin(), MakeFuncs.end(),
+                     [&](const auto &Prefix) {
+                       return FuncName.find(Prefix) != std::string::npos;
+                     });
+}
+
+std::string MakeFunctionToDirectCheck::getTemplateType(
+    const CXXConstructExpr *Construct) const {
+  if (!Construct)
+    return {};
+
+  const auto *RecordType =
+      dyn_cast<clang::RecordType>(Construct->getType().getTypePtr());
+  if (!RecordType)
+    return {};
+
+  return RecordType->getDecl()->getNameAsString();
+}
+
+void MakeFunctionToDirectCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Call = Result.Nodes.getNodeAs<CallExpr>("make_call");
+  if (!Call)
+    return;
+
+  const auto *FuncDecl = dyn_cast<FunctionDecl>(Call->getCalleeDecl());
+  if (!FuncDecl || !FuncDecl->getTemplateSpecializationArgs())
+    return;
+
+  std::string FuncName = FuncDecl->getNameAsString();
+  if (!isMakeFunction(FuncName))
+    return;
+
+  std::string Args;
+  if (Call->getNumArgs() > 0) {
+    SourceRange ArgRange(Call->getArg(0)->getBeginLoc(),
+                         Call->getArg(Call->getNumArgs() - 1)->getEndLoc());
+    Args = std::string(Lexer::getSourceText(
+        CharSourceRange::getTokenRange(ArgRange), *Result.SourceManager,
+        Result.Context->getLangOpts()));
+  }
+
+  std::string Replacement;
+  if (FuncName == "make_unique" || FuncName == "make_shared") {
+    const auto *TemplateArgs = FuncDecl->getTemplateSpecializationArgs();
+    if (!TemplateArgs || TemplateArgs->size() == 0)
+      return;
+
+    QualType Type = TemplateArgs->get(0).getAsType();
+    PrintingPolicy Policy(Result.Context->getLangOpts());
+    Policy.SuppressTagKeyword = true;
+    std::string TypeStr = Type.getAsString(Policy);
+
+    std::string SmartPtr =
+        (FuncName == "make_unique") ? "unique_ptr" : "shared_ptr";
+    Replacement = "std::" + SmartPtr + "(new " + TypeStr + "(" + Args + "))";
+  } else {
+    std::string TemplateType;
+    if (FuncName == "make_optional")
+      TemplateType = "std::optional";
+    else if (FuncName == "make_shared")
+      TemplateType = "std::shared_ptr";
+    else if (FuncName == "make_pair")
+      TemplateType = "std::pair";
+
+    if (TemplateType.empty())
+      return;
+
+    Replacement = TemplateType + "(" + Args + ")";
+  }
+
+  if (!Replacement.empty()) {
+    diag(Call->getBeginLoc(),
+         "use class template argument deduction (CTAD) instead of %0")
+        << FuncName
+        << FixItHint::CreateReplacement(
+               CharSourceRange::getTokenRange(Call->getSourceRange()),
+               Replacement);
+  }
+}
+
+} // namespace clang::tidy::modernize
\ No newline at end of file
diff --git a/clang-tools-extra/clang-tidy/modernize/MakeFunctionToDirectCheck.h b/clang-tools-extra/clang-tidy/modernize/MakeFunctionToDirectCheck.h
new file mode 100644
index 00000000000000..0fb786e827a71e
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/MakeFunctionToDirectCheck.h
@@ -0,0 +1,20 @@
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::modernize {
+
+class MakeFunctionToDirectCheck : public ClangTidyCheck {
+public:
+  MakeFunctionToDirectCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+  // Helper to check if the call is a make_xxx function
+  bool isMakeFunction(const std::string &FuncName) const;
+  // Get the template type from make_xxx call
+  std::string getTemplateType(const CXXConstructExpr *Construct) const;
+};
+
+} // namespace clang::tidy::modernize
\ No newline at end of file
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index 18607593320635..45754f0a0bc8f0 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -16,6 +16,7 @@
 #include "DeprecatedIosBaseAliasesCheck.h"
 #include "LoopConvertCheck.h"
 #include "MacroToEnumCheck.h"
+#include "MakeFunctionToDirectCheck.h"
 #include "MakeSharedCheck.h"
 #include "MakeUniqueCheck.h"
 #include "MinMaxUseInitializerListCheck.h"
@@ -122,6 +123,8 @@ class ModernizeModule : public ClangTidyModule {
     CheckFactories.registerCheck<UseUncaughtExceptionsCheck>(
         "modernize-use-uncaught-exceptions");
     CheckFactories.registerCheck<UseUsingCheck>("modernize-use-using");
+    CheckFactories.registerCheck<MakeFunctionToDirectCheck>(
+    "modernize-make-direct");
   }
 };
 
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index f050391110385e..c904b420688d5c 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -118,6 +118,12 @@ Improvements to clang-tidy
 New checks
 ^^^^^^^^^^
 
+- New :doc:`modernize-make-direct <clang-tidy/checks/modernize/make-direct>` check.
+
+  Converts std::make_* function calls to direct constructor calls using CTAD.
+  Transforms make_optional, make_unique, make_shared and make_pair into equivalent 
+  direct constructor calls using C++17's class template argument deduction.
+
 - New :doc:`bugprone-bitwise-pointer-cast
   <clang-tidy/checks/bugprone/bitwise-pointer-cast>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/modernize-make-direct.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/modernize-make-direct.rst
new file mode 100644
index 00000000000000..6860ff39c514c0
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/modernize-make-direct.rst
@@ -0,0 +1,17 @@
+.. title:: clang-tidy - modernize-make-direct
+
+modernize-make-direct
+====================
+
+Replaces ``std::make_*`` function calls with direct constructor calls using class template
+argument deduction (CTAD).
+
+================================== ====================================
+  Before                             After
+---------------------------------- ------------------------------------
+``std::make_optional<int>(42)``    ``std::optional(42)``
+``std::make_unique<Widget>(1)``    ``std::unique_ptr(new Widget(1))``
+``std::make_shared<Widget>(2)``    ``std::shared_ptr(new Widget(2))``
+``std::make_pair(1, "test")``      ``std::pair(1, "test")``
+================================== ====================================
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/make-direct-check.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/make-direct-check.cpp
new file mode 100644
index 00000000000000..db3e4fe1cb4ffb
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/make-direct-check.cpp
@@ -0,0 +1,66 @@
+// RUN: %check_clang_tidy %s modernize-make-direct %t
+
+namespace std {
+template<typename T>
+struct optional { 
+ optional(const T&) {} 
+};
+
+template<typename T> 
+optional<T> make_optional(const T& t) { return optional<T>(t); }
+
+template<typename T>
+struct unique_ptr {
+ explicit unique_ptr(T*) {}
+};
+
+template<typename T, typename... Args>
+unique_ptr<T> make_unique(Args&&... args) { 
+ return unique_ptr<T>(new T(args...));
+}
+
+template<typename T>
+struct shared_ptr {
+ shared_ptr(T*) {}
+};
+
+template<typename T, typename... Args>
+shared_ptr<T> make_shared(Args&&... args) {
+ return shared_ptr<T>(new T(args...));
+}
+
+template<typename T, typename U>
+struct pair {
+ T first;
+ U second;
+ pair(const T& x, const U& y) : first(x), second(y) {}
+};
+
+template<typename T, typename U>
+pair<T,U> make_pair(T&& t, U&& u) { 
+ return pair<T,U>(t, u); 
+}
+}
+
+struct Widget {
+ Widget(int x) {}
+};
+
+
+void basic_tests() {
+ auto o1 = std::make_optional<int>(42);
+ // CHECK-MESSAGES: warning: use class template argument deduction (CTAD) instead of make_optional [modernize-make-direct]
+ // CHECK-FIXES: auto o1 = std::optional(42);
+
+  auto u1 = std::make_unique<Widget>(1);
+ // CHECK-MESSAGES: warning: use class template argument deduction (CTAD) instead of make_unique [modernize-make-direct]
+ // CHECK-FIXES: auto u1 = std::unique_ptr(new Widget(1));
+
+ auto s1 = std::make_shared<Widget>(2);
+  // CHECK-MESSAGES: warning: use class template argument deduction (CTAD) instead of make_shared
+  // CHECK-FIXES: auto s1 = std::shared_ptr(new Widget(2));
+
+  auto p1 = std::make_pair(1, "test");
+  // CHECK-MESSAGES: warning: use class template argument deduction (CTAD) instead of make_pair
+  // CHECK-FIXES: auto p1 = std::pair(1, "test");
+}



More information about the cfe-commits mailing list