ccotter updated this revision to Diff 491127.
ccotter added a comment.
- Add fno-delayed-template-parsing
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/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,693 @@
+// 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;
+
+};
+
+
+////////////////////////////////
+// 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) {}{{$}}
+
+ int data;
+};
+
+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,24 @@
+.. title:: clang-tidy - modernize-use-constraints
+
+modernize-use-constraints
+======================
+
+Replace ``enable_if`` with C++20 requires clauses.
+
+``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 replaces common ``enable_if`` patterns that can be replaced
+by C++20 requires clauses. Uses that cannot be replaced automatically
+will emit a diagnostic without a replacement
+
+.. code-block:: c++
+
+ template <typename T>
+ std::enable_if_t<T::some_trait, int> only_if_t_has_the_trait() { ... }
+
+ template <typename T, std::enable_if_t<T::some_trait, int> = 0>
+ void another_version() { ... }
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
@@ -282,6 +282,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
@@ -141,6 +141,11 @@
Warns when `empty()` is used on a range and the result is ignored. Suggests `clear()`
if it is an existing member function.
+- New :doc:`modernize-use-constraints
+ <clang-tidy/checks/modernize/use-constraints>` check.
+
+ Replace ``enable_if`` with C++20 requires clauses.
+
New check aliases
^^^^^^^^^^^^^^^^^
Index: clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.h
@@ -0,0 +1,37 @@
+//===--- 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 {
+namespace tidy {
+namespace 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 modernize
+} // namespace tidy
+} // namespace clang
+
+#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,498 @@
+//===--- 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 <tuple>
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace 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>
+matchEnableIfSpecialization_impl_type(TypeLoc TheType) {
+ if (const auto Dep = TheType.getAs<DependentNameTypeLoc>()) {
+ if (Dep.getTypePtr()->getIdentifier()->getName() != "type" ||
+ Dep.getTypePtr()->getKeyword() != ETK_Typename) {
+ return std::nullopt;
+ }
+ return matchEnableIfSpecialization_impl_type(
+ Dep.getQualifierLoc().getTypeLoc());
+ } else if (const auto Elaborated = TheType.getAs<ElaboratedTypeLoc>()) {
+ return std::nullopt;
+ } else if (const auto Specialization =
+ TheType.getAs<TemplateSpecializationTypeLoc>()) {
+ std::string Name = TheType.getType().getAsString();
+ if (Name.find("enable_if<") == std::string::npos) {
+ return std::nullopt;
+ }
+ int NumArgs = Specialization.getNumArgs();
+ if (NumArgs != 1 && NumArgs != 2) {
+ return std::nullopt;
+ }
+ return std::make_optional(Specialization);
+ }
+ return std::nullopt;
+}
+
+static std::optional<TemplateSpecializationTypeLoc>
+matchEnableIfSpecialization_impl_t(TypeLoc TheType) {
+ if (const auto Dep = TheType.getAs<DependentNameTypeLoc>()) {
+ return std::nullopt;
+ } else if (const auto Elaborated = TheType.getAs<ElaboratedTypeLoc>()) {
+ return matchEnableIfSpecialization_impl_t(Elaborated.getNamedTypeLoc());
+ } else if (const auto Specialization =
+ TheType.getAs<TemplateSpecializationTypeLoc>()) {
+ std::string Name = TheType.getType().getAsString();
+ if (Name.find("enable_if_t<") == std::string::npos) {
+ return std::nullopt;
+ }
+ if (!Specialization.getTypePtr()->isTypeAlias()) {
+ return std::nullopt;
+ }
+ if (const auto *AliasedType = llvm::dyn_cast<DependentNameType>(
+ Specialization.getTypePtr()->getAliasedType())) {
+ if (AliasedType->getIdentifier()->getName() != "type" ||
+ AliasedType->getKeyword() != ETK_Typename) {
+ return std::nullopt;
+ }
+ } else {
+ return std::nullopt;
+ }
+ int NumArgs = Specialization.getNumArgs();
+ if (NumArgs != 1 && NumArgs != 2) {
+ return std::nullopt;
+ }
+ return std::make_optional(Specialization);
+ }
+ return std::nullopt;
+}
+
+static std::optional<TemplateSpecializationTypeLoc>
+matchEnableIfSpecialization_impl(TypeLoc TheType) {
+ std::optional<TemplateSpecializationTypeLoc> EnableIf;
+ EnableIf = matchEnableIfSpecialization_impl_type(TheType);
+ if (EnableIf) {
+ return EnableIf;
+ }
+ return matchEnableIfSpecialization_impl_t(TheType);
+}
+
+static std::optional<EnableIfData>
+matchEnableIfSpecialization(TypeLoc TheType) {
+ if (const auto Qualified = TheType.getAs<QualifiedTypeLoc>()) {
+ }
+ 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();
+ }
+
+ std::optional<TemplateSpecializationTypeLoc> EnableIf =
+ matchEnableIfSpecialization_impl(TheType);
+ if (EnableIf) {
+ return std::make_optional(EnableIfData{std::move(*EnableIf), TheType});
+ } else {
+ return std::nullopt;
+ }
+}
+
+static std::tuple<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 =
+ llvm::dyn_cast<NonTypeTemplateParmDecl>(LastParam)) {
+
+ if (!LastTemplateParam->hasDefaultArgument() ||
+ !LastTemplateParam->getName().empty()) {
+ return {};
+ }
+
+ return std::make_tuple(
+ matchEnableIfSpecialization(
+ LastTemplateParam->getTypeSourceInfo()->getTypeLoc()),
+ LastTemplateParam);
+ } else if (const auto *LastTemplateParam =
+ llvm::dyn_cast<TemplateTypeParmDecl>(LastParam)) {
+ if (LastTemplateParam->hasDefaultArgument() &&
+ LastTemplateParam->getIdentifier() == nullptr) {
+ return std::make_tuple(
+ 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));
+ } else {
+ 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));
+}
+
+static std::optional<std::string>
+getTypeText(ASTContext &Context,
+ const TemplateSpecializationTypeLoc &EnableIf) {
+ if (EnableIf.getNumArgs() > 1) {
+ const LangOptions &LangOpts = Context.getLangOpts();
+ const SourceManager &SM = Context.getSourceManager();
+ bool Invalid = false;
+ std::string Text =
+ Lexer::getSourceText(
+ CharSourceRange::getCharRange(getTypeRange(Context, EnableIf)), SM,
+ LangOpts, &Invalid)
+ .trim()
+ .str();
+ if (Invalid) {
+ return std::nullopt;
+ }
+ return std::make_optional(std::move(Text));
+ } else {
+ return std::make_optional("void");
+ }
+}
+
+static std::optional<SourceLocation>
+findInsertionForConstraint(const FunctionDecl *Function, ASTContext &Context) {
+ SourceManager &SM = Context.getSourceManager();
+ const LangOptions &LangOpts = Context.getLangOpts();
+
+ if (const auto *Constructor = llvm::dyn_cast<CXXConstructorDecl>(Function)) {
+ if (Constructor->init_begin() != Constructor->init_end()) {
+ const CXXCtorInitializer *FirstInit = *Constructor->init_begin();
+ return utils::lexer::findPreviousTokenKind(FirstInit->getSourceLocation(),
+ SM, LangOpts, tok::colon);
+ }
+ }
+ if (Function->isDeleted()) {
+ SourceRange ParamsRange = Function->getParametersSourceRange();
+ if (!ParamsRange.isValid()) {
+ return std::nullopt;
+ }
+ SourceLocation EndParens = utils::lexer::findNextAnyTokenKind(
+ ParamsRange.getEnd(), SM, LangOpts, tok::r_paren, tok::r_paren);
+ return utils::lexer::findNextAnyTokenKind(EndParens, SM, LangOpts,
+ tok::equal, tok::equal);
+ }
+ const Stmt *Body = Function->getBody();
+ if (!Body) {
+ return std::nullopt;
+ }
+ return Body->getBeginLoc();
+}
+
+static Token getPreviousToken(SourceLocation &Location, const SourceManager &SM,
+ const LangOptions &LangOpts,
+ bool SkipComments = false) {
+ Token Token;
+ Token.setKind(tok::unknown);
+
+ Location = Location.getLocWithOffset(-1);
+ if (Location.isInvalid())
+ return Token;
+
+ auto StartOfFile = SM.getLocForStartOfFile(SM.getFileID(Location));
+ while (Location != StartOfFile) {
+ Location = Lexer::GetBeginningOfToken(Location, SM, LangOpts);
+ if (!Lexer::getRawToken(Location, Token, SM, LangOpts) &&
+ (!SkipComments || !Token.is(tok::comment))) {
+ break;
+ }
+ Location = Location.getLocWithOffset(-1);
+ }
+ return Token;
+}
+
+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 = llvm::dyn_cast<ImplicitCastExpr>(Expression)) {
+ Expression = Cast->getSubExprAsWritten();
+ }
+ if (llvm::dyn_cast<ParenExpr>(Expression)) {
+ return true;
+ }
+ if (llvm::dyn_cast<DependentScopeDeclRefExpr>(Expression)) {
+ return true;
+ }
+
+ return false;
+}
+
+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;
+ }
+ Token PrevToken = getPreviousToken(PrevTokenLoc, SM, LangOpts);
+ 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();
+ } else {
+ return "(" + Text.str() + ")";
+ }
+ };
+
+ if (EndsWithDoubleSlash) {
+ return std::make_optional(AddParens(ConditionText.str()));
+ } else {
+ return std::make_optional(AddParens(ConditionText.trim().str()));
+ }
+}
+
+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);
+ std::optional<std::string> 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;
+}
+
+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
+ // template <...>
+ // enable_if<Condition, ReturnType> function();
+ //
+ // template <..., enable_if<Condition, Type> = Type{}>
+ // function();
+ //
+ // template <..., typename = enable_if<Condition, void>>
+ // function();
+
+ std::optional<EnableIfData> EnableIf;
+ EnableIf = matchEnableIfSpecialization(*ReturnType);
+ if (EnableIf.has_value()) {
+ std::vector<FixItHint> FixIts =
+ handleReturnType(Function, *ReturnType, *EnableIf, *Result.Context);
+ diag(ReturnType->getBeginLoc(),
+ "use C++20 requires constraints instead of enable_if")
+ << FixIts;
+ return;
+ }
+ const Decl *LastTemplateParam = nullptr;
+ std::tie(EnableIf, LastTemplateParam) =
+ matchTrailingTemplateParam(FunctionTemplate);
+ if (EnableIf.has_value() && LastTemplateParam) {
+ std::vector<FixItHint> FixIts = handleTrailingTemplateType(
+ FunctionTemplate, Function, LastTemplateParam, *EnableIf,
+ *Result.Context);
+ diag(LastTemplateParam->getSourceRange().getBegin(),
+ "use C++20 requires constraints instead of enable_if")
+ << FixIts;
+ return;
+ }
+}
+
+} // namespace modernize
+} // namespace tidy
+} // namespace clang
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
@@ -29,6 +29,7 @@
#include "UnaryStaticAssertCheck.h"
#include "UseAutoCheck.h"
#include "UseBoolLiteralsCheck.h"
+#include "UseConstraintsCheck.h"
#include "UseDefaultMemberInitCheck.h"
#include "UseEmplaceCheck.h"
#include "UseEqualsDefaultCheck.h"
@@ -81,6 +82,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
@@ -28,6 +28,7 @@
UnaryStaticAssertCheck.cpp
UseAutoCheck.cpp
UseBoolLiteralsCheck.cpp
+ UseConstraintsCheck.cpp
UseDefaultMemberInitCheck.cpp
UseEmplaceCheck.cpp
UseEqualsDefaultCheck.cpp
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits