Skip to content

Commit d182877

Browse files
authored
[clang] CTAD alias: fix the transformation for the require-clause expr (#90961)
In the clang AST, constraint nodes are deliberately not instantiated unless they are actively being evaluated. Consequently, occurrences of template parameters in the require-clause expression have a subtle "depth" difference compared to normal occurrences in places, such as function parameters. When transforming the require-clause, we must take this distinction into account. The existing implementation overlooks this consideration. This patch is to rewrite the implementation of the require-clause transformation to address this issue. Fixes #90177
1 parent 0bacffb commit d182877

File tree

3 files changed

+242
-14
lines changed

3 files changed

+242
-14
lines changed

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 134 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,31 +2758,149 @@ bool hasDeclaredDeductionGuides(DeclarationName Name, DeclContext *DC) {
27582758
return false;
27592759
}
27602760

2761+
unsigned getTemplateParameterDepth(NamedDecl *TemplateParam) {
2762+
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam))
2763+
return TTP->getDepth();
2764+
if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam))
2765+
return TTP->getDepth();
2766+
if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam))
2767+
return NTTP->getDepth();
2768+
llvm_unreachable("Unhandled template parameter types");
2769+
}
2770+
27612771
NamedDecl *transformTemplateParameter(Sema &SemaRef, DeclContext *DC,
27622772
NamedDecl *TemplateParam,
27632773
MultiLevelTemplateArgumentList &Args,
2764-
unsigned NewIndex) {
2774+
unsigned NewIndex, unsigned NewDepth) {
27652775
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam))
2766-
return transformTemplateTypeParam(SemaRef, DC, TTP, Args, TTP->getDepth(),
2776+
return transformTemplateTypeParam(SemaRef, DC, TTP, Args, NewDepth,
27672777
NewIndex);
27682778
if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam))
2769-
return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex,
2770-
TTP->getDepth());
2779+
return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex, NewDepth);
27712780
if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam))
2772-
return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex,
2773-
NTTP->getDepth());
2781+
return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex, NewDepth);
27742782
llvm_unreachable("Unhandled template parameter types");
27752783
}
27762784

