[llvm-branch-commits] [clang] [clang] Backport: use canonical arguments for checking function template constraints (PR #186930)

Matheus Izvekov via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Mar 16 18:55:40 PDT 2026


https://github.com/mizvekov updated https://github.com/llvm/llvm-project/pull/186930

>From 6d615b2b8c8f3f72b219a10b97aba1c13431d53a Mon Sep 17 00:00:00 2001
From: Matheus Izvekov <mizvekov at gmail.com>
Date: Mon, 16 Mar 2026 22:12:05 -0300
Subject: [PATCH] [clang] Backport: use canonical arguments for checking
 function template constraints

Backport from #186889

This is a partial revert of #161671, restoring the original behaviour
where the canonical template arguments are used for function template
constraint checking in diagnostics.

This reverts the fix from #183010, which attempted to fix #182344
but it causes regressions. These regressions now have test cases
included.

The attempt at #183010 is flawed because in the general case we can't
check satisfaction for constraints which have unsubstituted template
arguments, even if they don't affect the canonical type (ie they are
purely
syntactical), because these types can still turn out to be invalid after
substitution.

This is a problem when directly evaluating a concept specialization, but
it's not a problem with other template specializations because the
as-written types are preserved, and will be later substituted, and any
failures
here will cause the program to be ill-formed anyway.

The only downside of this revert is the loss of sugar in some
diagnostics.

This could be improved in the future by adding a new flag which would
allow ignoring this instantiation dependence in such cases where a
substitution failure will be handled later anyway.

This is not done in this patch because we want the safest thing
possible, to help backporting this patch to Clang 22.

This preserves the tests from #183010 and also adds the tests from

Since this fixes a regression since Clang 21 and will be backported to
Clang 22, there are no release notes.
---
 clang/lib/Sema/SemaConcept.cpp                |  7 ++-
 clang/lib/Sema/SemaTemplateDeduction.cpp      |  7 ++-
 .../expr.prim.req/nested-requirement.cpp      |  2 +-
 clang/test/SemaCXX/cxx2b-deducing-this.cpp    |  8 ++--
 .../SemaTemplate/concepts-recursive-inst.cpp  |  6 +--
 clang/test/SemaTemplate/concepts.cpp          | 43 +++++++++++++++++++
 6 files changed, 60 insertions(+), 13 deletions(-)

diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index f55f3a9a61ab8..5cab4f7a1a2a5 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -1154,8 +1154,13 @@ static bool CheckConstraintSatisfaction(
     return false;
   }
 
+  // In the general case, we can't check satisfaction if the arguments contain
+  // unsubstituted template parameters, even if they are purely syntactic,
+  // because they may still turn out to be invalid after substitution.
+  // This could be permitted in cases where this substitution will still be
+  // attempted later and diagnosed, such as function template specializations,
+  // but that's not the case for concept specializations.
   if (TemplateArgsLists.isAnyArgInstantiationDependent()) {
-    // No need to check satisfaction for dependent constraint expressions.
     Satisfaction.IsSatisfied = true;
     return false;
   }
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 0476872e282cf..2b2b9c60f5ad3 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -3981,12 +3981,11 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
     if (CheckFunctionTemplateConstraints(
             Info.getLocation(),
             FunctionTemplate->getCanonicalDecl()->getTemplatedDecl(),
-            CTAI.SugaredConverted, Info.AssociatedConstraintsSatisfaction))
+            CTAI.CanonicalConverted, Info.AssociatedConstraintsSatisfaction))
       return TemplateDeductionResult::MiscellaneousDeductionFailure;
     if (!Info.AssociatedConstraintsSatisfaction.IsSatisfied) {
-      Info.reset(
-          TemplateArgumentList::CreateCopy(Context, CTAI.SugaredConverted),
-          Info.takeCanonical());
+      Info.reset(Info.takeSugared(), TemplateArgumentList::CreateCopy(
+                                         Context, CTAI.CanonicalConverted));
       return TemplateDeductionResult::ConstraintsNotSatisfied;
     }
   }
