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