2777-
Expr *transformRequireClause(Sema &SemaRef, FunctionTemplateDecl *FTD,
2778-
llvm::ArrayRef<TemplateArgument> TransformedArgs) {
2779-
Expr *RC = FTD->getTemplateParameters()->getRequiresClause();
2785+
// Transform the require-clause of F if any.
2786+
// The return result is expected to be the require-clause for the synthesized
2787+
// alias deduction guide.
2788+
Expr *transformRequireClause(Sema &SemaRef, FunctionTemplateDecl *F,
2789+
TypeAliasTemplateDecl *AliasTemplate,
2790+
ArrayRef<DeducedTemplateArgument> DeduceResults) {
2791+
Expr *RC = F->getTemplateParameters()->getRequiresClause();
27802792
if (!RC)
27812793
return nullptr;
2794+
2795+
auto &Context = SemaRef.Context;
2796+
LocalInstantiationScope Scope(SemaRef);
2797+
2798+
// In the clang AST, constraint nodes are deliberately not instantiated unless
2799+
// they are actively being evaluated. Consequently, occurrences of template
2800+
// parameters in the require-clause expression have a subtle "depth"
2801+
// difference compared to normal occurrences in places, such as function
2802+
// parameters. When transforming the require-clause, we must take this
2803+
// distinction into account:
2804+
//
2805+
// 1) In the transformed require-clause, occurrences of template parameters
2806+
// must use the "uninstantiated" depth;
2807+
// 2) When substituting on the require-clause expr of the underlying
2808+
// deduction guide, we must use the entire set of template argument lists;
2809+
//
2810+
// It's important to note that we're performing this transformation on an
2811+
// *instantiated* AliasTemplate.
2812+
2813+
// For 1), if the alias template is nested within a class template, we
2814+
// calcualte the 'uninstantiated' depth by adding the substitution level back.
2815+
unsigned AdjustDepth = 0;
2816+
if (auto *PrimaryTemplate =
2817+
AliasTemplate->getInstantiatedFromMemberTemplate())
2818+
AdjustDepth = PrimaryTemplate->getTemplateDepth();
2819+
2820+
// We rebuild all template parameters with the uninstantiated depth, and
2821+
// build template arguments refer to them.
2822+
SmallVector<TemplateArgument> AdjustedAliasTemplateArgs;
2823+
2824+
for (auto *TP : *AliasTemplate->getTemplateParameters()) {
2825+
// Rebuild any internal references to earlier parameters and reindex
2826+
// as we go.
2827+
MultiLevelTemplateArgumentList Args;
2828+
Args.setKind(TemplateSubstitutionKind::Rewrite);
2829+
Args.addOuterTemplateArguments(AdjustedAliasTemplateArgs);
2830+
NamedDecl *NewParam = transformTemplateParameter(
2831+
SemaRef, AliasTemplate->getDeclContext(), TP, Args,
2832+
/*NewIndex=*/AdjustedAliasTemplateArgs.size(),
2833+
getTemplateParameterDepth(TP) + AdjustDepth);
2834+
2835+
auto NewTemplateArgument = Context.getCanonicalTemplateArgument(
2836+
Context.getInjectedTemplateArg(NewParam));
2837+
AdjustedAliasTemplateArgs.push_back(NewTemplateArgument);
2838+
}
2839+
// Template arguments used to transform the template arguments in
2840+
// DeducedResults.
2841+
SmallVector<TemplateArgument> TemplateArgsForBuildingRC(
2842+
F->getTemplateParameters()->size());
2843+
// Transform the transformed template args
27822844
MultiLevelTemplateArgumentList Args;
27832845
Args.setKind(TemplateSubstitutionKind::Rewrite);
2784-
Args.addOuterTemplateArguments(TransformedArgs);
2785-
ExprResult E = SemaRef.SubstExpr(RC, Args);
2846+
Args.addOuterTemplateArguments(AdjustedAliasTemplateArgs);
2847+
2848+
for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) {
2849+
const auto &D = DeduceResults[Index];
2850+
if (D.isNull())
2851+
continue;
2852+
TemplateArgumentLoc Input =
2853+
SemaRef.getTrivialTemplateArgumentLoc(D, QualType(), SourceLocation{});
2854+
TemplateArgumentLoc Output;
2855+
if (!SemaRef.SubstTemplateArgument(Input, Args, Output)) {
2856+
assert(TemplateArgsForBuildingRC[Index].isNull() &&
2857+
"InstantiatedArgs must be null before setting");
2858+
TemplateArgsForBuildingRC[Index] = Output.getArgument();
2859+
}
2860+
}
2861+
2862+
// A list of template arguments for transforming the require-clause of F.
2863+
// It must contain the entire set of template argument lists.
2864+
MultiLevelTemplateArgumentList ArgsForBuildingRC;
2865+
ArgsForBuildingRC.setKind(clang::TemplateSubstitutionKind::Rewrite);
2866+
ArgsForBuildingRC.addOuterTemplateArguments(TemplateArgsForBuildingRC);
2867+
// For 2), if the underlying F is instantiated from a member template, we need
2868+
// the entire template argument list, as the constraint AST in the
2869+
// require-clause of F remains completely uninstantiated.
2870+
//
2871+
// For example:
2872+
// template <typename T> // depth 0
2873+
// struct Outer {
2874+
// template <typename U>
2875+
// struct Foo { Foo(U); };
2876+
//
2877+
// template <typename U> // depth 1
2878+
// requires C<U>
2879+
// Foo(U) -> Foo<int>;
2880+
// };
2881+
// template <typename U>
2882+
// using AFoo = Outer<int>::Foo<U>;
2883+
//
2884+
// In this scenario, the deduction guide for `Foo` inside `Outer<int>`:
2885+
// - The occurrence of U in the require-expression is [depth:1, index:0]
2886+
// - The occurrence of U in the function parameter is [depth:0, index:0]
2887+
// - The template parameter of U is [depth:0, index:0]
2888+
//
2889+
// We add the outer template arguments which is [int] to the multi-level arg
2890+
// list to ensure that the occurrence U in `C<U>` will be replaced with int
2891+
// during the substitution.
2892+
if (F->getInstantiatedFromMemberTemplate()) {
2893+
auto OuterLevelArgs = SemaRef.getTemplateInstantiationArgs(
2894+
F, F->getLexicalDeclContext(),
2895+
/*Final=*/false, /*Innermost=*/std::nullopt,
2896+
/*RelativeToPrimary=*/true,
2897+
/*Pattern=*/nullptr,
2898+
/*ForConstraintInstantiation=*/true);
2899+
for (auto It : OuterLevelArgs)
2900+
ArgsForBuildingRC.addOuterTemplateArguments(It.Args);
2901+
}
2902+
2903+
ExprResult E = SemaRef.SubstExpr(RC, ArgsForBuildingRC);
27862904
if (E.isInvalid())
27872905
return nullptr;
27882906
return E.getAs<Expr>();
@@ -2920,7 +3038,8 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
29203038
Args.addOuterTemplateArguments(TransformedDeducedAliasArgs);
29213039
NamedDecl *NewParam = transformTemplateParameter(
29223040
SemaRef, AliasTemplate->getDeclContext(), TP, Args,
2923-
/*NewIndex=*/FPrimeTemplateParams.size());
3041+
/*NewIndex=*/FPrimeTemplateParams.size(),
3042+
getTemplateParameterDepth(TP));
29243043
FPrimeTemplateParams.push_back(NewParam);
29253044

29263045
auto NewTemplateArgument = Context.getCanonicalTemplateArgument(
@@ -2937,7 +3056,8 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
29373056
// TemplateArgsForBuildingFPrime.
29383057
Args.addOuterTemplateArguments(TemplateArgsForBuildingFPrime);
29393058
NamedDecl *NewParam = transformTemplateParameter(
2940-
SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size());
3059+
SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size(),
3060+
getTemplateParameterDepth(TP));
29413061
FPrimeTemplateParams.push_back(NewParam);
29423062

