llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Valentyn Yukhymenko (BaLiKfromUA) <details> <summary>Changes</summary> Fixes https://github.com/llvm/llvm-project/issues/124186 Fixes https://github.com/llvm/llvm-project/issues/42421 Fixes https://github.com/llvm/llvm-project/issues/53982 ---- 🔎 [**Compiler Explorer Link** to illustrate differences between GCC and Clang trunks](https://godbolt.org/z/MY4r8bMx8) **🤖 AI usage:** I used LLM to suggest test cases to improve coverage + to find existing open issues which my fix might address. **Note about my fix:** I checked existing callers of `ASTContext::isSameTemplateArgument` and I _think_ my change is safe. But please correct me if I am wrong! I will move the check somewhere to the relevant caller side then. --- Full diff: https://github.com/llvm/llvm-project/pull/225239.diff 3 Files Affected: - (modified) clang/docs/ReleaseNotes.md (+7) - (modified) clang/lib/AST/ASTContext.cpp (+4-1) - (modified) clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp (+148-14) ``````````diff diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 303f972fcaae1..53ad36c840e71 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -737,6 +737,13 @@ features cannot lower the translation-unit ABI level; lookup before asserting that the name is not dependent, avoiding an assertion after an earlier diagnostic has caused the declaration to be unavailable. (#GH220525) +- Fixed template argument deduction incorrectly selecting a class or variable + template partial specialization whose non-type template argument has a + different type from the argument it is matched against, when the corresponding + parameter of the primary template has a placeholder type. For example, given + `template <class T, auto V> struct S`, the partial specialization `S<T, 0>` is + no longer selected for `S<void, 0L>`. (#GH124186), (#GH42421), (#GH53982) + #### Bug Fixes to AST Handling - Fixed a non-deterministic ordering of unused local typedefs that made diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index e74423ca8c8a1..799f2bfc48a80 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -8040,7 +8040,10 @@ bool ASTContext::isSameTemplateArgument(const TemplateArgument &Arg1, getCanonicalTemplateName(Arg2.getAsTemplateOrTemplatePattern()); case TemplateArgument::Integral: - return llvm::APSInt::isSameValue(Arg1.getAsIntegral(), + // The types have to match as well as the values: + // C++ [temp.type]p2 + return hasSameType(Arg1.getIntegralType(), Arg2.getIntegralType()) && + llvm::APSInt::isSameValue(Arg1.getAsIntegral(), Arg2.getAsIntegral()); case TemplateArgument::StructuralValue: diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp index a39bb02084aa7..1472aaa632400 100644 --- a/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp +++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp @@ -267,22 +267,23 @@ namespace Auto { } namespace Decomposition { - // Types of deduced non-type template arguments must match exactly, so - // partial ordering fails in both directions here. - template<auto> struct Any; - template<int N> struct Any<N> { typedef int Int; }; // expected-note 3{{match}} - template<short N> struct Any<N> { typedef int Short; }; // expected-note 3{{match}} - Any<0>::Int is_int; // expected-error {{ambiguous}} - Any<(short)0>::Short is_short; // expected-error {{ambiguous}} - Any<(char)0>::Short is_char; // expected-error {{ambiguous}} + // Types of deduced non-type template arguments must match exactly, so each + // of these selects at most one partial specialization: the one whose + // parameter has the same type as the argument. + template<auto> struct Any; // expected-note {{template is declared here}} + template<int N> struct Any<N> { typedef int Int; }; + template<short N> struct Any<N> { typedef int Short; }; + Any<0>::Int is_int; + Any<(short)0>::Short is_short; + Any<(char)0>::Short is_char; // expected-error {{implicit instantiation of undefined template}} template<int, auto> struct NestedAny; - template<auto N> struct NestedAny<0, N>; // expected-note 3{{match}} - template<int N> struct NestedAny<0, N> { typedef int Int; }; // expected-note 3{{match}} - template<short N> struct NestedAny<0, N> { typedef int Short; }; // expected-note 3{{match}} - NestedAny<0, 0>::Int nested_int; // expected-error {{ambiguous}} - NestedAny<0, (short)0>::Short nested_short; // expected-error {{ambiguous}} - NestedAny<0, (char)0>::Short nested_char; // expected-error {{ambiguous}} + template<auto N> struct NestedAny<0, N>; // expected-note {{template is declared here}} + template<int N> struct NestedAny<0, N> { typedef int Int; }; + template<short N> struct NestedAny<0, N> { typedef int Short; }; + NestedAny<0, 0>::Int nested_int; + NestedAny<0, (short)0>::Short nested_short; + NestedAny<0, (char)0>::Short nested_char; // expected-error {{implicit instantiation of undefined template}} double foo(int, bool); template<auto& f> struct fn_result_type; @@ -649,6 +650,139 @@ namespace GH58682 { static_assert(B<A<(g)>>::k == 1, ""); } // namespace GH58682 +namespace GH124186 { + template <class T, auto V> struct S { + static constexpr int value = 0; + }; + + template <class T> struct S<T, 0> { + static constexpr int value = 1; + }; + + enum E { Zero }; + + static_assert(S<void, 0>::value == 1); + static_assert(S<void, 0L>::value == 0); + static_assert(S<void, 0U>::value == 0); + static_assert(S<void, false>::value == 0); + static_assert(S<void, Zero>::value == 0); + + // For a parameter of non-placeholder type the argument is converted to the + // type of the parameter, so these all still match. + template <class T, int V> struct F { + static constexpr int value = 0; + }; + + template <class T> struct F<T, 0> { + static constexpr int value = 1; + }; + + static_assert(F<void, 0>::value == 1); + static_assert(F<void, 0L>::value == 1); + static_assert(F<void, 0U>::value == 1); + + // Test with aliasing and cv modifiers + typedef int my_int; + using my_const_int = const int; + + template <class T, auto V> struct G { + static constexpr int value = 0; + }; + + template <class T> struct G<T, (my_int)0> { + static constexpr int value = 1; + }; + + static_assert(G<void, 0>::value == 1); + static_assert(G<void, 0L>::value == 0); + + template <auto V> struct H { + static constexpr int value = 0; + }; + + template <my_int V> struct H<V> { + static constexpr int value = 1; + }; + + template <auto V> struct I { + static constexpr int value = 0; + }; + + template <my_const_int V> struct I<V> { + static constexpr int value = 1; + }; + + static_assert(H<0>::value == 1); + static_assert(H<0L>::value == 0); + static_assert(I<0>::value == 1); + static_assert(I<0L>::value == 0); + + // Variable templates behave the same way. + template <class T, auto V> constexpr int value = 0; + template <class T> constexpr int value<T, 0> = 1; + + static_assert(value<void, 0> == 1); + static_assert(value<void, 0L> == 0); + static_assert(value<void, 0U> == 0); + static_assert(value<void, false> == 0); +} // namespace GH124186 + +namespace GH42421 { + template <auto V> struct S { + static constexpr int value = 0; + }; + + template <int I> struct S<I> { + static constexpr int value = 1; + }; + + static_assert(S<42>::value == 1); + // A long or unsigned argument does not match an int parameter. + static_assert(S<42L>::value == 0); + static_assert(S<42U>::value == 0); + + // Only the partial specialization is type-sensitive here; the explicit + // specialization is not a candidate for Q<42U, int> either way. + template <auto I, class T> struct Q { + static constexpr int value = 0; + }; + + template <class T> struct Q<42, T> { + static constexpr int value = 1; + }; + + template <> struct Q<42, int> { + static constexpr int value = 2; + }; + + static_assert(Q<42U, int>::value == 0); + // The explicit specialization still wins for an int argument. + static_assert(Q<42, int>::value == 2); +} // namespace GH42421 + +namespace GH53982 { + enum class E1 : unsigned int { E11 = 1 }; + enum class E2 : unsigned int { E21 = 1 }; + + template <int j, auto i> struct C { + static constexpr int value = 0; + }; + + template <int j> struct C<j, E1::E11> { + static constexpr int value = 1; + }; + + template <int j> struct C<j, E2::E21> { + static constexpr int value = 2; + }; + + static_assert(C<0, E1::E11>::value == 1); + static_assert(C<1, E2::E21>::value == 2); + + // The shared underlying value on its own matches neither. + static_assert(C<0, 1U>::value == 0); +} // namespace GH53982 + // C++26 [temp.deduct.type]p13, Example 8. namespace temp_deduct_type_p13 { template<long n> struct A { }; `````````` </details> https://github.com/llvm/llvm-project/pull/225239 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