diff --git a/clang/test/CXX/expr/expr.prim/expr.prim.req/nested-requirement.cpp b/clang/test/CXX/expr/expr.prim/expr.prim.req/nested-requirement.cpp
index 70a96bed05867..7b58150eaaf84 100644
--- a/clang/test/CXX/expr/expr.prim/expr.prim.req/nested-requirement.cpp
+++ b/clang/test/CXX/expr/expr.prim/expr.prim.req/nested-requirement.cpp
@@ -152,7 +152,7 @@ void func() {
   // expected-note@#bar {{while substituting template arguments into constraint expression here}}
   // expected-note@#bar {{while checking the satisfaction of nested requirement requested here}}
   // expected-note@#bar {{candidate template ignored: constraints not satisfied [with T = False]}}
-  // expected-note@#bar {{because 'X<False>::value' evaluated to false}}
+  // expected-note@#bar {{because 'X<SubstitutionFailureNestedRequires::ErrorExpressions_NotSF::False>::value' evaluated to false}}
 
   bar<int>();
   // expected-error at -1 {{no matching function for call to 'bar'}} \
diff --git a/clang/test/SemaCXX/cxx2b-deducing-this.cpp b/clang/test/SemaCXX/cxx2b-deducing-this.cpp
index a9e31c3d06676..29ed02e3dceff 100644
--- a/clang/test/SemaCXX/cxx2b-deducing-this.cpp
+++ b/clang/test/SemaCXX/cxx2b-deducing-this.cpp
@@ -1257,13 +1257,13 @@ void f() {
     (&A::e)(a, a);
     // expected-error at -1 {{no matching function for call to 'e'}} \
     // expected-note@#tpl-address-e{{candidate template ignored: constraints not satisfied [with T = A, U = A]}} \
-    // expected-note@#tpl-address-e{{because '__is_same(A, int)' evaluated to false}}
+    // expected-note@#tpl-address-e{{because '__is_same(tpl_address::A, int)' evaluated to false}}
 
     (&A::e<A>)(a, 0);
     (&A::e<A>)(a, a);
     // expected-error at -1 {{no matching function for call to 'e'}} \
     // expected-note@#tpl-address-e{{candidate template ignored: constraints not satisfied [with T = A, U = A]}} \
-    // expected-note@#tpl-address-e{{because '__is_same(A, int)' evaluated to false}}
+    // expected-note@#tpl-address-e{{because '__is_same(tpl_address::A, int)' evaluated to false}}
 
     (&A::e<A, int>)(a, 0);
 
@@ -1273,12 +1273,12 @@ void f() {
     (&A::f<A>)(a);
     // expected-error at -1 {{no matching function for call to 'f'}} \
     // expected-note@#tpl-address-f{{candidate template ignored: constraints not satisfied [with T = A]}} \
-    // expected-note@#tpl-address-f{{because '__is_same(A, int)' evaluated to false}}
+    // expected-note@#tpl-address-f{{because '__is_same(tpl_address::A, int)' evaluated to false}}
 
     (&A::f)(a);
     // expected-error at -1 {{no matching function for call to 'f'}} \
     // expected-note@#tpl-address-f{{candidate template ignored: constraints not satisfied [with T = A]}} \
-    // expected-note@#tpl-address-f{{because '__is_same(A, int)' evaluated to false}}
+    // expected-note@#tpl-address-f{{because '__is_same(tpl_address::A, int)' evaluated to false}}
 
     (&A::g)(a);
     (&A::g)(a, 0);
diff --git a/clang/test/SemaTemplate/concepts-recursive-inst.cpp b/clang/test/SemaTemplate/concepts-recursive-inst.cpp
index 5e1bce5f15684..7598962b01563 100644
--- a/clang/test/SemaTemplate/concepts-recursive-inst.cpp
+++ b/clang/test/SemaTemplate/concepts-recursive-inst.cpp
@@ -73,8 +73,8 @@ auto it = begin(rng); // #BEGIN_CALL
 // expected-note@#INF_BEGIN_EXPR {{while substituting deduced template arguments into function template 'begin'}}
 // expected-note@#INF_BEGIN_EXPR {{in instantiation of requirement here}}
 // expected-note@#INF_REQ {{while substituting template arguments into constraint expression here}}
-// expected-note@#INF_BEGIN {{while checking the satisfaction of concept 'Inf<struct my_range>' requested here}}
-// expected-note@#BEGIN_CALL {{while checking constraint satisfaction for template 'begin<struct my_range>' required here}}
+// expected-note@#INF_BEGIN {{while checking the satisfaction of concept 'Inf<DirectRecursiveCheck::my_range>' requested here}}
+// expected-note@#BEGIN_CALL {{while checking constraint satisfaction for template 'begin<DirectRecursiveCheck::my_range>' required here}}
 // expected-note@#BEGIN_CALL {{while substituting deduced template arguments into function template}}
 
 // Fallout of the failure is failed lookup, which is necessary to stop odd
@@ -99,7 +99,7 @@ namespace GH50891 {
   static_assert(Numeric<Deferred>); // #STATIC_ASSERT
   // expected-error@#NUMERIC{{satisfaction of constraint 'requires (T a) { foo(a); }' depends on itself}}
   // expected-note@#NUMERIC {{while substituting template arguments into constraint expression here}}
-  // expected-note@#OP_TO {{while checking the satisfaction of concept 'Numeric<Deferred>' requested here}}
+  // expected-note@#OP_TO {{while checking the satisfaction of concept 'Numeric<GH50891::Deferred>' requested here}}
   // expected-note@#OP_TO {{skipping 1 context}}
   // expected-note@#FOO_CALL 2{{while checking constraint satisfaction for template}}
   // expected-note@#FOO_CALL 2{{while substituting deduced template arguments into function template}}
diff --git a/clang/test/SemaTemplate/concepts.cpp b/clang/test/SemaTemplate/concepts.cpp
index d93391baf9926..c5cbd129e33fd 100644
--- a/clang/test/SemaTemplate/concepts.cpp
+++ b/clang/test/SemaTemplate/concepts.cpp
@@ -1704,6 +1704,49 @@ struct ice_point : relative_point_origin<point<kelvin>> {};
 
 }
 
+namespace GH182344 {
+
+template <typename T>
+  requires true
+void f() {}
+
+template <typename T>
+  requires false
+void f() = delete;
+
+struct Bar {};
+
+template <typename> using Foo = Bar;
+
+template <int T>
+  requires true
+void f2() {}
+
+template <int T>
+  requires false
+void f2() = delete;
+
+template <int> constexpr auto Value = 1;
+
+template <template <typename> class> using FooTemp = Bar;
+
+template <typename T, int N, template <typename> class C> void use() {
+  f<Foo<T>>();
+  f2<Value<N>>();
+  f<FooTemp<C>>();
+}
+
+}
+
+namespace instantiation_dependent {
+  template <class T> concept C = sizeof(T) >= 1;
+  template <class U> using X = int;
+  template <class V> requires C<X<V&>> struct Y {};
+  Y<void> y;
+  // expected-error at -1 {{constraints not satisfied for class template 'Y' [with V = void]}}
+  // expected-note at -3  {{because substituted constraint expression is ill-formed: cannot form a reference to 'void'}}
+} // namespace instantiation_dependent
+
 namespace GH174667 {
 
 template<class T, class, class U>



More information about the llvm-branch-commits mailing list