Skip to content

Commit 9cc438e

Browse files
committed
[clang] CTAD alias: refine the transformation for the require-clause
expr. 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 place contexts, 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.
1 parent 099c152 commit 9cc438e

File tree

3 files changed

+242
-13
lines changed

3 files changed

+242
-13
lines changed

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 134 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,31 +2758,151 @@ 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))
27692779
return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex,
2770-
TTP->getDepth());
2780+
NewDepth);
27712781
if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam))
27722782
return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex,
2773-
NTTP->getDepth());
2783+
NewDepth);
27742784
llvm_unreachable("Unhandled template parameter types");
27752785
}
27762786

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

29263046
auto NewTemplateArgument = Context.getCanonicalTemplateArgument(
@@ -2937,7 +3057,8 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
29373057
// TemplateArgsForBuildingFPrime.
29383058
Args.addOuterTemplateArguments(TemplateArgsForBuildingFPrime);
29393059
NamedDecl *NewParam = transformTemplateParameter(
2940-
SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size());
3060+
SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size(),
3061+
getTemplateParameterDepth(TP));
29413062
FPrimeTemplateParams.push_back(NewParam);
29423063

29433064
assert(TemplateArgsForBuildingFPrime[FTemplateParamIdx].isNull() &&
@@ -2992,8 +3113,8 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
29923113
Sema::CodeSynthesisContext::BuildingDeductionGuides)) {
29933114
auto *GG = cast<CXXDeductionGuideDecl>(FPrime);
29943115

2995-
Expr *RequiresClause =
2996-
transformRequireClause(SemaRef, F, TemplateArgsForBuildingFPrime);
3116+
Expr *RequiresClause = transformRequireClause(
3117+
SemaRef, F, AliasTemplate, DeduceResults);
29973118

29983119
// FIXME: implement the is_deducible constraint per C++
29993120
// [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)