Index: clang-tidy/modernize/CMakeLists.txt =================================================================== --- clang-tidy/modernize/CMakeLists.txt +++ clang-tidy/modernize/CMakeLists.txt @@ -13,6 +13,7 @@ RawStringLiteralCheck.cpp RedundantVoidArgCheck.cpp ReplaceAutoPtrCheck.cpp + ReplaceGenericFunctorCallCheck.cpp ReplaceRandomShuffleCheck.cpp ReturnBracedInitListCheck.cpp ShrinkToFitCheck.cpp Index: clang-tidy/modernize/ModernizeTidyModule.cpp =================================================================== --- clang-tidy/modernize/ModernizeTidyModule.cpp +++ clang-tidy/modernize/ModernizeTidyModule.cpp @@ -19,6 +19,7 @@ #include "RawStringLiteralCheck.h" #include "RedundantVoidArgCheck.h" #include "ReplaceAutoPtrCheck.h" +#include "ReplaceGenericFunctorCallCheck.h" #include "ReplaceRandomShuffleCheck.h" #include "ReturnBracedInitListCheck.h" #include "ShrinkToFitCheck.h" @@ -58,6 +59,8 @@ "modernize-redundant-void-arg"); CheckFactories.registerCheck( "modernize-replace-auto-ptr"); + CheckFactories.registerCheck( + "modernize-replace-generic-functor-call"); CheckFactories.registerCheck( "modernize-replace-random-shuffle"); CheckFactories.registerCheck( Index: clang-tidy/modernize/ReplaceGenericFunctorCallCheck.h =================================================================== --- /dev/null +++ clang-tidy/modernize/ReplaceGenericFunctorCallCheck.h @@ -0,0 +1,42 @@ +//===--- ReplaceGenericFunctorCallCheck.h - clang-tidy-----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REPLACEGENERICFUNCTORCALLCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REPLACEGENERICFUNCTORCALLCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Use std::invoke to call callable objects in generic code +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-replace-generic-functor-call.html +class ReplaceGenericFunctorCallCheck : public ClangTidyCheck { +public: + ReplaceGenericFunctorCallCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void diagnose(const CallExpr *Functor, const std::string &Param, + const std::string &OriginalParam); + std::string strFromLoc(const SourceManager *Source, + const SourceLocation &Start, + const SourceLocation &End); +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_REPLACEGENERICFUNCTORCALLCHECK_H Index: clang-tidy/modernize/ReplaceGenericFunctorCallCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/modernize/ReplaceGenericFunctorCallCheck.cpp @@ -0,0 +1,116 @@ +//===--- ReplaceGenericFunctorCallCheck.cpp - clang-tidy-------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ReplaceGenericFunctorCallCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +void ReplaceGenericFunctorCallCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus17) + return; + + // template + // void f(T func) { + // func(); + // ^~~~~~~ + // ::std::invoke(func, 1) + Finder->addMatcher(callExpr(has(declRefExpr())).bind("functor"), this); + + // template + // void f(T mfp) { + // Foo foo; + // (foo.*mfp)(); + // ^~~~~~~~~~~~~ + // ::std::invoke(foo, func, 1) + Finder->addMatcher( + callExpr(has(parenExpr(has(binaryOperator(hasOperatorName(".*")))))) + .bind("mfunctor"), + this); +} + +void ReplaceGenericFunctorCallCheck::check( + const MatchFinder::MatchResult &Result) { + + const auto *Source = Result.SourceManager; + + const auto *Functor = Result.Nodes.getNodeAs("functor"); + if (Functor && Functor->isTypeDependent()) { + const auto EndLoc = Functor->getNumArgs() == 0 + ? Functor->getRParenLoc() + : Functor->getArg(0)->getLocStart(); + const std::string Param = + strFromLoc(Source, Functor->getLocStart(), EndLoc.getLocWithOffset(-1)); + const std::string OriginalParams = + Functor->getNumArgs() == 0 + ? "" + : strFromLoc(Source, Functor->getArg(0)->getLocStart(), + Functor->getLocEnd()); + + diagnose(Functor, Param, OriginalParams + ")"); + return; + } + + const auto *MFunctor = Result.Nodes.getNodeAs("mfunctor"); + if (MFunctor && MFunctor->isTypeDependent()) { + const auto *Paren = static_cast(MFunctor->getCallee()); + const auto *BinOp = + static_cast(Paren->getSubExpr()); + + const auto *Obj = BinOp->getLHS(); + const std::string ObjName = + strFromLoc(Source, Obj->getLocStart(), BinOp->getOperatorLoc()); + + const auto *MFunc = BinOp->getRHS(); + const char *MFuncEnd = Source->getCharacterData(Paren->getRParen()); + const std::string FuncName = + strFromLoc(Source, MFunc->getLocStart(), Paren->getRParen()); + + std::string OriginalParams(MFuncEnd + 2, + Source->getCharacterData(MFunctor->getLocEnd()) - + (MFuncEnd + 1)); + + const std::string Param = ObjName + ", " + FuncName; + diagnose(MFunctor, Param, OriginalParams); + return; + } +} + +void ReplaceGenericFunctorCallCheck::diagnose( + const CallExpr *Functor, const std::string &Param, + const std::string &OriginalParams) { + + std::ostringstream Replace; + Replace << "::std::invoke(" << Param + << (Functor->getNumArgs() == 0 ? "" : ", ") << OriginalParams; + diag(Functor->getExprLoc(), + "Use ::std::invoke to invoke type dependent callable objects.") + << FixItHint::CreateReplacement(Functor->getSourceRange(), Replace.str()); +} + +std::string +ReplaceGenericFunctorCallCheck::strFromLoc(const SourceManager *Source, + const SourceLocation &Start, + const SourceLocation &End) { + const char *StartPos = Source->getCharacterData(Start); + const char *EndPos = Source->getCharacterData(End); + int Len = EndPos - StartPos >= 0 ? EndPos - StartPos : 0; + return std::string(StartPos, Len); +} +} // namespace modernize +} // namespace tidy +} // namespace clang Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -57,6 +57,12 @@ Improvements to clang-tidy -------------------------- +- New `modernize-replace-generic-functor-call + `_ check + + Replace type dependent functor, function pointer and pointer to member call + to the proper ``std::invoke()`` in C++17 or newer codebases. + - The 'misc-incorrect-roundings' check was renamed to `bugprone-incorrect-roundings `_ Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -163,6 +163,7 @@ modernize-raw-string-literal modernize-redundant-void-arg modernize-replace-auto-ptr + modernize-replace-generic-functor-call modernize-replace-random-shuffle modernize-return-braced-init-list modernize-shrink-to-fit Index: docs/clang-tidy/checks/modernize-replace-generic-functor-call.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/modernize-replace-generic-functor-call.rst @@ -0,0 +1,31 @@ +.. title:: clang-tidy - modernize-replace-generic-functor-call + +modernize-replace-generic-functor-call +====================================== + +This check finds and replace the functor, function pointer and pointer to member +calls to the proper ``std::invoke()`` call. It works only in C++17 or newer. + +Examples: + +.. code-block:: c++ + + template + void f(T1 func, T2 mfp) { + func(2); // Replace to ::std::invoke(func, 2) + Foo foo; + (foo.*mfp)(1, 2); // Replace to ::std::invoke(foo, mfp, 1, 2) + (Foo().*mfp)(3); // Replace to ::std::invoke(Foo(), mfp, 3) + } + + // Neither of them is type dependent, no diagnose + void g(std::function func, int (*fp)(int), void (Foo::*mfp)(int)) { + func3(); + std::max(1,2); + Foo::print_(); + [](){}(); + func(); + fp(3); + Foo foo; + (foo.*(mfp))(1); + } Index: test/clang-tidy/modernize-replace-generic-functor-call.cpp =================================================================== --- /dev/null +++ test/clang-tidy/modernize-replace-generic-functor-call.cpp @@ -0,0 +1,56 @@ +// RUN: %check_clang_tidy %s modernize-replace-generic-functor-call %t -- -- -std=c++17 + +namespace std { + +template +T max(T a, T b) { + return a < b ? b : a; +} + +struct function { + void operator()(); +}; + +} // namespace std + +struct Foo { + static void print_() {} + void print() const {} + int num_; +}; + +// Something that triggers the check +template +void func2(T func) { + func(1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Use ::std::invoke to invoke type dependent callable objects. [modernize-replace-generic-functor-call] + // CHECK-FIXES: ::std::invoke(func, 1); + func(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Use ::std::invoke to invoke type dependent callable objects. [modernize-replace-generic-functor-call] + // CHECK-FIXES: ::std::invoke(func); + + Foo foo; + (foo.*func)(1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Use ::std::invoke to invoke type dependent callable objects. [modernize-replace-generic-functor-call] + // CHECK-FIXES: ::std::invoke(foo, func, 1); + (foo.*func)(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Use ::std::invoke to invoke type dependent callable objects. [modernize-replace-generic-functor-call] + // CHECK-FIXES: ::std::invoke(foo, func); + (Foo().*func)(1, 2); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Use ::std::invoke to invoke type dependent callable objects. [modernize-replace-generic-functor-call] + // CHECK-FIXES: ::std::invoke(Foo(), func, 1, 2); +} + +// Something that doesn't triggers the check +void func3() {} + +void g(std::function func, int (*fp)(int), void (Foo::*mfp)(int)) { + func3(); // Regular function call + std::max(1, 2); // Template function call + Foo::print_(); // Static function call + []() {}(); // Lambda call + func(); // Call through std::function + fp(3); // Call through function pointer + Foo foo; + (foo.*(mfp))(1); // Call through member function pointer +}