Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[clang-tidy] Add modernize-make-direct check #118120

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ add_clang_library(clangTidyModernizeModule STATIC
UseTransparentFunctorsCheck.cpp
UseUncaughtExceptionsCheck.cpp
UseUsingCheck.cpp
MakeFunctionToDirectCheck.cpp

LINK_LIBS
clangTidy
Expand Down
108 changes: 108 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/MakeFunctionToDirectCheck.cpp
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/MakeFunctionToDirectCheck.h
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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");
}
};

Expand Down
6 changes: 6 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
@@ -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")``
================================== ====================================

Original file line number Diff line number Diff line change
@@ -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");
}
Loading