https://github.com/ojhunt created 
https://github.com/llvm/llvm-project/pull/214130

When partial-ordering function templates, CheckDeductionConsistency substitutes 
the canonical type for each parameter. When the parameter type is of the form 
`decltype(some_parameter)`. The semantic type of such a parameter is 
`decltype({template depth, parameter index})`, and that is what we used to 
perform canonicalization.

The problem came from us not canonicalizing the type of such a decl to the 
dependent representation of that type, but instead just the first declaration 
of it. As a result a parameter declaration in a different function that 
happened to have a decltype'd parameter that referenced the same parameter 
index would canonalize to the first declaration, even though that was a 
decltype over a parameter in an unrelated function.

Instantiate the parameter-side parameters into the scope in declaration order 
so decltype resolves, and then substitute the sugared parameter type.

Fixes #133792

>From 32fbbb7bd27312bc4ee7d3e32b72560dfb2b8c09 Mon Sep 17 00:00:00 2001
From: Oliver Hunt <[email protected]>
Date: Fri, 31 Jul 2026 19:47:15 -0700
Subject: [PATCH] [clang] fix crash substituting decltype parameters when
 partial ordering

When partial-ordering function templates, CheckDeductionConsistency substitutes
the canonical type for each parameter. When the parameter type is of the form
`decltype(some_parameter)`. The semantic type of such a parameter is
`decltype({template depth, parameter index})`, and that is what we
used to perform canonicalization.

The problem came from us not canonicalizing the type of such a decl to the
dependent representation of that type, but instead just the first declaration
of it. As a result a parameter declaration in a different function that
happened to have a decltype'd parameter that referenced the same parameter
index would canonalize to the first declaration, even though that was a
decltype over a parameter in an unrelated function.

Instantiate the parameter-side parameters into the scope in declaration order so
decltype resolves, and then substitute the sugared parameter type.

Fixes #133792
---
 clang/lib/Sema/SemaTemplateDeduction.cpp      |  38 ++++-
 ...tial-ordering-decltype-canonical-alias.cpp | 150 ++++++++++++++++++
 2 files changed, 180 insertions(+), 8 deletions(-)
 create mode 100644 
clang/test/SemaTemplate/partial-ordering-decltype-canonical-alias.cpp

diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp 
b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 3c45806c47a6e..44a8671aed182 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -5594,9 +5594,6 @@ static TemplateDeductionResult CheckDeductionConsistency(
       S,
       ArgIdx ? ::getPackIndexForParam(S, FTD, MLTAL, *ArgIdx) : std::nullopt);
   bool IsIncompleteSubstitution = false;
-  // FIXME: A substitution can be incomplete on a non-structural part of the
-  // type. Use the canonical type for now, until the TemplateInstantiator can
-  // deal with that.
 
   // Workaround: Implicit deduction guides use InjectedClassNameTypes, whereas
   // the explicit guides don't. The substitution doesn't transform these types,
@@ -5607,8 +5604,12 @@ static TemplateDeductionResult CheckDeductionConsistency(
       P = Injected->getDecl()->getCanonicalTemplateSpecializationType(
           S.Context);
   }
-  QualType InstP = S.SubstType(P.getCanonicalType(), MLTAL, FTD->getLocation(),
-                               FTD->getDeclName(), &IsIncompleteSubstitution);
+  // Substitute the adjusted parameter type, not the decay sugar over the
+  // original array or function type.
+  if (const auto *Adjusted = P->getAs<AdjustedType>())
+    P = Adjusted->getAdjustedType();
+  QualType InstP = S.SubstType(P, MLTAL, FTD->getLocation(), 
FTD->getDeclName(),
+                               &IsIncompleteSubstitution);
   if (InstP.isNull() && !IsIncompleteSubstitution)
     return TemplateDeductionResult::SubstitutionFailure;
   if (!CheckConsistency)
