ccotter updated this revision to Diff 493050. ccotter added a comment. Rebase + Simplify match logic
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 @@ -97,6 +97,11 @@ New checks ^^^^^^^^^^ +- 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,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,493 @@ +//===--- 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::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>()) { + if (Dep.getTypePtr()->getIdentifier()->getName() != "type" || + Dep.getTypePtr()->getKeyword() != ETK_Typename) { + return std::nullopt; + } + TheType = Dep.getQualifierLoc().getTypeLoc(); + } + + 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> +matchEnableIfSpecializationImplTrait(TypeLoc TheType) { + if (const auto Elaborated = TheType.getAs<ElaboratedTypeLoc>()) { + TheType = Elaborated.getNamedTypeLoc(); + } + + 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 = matchEnableIfSpecializationImplTypename(TheType); + if (EnableIf) { + return EnableIf; + } + return matchEnableIfSpecializationImplTrait(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 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 @@ -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 cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits