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