This revision was automatically updated to reflect the committed changes.
Closed by commit rG893d53d11c01: [clang-tidy] Implement 
modernize-use-constraints (authored by ccotter, committed by PiotrZSL).

Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D141892/new/

https://reviews.llvm.org/D141892

Files:
  clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
  clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
  clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp
  clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.h
  clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
  clang-tools-extra/clang-tidy/utils/LexerUtils.h
  clang-tools-extra/docs/ReleaseNotes.rst
  clang-tools-extra/docs/clang-tidy/checks/list.rst
  clang-tools-extra/docs/clang-tidy/checks/modernize/use-constraints.rst
  
clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints-first-greatergreater.cpp
  clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints.cpp

Index: clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints.cpp
@@ -0,0 +1,726 @@
+// RUN: %check_clang_tidy -std=c++20 %s modernize-use-constraints %t -- -- -fno-delayed-template-parsing
+
+// NOLINTBEGIN
+namespace std {
+template <bool B, class T = void> struct enable_if { };
+
+template <class T> struct enable_if<true, T> { typedef T type; };
+
+template <bool B, class T = void>
+using enable_if_t = typename enable_if<B, T>::type;
+
+} // namespace std
+// NOLINTEND
+
+template <typename...>
+struct ConsumeVariadic;
+
+struct Obj {
+};
+
+namespace enable_if_in_return_type {
+
+////////////////////////////////
+// Section 1: enable_if in return type of function
+////////////////////////////////
+
+////////////////////////////////
+// General tests
+////////////////////////////////
+
+template <typename T>
+typename std::enable_if<T::some_value, Obj>::type basic() {
+  return Obj{};
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}Obj basic() requires T::some_value {{{$}}
+
+template <typename T>
+std::enable_if_t<T::some_value, Obj> basic_t() {
+  return Obj{};
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}Obj basic_t() requires T::some_value {{{$}}
+
+template <typename T>
+auto basic_trailing() -> typename std::enable_if<T::some_value, Obj>::type {
+  return Obj{};
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:26: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}auto basic_trailing() -> Obj requires T::some_value {{{$}}
+
+template <typename T>
+typename std::enable_if<T::some_value, Obj>::type existing_constraint() requires (T::another_value) {
+  return Obj{};
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}typename std::enable_if<T::some_value, Obj>::type existing_constraint() requires (T::another_value) {{{$}}
+
+template <typename U>
+typename std::enable_if<U::some_value, Obj>::type decl_without_def();
+
+template <typename U>
+typename std::enable_if<U::some_value, Obj>::type decl_with_separate_def();
+
+template <typename U>
+typename std::enable_if<U::some_value, Obj>::type decl_with_separate_def() {
+  return Obj{};
+}
+// FIXME - Support definitions with separate decls
+
+template <typename U>
+std::enable_if_t<true, Obj> no_dependent_type(U) {
+  return Obj{};
+}
+// FIXME - Support non-dependent enable_ifs. Low priority though...
+
+template <typename T>
+typename std::enable_if<T::some_value, int>::type* pointer_of_enable_if() {
+  return nullptr;
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}int* pointer_of_enable_if() requires T::some_value {{{$}}
+
+template <typename T>
+std::enable_if_t<T::some_value, int>* pointer_of_enable_if_t() {
+  return nullptr;
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}int* pointer_of_enable_if_t() requires T::some_value {{{$}}
+
+template <typename T>
+const std::enable_if_t<T::some_value, int>* const_pointer_of_enable_if_t() {
+  return nullptr;
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}const int* const_pointer_of_enable_if_t() requires T::some_value {{{$}}
+
+template <typename T>
+std::enable_if_t<T::some_value, int> const * const_pointer_of_enable_if_t2() {
+  return nullptr;
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}int const * const_pointer_of_enable_if_t2() requires T::some_value {{{$}}
+
+
+template <typename T>
+std::enable_if_t<T::some_value, int>& reference_of_enable_if_t() {
+  static int x; return x;
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}int& reference_of_enable_if_t() requires T::some_value {{{$}}
+
+template <typename T>
+const std::enable_if_t<T::some_value, int>& const_reference_of_enable_if_t() {
+  static int x; return x;
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}const int& const_reference_of_enable_if_t() requires T::some_value {{{$}}
+
+template <typename T>
+typename std::enable_if<T::some_value>::type enable_if_default_void() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}void enable_if_default_void() requires T::some_value {{{$}}
+
+template <typename T>
+std::enable_if_t<T::some_value> enable_if_t_default_void() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}void enable_if_t_default_void() requires T::some_value {{{$}}
+
+template <typename T>
+std::enable_if_t<T::some_value>* enable_if_t_default_void_pointer() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}void* enable_if_t_default_void_pointer() requires T::some_value {{{$}}
+
+namespace using_namespace_std {
+
+using namespace std;
+
+template <typename T>
+typename enable_if<T::some_value>::type with_typename() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}void with_typename() requires T::some_value {{{$}}
+
+template <typename T>
+enable_if_t<T::some_value> with_t() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}void with_t() requires T::some_value {{{$}}
+
+template <typename T>
+typename enable_if<T::some_value, int>::type with_typename_and_type() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}int with_typename_and_type() requires T::some_value {{{$}}
+
+template <typename T>
+enable_if_t<T::some_value, int> with_t_and_type() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}int with_t_and_type() requires T::some_value {{{$}}
+
+} // namespace using_namespace_std
+
+
+////////////////////////////////
+// Negative tests - incorrect uses of enable_if
+////////////////////////////////
+template <typename U>
+std::enable_if<U::some_value, Obj> not_enable_if() {
+  return {};
+}
+template <typename U>
+typename std::enable_if<U::some_value, Obj>::type123 not_enable_if_wrong_type() {
+  return {};
+}
+template <typename U>
+typename std::enable_if_t<U::some_value, Obj>::type not_enable_if_t() {
+  return {};
+}
+template <typename U>
+typename std::enable_if_t<U::some_value, Obj>::type123 not_enable_if_t_again() {
+  return {};
+}
+template <typename U>
+std::enable_if<U::some_value, int>* not_pointer_of_enable_if() {
+  return nullptr;
+}
+template <typename U>
+typename std::enable_if<U::some_value, int>::type123 * not_pointer_of_enable_if_t() {
+  return nullptr;
+}
+
+
+namespace primary_expression_tests {
+
+////////////////////////////////
+// Primary/non-primary expression tests
+////////////////////////////////
+
+template <typename T> struct Traits;
+
+template <typename T>
+std::enable_if_t<Traits<T>::value> type_trait_value() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}void type_trait_value() requires Traits<T>::value {{{$}}
+
+template <typename T>
+std::enable_if_t<Traits<T>::member()> type_trait_member_call() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}void type_trait_member_call() requires (Traits<T>::member()) {{{$}}
+
+template <typename T>
+std::enable_if_t<!Traits<T>::value> negate() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}void negate() requires (!Traits<T>::value) {{{$}}
+
+template <typename T>
+std::enable_if_t<Traits<T>::value1 && Traits<T>::value2> conjunction() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}void conjunction() requires (Traits<T>::value1 && Traits<T>::value2) {{{$}}
+
+template <typename T>
+std::enable_if_t<Traits<T>::value1 || Traits<T>::value2> disjunction() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}void disjunction() requires (Traits<T>::value1 || Traits<T>::value2) {{{$}}
+
+template <typename T>
+std::enable_if_t<Traits<T>::value1 && !Traits<T>::value2> conjunction_with_negate() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}void conjunction_with_negate() requires (Traits<T>::value1 && !Traits<T>::value2) {{{$}}
+
+template <typename T>
+std::enable_if_t<Traits<T>::value1 == (Traits<T>::value2 + 5)> complex_operators() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}void complex_operators() requires (Traits<T>::value1 == (Traits<T>::value2 + 5)) {{{$}}
+
+} // namespace primary_expression_tests
+
+
+////////////////////////////////
+// Functions with specifier
+////////////////////////////////
+
+template <typename T>
+constexpr typename std::enable_if<T::some_value, int>::type constexpr_decl() {
+  return 10;
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:11: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}constexpr int constexpr_decl() requires T::some_value {{{$}}
+
+template <typename T>
+static inline constexpr typename std::enable_if<T::some_value, int>::type static_inline_constexpr_decl() {
+  return 10;
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}static inline constexpr int static_inline_constexpr_decl() requires T::some_value {{{$}}
+
+template <typename T>
+static
+typename std::enable_if<T::some_value, int>::type
+static_decl() {
+  return 10;
+}
+// CHECK-MESSAGES: :[[@LINE-4]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}static{{$}}
+// CHECK-FIXES-NEXT: {{^}}int{{$}}
+// CHECK-FIXES-NEXT: {{^}}static_decl() requires T::some_value {{{$}}
+
+template <typename T>
+constexpr /* comment */ typename std::enable_if<T::some_value, int>::type constexpr_comment_decl() {
+  return 10;
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}constexpr /* comment */ int constexpr_comment_decl() requires T::some_value {{{$}}
+
+
+////////////////////////////////
+// Class definition tests
+////////////////////////////////
+
+struct AClass {
+
+  template <typename T>
+  static typename std::enable_if<T::some_value, Obj>::type static_method() {
+    return Obj{};
+  }
+  // CHECK-MESSAGES: :[[@LINE-3]]:10: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  static Obj static_method() requires T::some_value {{{$}}
+
+  template <typename T>
+  typename std::enable_if<T::some_value, Obj>::type member() {
+    return Obj{};
+  }
+  // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  Obj member() requires T::some_value {{{$}}
+
+  template <typename T>
+  typename std::enable_if<T::some_value, Obj>::type const_qualifier() const {
+    return Obj{};
+  }
+  // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  Obj const_qualifier() const requires T::some_value {{{$}}
+
+  template <typename T>
+  typename std::enable_if<T::some_value, Obj>::type rvalue_ref_qualifier() && {
+    return Obj{};
+  }
+  // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  Obj rvalue_ref_qualifier() && requires T::some_value {{{$}}
+
+  template <typename T>
+  typename std::enable_if<T::some_value, Obj>::type rvalue_ref_qualifier_comment() /* c1 */ && /* c2 */ {
+    return Obj{};
+  }
+  // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  Obj rvalue_ref_qualifier_comment() /* c1 */ && /* c2 */ requires T::some_value {{{$}}
+
+  template <typename T>
+  std::enable_if_t<T::some_value, AClass&> operator=(T&&) = delete;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  AClass& operator=(T&&) requires T::some_value = delete;
+
+  template<typename T>
+  std::enable_if_t<T::some_value, AClass&> operator=(ConsumeVariadic<T>) noexcept(requires (T t) { t = 4; }) = delete;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  AClass& operator=(ConsumeVariadic<T>) noexcept(requires (T t) { t = 4; }) requires T::some_value = delete;
+
+};
+
+
+////////////////////////////////
+// Comments and whitespace tests
+////////////////////////////////
+
+template <typename T>
+typename std::enable_if</* check1 */ T::some_value, Obj>::type leading_comment() {
+  return Obj{};
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}Obj leading_comment() requires /* check1 */ T::some_value {{{$}}
+
+template <typename T>
+typename std::enable_if<T::some_value, Obj>::type body_on_next_line()
+{
+  return Obj{};
+}
+// CHECK-MESSAGES: :[[@LINE-4]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}Obj body_on_next_line(){{$}}
+// CHECK-FIXES-NEXT: {{^}}requires T::some_value {{{$}}
+
+template <typename T>
+typename std::enable_if<  /* check1 */ T::some_value, Obj>::type leading_comment_whitespace() {
+  return Obj{};
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}Obj leading_comment_whitespace() requires /* check1 */ T::some_value {{{$}}
+
+template <typename T>
+typename std::enable_if</* check1 */ T::some_value /* check2 */, Obj>::type leading_and_trailing_comment() {
+  return Obj{};
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}Obj leading_and_trailing_comment() requires /* check1 */ T::some_value /* check2 */ {{{$}}
+
+template <typename T, typename U>
+typename std::enable_if<T::some_value &&
+                        U::another_value, Obj>::type condition_on_two_lines() {
+  return Obj{};
+}
+// CHECK-MESSAGES: :[[@LINE-4]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}Obj condition_on_two_lines() requires (T::some_value &&{{$}}
+// CHECK-FIXES-NEXT: U::another_value) {{{$}}
+
+template <typename T>
+typename std::enable_if<T::some_value, int> :: type* pointer_of_enable_if_t_with_spaces() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}int* pointer_of_enable_if_t_with_spaces() requires T::some_value {{{$}}
+
+template <typename T>
+typename std::enable_if<T::some_value, int> :: /*c*/ type* pointer_of_enable_if_t_with_comment() {
+}
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}int* pointer_of_enable_if_t_with_comment() requires T::some_value {{{$}}
+
+template <typename T>
+std::enable_if_t<T::some_value // comment
+              > trailing_slash_slash_comment() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void trailing_slash_slash_comment() requires T::some_value // comment{{$}}
+// CHECK-FIXES-NEXT: {{^}}               {{{$}}
+
+} // namespace enable_if_in_return_type
+
+
+namespace enable_if_trailing_non_type_parameter {
+
+////////////////////////////////
+// Section 2: enable_if as final template non-type parameter
+////////////////////////////////
+
+template <typename T, typename std::enable_if<T::some_value, int>::type = 0>
+void basic() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void basic() requires T::some_value {{{$}}
+
+template <typename T, std::enable_if_t<T::some_value, int> = 0>
+void basic_t() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void basic_t() requires T::some_value {{{$}}
+
+template <typename T, template <typename> class U, class V, std::enable_if_t<T::some_value, int> = 0>
+void basic_many_template_params() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:61: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T, template <typename> class U, class V>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void basic_many_template_params() requires T::some_value {{{$}}
+
+template <std::enable_if_t<true, int> = 0>
+void no_dependent_type() {
+}
+// FIXME - Support non-dependent enable_ifs. Low priority though...
+
+struct ABaseClass {
+  ABaseClass();
+  ABaseClass(int);
+};
+
+template <typename T>
+struct AClass : ABaseClass {
+  template <std::enable_if_t<T::some_value, int> = 0>
+  void no_other_template_params() {
+  }
+  // CHECK-MESSAGES: :[[@LINE-3]]:13: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  {{$}}
+  // CHECK-FIXES-NEXT: {{^}}  void no_other_template_params() requires T::some_value {{{$}}
+
+  template <typename U, std::enable_if_t<U::some_value, int> = 0>
+  AClass() {}
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  template <typename U>{{$}}
+  // CHECK-FIXES-NEXT: {{^}}  AClass() requires U::some_value {}{{$}}
+
+  template <typename U, std::enable_if_t<U::some_value, int> = 0>
+  AClass(int) : data(0) {}
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  template <typename U>{{$}}
+  // CHECK-FIXES-NEXT: {{^}}  AClass(int) requires U::some_value : data(0) {}{{$}}
+
+  template <typename U, std::enable_if_t<U::some_value, int> = 0>
+  AClass(int, int) : AClass(0) {}
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  template <typename U>{{$}}
+  // CHECK-FIXES-NEXT: {{^}}  AClass(int, int) requires U::some_value : AClass(0) {}{{$}}
+
+  template <typename U, std::enable_if_t<U::some_value, int> = 0>
+  AClass(int, int, int) : ABaseClass(0) {}
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  template <typename U>{{$}}
+  // CHECK-FIXES-NEXT: {{^}}  AClass(int, int, int) requires U::some_value : ABaseClass(0) {}{{$}}
+
+  template <typename U, std::enable_if_t<U::some_value, int> = 0>
+  AClass(int, int, int, int) : data2(), data() {}
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  template <typename U>{{$}}
+  // CHECK-FIXES-NEXT: {{^}}  AClass(int, int, int, int) requires U::some_value : data2(), data() {}{{$}}
+
+  int data;
+  int data2;
+};
+
+template <typename T>
+struct AClass2 : ABaseClass {
+
+  template <typename U, std::enable_if_t<U::some_value, int> = 0>
+  AClass2() {}
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  template <typename U>{{$}}
+  // CHECK-FIXES-NEXT: {{^}}  AClass2() requires U::some_value {}{{$}}
+
+  template <typename U, std::enable_if_t<U::some_value, int> = 0>
+  AClass2(int) : data2(0) {}
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  template <typename U>{{$}}
+  // CHECK-FIXES-NEXT: {{^}}  AClass2(int) requires U::some_value : data2(0) {}{{$}}
+
+  int data = 10;
+  int data2;
+  int data3;
+};
+
+template <typename T, std::enable_if_t<T::some_value, T>* = 0>
+void pointer_type() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void pointer_type() requires T::some_value {{{$}}
+
+template <typename T,
+          std::enable_if_t<T::some_value, T>* = nullptr>
+void param_on_newline() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:11: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void param_on_newline() requires T::some_value {{{$}}
+
+template <typename T,
+          typename U,
+          std::enable_if_t<
+            ConsumeVariadic<T,
+                            U>::value, T>* = nullptr>
+void param_split_on_two_lines() {
+}
+// CHECK-MESSAGES: :[[@LINE-5]]:11: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T,{{$}}
+// CHECK-FIXES-NEXT: {{^}}          typename U>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void param_split_on_two_lines() requires ConsumeVariadic<T,{{$}}
+// CHECK-FIXES-NEXT: {{^}}                            U>::value {{{$}}
+
+template <typename T, std::enable_if_t<T::some_value // comment
+         >* = nullptr>
+void trailing_slash_slash_comment() {
+}
+// CHECK-MESSAGES: :[[@LINE-4]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void trailing_slash_slash_comment() requires T::some_value // comment{{$}}
+// CHECK-FIXES-NEXT: {{^}}          {{{$}}
+
+template <typename T, std::enable_if_t<T::some_value>* = nullptr, std::enable_if_t<T::another_value>* = nullptr>
+void two_enable_ifs() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:67: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T, std::enable_if_t<T::some_value>* = nullptr>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void two_enable_ifs() requires T::another_value {{{$}}
+
+////////////////////////////////
+// Negative tests
+////////////////////////////////
+
+template <typename U, std::enable_if_t<U::some_value, int> V = 0>
+void non_type_param_has_name() {
+}
+template <typename U, std::enable_if_t<U::some_value, int>>
+void non_type_param_has_no_default() {
+}
+template <typename U, std::enable_if_t<U::some_value, int> V>
+void non_type_param_has_name_and_no_default() {
+}
+template <typename U, std::enable_if_t<U::some_value, int>...>
+void non_type_variadic() {
+}
+template <typename U, std::enable_if_t<U::some_value, int> = 0, int = 0>
+void non_type_not_last() {
+}
+
+#define TEMPLATE_REQUIRES(U, IF) template <typename U, std::enable_if_t<IF, int> = 0>
+TEMPLATE_REQUIRES(U, U::some_value)
+void macro_entire_enable_if() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-MESSAGES: :[[@LINE-5]]:56: note: expanded from macro 'TEMPLATE_REQUIRES'
+// CHECK-FIXES: {{^}}TEMPLATE_REQUIRES(U, U::some_value)
+// CHECK-FIXES-NEXT: {{^}}void macro_entire_enable_if() {{{$}}
+
+#define CONDITION U::some_value
+template <typename U, std::enable_if_t<CONDITION, int> = 0>
+void macro_condition() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename U>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void macro_condition() requires CONDITION {{{$}}
+
+#undef CONDITION
+#define CONDITION !U::some_value
+template <typename U, std::enable_if_t<CONDITION, int> = 0>
+void macro_condition_not_primary() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename U>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void macro_condition_not_primary() requires (CONDITION) {{{$}}
+
+} // namespace enable_if_trailing_non_type_parameter
+
+
+namespace enable_if_trailing_type_parameter {
+
+////////////////////////////////
+// Section 3: enable_if as final template nameless defaulted type parameter
+////////////////////////////////
+
+template <typename T, typename = std::enable_if<T::some_value>::type>
+void basic() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void basic() requires T::some_value {{{$}}
+
+template <typename T, typename = std::enable_if_t<T::some_value>>
+void basic_t() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void basic_t() requires T::some_value {{{$}}
+
+template <typename T, template <typename> class U, class V, typename = std::enable_if_t<T::some_value>>
+void basic_many_template_params() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:61: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T, template <typename> class U, class V>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void basic_many_template_params() requires T::some_value {{{$}}
+
+struct ABaseClass {
+  ABaseClass();
+  ABaseClass(int);
+};
+
+template <typename T>
+struct AClass : ABaseClass {
+  template <typename = std::enable_if_t<T::some_value>>
+  void no_other_template_params() {
+  }
+  // CHECK-MESSAGES: :[[@LINE-3]]:13: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  {{$}}
+  // CHECK-FIXES-NEXT: {{^}}  void no_other_template_params() requires T::some_value {{{$}}
+
+  template <typename U, typename = std::enable_if_t<U::some_value>>
+  AClass() {}
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  template <typename U>{{$}}
+  // CHECK-FIXES-NEXT: {{^}}  AClass() requires U::some_value {}{{$}}
+
+  template <typename U, typename = std::enable_if_t<U::some_value>>
+  AClass(int) : data(0) {}
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  template <typename U>{{$}}
+  // CHECK-FIXES-NEXT: {{^}}  AClass(int) requires U::some_value : data(0) {}{{$}}
+
+  template <typename U, typename = std::enable_if_t<U::some_value>>
+  AClass(int, int) : AClass(0) {}
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  template <typename U>{{$}}
+  // CHECK-FIXES-NEXT: {{^}}  AClass(int, int) requires U::some_value : AClass(0) {}{{$}}
+
+  template <typename U, typename = std::enable_if_t<U::some_value>>
+  AClass(int, int, int) : ABaseClass(0) {}
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  // CHECK-FIXES: {{^}}  template <typename U>{{$}}
+  // CHECK-FIXES-NEXT: {{^}}  AClass(int, int, int) requires U::some_value : ABaseClass(0) {}{{$}}
+
+  int data;
+};
+
+template <typename T, typename = std::enable_if_t<T::some_value>*>
+void pointer_type() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void pointer_type() requires T::some_value {{{$}}
+
+template <typename T, typename = std::enable_if_t<T::some_value>&>
+void reference_type() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void reference_type() requires T::some_value {{{$}}
+
+template <typename T,
+          typename = std::enable_if_t<T::some_value>*>
+void param_on_newline() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:11: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void param_on_newline() requires T::some_value {{{$}}
+
+template <typename T,
+          typename U,
+          typename = std::enable_if_t<
+            ConsumeVariadic<T,
+                            U>::value>>
+void param_split_on_two_lines() {
+}
+// CHECK-MESSAGES: :[[@LINE-5]]:11: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T,{{$}}
+// CHECK-FIXES-NEXT: {{^}}          typename U>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void param_split_on_two_lines() requires ConsumeVariadic<T,{{$}}
+// CHECK-FIXES-NEXT: {{^}}                            U>::value {{{$}}
+
+
+////////////////////////////////
+// Negative tests
+////////////////////////////////
+
+template <typename U, typename Named = std::enable_if_t<U::some_value>>
+void param_has_name() {
+}
+
+template <typename U, typename = std::enable_if_t<U::some_value>, typename = int>
+void not_last_param() {
+}
+
+} // namespace enable_if_trailing_type_parameter
Index: clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints-first-greatergreater.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints-first-greatergreater.cpp
@@ -0,0 +1,22 @@
+// RUN: %check_clang_tidy -std=c++20 %s modernize-use-constraints %t -- -- -fno-delayed-template-parsing
+
+// NOLINTBEGIN
+namespace std {
+template <bool B, class T = void> struct enable_if { };
+
+template <class T> struct enable_if<true, T> { typedef T type; };
+
+template <bool B, class T = void>
+using enable_if_t = typename enable_if<B, T>::type;
+
+} // namespace std
+// NOLINTEND
+
+// Separate test file for the case where the first '>>' token part of
+// an enable_if expression correctly handles the synthesized token.
+
+template <typename T, typename = std::enable_if_t<T::some_value>>
+void first_greatergreater_is_enable_if() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}void first_greatergreater_is_enable_if() requires T::some_value {{{$}}
Index: clang-tools-extra/docs/clang-tidy/checks/modernize/use-constraints.rst
===================================================================
--- /dev/null
+++ clang-tools-extra/docs/clang-tidy/checks/modernize/use-constraints.rst
@@ -0,0 +1,70 @@
+.. title:: clang-tidy - modernize-use-constraints
+
+modernize-use-constraints
+=========================
+
+Replace ``std::enable_if`` with C++20 requires clauses.
+
+``std::enable_if`` is a SFINAE mechanism for selecting the desired function or
+class template based on type traits or other requirements. ``enable_if`` changes
+the meta-arity of the template, and has other
+`adverse side effects
+<https://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0225r0.html>`_
+in the code. C++20 introduces concepts and constraints as a cleaner language
+provided solution to achieve the same outcome.
+
+This check finds some common ``std::enable_if`` patterns that can be replaced
+by C++20 requires clauses. The tool can replace some of these patterns
+automatically, otherwise, the tool will emit a diagnostic without a
+replacement. The tool can detect the following ``std::enable_if`` patterns
+
+1. ``std::enable_if`` in the return type of a function
+2. ``std::enable_if`` as the trailing template parameter for function templates
+
+Other uses, for example, in class templates for function parameters, are not
+currently supported by this tool. Other variants such as ``boost::enable_if``
+are not currently supported by this tool.
+
+Below are some examples of code using ``std::enable_if``.
+
+.. code-block:: c++
+
+  // enable_if in function return type
+  template <typename T>
+  std::enable_if_t<T::some_trait, int> only_if_t_has_the_trait() { ... }
+
+  // enable_if in the trailing template parameter
+  template <typename T, std::enable_if_t<T::some_trait, int> = 0>
+  void another_version() { ... }
+
+  template <typename T>
+  typename std::enable_if<T::some_value, Obj>::type existing_constraint() requires (T::another_value) {
+    return Obj{};
+  }
+
+  template <typename T, std::enable_if_t<T::some_trait, int> = 0>
+  struct my_class {};
+
+The tool will replace the above code with,
+
+.. code-block:: c++
+
+  // warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  template <typename T>
+  int only_if_t_has_the_trait() requires T::some_trait { ... }
+
+  // warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  template <typename T>
+  void another_version() requires T::some_trait { ... }
+
+  // The tool will emit a diagnostic for the following, but will
+  // not attempt to replace the code.
+  // warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+  template <typename T>
+  typename std::enable_if<T::some_value, Obj>::type existing_constraint() requires (T::another_value) {
+    return Obj{};
+  }
+
+  // The tool will not emit a diagnostic or attempt to replace the code.
+  template <typename T, std::enable_if_t<T::some_trait, int> = 0>
+  struct my_class {};
Index: clang-tools-extra/docs/clang-tidy/checks/list.rst
===================================================================
--- clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -298,6 +298,7 @@
    `modernize-unary-static-assert <modernize/unary-static-assert.html>`_, "Yes"
    `modernize-use-auto <modernize/use-auto.html>`_, "Yes"
    `modernize-use-bool-literals <modernize/use-bool-literals.html>`_, "Yes"
+   `modernize-use-constraints <modernize/use-constraints.html>`_, "Yes"
    `modernize-use-default-member-init <modernize/use-default-member-init.html>`_, "Yes"
    `modernize-use-emplace <modernize/use-emplace.html>`_, "Yes"
    `modernize-use-equals-default <modernize/use-equals-default.html>`_, "Yes"
Index: clang-tools-extra/docs/ReleaseNotes.rst
===================================================================
--- clang-tools-extra/docs/ReleaseNotes.rst
+++ clang-tools-extra/docs/ReleaseNotes.rst
@@ -129,6 +129,11 @@
   Detects implicit conversions between pointers of different levels of
   indirection.
 
+- New :doc:`modernize-use-constraints
+  <clang-tidy/checks/modernize/use-constraints>` check.
+
+  Replace ``enable_if`` with C++20 requires clauses.
+
 - New :doc:`performance-enum-size
   <clang-tidy/checks/performance/enum-size>` check.
 
Index: clang-tools-extra/clang-tidy/utils/LexerUtils.h
===================================================================
--- clang-tools-extra/clang-tidy/utils/LexerUtils.h
+++ clang-tools-extra/clang-tidy/utils/LexerUtils.h
@@ -13,6 +13,7 @@
 #include "clang/Basic/TokenKinds.h"
 #include "clang/Lex/Lexer.h"
 #include <optional>
+#include <utility>
 
 namespace clang {
 
@@ -23,6 +24,9 @@
 /// Returns previous token or ``tok::unknown`` if not found.
 Token getPreviousToken(SourceLocation Location, const SourceManager &SM,
                        const LangOptions &LangOpts, bool SkipComments = true);
+std::pair<Token, SourceLocation>
+getPreviousTokenAndStart(SourceLocation Location, const SourceManager &SM,
+                         const LangOptions &LangOpts, bool SkipComments = true);
 
 SourceLocation findPreviousTokenStart(SourceLocation Start,
                                       const SourceManager &SM,
Index: clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
===================================================================
--- clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
+++ clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
@@ -10,17 +10,19 @@
 #include "clang/AST/AST.h"
 #include "clang/Basic/SourceManager.h"
 #include <optional>
+#include <utility>
 
 namespace clang::tidy::utils::lexer {
 
-Token getPreviousToken(SourceLocation Location, const SourceManager &SM,
-                       const LangOptions &LangOpts, bool SkipComments) {
+std::pair<Token, SourceLocation>
+getPreviousTokenAndStart(SourceLocation Location, const SourceManager &SM,
+                         const LangOptions &LangOpts, bool SkipComments) {
   Token Token;
   Token.setKind(tok::unknown);
 
   Location = Location.getLocWithOffset(-1);
   if (Location.isInvalid())
-    return Token;
+    return {Token, Location};
 
   auto StartOfFile = SM.getLocForStartOfFile(SM.getFileID(Location));
   while (Location != StartOfFile) {
@@ -31,6 +33,13 @@
     }
     Location = Location.getLocWithOffset(-1);
   }
+  return {Token, Location};
+}
+
+Token getPreviousToken(SourceLocation Location, const SourceManager &SM,
+                       const LangOptions &LangOpts, bool SkipComments) {
+  auto [Token, Start] =
+      getPreviousTokenAndStart(Location, SM, LangOpts, SkipComments);
   return Token;
 }
 
Index: clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.h
@@ -0,0 +1,33 @@
+//===--- UseConstraintsCheck.h - clang-tidy ---------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USECONSTRAINTSCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USECONSTRAINTSCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::modernize {
+
+/// Replace enable_if with C++20 requires clauses.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-constraints.html
+class UseConstraintsCheck : public ClangTidyCheck {
+public:
+  UseConstraintsCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus20;
+  }
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USECONSTRAINTSCHECK_H
Index: clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp
@@ -0,0 +1,488 @@
+//===--- UseConstraintsCheck.cpp - clang-tidy -----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseConstraintsCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+#include "../utils/LexerUtils.h"
+
+#include <optional>
+#include <utility>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+struct EnableIfData {
+  TemplateSpecializationTypeLoc Loc;
+  TypeLoc Outer;
+};
+
+namespace {
+AST_MATCHER(FunctionDecl, hasOtherDeclarations) {
+  auto It = Node.redecls_begin();
+  auto EndIt = Node.redecls_end();
+
+  if (It == EndIt)
+    return false;
+
+  ++It;
+  return It != EndIt;
+}
+} // namespace
+
+void UseConstraintsCheck::registerMatchers(MatchFinder *Finder) {
+  Finder->addMatcher(
+      functionTemplateDecl(
+          has(functionDecl(unless(hasOtherDeclarations()), isDefinition(),
+                           hasReturnTypeLoc(typeLoc().bind("return")))
+                  .bind("function")))
+          .bind("functionTemplate"),
+      this);
+}
+
+static std::optional<TemplateSpecializationTypeLoc>
+matchEnableIfSpecializationImplTypename(TypeLoc TheType) {
+  if (const auto Dep = TheType.getAs<DependentNameTypeLoc>()) {
+    const IdentifierInfo *Identifier = Dep.getTypePtr()->getIdentifier();
+    if (!Identifier || Identifier->getName() != "type" ||
+        Dep.getTypePtr()->getKeyword() != ETK_Typename) {
+      return std::nullopt;
+    }
+    TheType = Dep.getQualifierLoc().getTypeLoc();
+  }
+
+  if (const auto SpecializationLoc =
+          TheType.getAs<TemplateSpecializationTypeLoc>()) {
+
+    const auto *Specialization =
+        dyn_cast<TemplateSpecializationType>(SpecializationLoc.getTypePtr());
+    if (!Specialization)
+      return std::nullopt;
+
+    const TemplateDecl *TD =
+        Specialization->getTemplateName().getAsTemplateDecl();
+    if (!TD || TD->getName() != "enable_if")
+      return std::nullopt;
+
+    int NumArgs = SpecializationLoc.getNumArgs();
+    if (NumArgs != 1 && NumArgs != 2)
+      return std::nullopt;
+
+    return SpecializationLoc;
+  }
+  return std::nullopt;
+}
+
+static std::optional<TemplateSpecializationTypeLoc>
+matchEnableIfSpecializationImplTrait(TypeLoc TheType) {
+  if (const auto Elaborated = TheType.getAs<ElaboratedTypeLoc>())
+    TheType = Elaborated.getNamedTypeLoc();
+
+  if (const auto SpecializationLoc =
+          TheType.getAs<TemplateSpecializationTypeLoc>()) {
+
+    const auto *Specialization =
+        dyn_cast<TemplateSpecializationType>(SpecializationLoc.getTypePtr());
+    if (!Specialization)
+      return std::nullopt;
+
+    const TemplateDecl *TD =
+        Specialization->getTemplateName().getAsTemplateDecl();
+    if (!TD || TD->getName() != "enable_if_t")
+      return std::nullopt;
+
+    if (!Specialization->isTypeAlias())
+      return std::nullopt;
+
+    if (const auto *AliasedType =
+            dyn_cast<DependentNameType>(Specialization->getAliasedType())) {
+      if (AliasedType->getIdentifier()->getName() != "type" ||
+          AliasedType->getKeyword() != ETK_Typename) {
+        return std::nullopt;
+      }
+    } else {
+      return std::nullopt;
+    }
+    int NumArgs = SpecializationLoc.getNumArgs();
+    if (NumArgs != 1 && NumArgs != 2)
+      return std::nullopt;
+
+    return SpecializationLoc;
+  }
+  return std::nullopt;
+}
+
+static std::optional<TemplateSpecializationTypeLoc>
+matchEnableIfSpecializationImpl(TypeLoc TheType) {
+  if (auto EnableIf = matchEnableIfSpecializationImplTypename(TheType))
+    return EnableIf;
+  return matchEnableIfSpecializationImplTrait(TheType);
+}
+
+static std::optional<EnableIfData>
+matchEnableIfSpecialization(TypeLoc TheType) {
+  if (const auto Pointer = TheType.getAs<PointerTypeLoc>())
+    TheType = Pointer.getPointeeLoc();
+  else if (const auto Reference = TheType.getAs<ReferenceTypeLoc>())
+    TheType = Reference.getPointeeLoc();
+  if (const auto Qualified = TheType.getAs<QualifiedTypeLoc>())
+    TheType = Qualified.getUnqualifiedLoc();
+
+  if (auto EnableIf = matchEnableIfSpecializationImpl(TheType))
+    return EnableIfData{std::move(*EnableIf), TheType};
+  return std::nullopt;
+}
+
+static std::pair<std::optional<EnableIfData>, const Decl *>
+matchTrailingTemplateParam(const FunctionTemplateDecl *FunctionTemplate) {
+  // For non-type trailing param, match very specifically
+  // 'template <..., enable_if_type<Condition, Type> = Default>' where
+  // enable_if_type is 'enable_if' or 'enable_if_t'. E.g., 'template <typename
+  // T, enable_if_t<is_same_v<T, bool>, int*> = nullptr>
+  //
+  // Otherwise, match a trailing default type arg.
+  // E.g., 'template <typename T, typename = enable_if_t<is_same_v<T, bool>>>'
+
+  const TemplateParameterList *TemplateParams =
+      FunctionTemplate->getTemplateParameters();
+  if (TemplateParams->size() == 0)
+    return {};
+
+  const NamedDecl *LastParam =
+      TemplateParams->getParam(TemplateParams->size() - 1);
+  if (const auto *LastTemplateParam =
+          dyn_cast<NonTypeTemplateParmDecl>(LastParam)) {
+
+    if (!LastTemplateParam->hasDefaultArgument() ||
+        !LastTemplateParam->getName().empty())
+      return {};
+
+    return {matchEnableIfSpecialization(
+                LastTemplateParam->getTypeSourceInfo()->getTypeLoc()),
+            LastTemplateParam};
+  } else if (const auto *LastTemplateParam =
+                 dyn_cast<TemplateTypeParmDecl>(LastParam)) {
+    if (LastTemplateParam->hasDefaultArgument() &&
+        LastTemplateParam->getIdentifier() == nullptr) {
+      return {matchEnableIfSpecialization(
+                  LastTemplateParam->getDefaultArgumentInfo()->getTypeLoc()),
+              LastTemplateParam};
+    }
+  }
+  return {};
+}
+
+template <typename T>
+static SourceLocation getRAngleFileLoc(const SourceManager &SM,
+                                       const T &Element) {
+  // getFileLoc handles the case where the RAngle loc is part of a synthesized
+  // '>>', which ends up allocating a 'scratch space' buffer in the source
+  // manager.
+  return SM.getFileLoc(Element.getRAngleLoc());
+}
+
+static SourceRange
+getConditionRange(ASTContext &Context,
+                  const TemplateSpecializationTypeLoc &EnableIf) {
+  // TemplateArgumentLoc's SourceRange End is the location of the last token
+  // (per UnqualifiedId docs). E.g., in `enable_if<AAA && BBB>`, the End
+  // location will be the first 'B' in 'BBB'.
+  const LangOptions &LangOpts = Context.getLangOpts();
+  const SourceManager &SM = Context.getSourceManager();
+  if (EnableIf.getNumArgs() > 1) {
+    TemplateArgumentLoc NextArg = EnableIf.getArgLoc(1);
+    return SourceRange(
+        EnableIf.getLAngleLoc().getLocWithOffset(1),
+        utils::lexer::findPreviousTokenKind(NextArg.getSourceRange().getBegin(),
+                                            SM, LangOpts, tok::comma));
+  }
+
+  return SourceRange(EnableIf.getLAngleLoc().getLocWithOffset(1),
+                     getRAngleFileLoc(SM, EnableIf));
+}
+
+static SourceRange getTypeRange(ASTContext &Context,
+                                const TemplateSpecializationTypeLoc &EnableIf) {
+  TemplateArgumentLoc Arg = EnableIf.getArgLoc(1);
+  const LangOptions &LangOpts = Context.getLangOpts();
+  const SourceManager &SM = Context.getSourceManager();
+  return SourceRange(
+      utils::lexer::findPreviousTokenKind(Arg.getSourceRange().getBegin(), SM,
+                                          LangOpts, tok::comma)
+          .getLocWithOffset(1),
+      getRAngleFileLoc(SM, EnableIf));
+}
+
+// Returns the original source text of the second argument of a call to
+// enable_if_t. E.g., in enable_if_t<Condition, TheType>, this function
+// returns 'TheType'.
+static std::optional<StringRef>
+getTypeText(ASTContext &Context,
+            const TemplateSpecializationTypeLoc &EnableIf) {
+  if (EnableIf.getNumArgs() > 1) {
+    const LangOptions &LangOpts = Context.getLangOpts();
+    const SourceManager &SM = Context.getSourceManager();
+    bool Invalid = false;
+    StringRef Text = Lexer::getSourceText(CharSourceRange::getCharRange(
+                                              getTypeRange(Context, EnableIf)),
+                                          SM, LangOpts, &Invalid)
+                         .trim();
+    if (Invalid)
+      return std::nullopt;
+
+    return Text;
+  }
+
+  return "void";
+}
+
+static std::optional<SourceLocation>
+findInsertionForConstraint(const FunctionDecl *Function, ASTContext &Context) {
+  SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  if (const auto *Constructor = dyn_cast<CXXConstructorDecl>(Function)) {
+    for (const CXXCtorInitializer *Init : Constructor->inits()) {
+      if (Init->getSourceOrder() == 0)
+        return utils::lexer::findPreviousTokenKind(Init->getSourceLocation(),
+                                                   SM, LangOpts, tok::colon);
+    }
+    if (Constructor->init_begin() != Constructor->init_end())
+      return std::nullopt;
+  }
+  if (Function->isDeleted()) {
+    SourceLocation FunctionEnd = Function->getSourceRange().getEnd();
+    return utils::lexer::findNextAnyTokenKind(FunctionEnd, SM, LangOpts,
+                                              tok::equal, tok::equal);
+  }
+  const Stmt *Body = Function->getBody();
+  if (!Body)
+    return std::nullopt;
+
+  return Body->getBeginLoc();
+}
+
+bool isPrimaryExpression(const Expr *Expression) {
+  // This function is an incomplete approximation of checking whether
+  // an Expr is a primary expression. In particular, if this function
+  // returns true, the expression is a primary expression. The converse
+  // is not necessarily true.
+
+  if (const auto *Cast = dyn_cast<ImplicitCastExpr>(Expression))
+    Expression = Cast->getSubExprAsWritten();
+  if (isa<ParenExpr, DependentScopeDeclRefExpr>(Expression))
+    return true;
+
+  return false;
+}
+
+// Return the original source text of an enable_if_t condition, i.e., the
+// first template argument). For example, in
+// 'enable_if_t<FirstCondition || SecondCondition, AType>', the text
+// the text 'FirstCondition || SecondCondition' is returned.
+static std::optional<std::string> getConditionText(const Expr *ConditionExpr,
+                                                   SourceRange ConditionRange,
+                                                   ASTContext &Context) {
+  SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  SourceLocation PrevTokenLoc = ConditionRange.getEnd();
+  if (PrevTokenLoc.isInvalid())
+    return std::nullopt;
+
+  const bool SkipComments = false;
+  Token PrevToken;
+  std::tie(PrevToken, PrevTokenLoc) = utils::lexer::getPreviousTokenAndStart(
+      PrevTokenLoc, SM, LangOpts, SkipComments);
+  bool EndsWithDoubleSlash =
+      PrevToken.is(tok::comment) &&
+      Lexer::getSourceText(CharSourceRange::getCharRange(
+                               PrevTokenLoc, PrevTokenLoc.getLocWithOffset(2)),
+                           SM, LangOpts) == "//";
+
+  bool Invalid = false;
+  llvm::StringRef ConditionText = Lexer::getSourceText(
+      CharSourceRange::getCharRange(ConditionRange), SM, LangOpts, &Invalid);
+  if (Invalid)
+    return std::nullopt;
+
+  auto AddParens = [&](llvm::StringRef Text) -> std::string {
+    if (isPrimaryExpression(ConditionExpr))
+      return Text.str();
+    return "(" + Text.str() + ")";
+  };
+
+  if (EndsWithDoubleSlash)
+    return AddParens(ConditionText);
+  return AddParens(ConditionText.trim());
+}
+
+// Handle functions that return enable_if_t, e.g.,
+//   template <...>
+//   enable_if_t<Condition, ReturnType> function();
+//
+// Return a vector of FixItHints if the code can be replaced with
+// a C++20 requires clause. In the example above, returns FixItHints
+// to result in
+//   template <...>
+//   ReturnType function() requires Condition {}
+static std::vector<FixItHint> handleReturnType(const FunctionDecl *Function,
+                                               const TypeLoc &ReturnType,
+                                               const EnableIfData &EnableIf,
+                                               ASTContext &Context) {
+  TemplateArgumentLoc EnableCondition = EnableIf.Loc.getArgLoc(0);
+
+  SourceRange ConditionRange = getConditionRange(Context, EnableIf.Loc);
+
+  std::optional<std::string> ConditionText = getConditionText(
+      EnableCondition.getSourceExpression(), ConditionRange, Context);
+  if (!ConditionText)
+    return {};
+
+  std::optional<StringRef> TypeText = getTypeText(Context, EnableIf.Loc);
+  if (!TypeText)
+    return {};
+
+  SmallVector<const Expr *, 3> ExistingConstraints;
+  Function->getAssociatedConstraints(ExistingConstraints);
+  if (ExistingConstraints.size() > 0) {
+    // FIXME - Support adding new constraints to existing ones. Do we need to
+    // consider subsumption?
+    return {};
+  }
+
+  std::optional<SourceLocation> ConstraintInsertionLoc =
+      findInsertionForConstraint(Function, Context);
+  if (!ConstraintInsertionLoc)
+    return {};
+
+  std::vector<FixItHint> FixIts;
+  FixIts.push_back(FixItHint::CreateReplacement(
+      CharSourceRange::getTokenRange(EnableIf.Outer.getSourceRange()),
+      *TypeText));
+  FixIts.push_back(FixItHint::CreateInsertion(
+      *ConstraintInsertionLoc, "requires " + *ConditionText + " "));
+  return FixIts;
+}
+
+// Handle enable_if_t in a trailing template parameter, e.g.,
+//   template <..., enable_if_t<Condition, Type> = Type{}>
+//   ReturnType function();
+//
+// Return a vector of FixItHints if the code can be replaced with
+// a C++20 requires clause. In the example above, returns FixItHints
+// to result in
+//   template <...>
+//   ReturnType function() requires Condition {}
+static std::vector<FixItHint>
+handleTrailingTemplateType(const FunctionTemplateDecl *FunctionTemplate,
+                           const FunctionDecl *Function,
+                           const Decl *LastTemplateParam,
+                           const EnableIfData &EnableIf, ASTContext &Context) {
+  SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  TemplateArgumentLoc EnableCondition = EnableIf.Loc.getArgLoc(0);
+
+  SourceRange ConditionRange = getConditionRange(Context, EnableIf.Loc);
+
+  std::optional<std::string> ConditionText = getConditionText(
+      EnableCondition.getSourceExpression(), ConditionRange, Context);
+  if (!ConditionText)
+    return {};
+
+  SmallVector<const Expr *, 3> ExistingConstraints;
+  Function->getAssociatedConstraints(ExistingConstraints);
+  if (ExistingConstraints.size() > 0) {
+    // FIXME - Support adding new constraints to existing ones. Do we need to
+    // consider subsumption?
+    return {};
+  }
+
+  SourceRange RemovalRange;
+  const TemplateParameterList *TemplateParams =
+      FunctionTemplate->getTemplateParameters();
+  if (!TemplateParams || TemplateParams->size() == 0)
+    return {};
+
+  if (TemplateParams->size() == 1) {
+    RemovalRange =
+        SourceRange(TemplateParams->getTemplateLoc(),
+                    getRAngleFileLoc(SM, *TemplateParams).getLocWithOffset(1));
+  } else {
+    RemovalRange =
+        SourceRange(utils::lexer::findPreviousTokenKind(
+                        LastTemplateParam->getSourceRange().getBegin(), SM,
+                        LangOpts, tok::comma),
+                    getRAngleFileLoc(SM, *TemplateParams));
+  }
+
+  std::optional<SourceLocation> ConstraintInsertionLoc =
+      findInsertionForConstraint(Function, Context);
+  if (!ConstraintInsertionLoc)
+    return {};
+
+  std::vector<FixItHint> FixIts;
+  FixIts.push_back(
+      FixItHint::CreateRemoval(CharSourceRange::getCharRange(RemovalRange)));
+  FixIts.push_back(FixItHint::CreateInsertion(
+      *ConstraintInsertionLoc, "requires " + *ConditionText + " "));
+  return FixIts;
+}
+
+void UseConstraintsCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *FunctionTemplate =
+      Result.Nodes.getNodeAs<FunctionTemplateDecl>("functionTemplate");
+  const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function");
+  const auto *ReturnType = Result.Nodes.getNodeAs<TypeLoc>("return");
+  if (!FunctionTemplate || !Function || !ReturnType)
+    return;
+
+  // Check for
+  //
+  //   Case 1. Return type of function
+  //
+  //     template <...>
+  //     enable_if_t<Condition, ReturnType>::type function() {}
+  //
+  //   Case 2. Trailing template parameter
+  //
+  //     template <..., enable_if_t<Condition, Type> = Type{}>
+  //     ReturnType function() {}
+  //
+  //     or
+  //
+  //     template <..., typename = enable_if_t<Condition, void>>
+  //     ReturnType function() {}
+  //
+
+  // Case 1. Return type of function
+  if (auto EnableIf = matchEnableIfSpecialization(*ReturnType)) {
+    diag(ReturnType->getBeginLoc(),
+         "use C++20 requires constraints instead of enable_if")
+        << handleReturnType(Function, *ReturnType, *EnableIf, *Result.Context);
+    return;
+  }
+
+  // Case 2. Trailing template parameter
+  if (auto [EnableIf, LastTemplateParam] =
+          matchTrailingTemplateParam(FunctionTemplate);
+      EnableIf && LastTemplateParam) {
+    diag(LastTemplateParam->getSourceRange().getBegin(),
+         "use C++20 requires constraints instead of enable_if")
+        << handleTrailingTemplateType(FunctionTemplate, Function,
+                                      LastTemplateParam, *EnableIf,
+                                      *Result.Context);
+    return;
+  }
+}
+
+} // namespace clang::tidy::modernize
Index: clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
===================================================================
--- clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -30,6 +30,7 @@
 #include "UnaryStaticAssertCheck.h"
 #include "UseAutoCheck.h"
 #include "UseBoolLiteralsCheck.h"
+#include "UseConstraintsCheck.h"
 #include "UseDefaultMemberInitCheck.h"
 #include "UseEmplaceCheck.h"
 #include "UseEqualsDefaultCheck.h"
@@ -85,6 +86,8 @@
     CheckFactories.registerCheck<UseAutoCheck>("modernize-use-auto");
     CheckFactories.registerCheck<UseBoolLiteralsCheck>(
         "modernize-use-bool-literals");
+    CheckFactories.registerCheck<UseConstraintsCheck>(
+        "modernize-use-constraints");
     CheckFactories.registerCheck<UseDefaultMemberInitCheck>(
         "modernize-use-default-member-init");
     CheckFactories.registerCheck<UseEmplaceCheck>("modernize-use-emplace");
Index: clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
===================================================================
--- clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -29,6 +29,7 @@
   UnaryStaticAssertCheck.cpp
   UseAutoCheck.cpp
   UseBoolLiteralsCheck.cpp
+  UseConstraintsCheck.cpp
   UseDefaultMemberInitCheck.cpp
   UseEmplaceCheck.cpp
   UseEqualsDefaultCheck.cpp
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to