@@ -5664,10 +5665,31 @@ static TemplateDeductionResult 
FinishTemplateArgumentDeduction(
 
   Info.reset(SugaredDeducedArgumentList, CanonicalDeducedArgumentList);
 
-  // Substitute the deduced template arguments into the argument
-  // and verify that the instantiated argument is both valid
-  // and equivalent to the parameter.
+  // ***REVIEWER***: is this correct? i'm assuming i need to construct a new
+  // template instantiation scope to hold the following parameter declarations.
   LocalInstantiationScope InstScope(S);
+
+  // Yeet the function parameters into the scope so that later references can
+  // actually use them.
+  MultiLevelTemplateArgumentList MLTAL(FTD, CTAI.SugaredConverted,
+                                       /*Final=*/true);
+  for (ParmVarDecl *Param : FTD->getTemplatedDecl()->parameters()) {
+    bool IsIncompleteSubstitution = false;
+    QualType SubstT =
+        S.SubstType(Param->getType(), MLTAL, Param->getLocation(),
+                    Param->getDeclName(), &IsIncompleteSubstitution);
+    if (!SubstT.isNull() && !IsIncompleteSubstitution)
+      S.SubstParmVarDecl(Param, MLTAL, /*indexAdjustment=*/0,
+                         /*NumExpansions=*/std::nullopt,
+                         /*ExpectParameterPack=*/false,
+                         /*EvaluateConstraints=*/false);
+    // A parameter pack whose substitution is incomplete falls through
+    // unregistered. Don't know how to fix this correctly, and cannot exercise
+    // the code path due to issue #213760.
+    else if (!Param->isParameterPack())
+      InstScope.InstantiatedLocal(Param, Param);
+  }
+
   return CheckDeductionConsistency(S, FTD, CTAI.SugaredConverted);
 }
 