29433063
assert(TemplateArgsForBuildingFPrime[FTemplateParamIdx].isNull() &&
@@ -2993,7 +3113,7 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
29933113
auto *GG = cast<CXXDeductionGuideDecl>(FPrime);
29943114

29953115
Expr *RequiresClause =
2996-
transformRequireClause(SemaRef, F, TemplateArgsForBuildingFPrime);
3116+
transformRequireClause(SemaRef, F, AliasTemplate, DeduceResults);
29973117

29983118
// FIXME: implement the is_deducible constraint per C++
29993119
// [over.match.class.deduct]p3.3:
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++2a -ast-dump %s | FileCheck -strict-whitespace %s
2+
3+
template <typename, typename>
4+
constexpr bool Concept = true;
5+
template<typename T> // depth 0
6+
struct Out {
7+
template<typename U> // depth 1
8+
struct Inner {
9+
U t;
10+
};
11+
12+
template<typename V> // depth1
13+
requires Concept<T, V>
14+
Inner(V) -> Inner<V>;
15+
};
16+
17+
template <typename X>
18+
struct Out2 {
19+
template<typename Y> // depth1
20+
using AInner = Out<int>::Inner<Y>;
21+
};
22+
Out2<double>::AInner t(1.0);
23+
24+
// Verify that the require-clause of alias deduction guide is transformed correctly:
25+
// - Occurrence T should be replaced with `int`;
26+
// - Occurrence V should be replaced with the Y with depth 1
27+
//
28+
// CHECK: | `-FunctionTemplateDecl {{.*}} <deduction guide for AInner>
29+
// CHECK-NEXT: | |-TemplateTypeParmDecl {{.*}} typename depth 0 index 0 Y
30+
// CHECK-NEXT: | |-UnresolvedLookupExpr {{.*}} '<dependent type>' lvalue (no ADL) = 'Concept'
31+
// CHECK-NEXT: | | |-TemplateArgument type 'int'
32+
// CHECK-NEXT: | | | `-BuiltinType {{.*}} 'int'
33+
// CHECK-NEXT: | | `-TemplateArgument type 'type-parameter-1-0'
34+
// CHECK-NEXT: | | `-TemplateTypeParmType {{.*}} 'type-parameter-1-0' dependent depth 1 index 0
35+
// CHECK-NEXT: | |-CXXDeductionGuideDecl {{.*}} <deduction guide for AInner> 'auto (type-parameter-0-0) -> Inner<type-parameter-0-0>'
36+
// CHECK-NEXT: | | `-ParmVarDecl {{.*}} 'type-parameter-0-0'
37+
// CHECK-NEXT: | `-CXXDeductionGuideDecl {{.*}} used <deduction guide for AInner> 'auto (double) -> Inner<double>' implicit_instantiation
38+
// CHECK-NEXT: | |-TemplateArgument type 'double'
39+
// CHECK-NEXT: | | `-BuiltinType {{.*}} 'double'
40+
// CHECK-NEXT: | `-ParmVarDecl {{.*}} 'double'

clang/test/SemaCXX/cxx20-ctad-type-alias.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,3 +321,71 @@ AG ag(1.0);
321321
// choosen.
322322
static_assert(__is_same(decltype(ag.t1), int));
323323
} // namespace test23
324+
325+
// GH90177
326+
// verify that the transformed require-clause of the alias deduction gudie has
327+
// the right depth info.
328+
namespace test24 {
329+
class Forward;
330+
class Key {};
331+
332+
template <typename D>
333+
constexpr bool C = sizeof(D);
334+
335+
// Case1: the alias template and the underlying deduction guide are in the same
336+
// scope.
337+
template <typename T>
338+
struct Case1 {
339+
template <typename U>
340+
struct Foo {
341+
Foo(U);
342+
};
343+
344+
template <typename V>
345+
requires (C<V>)
346+
Foo(V) -> Foo<V>;
347+
348+
template <typename Y>
349+
using Alias = Foo<Y>;
350+
};
351+
// The require-clause should be evaluated on the type Key.
352+
Case1<Forward>::Alias t2 = Key();
353+
354+
355+
// Case2: the alias template and underlying deduction guide are in different
356+
// scope.
357+
template <typename T>
358+
struct Foo {
359+
Foo(T);
360+
};
361+
template <typename U>
362+
requires (C<U>)
363+
Foo(U) -> Foo<U>;
364+
365+
template <typename T>
366+
struct Case2 {
367+
template <typename Y>
368+
using Alias = Foo<Y>;
369+
};
370+
// The require-caluse should be evaluated on the type Key.
371+
Case2<Forward>::Alias t1 = Key();
372+
373+
// Case3: crashes on the constexpr evaluator due to the mixed-up depth in
374+
// require-expr.
375+
template <class T1>
376+
struct A1 {
377+
template<class T2>
378+
struct A2 {
379+
template <class T3>
380+
struct Foo {
381+
Foo(T3);
382+
};
383+
template <class T3>
384+
requires C<T3>
385+
Foo(T3) -> Foo<T3>;
386+
};
387+
};
388+
template <typename U>
389+
using AFoo = A1<int>::A2<int>::Foo<U>;
390+
AFoo case3(1);
391+
} // namespace test24

0 commit comments

Comments
 (0)