forked from OSchip/llvm-project
[clang-tidy] Adds modernize-avoid-bind check
Summary: This patch adds a check that replaces std::bind with a lambda. Not yet working for member functions. Reviewers: aaron.ballman, alexfh Subscribers: cfe-commits Differential Revision: http://reviews.llvm.org/D16962 llvm-svn: 269341
This commit is contained in:
parent
3fe7158174
commit
a30c69c9e4
|
@ -0,0 +1,163 @@
|
|||
//===--- AvoidBindCheck.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 "AvoidBindCheck.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include "clang/Lex/Lexer.h"
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace modernize {
|
||||
|
||||
namespace {
|
||||
enum BindArgumentKind { BK_Temporary, BK_Placeholder, BK_CallExpr, BK_Other };
|
||||
|
||||
struct BindArgument {
|
||||
StringRef Tokens;
|
||||
BindArgumentKind Kind = BK_Other;
|
||||
size_t PlaceHolderIndex = 0;
|
||||
};
|
||||
|
||||
} // end namespace
|
||||
|
||||
static SmallVector<BindArgument, 4>
|
||||
buildBindArguments(const MatchFinder::MatchResult &Result, const CallExpr *C) {
|
||||
SmallVector<BindArgument, 4> BindArguments;
|
||||
llvm::Regex MatchPlaceholder("^_([0-9]+)$");
|
||||
|
||||
// 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->GetTemporaryExpr();
|
||||
B.Kind = isa<CallExpr>(TE) ? BK_CallExpr : BK_Temporary;
|
||||
}
|
||||
|
||||
B.Tokens = Lexer::getSourceText(
|
||||
CharSourceRange::getTokenRange(E->getLocStart(), E->getLocEnd()),
|
||||
*Result.SourceManager, Result.Context->getLangOpts());
|
||||
|
||||
SmallVector<StringRef, 2> Matches;
|
||||
if (B.Kind == BK_Other && MatchPlaceholder.match(B.Tokens, &Matches)) {
|
||||
B.Kind = BK_Placeholder;
|
||||
B.PlaceHolderIndex = std::stoi(Matches[1]);
|
||||
}
|
||||
BindArguments.push_back(B);
|
||||
}
|
||||
return BindArguments;
|
||||
}
|
||||
|
||||
static void addPlaceholderArgs(const ArrayRef<BindArgument> Args,
|
||||
llvm::raw_ostream &Stream) {
|
||||
auto MaxPlaceholderIt =
|
||||
std::max_element(Args.begin(), Args.end(),
|
||||
[](const BindArgument &B1, const BindArgument &B2) {
|
||||
return B1.PlaceHolderIndex < B2.PlaceHolderIndex;
|
||||
});
|
||||
|
||||
// Placeholders (if present) have index 1 or greater.
|
||||
if (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;
|
||||
Delimiter = ", ";
|
||||
}
|
||||
Stream << ")";
|
||||
}
|
||||
|
||||
static void addFunctionCallArgs(const 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;
|
||||
Delimiter = ", ";
|
||||
}
|
||||
}
|
||||
|
||||
static bool isPlaceHolderIndexRepeated(const ArrayRef<BindArgument> Args) {
|
||||
llvm::SmallSet<size_t, 4> PlaceHolderIndices;
|
||||
for (const BindArgument &B : Args) {
|
||||
if (B.PlaceHolderIndex) {
|
||||
if (!PlaceHolderIndices.insert(B.PlaceHolderIndex).second)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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("bind"),
|
||||
this);
|
||||
}
|
||||
|
||||
void AvoidBindCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("bind");
|
||||
auto Diag = diag(MatchedDecl->getLocStart(), "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 difficult 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))
|
||||
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;
|
||||
|
||||
std::string Buffer;
|
||||
llvm::raw_string_ostream Stream(Buffer);
|
||||
|
||||
bool HasCapturedArgument = llvm::any_of(
|
||||
Args, [](const BindArgument &B) { return B.Kind == BK_Other; });
|
||||
|
||||
Stream << "[" << (HasCapturedArgument ? "=" : "") << "]";
|
||||
addPlaceholderArgs(Args, Stream);
|
||||
Stream << " { return " << F->getName() << "(";
|
||||
addFunctionCallArgs(Args, Stream);
|
||||
Stream << "); };";
|
||||
|
||||
Diag << FixItHint::CreateReplacement(MatchedDecl->getSourceRange(), Stream.str());
|
||||
}
|
||||
|
||||
} // namespace modernize
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
|
@ -0,0 +1,36 @@
|
|||
//===--- AvoidBindCheck.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_AVOID_BIND_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_AVOID_BIND_H
|
||||
|
||||
#include "../ClangTidy.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace modernize {
|
||||
|
||||
/// Replace simple uses of std::bind with a lambda.
|
||||
///
|
||||
/// FIXME: Add support for function references and member function references.
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// 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) {}
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
};
|
||||
} // namespace modernize
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_AVOID_BIND_H
|
|
@ -1,6 +1,7 @@
|
|||
set(LLVM_LINK_COMPONENTS support)
|
||||
|
||||
add_clang_library(clangTidyModernizeModule
|
||||
AvoidBindCheck.cpp
|
||||
DeprecatedHeadersCheck.cpp
|
||||
LoopConvertCheck.cpp
|
||||
LoopConvertUtils.cpp
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "../ClangTidy.h"
|
||||
#include "../ClangTidyModule.h"
|
||||
#include "../ClangTidyModuleRegistry.h"
|
||||
#include "AvoidBindCheck.h"
|
||||
#include "DeprecatedHeadersCheck.h"
|
||||
#include "LoopConvertCheck.h"
|
||||
#include "MakeSharedCheck.h"
|
||||
|
@ -34,6 +35,8 @@ namespace modernize {
|
|||
class ModernizeModule : public ClangTidyModule {
|
||||
public:
|
||||
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
|
||||
CheckFactories.registerCheck<AvoidBindCheck>(
|
||||
"modernize-avoid-bind");
|
||||
CheckFactories.registerCheck<DeprecatedHeadersCheck>(
|
||||
"modernize-deprecated-headers");
|
||||
CheckFactories.registerCheck<LoopConvertCheck>("modernize-loop-convert");
|
||||
|
|
|
@ -91,6 +91,7 @@ Clang-Tidy Checks
|
|||
misc-unused-raii
|
||||
misc-unused-using-decls
|
||||
misc-virtual-near-miss
|
||||
modernize-avoid-bind
|
||||
modernize-deprecated-headers
|
||||
modernize-loop-convert
|
||||
modernize-make-shared
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
.. title:: clang-tidy - modernize-avoid-std-bind
|
||||
|
||||
modernize-avoid-bind
|
||||
==========================
|
||||
|
||||
The check finds uses of ``std::bind`` and replaces simple uses with lambdas.
|
||||
Lambdas will use value-capture where required.
|
||||
|
||||
Right now it only handles free functions, not member functions.
|
||||
|
||||
Given:
|
||||
|
||||
.. code:: C++
|
||||
|
||||
int add(int x, int y) { return x + y; }
|
||||
|
||||
Then:
|
||||
|
||||
.. code:: C++
|
||||
|
||||
void f() {
|
||||
int x = 2;
|
||||
auto clj = std::bind(add, x, _1);
|
||||
}
|
||||
|
||||
is replaced by:
|
||||
|
||||
.. code:: C++
|
||||
|
||||
void f() {
|
||||
int x = 2;
|
||||
auto clj = [=](auto && arg1) { return add(x, arg1); };
|
||||
}
|
||||
|
||||
``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.
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// RUN: %check_clang_tidy %s modernize-avoid-bind %t -- -- -std=c++14
|
||||
|
||||
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 &&...);
|
||||
}
|
||||
}
|
||||
|
||||
int add(int x, int y) { return x + y; }
|
||||
|
||||
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); };
|
||||
}
|
||||
|
||||
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); };
|
||||
}
|
||||
|
||||
struct placeholder {};
|
||||
placeholder _1;
|
||||
placeholder _2;
|
||||
|
||||
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); };
|
||||
}
|
||||
|
||||
struct A;
|
||||
struct B;
|
||||
bool ABTest(const A &, const B &);
|
||||
|
||||
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); };
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
Loading…
Reference in New Issue