diff --git 
a/clang/test/SemaTemplate/partial-ordering-decltype-canonical-alias.cpp 
b/clang/test/SemaTemplate/partial-ordering-decltype-canonical-alias.cpp
new file mode 100644
index 0000000000000..d79ad3cbe9da1
--- /dev/null
+++ b/clang/test/SemaTemplate/partial-ordering-decltype-canonical-alias.cpp
@@ -0,0 +1,150 @@
+// RUN: %clang_cc1 -std=c++20 -fblocks -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++23 -fblocks -fsyntax-only -verify %s
+
+namespace aliasing_decltype {
+template <class T> void foo(T a, decltype(a)); // #foo1
+template <class T> void foo(T a, decltype(a), int = 0); // #foo2
+template <class T> void bar(T a, decltype(a)); // #bar1
+template <class T> void bar(T a, decltype(a), int = 0); // #bar2
+void trigger() {
+  foo(0, 0);
+  // expected-error@-1 {{call to 'foo' is ambiguous}}
+  // expected-note@#foo1 {{candidate function}}
+  // expected-note@#foo2 {{candidate function}}
+  bar(0, 0);
+  // expected-error@-1 {{call to 'bar' is ambiguous}}
+  // expected-note@#bar1 {{candidate function}}
+  // expected-note@#bar2 {{candidate function}}
+}
+}
+
+namespace ambiguous_overload {
+
+template <class> struct S {
+  template <class T> S(T);
+};
+
+struct S1 {};
+struct S2 {
+  operator S1();
+};
+
+template <typename T> auto foo(T, S<decltype(0)>); // #ambiguous_overload1
+template <typename T> auto foo(T arg, decltype(arg)) { foo(arg, S2{}); }
+// expected-error@-1 {{function 'foo<ambiguous_overload::S1>' with deduced 
return type cannot be used before it is defined}}
+void bar(S1 d) { foo(d, S1{}); }
+// expected-note@-1 {{in instantiation of function template specialization 
'ambiguous_overload::foo<ambiguous_overload::S1>' requested here}}
+// expected-note@#ambiguous_overload1 {{'foo<ambiguous_overload::S1>' declared 
here}}
+}
+
+namespace explicit_specialization {
+template <typename T> void foo(T, int);
+template <typename T> void foo(T arg, decltype(arg));
+template <> void foo(int, int) {}
+}
+
+namespace recursive_lambda {
+template <typename Func>
+auto foo(Func func, decltype(func()) (*bar)()) -> decltype(func()) { return 
bar(); }
+template <typename Func>
+auto foo(Func func, decltype(func()) Value) -> decltype(func()) {
+  return foo(func, [=] { return Value; });
+}
+void *foo(void *(*func)()) { return foo(func, nullptr); }
+}
+
+namespace dependent_nested_param {
+struct X { using Nested = int; };
+template <class T> void foo(typename T::Nested a, decltype(a)); // 
#dependent_nested_param1
+template <class T> void foo(typename T::Nested a, decltype(a), int = 0); // 
#dependent_nested_param2
+void trigger() { foo<X>(0, 0); }
+// expected-error@-1 {{call to 'foo' is ambiguous}}
+// expected-note@#dependent_nested_param1 {{candidate function}}
+// expected-note@#dependent_nested_param2 {{candidate function}}
+}
+
+namespace nested_decltype_type {
+struct X { using Nested = int; };
+template <class T> void foo(T a, typename decltype(a)::Nested); // 
#nested_decltype_type1
+template <class T> void foo(T a, typename decltype(a)::Nested, int = 0); // 
#nested_decltype_type2
+void trigger() { foo<X>(X{}, 0); }
+// expected-error@-1 {{call to 'foo' is ambiguous}}
+// expected-note@#nested_decltype_type1 {{candidate function}}
+// expected-note@#nested_decltype_type2 {{candidate function}}
+}
+
+namespace auto_decltype {
+void foo(auto a, decltype(a)); // #auto_decltype1
+void foo(auto a, decltype(a), int = 0); // #auto_decltype2
+void trigger() { foo(0, 0); }
+// expected-error@-1 {{call to 'foo' is ambiguous}}
+// expected-note@#auto_decltype1 {{candidate function}}
+// expected-note@#auto_decltype2 {{candidate function}}
+}
+
+namespace wrapped_decltype {
+template <class> struct S {};
+template <class T> void foo(T a, S<decltype(a)>); // #wrapped_decltype1
+template <class T> void foo(T a, S<decltype(a)>, int = 0); // 
#wrapped_decltype2
+void trigger() { foo(0, S<int>{}); }
+// expected-error@-1 {{call to 'foo' is ambiguous}}
+// expected-note@#wrapped_decltype1 {{candidate function}}
+// expected-note@#wrapped_decltype2 {{candidate function}}
+}
+
+namespace variadic_decltype {
+template <class T, class... Ts> void foo(T a, decltype(a), Ts...);
+template <class T> void foo(T a, decltype(a));
+void bar() { foo(0, 0); }
+}
+
+namespace pack_decltype {
+void foo(auto a, decltype(a), auto...);
+void foo(auto a, decltype(a));
+void trigger() { foo(0, 0); }
+}
+
+namespace candidate_deleted {
+template <class T> void foo(T a, decltype(a)); // #non_deleted
+template <class T> void foo(T *a, decltype(a)) = delete; // #deleted_candidate
+void trigger() { int x; foo(&x, &x); }
+// expected-error@-1 {{call to deleted function 'foo'}}
+// expected-note@#non_deleted {{candidate function}}
+// expected-note@#deleted_candidate {{candidate function}}
+}
+
+namespace default_decltype {
+template <class T> void foo(T a, int, decltype(a) = 0); // #default_decltype1
+template <class T> void foo(T a, int, decltype(a) = 0, int = 0); // 
#default_decltype2
+void trigger() { foo(0, 0); }
+// expected-error@-1 {{call to 'foo' is ambiguous}}
+// expected-note@#default_decltype1 {{candidate function}}
+// expected-note@#default_decltype2 {{candidate function}}
+}
+
+namespace default_lambda {
+template <class T> void foo(T a, auto f = [](decltype(a)){}); // 
#default_lambda1
+template <class T> void foo(T a, auto f = [](decltype(a)){}, int = 0); // 
#default_lambda2
+void trigger() { foo(0, [](int){}); }
+// expected-error@-1 {{call to 'foo' is ambiguous}}
+// expected-note@#default_lambda1 {{candidate function}}
+// expected-note@#default_lambda2 {{candidate function}}
+}
+
+namespace lambda_return_decltype {
+template <class T> void foo(T a, auto f = []{ return decltype(a){}; }); // 
#lambda_return_decltype1
+template <class T> void foo(T a, auto f = []{ return decltype(a){}; }, int = 
0); // #lambda_return_decltype2
+void trigger() { foo(0, []{ return 0; }); }
+// expected-error@-1 {{call to 'foo' is ambiguous}}
+// expected-note@#lambda_return_decltype1 {{candidate function}}
+// expected-note@#lambda_return_decltype2 {{candidate function}}
+}
+
+namespace decltype_blocks {
+template <class T> void foo(T a, void (^)(decltype(a))); // #decltype_blocks1
+template <class T> void foo(T a, void (^)(decltype(a)), int = 0); // 
#decltype_blocks2
+void trigger() { foo(0, (void (^)(int))0); }
+// expected-error@-1 {{call to 'foo' is ambiguous}}
+// expected-note@#decltype_blocks1 {{candidate function}}
+// expected-note@#decltype_blocks2 {{candidate function}}
+}

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

Reply via email to