[clang-tools-extra] 64f74bf - [clang-tidy] Rewrite modernize-avoid-bind check.
Zachary Turner via cfe-commits
cfe-commits at lists.llvm.org
Mon Dec 2 15:36:43 PST 2019
Author: Zachary Turner
Date: 2019-12-02T15:36:26-08:00
New Revision: 64f74bf72eb484aa32e1104050cb54745116decf
URL: https://github.com/llvm/llvm-project/commit/64f74bf72eb484aa32e1104050cb54745116decf
DIFF: https://github.com/llvm/llvm-project/commit/64f74bf72eb484aa32e1104050cb54745116decf.diff
LOG: [clang-tidy] Rewrite modernize-avoid-bind check.
This represents largely a full re-write of modernize-avoid-bind, adding
significant new functionality in the process. In particular:
* Both boost::bind and std::bind are now supported
* Function objects are supported in addition to functions
* Member functions are supported
* Nested calls are supported using capture-init syntax
* std::ref() and boost::ref() are now recognized, and will capture by reference.
* Rather than capturing with a global =, we now build up an individual
capture list that is both necessary and sufficient for the call.
* Fixits are supported in a much larger variety of scenarios than before.
All previous tests pass under the re-write, but a large number of new
tests have been added as well.
Differential Revision: https://reviews.llvm.org/D70368
Added:
clang-tools-extra/test/clang-tidy/checkers/modernize-avoid-bind-permissive-parameter-list.cpp
Modified:
clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.cpp
clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.h
clang-tools-extra/docs/ReleaseNotes.rst
clang-tools-extra/docs/clang-tidy/checks/modernize-avoid-bind.rst
clang-tools-extra/test/clang-tidy/checkers/modernize-avoid-bind.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.cpp b/clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.cpp
index 2d4475c991ca..d1994073bd07 100644
--- a/clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.cpp
@@ -14,11 +14,12 @@
#include "clang/Basic/SourceLocation.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
-#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
+#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
@@ -34,45 +35,270 @@ namespace modernize {
namespace {
enum BindArgumentKind { BK_Temporary, BK_Placeholder, BK_CallExpr, BK_Other };
+enum CaptureMode { CM_None, CM_ByRef, CM_ByValue, CM_InitExpression };
+
+enum CallableType {
+ CT_Other, // unknown
+ CT_Function, // global or static function
+ CT_MemberFunction, // member function with implicit this
+ CT_Object, // object with operator()
+};
+
+enum CallableMaterializationKind {
+ CMK_Other, // unknown
+ CMK_Function, // callable is the name of a member or non-member function.
+ CMK_VariableRef, // callable is a simple expression involving a global or
+ // local variable.
+ CMK_CallExpression, // callable is obtained as the result of a call expression
+};
struct BindArgument {
- StringRef Tokens;
+ // A rough classification of the type of expression this argument was.
BindArgumentKind Kind = BK_Other;
+
+ // If this argument required a capture, a value indicating how it was
+ // captured.
+ CaptureMode CaptureMode = CM_None;
+
+ // The exact spelling of this argument in the source code.
+ StringRef SourceTokens;
+
+ // The identifier of the variable within the capture list. This may be
+ //
diff erent from UsageIdentifier for example in the expression *d, where the
+ // variable is captured as d, but referred to as *d.
+ std::string CaptureIdentifier;
+
+ // If this is a placeholder or capture init expression, contains the tokens
+ // used to refer to this parameter from within the body of the lambda.
+ std::string UsageIdentifier;
+
+ // If Kind == BK_Placeholder, the index of the placeholder.
size_t PlaceHolderIndex = 0;
+
+ // True if the argument is used inside the lambda, false otherwise.
+ bool IsUsed = false;
+
+ // The actual Expr object representing this expression.
+ const Expr *E = nullptr;
+};
+
+struct CallableInfo {
+ CallableType Type = CT_Other;
+ CallableMaterializationKind Materialization = CMK_Other;
+ CaptureMode CaptureMode = CM_None;
+ StringRef SourceTokens;
+ std::string CaptureIdentifier;
+ std::string UsageIdentifier;
+ StringRef CaptureInitializer;
+ const FunctionDecl *Decl = nullptr;
+};
+
+struct LambdaProperties {
+ CallableInfo Callable;
+ SmallVector<BindArgument, 4> BindArguments;
+ StringRef BindNamespace;
+ bool IsFixitSupported = false;
};
} // end namespace
+static const Expr *ignoreTemporariesAndPointers(const Expr *E) {
+ if (const auto *T = dyn_cast<UnaryOperator>(E))
+ return ignoreTemporariesAndPointers(T->getSubExpr());
+
+ const Expr *F = E->IgnoreImplicit();
+ if (E != F)
+ return ignoreTemporariesAndPointers(F);
+
+ return E;
+}
+
+static const Expr *ignoreTemporariesAndConstructors(const Expr *E) {
+ if (const auto *T = dyn_cast<CXXConstructExpr>(E))
+ return ignoreTemporariesAndConstructors(T->getArg(0));
+
+ const Expr *F = E->IgnoreImplicit();
+ if (E != F)
+ return ignoreTemporariesAndPointers(F);
+
+ return E;
+}
+
+static StringRef getSourceTextForExpr(const MatchFinder::MatchResult &Result,
+ const Expr *E) {
+ return Lexer::getSourceText(
+ CharSourceRange::getTokenRange(E->getBeginLoc(), E->getEndLoc()),
+ *Result.SourceManager, Result.Context->getLangOpts());
+}
+
+static bool isCallExprNamed(const Expr *E, StringRef Name) {
+ const auto *CE = dyn_cast<CallExpr>(E->IgnoreImplicit());
+ if (!CE)
+ return false;
+ const auto *ND = dyn_cast<NamedDecl>(CE->getCalleeDecl());
+ if (!ND)
+ return false;
+ return ND->getQualifiedNameAsString() == Name;
+}
+
+static void
+initializeBindArgumentForCallExpr(const MatchFinder::MatchResult &Result,
+ BindArgument &B, const CallExpr *CE,
+ unsigned &CaptureIndex) {
+ // std::ref(x) means to capture x by reference.
+ if (isCallExprNamed(CE, "boost::ref") || isCallExprNamed(CE, "std::ref")) {
+ B.Kind = BK_Other;
+ B.CaptureMode = CM_ByRef;
+ B.UsageIdentifier = getSourceTextForExpr(Result, CE->getArg(0));
+ } else {
+ B.Kind = BK_CallExpr;
+ B.CaptureMode = CM_InitExpression;
+ B.UsageIdentifier = "capture" + llvm::utostr(CaptureIndex++);
+ }
+ B.CaptureIdentifier = B.UsageIdentifier;
+}
+
+static bool anyDescendantIsLocal(const Stmt *Statement) {
+ if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Statement)) {
+ const ValueDecl *Decl = DeclRef->getDecl();
+ if (const auto *Var = dyn_cast_or_null<VarDecl>(Decl)) {
+ if (Var->isLocalVarDeclOrParm())
+ return true;
+ }
+ } else if (isa<CXXThisExpr>(Statement))
+ return true;
+
+ return any_of(Statement->children(), anyDescendantIsLocal);
+}
+
+static bool tryCaptureAsLocalVariable(const MatchFinder::MatchResult &Result,
+ BindArgument &B, const Expr *E) {
+ if (const auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E)) {
+ if (const auto *CE = dyn_cast<CXXConstructExpr>(BTE->getSubExpr()))
+ return tryCaptureAsLocalVariable(Result, B, CE->getArg(0));
+ return false;
+ }
+
+ const auto *DRE = dyn_cast<DeclRefExpr>(E->IgnoreImplicit());
+ if (!DRE)
+ return false;
+
+ const auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
+ if (!VD || !VD->isLocalVarDeclOrParm())
+ return false;
+
+ B.CaptureMode = CM_ByValue;
+ B.UsageIdentifier = getSourceTextForExpr(Result, E);
+ B.CaptureIdentifier = B.UsageIdentifier;
+ return true;
+}
+
+static bool tryCaptureAsMemberVariable(const MatchFinder::MatchResult &Result,
+ BindArgument &B, const Expr *E) {
+ if (const auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E)) {
+ if (const auto *CE = dyn_cast<CXXConstructExpr>(BTE->getSubExpr()))
+ return tryCaptureAsMemberVariable(Result, B, CE->getArg(0));
+ return false;
+ }
+
+ E = E->IgnoreImplicit();
+ if (isa<CXXThisExpr>(E)) {
+ B.CaptureMode = CM_ByValue;
+ B.UsageIdentifier = getSourceTextForExpr(Result, E);
+ B.CaptureIdentifier = "this";
+ return true;
+ }
+
+ const auto *ME = dyn_cast<MemberExpr>(E);
+ if (!ME)
+ return false;
+
+ if (!ME->isLValue() || !isa<FieldDecl>(ME->getMemberDecl()))
+ return false;
+
+ B.CaptureMode = CM_ByValue;
+ B.UsageIdentifier = getSourceTextForExpr(Result, E);
+ B.CaptureIdentifier = "this";
+ return true;
+}
+
static SmallVector<BindArgument, 4>
-buildBindArguments(const MatchFinder::MatchResult &Result, const CallExpr *C) {
+buildBindArguments(const MatchFinder::MatchResult &Result,
+ const CallableInfo &Callable) {
SmallVector<BindArgument, 4> BindArguments;
llvm::Regex MatchPlaceholder("^_([0-9]+)$");
+ const auto *BindCall = Result.Nodes.getNodeAs<CallExpr>("bind");
+
// Start at index 1 as first argument to bind is the function name.
- for (size_t I = 1, ArgCount = C->getNumArgs(); I < ArgCount; ++I) {
- const Expr *E = C->getArg(I);
- BindArgument B;
- if (const auto *M = dyn_cast<MaterializeTemporaryExpr>(E)) {
- const auto *TE = M->getSubExpr();
- B.Kind = isa<CallExpr>(TE) ? BK_CallExpr : BK_Temporary;
- }
+ unsigned CaptureIndex = 0;
+ for (size_t I = 1, ArgCount = BindCall->getNumArgs(); I < ArgCount; ++I) {
+
+ const Expr *E = BindCall->getArg(I);
+ BindArgument &B = BindArguments.emplace_back();
+
+ size_t ArgIndex = I - 1;
+ if (Callable.Type == CT_MemberFunction)
+ --ArgIndex;
+
+ bool IsObjectPtr = (I == 1 && Callable.Type == CT_MemberFunction);
+ B.E = E;
+ B.SourceTokens = getSourceTextForExpr(Result, E);
- B.Tokens = Lexer::getSourceText(
- CharSourceRange::getTokenRange(E->getBeginLoc(), E->getEndLoc()),
- *Result.SourceManager, Result.Context->getLangOpts());
+ if (!Callable.Decl || ArgIndex < Callable.Decl->getNumParams() ||
+ IsObjectPtr)
+ B.IsUsed = true;
SmallVector<StringRef, 2> Matches;
- if (B.Kind == BK_Other && MatchPlaceholder.match(B.Tokens, &Matches)) {
+ if (MatchPlaceholder.match(B.SourceTokens, &Matches)) {
B.Kind = BK_Placeholder;
B.PlaceHolderIndex = std::stoi(Matches[1]);
+ B.UsageIdentifier = "PH" + llvm::utostr(B.PlaceHolderIndex);
+ B.CaptureIdentifier = B.UsageIdentifier;
+ continue;
+ }
+
+ if (const auto *CE =
+ dyn_cast<CallExpr>(ignoreTemporariesAndConstructors(E))) {
+ initializeBindArgumentForCallExpr(Result, B, CE, CaptureIndex);
+ continue;
+ }
+
+ if (tryCaptureAsLocalVariable(Result, B, B.E) ||
+ tryCaptureAsMemberVariable(Result, B, B.E))
+ continue;
+
+ // If it's not something we recognize, capture it by init expression to be
+ // safe.
+ B.Kind = BK_Other;
+ if (IsObjectPtr) {
+ B.CaptureMode = CM_InitExpression;
+ B.UsageIdentifier = "ObjectPtr";
+ B.CaptureIdentifier = B.UsageIdentifier;
+ } else if (anyDescendantIsLocal(B.E)) {
+ B.CaptureMode = CM_InitExpression;
+ B.CaptureIdentifier = "capture" + llvm::utostr(CaptureIndex++);
+ B.UsageIdentifier = B.CaptureIdentifier;
}
- BindArguments.push_back(B);
}
return BindArguments;
}
-static void addPlaceholderArgs(const ArrayRef<BindArgument> Args,
- llvm::raw_ostream &Stream) {
+static int findPositionOfPlaceholderUse(ArrayRef<BindArgument> Args,
+ size_t PlaceholderIndex) {
+ for (size_t I = 0; I < Args.size(); ++I)
+ if (Args[I].PlaceHolderIndex == PlaceholderIndex)
+ return I;
+
+ return -1;
+}
+
+static void addPlaceholderArgs(const LambdaProperties &LP,
+ llvm::raw_ostream &Stream,
+ bool PermissiveParameterList) {
+
+ ArrayRef<BindArgument> Args = LP.BindArguments;
+
auto MaxPlaceholderIt =
std::max_element(Args.begin(), Args.end(),
[](const BindArgument &B1, const BindArgument &B2) {
@@ -80,27 +306,41 @@ static void addPlaceholderArgs(const ArrayRef<BindArgument> Args,
});
// Placeholders (if present) have index 1 or greater.
- if (MaxPlaceholderIt == Args.end() || MaxPlaceholderIt->PlaceHolderIndex == 0)
+ if (!PermissiveParameterList && (MaxPlaceholderIt == Args.end() ||
+ MaxPlaceholderIt->PlaceHolderIndex == 0))
return;
size_t PlaceholderCount = MaxPlaceholderIt->PlaceHolderIndex;
Stream << "(";
StringRef Delimiter = "";
for (size_t I = 1; I <= PlaceholderCount; ++I) {
- Stream << Delimiter << "auto && arg" << I;
+ Stream << Delimiter << "auto &&";
+
+ int ArgIndex = findPositionOfPlaceholderUse(Args, I);
+
+ if (ArgIndex != -1 && Args[ArgIndex].IsUsed)
+ Stream << " " << Args[ArgIndex].UsageIdentifier;
Delimiter = ", ";
}
+ if (PermissiveParameterList)
+ Stream << Delimiter << "auto && ...";
Stream << ")";
}
-static void addFunctionCallArgs(const ArrayRef<BindArgument> Args,
+static void addFunctionCallArgs(ArrayRef<BindArgument> Args,
llvm::raw_ostream &Stream) {
StringRef Delimiter = "";
- for (const auto &B : Args) {
- if (B.PlaceHolderIndex)
- Stream << Delimiter << "arg" << B.PlaceHolderIndex;
- else
- Stream << Delimiter << B.Tokens;
+
+ for (int I = 0, Size = Args.size(); I < Size; ++I) {
+ const BindArgument &B = Args[I];
+
+ Stream << Delimiter;
+
+ if (B.Kind == BK_Placeholder || B.CaptureMode != CM_None)
+ Stream << B.UsageIdentifier;
+ else if (B.CaptureMode == CM_None)
+ Stream << B.SourceTokens;
+
Delimiter = ", ";
}
}
@@ -116,59 +356,301 @@ static bool isPlaceHolderIndexRepeated(const ArrayRef<BindArgument> Args) {
return false;
}
+static std::vector<const CXXMethodDecl *>
+findCandidateCallOperators(const CXXRecordDecl *RecordDecl, size_t NumArgs) {
+ std::vector<const CXXMethodDecl *> Candidates;
+
+ for (const clang::CXXMethodDecl *Method : RecordDecl->methods()) {
+ OverloadedOperatorKind OOK = Method->getOverloadedOperator();
+
+ if (OOK != OverloadedOperatorKind::OO_Call)
+ continue;
+
+ if (Method->getNumParams() > NumArgs)
+ continue;
+
+ Candidates.push_back(Method);
+ }
+
+ return Candidates;
+}
+
+static bool isFixitSupported(const CallableInfo &Callee,
+ ArrayRef<BindArgument> Args) {
+ // Do not attempt to create fixits for nested std::bind or std::ref.
+ // Supporting nested std::bind will be more
diff icult due to placeholder
+ // sharing between outer and inner std::bind invocations, and std::ref
+ // requires us to capture some parameters by reference instead of by value.
+ if (any_of(Args, [](const BindArgument &B) {
+ return isCallExprNamed(B.E, "boost::bind") ||
+ isCallExprNamed(B.E, "std::bind");
+ })) {
+ return false;
+ }
+
+ // Do not attempt to create fixits when placeholders are reused.
+ // Unused placeholders are supported by requiring C++14 generic lambdas.
+ // FIXME: Support this case by deducing the common type.
+ if (isPlaceHolderIndexRepeated(Args))
+ return false;
+
+ // If we can't determine the Decl being used, don't offer a fixit.
+ if (!Callee.Decl)
+ return false;
+
+ if (Callee.Type == CT_Other || Callee.Materialization == CMK_Other)
+ return false;
+
+ return true;
+}
+
+const FunctionDecl *getCallOperator(const CXXRecordDecl *Callable,
+ size_t NumArgs) {
+ std::vector<const CXXMethodDecl *> Candidates =
+ findCandidateCallOperators(Callable, NumArgs);
+ if (Candidates.size() != 1)
+ return nullptr;
+
+ return Candidates.front();
+}
+
+const FunctionDecl *
+getCallMethodDecl(const MatchFinder::MatchResult &Result, CallableType Type,
+ CallableMaterializationKind Materialization) {
+
+ const Expr *Callee = Result.Nodes.getNodeAs<Expr>("ref");
+ const Expr *CallExpression = ignoreTemporariesAndPointers(Callee);
+
+ if (Type == CT_Object) {
+ const auto *BindCall = Result.Nodes.getNodeAs<CallExpr>("bind");
+ size_t NumArgs = BindCall->getNumArgs() - 1;
+ return getCallOperator(Callee->getType()->getAsCXXRecordDecl(), NumArgs);
+ }
+
+ if (Materialization == CMK_Function) {
+ if (const auto *DRE = dyn_cast<DeclRefExpr>(CallExpression))
+ return dyn_cast<FunctionDecl>(DRE->getDecl());
+ }
+
+ // Maybe this is an indirect call through a function pointer or something
+ // where we can't determine the exact decl.
+ return nullptr;
+}
+
+static CallableType getCallableType(const MatchFinder::MatchResult &Result) {
+ const auto *CallableExpr = Result.Nodes.getNodeAs<Expr>("ref");
+
+ QualType QT = CallableExpr->getType();
+ if (QT->isMemberFunctionPointerType())
+ return CT_MemberFunction;
+
+ if (QT->isFunctionPointerType() || QT->isFunctionReferenceType() ||
+ QT->isFunctionType())
+ return CT_Function;
+
+ if (QT->isRecordType()) {
+ const CXXRecordDecl *Decl = QT->getAsCXXRecordDecl();
+ if (!Decl)
+ return CT_Other;
+
+ return CT_Object;
+ }
+
+ return CT_Other;
+}
+
+static CallableMaterializationKind
+getCallableMaterialization(const MatchFinder::MatchResult &Result) {
+ const auto *CallableExpr = Result.Nodes.getNodeAs<Expr>("ref");
+
+ const auto *NoTemporaries = ignoreTemporariesAndPointers(CallableExpr);
+
+ if (isa<CallExpr>(NoTemporaries))
+ return CMK_CallExpression;
+
+ if (isa<CXXFunctionalCastExpr>(NoTemporaries) ||
+ isa<CXXConstructExpr>(NoTemporaries))
+ return CMK_Function;
+
+ if (const auto *DRE = dyn_cast<DeclRefExpr>(NoTemporaries)) {
+ if (const auto *FD = dyn_cast<FunctionDecl>(DRE->getDecl()))
+ return CMK_Function;
+ if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+ return CMK_VariableRef;
+ }
+
+ return CMK_Other;
+}
+
+static LambdaProperties
+getLambdaProperties(const MatchFinder::MatchResult &Result) {
+ const auto *CalleeExpr = Result.Nodes.getNodeAs<Expr>("ref");
+
+ LambdaProperties LP;
+
+ const auto *Bind = Result.Nodes.getNodeAs<CallExpr>("bind");
+ const auto *Decl = dyn_cast<FunctionDecl>(Bind->getCalleeDecl());
+ const auto *NS =
+ dyn_cast<NamespaceDecl>(Decl->getEnclosingNamespaceContext());
+ while (NS->isInlineNamespace())
+ NS = dyn_cast<NamespaceDecl>(NS->getDeclContext());
+ LP.BindNamespace = NS->getName();
+
+ LP.Callable.Type = getCallableType(Result);
+ LP.Callable.Materialization = getCallableMaterialization(Result);
+ LP.Callable.Decl =
+ getCallMethodDecl(Result, LP.Callable.Type, LP.Callable.Materialization);
+ LP.Callable.SourceTokens = getSourceTextForExpr(Result, CalleeExpr);
+ if (LP.Callable.Materialization == CMK_VariableRef) {
+ LP.Callable.CaptureMode = CM_ByValue;
+ LP.Callable.UsageIdentifier = getSourceTextForExpr(Result, CalleeExpr);
+ LP.Callable.CaptureIdentifier =
+ getSourceTextForExpr(Result, ignoreTemporariesAndPointers(CalleeExpr));
+ } else if (LP.Callable.Materialization == CMK_CallExpression) {
+ LP.Callable.CaptureMode = CM_InitExpression;
+ LP.Callable.UsageIdentifier = "Func";
+ LP.Callable.CaptureIdentifier = "Func";
+ LP.Callable.CaptureInitializer = getSourceTextForExpr(Result, CalleeExpr);
+ }
+
+ LP.BindArguments = buildBindArguments(Result, LP.Callable);
+
+ LP.IsFixitSupported = isFixitSupported(LP.Callable, LP.BindArguments);
+
+ return LP;
+}
+
+static bool emitCapture(llvm::StringSet<> &CaptureSet, StringRef Delimiter,
+ CaptureMode CM, StringRef Identifier,
+ StringRef InitExpression, raw_ostream &Stream) {
+ if (CM == CM_None)
+ return false;
+
+ // This capture has already been emitted.
+ if (CaptureSet.count(Identifier) != 0)
+ return false;
+
+ Stream << Delimiter;
+
+ if (CM == CM_ByRef)
+ Stream << "&";
+ Stream << Identifier;
+ if (CM == CM_InitExpression)
+ Stream << " = " << InitExpression;
+
+ CaptureSet.insert(Identifier);
+ return true;
+}
+
+static void emitCaptureList(const LambdaProperties &LP,
+ const MatchFinder::MatchResult &Result,
+ raw_ostream &Stream) {
+ llvm::StringSet<> CaptureSet;
+ bool AnyCapturesEmitted = false;
+
+ AnyCapturesEmitted = emitCapture(CaptureSet, "", LP.Callable.CaptureMode,
+ LP.Callable.CaptureIdentifier,
+ LP.Callable.CaptureInitializer, Stream);
+
+ for (const BindArgument &B : LP.BindArguments) {
+ if (B.CaptureMode == CM_None || !B.IsUsed)
+ continue;
+
+ StringRef Delimiter = AnyCapturesEmitted ? ", " : "";
+
+ if (emitCapture(CaptureSet, Delimiter, B.CaptureMode, B.CaptureIdentifier,
+ B.SourceTokens, Stream))
+ AnyCapturesEmitted = true;
+ }
+}
+
+static ArrayRef<BindArgument>
+getForwardedArgumentList(const LambdaProperties &P) {
+ ArrayRef<BindArgument> Args = makeArrayRef(P.BindArguments);
+ if (P.Callable.Type != CT_MemberFunction)
+ return Args;
+
+ return Args.drop_front();
+}
+AvoidBindCheck::AvoidBindCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ PermissiveParameterList(Options.get("PermissiveParameterList", 0) != 0) {}
+
void AvoidBindCheck::registerMatchers(MatchFinder *Finder) {
if (!getLangOpts().CPlusPlus14) // Need C++14 for generic lambdas.
return;
Finder->addMatcher(
callExpr(
- callee(namedDecl(hasName("::std::bind"))),
- hasArgument(0, declRefExpr(to(functionDecl().bind("f"))).bind("ref")))
+ callee(namedDecl(
+ anyOf(hasName("::boost::bind"), hasName("::std::bind")))),
+ hasArgument(
+ 0, anyOf(expr(hasType(memberPointerType())).bind("ref"),
+ expr(hasParent(materializeTemporaryExpr().bind("ref"))),
+ expr().bind("ref"))))
.bind("bind"),
this);
}
void AvoidBindCheck::check(const MatchFinder::MatchResult &Result) {
const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("bind");
- auto Diag = diag(MatchedDecl->getBeginLoc(), "prefer a lambda to std::bind");
-
- const auto Args = buildBindArguments(Result, MatchedDecl);
- // Do not attempt to create fixits for nested call expressions.
- // FIXME: Create lambda capture variables to capture output of calls.
- // NOTE: Supporting nested std::bind will be more
diff icult due to placeholder
- // sharing between outer and inner std:bind invocations.
- if (llvm::any_of(Args,
- [](const BindArgument &B) { return B.Kind == BK_CallExpr; }))
- return;
-
- // Do not attempt to create fixits when placeholders are reused.
- // Unused placeholders are supported by requiring C++14 generic lambdas.
- // FIXME: Support this case by deducing the common type.
- if (isPlaceHolderIndexRepeated(Args))
+ LambdaProperties LP = getLambdaProperties(Result);
+ auto Diag =
+ diag(MatchedDecl->getBeginLoc(),
+ formatv("prefer a lambda to {0}::bind", LP.BindNamespace).str());
+ if (!LP.IsFixitSupported)
return;
- const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("f");
-
- // std::bind can support argument count mismatch between its arguments and the
- // bound function's arguments. Do not attempt to generate a fixit for such
- // cases.
- // FIXME: Support this case by creating unused lambda capture variables.
- if (F->getNumParams() != Args.size())
- return;
+ const auto *Ref = Result.Nodes.getNodeAs<Expr>("ref");
std::string Buffer;
llvm::raw_string_ostream Stream(Buffer);
- bool HasCapturedArgument = llvm::any_of(
- Args, [](const BindArgument &B) { return B.Kind == BK_Other; });
- const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>("ref");
- Stream << "[" << (HasCapturedArgument ? "=" : "") << "]";
- addPlaceholderArgs(Args, Stream);
- Stream << " { return ";
- Ref->printPretty(Stream, nullptr, Result.Context->getPrintingPolicy());
+ Stream << "[";
+ emitCaptureList(LP, Result, Stream);
+ Stream << "]";
+
+ ArrayRef<BindArgument> FunctionCallArgs = makeArrayRef(LP.BindArguments);
+
+ addPlaceholderArgs(LP, Stream, PermissiveParameterList);
+
+ if (LP.Callable.Type == CT_Function) {
+ StringRef SourceTokens = LP.Callable.SourceTokens;
+ SourceTokens.consume_front("&");
+ Stream << " { return " << SourceTokens;
+ } else if (LP.Callable.Type == CT_MemberFunction) {
+ const auto *MethodDecl = dyn_cast<CXXMethodDecl>(LP.Callable.Decl);
+ const BindArgument &ObjPtr = FunctionCallArgs.front();
+
+ Stream << " { ";
+ if (!isa<CXXThisExpr>(ignoreTemporariesAndPointers(ObjPtr.E))) {
+ Stream << ObjPtr.UsageIdentifier;
+ Stream << "->";
+ }
+
+ Stream << MethodDecl->getName();
+ } else {
+ Stream << " { return ";
+ switch (LP.Callable.CaptureMode) {
+ case CM_ByValue:
+ case CM_ByRef:
+ if (LP.Callable.UsageIdentifier != LP.Callable.CaptureIdentifier) {
+ Stream << "(" << LP.Callable.UsageIdentifier << ")";
+ break;
+ }
+ LLVM_FALLTHROUGH;
+ case CM_InitExpression:
+ Stream << LP.Callable.UsageIdentifier;
+ break;
+ default:
+ Ref->printPretty(Stream, nullptr, Result.Context->getPrintingPolicy());
+ }
+ }
+
Stream << "(";
- addFunctionCallArgs(Args, Stream);
+
+ addFunctionCallArgs(getForwardedArgumentList(LP), Stream);
Stream << "); }";
Diag << FixItHint::CreateReplacement(MatchedDecl->getSourceRange(),
diff --git a/clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.h b/clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.h
index 4b393303b7ef..5576fe6c3bd5 100644
--- a/clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.h
+++ b/clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.h
@@ -23,10 +23,12 @@ namespace modernize {
/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-avoid-std-bind.html
class AvoidBindCheck : public ClangTidyCheck {
public:
- AvoidBindCheck(StringRef Name, ClangTidyContext *Context)
- : ClangTidyCheck(Name, Context) {}
+ AvoidBindCheck(StringRef Name, ClangTidyContext *Context);
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+ bool PermissiveParameterList = false;
};
} // namespace modernize
} // namespace tidy
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index b96feecdf3d6..91a196deb6f4 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -196,6 +196,14 @@ Improvements to clang-tidy
<clang-tidy/checks/readability-redundant-string-init>` check now supports a
`StringNames` option enabling its application to custom string classes.
+- Improved :doc:`modernize-avoid-bind
+ <clang-tidy/checks/modernize-avoid-bind>` check.
+
+ The check now supports supports diagnosing and fixing arbitrary callables instead of
+ only simple free functions. The `PermissiveParameterList` option has also been
+ added to address situations where the existing fix-it logic would sometimes generate
+ code that no longer compiles.
+
Improvements to include-fixer
-----------------------------
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize-avoid-bind.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize-avoid-bind.rst
index 7ea9beca8e88..82c290e4a21b 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize-avoid-bind.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize-avoid-bind.rst
@@ -3,10 +3,15 @@
modernize-avoid-bind
====================
-The check finds uses of ``std::bind`` and replaces simple uses with lambdas.
-Lambdas will use value-capture where required.
+The check finds uses of ``std::bind`` and ``boost::bind`` and replaces them
+with lambdas. Lambdas will use value-capture unless reference capture is
+explicitly requested with ``std::ref`` or ``boost::ref``.
-Right now it only handles free functions, not member functions.
+It supports arbitrary callables including member functions, function objects,
+and free functions, and all variations thereof. Anything that you can pass
+to the first argument of ``bind`` should be diagnosable. Currently, the only
+known case where a fix-it is unsupported is when the same placeholder is
+specified multiple times in the parameter list.
Given:
@@ -35,3 +40,49 @@ is replaced by:
``std::bind`` can be hard to read and can result in larger object files and
binaries due to type information that will not be produced by equivalent
lambdas.
+
+Options
+-------
+
+.. option:: PermissiveParameterList
+
+ If the option is set to non-zero, the check will append ``auto&&...`` to the end
+ of every placeholder parameter list. Without this, it is possible for a fix-it
+ to perform an incorrect transformation in the case where the result of the ``bind``
+ is used in the context of a type erased functor such as ``std::function`` which
+ allows mismatched arguments. For example:
+
+
+.. code-block:: c++
+
+ int add(int x, int y) { return x + y; }
+ int foo() {
+ std::function<int(int,int)> ignore_args = std::bind(add, 2, 2);
+ return ignore_args(3, 3);
+ }
+
+is valid code, and returns `4`. The actual values passed to ``ignore_args`` are
+simply ignored. Without ``PermissiveParameterList``, this would be transformed into
+
+.. code-block:: c++
+
+ int add(int x, int y) { return x + y; }
+ int foo() {
+ std::function<int(int,int)> ignore_args = [] { return add(2, 2); }
+ return ignore_args(3, 3);
+ }
+
+which will *not* compile, since the lambda does not contain an ``operator()`` that
+that accepts 2 arguments. With permissive parameter list, it instead generates
+
+.. code-block:: c++
+
+ int add(int x, int y) { return x + y; }
+ int foo() {
+ std::function<int(int,int)> ignore_args = [](auto&&...) { return add(2, 2); }
+ return ignore_args(3, 3);
+ }
+
+which is correct.
+
+This check requires using C++14 or higher to run.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize-avoid-bind-permissive-parameter-list.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize-avoid-bind-permissive-parameter-list.cpp
new file mode 100644
index 000000000000..6c81a6e9ab97
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize-avoid-bind-permissive-parameter-list.cpp
@@ -0,0 +1,58 @@
+// RUN: %check_clang_tidy -std=c++14-or-later %s modernize-avoid-bind %t -- \
+// RUN: -config="{CheckOptions: [ \
+// RUN: {key: modernize-avoid-bind.PermissiveParameterList, value: 1}]}" --
+
+namespace std {
+inline namespace impl {
+template <class Fp, class... Arguments>
+class bind_rt {};
+
+template <class Fp, class... Arguments>
+bind_rt<Fp, Arguments...> bind(Fp &&, Arguments &&...);
+} // namespace impl
+
+template <typename T>
+T ref(T &t);
+} // namespace std
+
+int add(int x, int y) { return x + y; }
+
+// Let's fake a minimal std::function-like facility.
+namespace std {
+template <typename _Tp>
+_Tp declval();
+
+template <typename _Functor, typename... _ArgTypes>
+struct __res {
+ template <typename... _Args>
+ static decltype(declval<_Functor>()(_Args()...)) _S_test(int);
+
+ template <typename...>
+ static void _S_test(...);
+
+ using type = decltype(_S_test<_ArgTypes...>(0));
+};
+
+template <typename>
+struct function;
+
+template <typename... _ArgTypes>
+struct function<void(_ArgTypes...)> {
+ template <typename _Functor,
+ typename = typename __res<_Functor, _ArgTypes...>::type>
+ function(_Functor) {}
+};
+} // namespace std
+
+struct placeholder {};
+placeholder _1;
+
+void testLiteralParameters() {
+ auto AAA = std::bind(add, 2, 2);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind [modernize-avoid-bind]
+ // CHECK-FIXES: auto AAA = [](auto && ...) { return add(2, 2); };
+
+ auto BBB = std::bind(add, _1, 2);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind [modernize-avoid-bind]
+ // CHECK-FIXES: auto BBB = [](auto && PH1, auto && ...) { return add(PH1, 2); };
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize-avoid-bind.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize-avoid-bind.cpp
index fa60cdc2c9d0..7e00858c1acc 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize-avoid-bind.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize-avoid-bind.cpp
@@ -8,75 +8,62 @@ class bind_rt {};
template <class Fp, class... Arguments>
bind_rt<Fp, Arguments...> bind(Fp &&, Arguments &&...);
}
+
+template <typename T>
+T ref(T &t);
}
-int add(int x, int y) { return x + y; }
+namespace boost {
+template <class Fp, class... Arguments>
+class bind_rt {};
-void f() {
- auto clj = std::bind(add, 2, 2);
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind [modernize-avoid-bind]
- // CHECK-FIXES: auto clj = [] { return add(2, 2); };
-}
+template <class Fp, class... Arguments>
+bind_rt<Fp, Arguments...> bind(const Fp &, Arguments...);
-void g() {
- int x = 2;
- int y = 2;
- auto clj = std::bind(add, x, y);
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
- // CHECK-FIXES: auto clj = [=] { return add(x, y); };
+template <class T>
+struct reference_wrapper {
+ explicit reference_wrapper(T &t) {}
+};
+
+template <class T>
+reference_wrapper<T> const ref(T &t) {
+ return reference_wrapper<T>(t);
}
-struct placeholder {};
-placeholder _1;
-placeholder _2;
+} // namespace boost
-void h() {
- int x = 2;
- auto clj = std::bind(add, x, _1);
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
- // CHECK-FIXES: auto clj = [=](auto && arg1) { return add(x, arg1); };
-}
+namespace C {
+int add(int x, int y) { return x + y; }
+} // namespace C
-struct A;
-struct B;
-bool ABTest(const A &, const B &);
+struct Foo {
+ static int add(int x, int y) { return x + y; }
+};
-void i() {
- auto BATest = std::bind(ABTest, _2, _1);
- // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: prefer a lambda to std::bind
- // CHECK-FIXES: auto BATest = [](auto && arg1, auto && arg2) { return ABTest(arg2, arg1); };
-}
+struct D {
+ D() = default;
+ void operator()(int x, int y) const {}
-void j() {
- auto clj = std::bind(add, 2, 2, 2);
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
- // No fix is applied for argument mismatches.
- // CHECK-FIXES: auto clj = std::bind(add, 2, 2, 2);
-}
+ void MemberFunction(int x) {}
-void k() {
- auto clj = std::bind(add, _1, _1);
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
- // No fix is applied for reused placeholders.
- // CHECK-FIXES: auto clj = std::bind(add, _1, _1);
-}
+ static D *create();
+};
-void m() {
- auto clj = std::bind(add, 1, add(2, 5));
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
- // No fix is applied for nested calls.
- // CHECK-FIXES: auto clj = std::bind(add, 1, add(2, 5));
-}
+struct F {
+ F(int x) {}
+ ~F() {}
-namespace C {
- int add(int x, int y){ return x + y; }
-}
+ int get() { return 42; }
+};
-void n() {
- auto clj = std::bind(C::add, 1, 1);
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
- // CHECK-FIXES: auto clj = [] { return C::add(1, 1); };
-}
+void UseF(F);
+
+struct placeholder {};
+placeholder _1;
+placeholder _2;
+
+int add(int x, int y) { return x + y; }
+int addThree(int x, int y, int z) { return x + y + z; }
// Let's fake a minimal std::function-like facility.
namespace std {
@@ -114,10 +101,213 @@ struct Callback {
void Reset(std::function<void()>);
};
-void test(Thing *t) {
+int GlobalVariable = 42;
+
+struct TestCaptureByValueStruct {
+ int MemberVariable;
+ static int StaticMemberVariable;
+ F MemberStruct;
+
+ void testCaptureByValue(int Param, F f) {
+ int x = 3;
+ int y = 4;
+ auto AAA = std::bind(add, x, y);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer a lambda to std::bind [modernize-avoid-bind]
+ // CHECK-FIXES: auto AAA = [x, y] { return add(x, y); };
+
+ // When the captured variable is repeated, it should only appear in the capture list once.
+ auto BBB = std::bind(add, x, x);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer a lambda to std::bind [modernize-avoid-bind]
+ // CHECK-FIXES: auto BBB = [x] { return add(x, x); };
+
+ int LocalVariable;
+ // Global variables shouldn't be captured at all, and members should be captured through this.
+ auto CCC = std::bind(add, MemberVariable, GlobalVariable);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer a lambda to std::bind [modernize-avoid-bind]
+ // CHECK-FIXES: auto CCC = [this] { return add(MemberVariable, GlobalVariable); };
+
+ // Static member variables shouldn't be captured, but locals should
+ auto DDD = std::bind(add, TestCaptureByValueStruct::StaticMemberVariable, LocalVariable);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer a lambda to std::bind [modernize-avoid-bind]
+ // CHECK-FIXES: auto DDD = [LocalVariable] { return add(TestCaptureByValueStruct::StaticMemberVariable, LocalVariable); };
+
+ auto EEE = std::bind(add, Param, Param);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer a lambda to std::bind [modernize-avoid-bind]
+ // CHECK-FIXES: auto EEE = [Param] { return add(Param, Param); };
+
+ // The signature of boost::bind() is
diff erent, and causes
+ // CXXBindTemporaryExprs to be created in certain cases. So let's test
+ // those here.
+ auto FFF = boost::bind(UseF, f);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer a lambda to boost::bind [modernize-avoid-bind]
+ // CHECK-FIXES: auto FFF = [f] { return UseF(f); };
+
+ auto GGG = boost::bind(UseF, MemberStruct);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer a lambda to boost::bind [modernize-avoid-bind]
+ // CHECK-FIXES: auto GGG = [this] { return UseF(MemberStruct); };
+ }
+};
+
+void testLiteralParameters() {
+ auto AAA = std::bind(add, 2, 2);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind [modernize-avoid-bind]
+ // CHECK-FIXES: auto AAA = [] { return add(2, 2); };
+
+ auto BBB = std::bind(addThree, 2, 3, 4);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind [modernize-avoid-bind]
+ // CHECK-FIXES: auto BBB = [] { return addThree(2, 3, 4); };
+}
+
+void testCaptureByReference() {
+ int x = 2;
+ int y = 2;
+ auto AAA = std::bind(add, std::ref(x), std::ref(y));
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto AAA = [&x, &y] { return add(x, y); };
+
+ auto BBB = std::bind(add, std::ref(x), y);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto BBB = [&x, y] { return add(x, y); };
+
+ auto CCC = std::bind(add, y, std::ref(x));
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto CCC = [y, &x] { return add(y, x); };
+
+ // Make sure it works with boost::ref() too which has slightly
diff erent
+ // semantics.
+ auto DDD = boost::bind(add, boost::ref(x), boost::ref(y));
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to boost::bind
+ // CHECK-FIXES: auto DDD = [&x, &y] { return add(x, y); };
+
+ auto EEE = boost::bind(add, boost::ref(x), y);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to boost::bind
+ // CHECK-FIXES: auto EEE = [&x, y] { return add(x, y); };
+
+ auto FFF = boost::bind(add, y, boost::ref(x));
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to boost::bind
+ // CHECK-FIXES: auto FFF = [y, &x] { return add(y, x); };
+}
+
+void testCaptureByInitExpression() {
+ int x = 42;
+ auto AAA = std::bind(add, x, F(x).get());
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto AAA = [x, capture0 = F(x).get()] { return add(x, capture0); };
+}
+
+void testFunctionObjects() {
+ D d;
+ D *e = nullptr;
+ auto AAA = std::bind(d, 1, 2);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto AAA = [d] { return d(1, 2); }
+
+ auto BBB = std::bind(*e, 1, 2);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto BBB = [e] { return (*e)(1, 2); }
+
+ auto CCC = std::bind(D{}, 1, 2);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto CCC = [] { return D{}(1, 2); }
+
+ auto DDD = std::bind(D(), 1, 2);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto DDD = [] { return D()(1, 2); }
+
+ auto EEE = std::bind(*D::create(), 1, 2);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto EEE = [Func = *D::create()] { return Func(1, 2); };
+}
+
+void testPlaceholders() {
+ int x = 2;
+ auto AAA = std::bind(add, x, _1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto AAA = [x](auto && PH1) { return add(x, PH1); };
+
+ auto BBB = std::bind(add, _2, _1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto BBB = [](auto && PH1, auto && PH2) { return add(PH2, PH1); };
+
+ // No fix is applied for reused placeholders.
+ auto CCC = std::bind(add, _1, _1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto CCC = std::bind(add, _1, _1);
+
+ // When a placeholder is skipped, we always add skipped ones to the lambda as
+ // unnamed parameters.
+ auto DDD = std::bind(add, _2, 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto DDD = [](auto &&, auto && PH2) { return add(PH2, 1); };
+}
+
+void testGlobalFunctions() {
+ auto AAA = std::bind(C::add, 1, 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto AAA = [] { return C::add(1, 1); };
+
+ auto BBB = std::bind(Foo::add, 1, 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto BBB = [] { return Foo::add(1, 1); };
+
+ // The & should get removed inside of the lambda body.
+ auto CCC = std::bind(&C::add, 1, 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto CCC = [] { return C::add(1, 1); };
+
+ auto DDD = std::bind(&Foo::add, 1, 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto DDD = [] { return Foo::add(1, 1); };
+
+ auto EEE = std::bind(&add, 1, 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto EEE = [] { return add(1, 1); };
+}
+
+void testCapturedSubexpressions() {
+ int x = 3;
+ int y = 3;
+
+ auto AAA = std::bind(add, 1, add(2, 5));
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // Results of nested calls are captured by value.
+ // CHECK-FIXES: auto AAA = [capture0 = add(2, 5)] { return add(1, capture0); };
+
+ auto BBB = std::bind(add, x, add(y, 5));
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
+ // Results of nested calls are captured by value.
+ // CHECK-FIXES: auto BBB = [x, capture0 = add(y, 5)] { return add(x, capture0); };
+}
+
+struct E {
+ void MemberFunction(int x) {}
+
+ void testMemberFunctions() {
+ D *d;
+ D dd;
+ auto AAA = std::bind(&D::MemberFunction, d, 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto AAA = [d] { d->MemberFunction(1); };
+
+ auto BBB = std::bind(&D::MemberFunction, &dd, 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto BBB = [ObjectPtr = &dd] { ObjectPtr->MemberFunction(1); };
+
+ auto CCC = std::bind(&E::MemberFunction, this, 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto CCC = [this] { MemberFunction(1); };
+
+ // Test what happens when the object pointer is itself a placeholder.
+ auto DDD = std::bind(&D::MemberFunction, _1, 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer a lambda to std::bind
+ // CHECK-FIXES: auto DDD = [](auto && PH1) { PH1->MemberFunction(1); };
+ }
+};
+
+void testStdFunction(Thing *t) {
Callback cb;
if (t)
cb.Reset(std::bind(UseThing, t));
// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
- // CHECK-FIXES: cb.Reset([=] { return UseThing(t); });
+ // CHECK-FIXES: cb.Reset([t] { return UseThing(t); });
}
More information about the cfe-commits
mailing list