https://github.com/BaLiKfromUA updated 
https://github.com/llvm/llvm-project/pull/225239

>From 368757fd84d130c0ed250efa489cb56e8a3e2449 Mon Sep 17 00:00:00 2001
From: BaLiKfromUA <[email protected]>
Date: Tue, 22 Sep 2026 01:01:23 +0100
Subject: [PATCH 1/4] [clang] Fix partial specialization matching for NTTPs of
 placeholder type

---
 clang/docs/ReleaseNotes.md                    |   7 +
 clang/lib/AST/ASTContext.cpp                  |   6 +-
 .../SemaTemplate/temp_arg_nontype_cxx1z.cpp   | 162 ++++++++++++++++--
 3 files changed, 160 insertions(+), 15 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index a1f24a8caedae..63e698a84a53b 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -727,6 +727,13 @@ features cannot lower the translation-unit ABI level;
 - Fixed ambiguous overload where two non-static member functions with
   different signatures could be incorrectly considered equivalent. (#GH224499)
 
+- 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 ee8663e6906af..926a8f7b7ee4b 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -8040,7 +8040,11 @@ 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
+    // C++ [temp.deduct.type]p20
+    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..1621983be64fa 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 'Auto::Decomposition::Any<'\x00'>'}}
 
     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 'Auto::Decomposition::NestedAny<0, 
'\x00'>'}}
 
     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 { };

>From c39fe2aaef36210dfe226e5105f016e541e11024 Mon Sep 17 00:00:00 2001
From: Valentyn Yukhymenko <[email protected]>
Date: Tue, 22 Sep 2026 01:37:28 +0100
Subject: [PATCH 2/4] Simplify error message matching

---
 clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp 
b/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp
index 1621983be64fa..1472aaa632400 100644
--- a/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp
+++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp
@@ -275,7 +275,7 @@ namespace Auto {
     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 'Auto::Decomposition::Any<'\x00'>'}}
+    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 {{template is 
declared here}}
@@ -283,7 +283,7 @@ namespace Auto {
     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 'Auto::Decomposition::NestedAny<0, 
'\x00'>'}}
+    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;

>From ecba51ddc3bea0baa9b9ca4a1d62972456efc826 Mon Sep 17 00:00:00 2001
From: Valentyn Yukhymenko <[email protected]>
Date: Tue, 22 Sep 2026 01:39:16 +0100
Subject: [PATCH 3/4] Remove second reference to temp.deduce

---
 clang/lib/AST/ASTContext.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index ef078a12ba164..799f2bfc48a80 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -8042,7 +8042,6 @@ bool ASTContext::isSameTemplateArgument(const 
TemplateArgument &Arg1,
   case TemplateArgument::Integral:
     // The types have to match as well as the values:
     // C++ [temp.type]p2
-    // C++ [temp.deduct.type]p20
     return hasSameType(Arg1.getIntegralType(), Arg2.getIntegralType()) &&
            llvm::APSInt::isSameValue(Arg1.getAsIntegral(),
                                      Arg2.getAsIntegral());

>From 4e6ef2d94490ff84b9f4f45a07898472a219b62d Mon Sep 17 00:00:00 2001
From: BaLiKfromUA <[email protected]>
Date: Wed, 23 Sep 2026 22:56:07 +0100
Subject: [PATCH 4/4] Add more precise reference to wording

---
 clang/lib/AST/ASTContext.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 799f2bfc48a80..c6087cf4ba98d 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -8040,8 +8040,9 @@ bool ASTContext::isSameTemplateArgument(const 
TemplateArgument &Arg1,
            getCanonicalTemplateName(Arg2.getAsTemplateOrTemplatePattern());
 
   case TemplateArgument::Integral:
-    // The types have to match as well as the values:
-    // C++ [temp.type]p2
+    // Per C++20 [temp.type]p2:
+    // "Two values are template-argument-equivalent if they are of the same 
type
+    // and they are of integral type and their values are the same".
     return hasSameType(Arg1.getIntegralType(), Arg2.getIntegralType()) &&
            llvm::APSInt::isSameValue(Arg1.getAsIntegral(),
                                      Arg2.getAsIntegral());

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to