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,43 @@ +//===--- 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,115 @@ +//===--- 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 "llvm/ADT/Twine.h" +#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 SourceManager *Source = Result.SourceManager; + + const auto *Functor = Result.Nodes.getNodeAs("functor"); + if (Functor && Functor->isTypeDependent()) { + const SourceLocation 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 Expr *Obj = BinOp->getLHS(); + const std::string ObjName = + strFromLoc(Source, Obj->getLocStart(), BinOp->getOperatorLoc()); + + const Expr *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) { + const Twine &Replace = Twine("::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 @@ -142,6 +142,12 @@ Checks on ``switch`` and ``if`` - ``else if`` constructs that do not cover all possible code paths. +- New :doc:`modernize-replace-generic-functor-call + ` check. + + Replaces type dependent functor, function pointer and pointer to member call + to the proper ``std::invoke()`` in C++17 or newer codebases. + - New :doc:`modernize-use-uncaught-exceptions ` check. Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -171,6 +171,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,39 @@ +.. title:: clang-tidy - modernize-replace-generic-functor-call + +modernize-replace-generic-functor-call +====================================== + +Replaces type dependent functor, function pointer and pointer to member call +to the proper ``std::invoke()`` in C++17 or newer codebases. + +Examples: + +.. code-block:: c++ + + template + void f(T1 functionPointer, T2 memberFunctionPointer) { + functionPointer(2); // Replace to ::std::invoke(functionPointer, 2) + + Foo foo; + (foo.*memberFunctionPointer)(1, 2); // Replace to ::std::invoke(foo, memberFunctionPointer, 1, 2) + + (Foo().*memberFunctionPointer)(3); // Replace to ::std::invoke(Foo(), memberFunctionPointer, 3) + } + + // Neither of them is type dependent, no diagnose + void g(std::function func, int (*functionPointer)(int), void (Foo::*memberFunctionPointer)(int)) { + freeFunction(); + + std::max(1,2); + + Foo::print_(); + + [](){}(); + + func(); + + functionPointer(3); + + Foo foo; + (foo.*(memberFunctionPointer))(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 +}