modernize-avoid-bind.cpp 12.7 KB
// RUN: %check_clang_tidy -std=c++14-or-later %s modernize-avoid-bind %t

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

namespace boost {
template <class Fp, class... Arguments>
class bind_rt {};

template <class Fp, class... Arguments>
bind_rt<Fp, Arguments...> bind(const Fp &, Arguments...);

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);
}

} // namespace boost

namespace C {
int add(int x, int y) { return x + y; }
} // namespace C

struct Foo {
  static int add(int x, int y) { return x + y; }
};

struct D {
  D() = default;
  void operator()(int x, int y) const {}

  void MemberFunction(int x) {}

  static D *create();
};

struct F {
  F(int x) {}
  ~F() {}

  int get() { return 42; }
};

void UseF(F);

struct G {
  G() : _member(0) {}
  G(int m) : _member(m) {}

  template <typename T>
  void operator()(T) const {}

  int _member;
};

template <typename T>
struct H {
  void operator()(T) const {};
};

struct placeholder {};
placeholder _1;
placeholder _2;

namespace placeholders {
using ::_1;
using ::_2;
} // namespace placeholders

int add(int x, int y) { return x + y; }
int addThree(int x, int y, int z) { return x + y + z; }
void sub(int &x, int y) { 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 Thing {};
void UseThing(Thing *);

struct Callback {
  Callback();
  Callback(std::function<void()>);
  void Reset(std::function<void()>);
};

int GlobalVariable = 42;

struct TestCaptureByValueStruct {
  int MemberVariable;
  static int StaticMemberVariable;
  F MemberStruct;
  G MemberStructWithData;

  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 different, 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); };

    auto HHH = std::bind(add, MemberStructWithData._member, 1);
    // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer a lambda to std::bind
    // Correctly distinguish data members of other classes
    // CHECK-FIXES: auto HHH = [capture0 = MemberStructWithData._member] { return add(capture0, 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 = [] { 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 different
  // 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); };

  auto FFF = std::bind(G(), 1);
  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
  // Templated function call operators may be used
  // CHECK-FIXES: auto FFF = [] { return G()(1); };

  int CTorArg = 42;
  auto GGG = std::bind(G(CTorArg), 1);
  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
  // Function objects with constructor arguments should be captured
  // CHECK-FIXES: auto GGG = [Func = G(CTorArg)] { return Func(1); };
}

template <typename T>
void testMemberFnOfClassTemplate(T) {
  auto HHH = std::bind(H<T>(), 42);
  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
  // Ensure function class template arguments are preserved
  // CHECK-FIXES: auto HHH = [] { return H<T>()(42); };
}

template void testMemberFnOfClassTemplate(int);

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, std::forward<decltype(PH1)>(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(std::forward<decltype(PH2)>(PH2), std::forward<decltype(PH1)>(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(std::forward<decltype(PH2)>(PH2), 1); };

  // Namespace-qualified placeholders are valid too
  auto EEE = std::bind(add, placeholders::_2, 1);
  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
  // CHECK-FIXES: auto EEE = [](auto &&, auto && PH2) { return add(std::forward<decltype(PH2)>(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;
  int *p = &x;

  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); };

  auto CCC = std::bind(sub, std::ref(*p), _1);
  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: prefer a lambda to std::bind
  // Expressions returning references are captured
  // CHECK-FIXES: auto CCC = [&capture0 = *p](auto && PH1) { return sub(capture0, std::forward<decltype(PH1)>(PH1)); };
}

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([t] { return UseThing(t); });
}