Skip to content

Commit 94d5225

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 6218992 commit 94d5225

File tree

3 files changed

+246
-13
lines changed

3 files changed

+246
-13
lines changed

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 138 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2744,31 +2744,155 @@ bool hasDeclaredDeductionGuides(DeclarationName Name, DeclContext *DC) {
27442744
return false;
27452745
}
27462746

2747+
unsigned getTemplateDepth(NamedDecl *TemplateParam) {
2748+
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam))
2749+
return TTP->getDepth();
2750+
if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam))
2751+
return TTP->getDepth();
2752+
if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam))
2753+
return NTTP->getDepth();
2754+
llvm_unreachable("Unhandled template parameter types");
2755+
}
2756+
27472757
NamedDecl *transformTemplateParameter(Sema &SemaRef, DeclContext *DC,
27482758
NamedDecl *TemplateParam,
27492759
MultiLevelTemplateArgumentList &Args,
2750-
unsigned NewIndex) {
2760+
unsigned NewIndex, unsigned NewDepth) {
27512761
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam))
2752-
return transformTemplateTypeParam(SemaRef, DC, TTP, Args, TTP->getDepth(),
2762+
return transformTemplateTypeParam(SemaRef, DC, TTP, Args, NewDepth,
27532763
NewIndex);
27542764
if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam))
27552765
return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex,
2756-
TTP->getDepth());
2766+
NewDepth);
27572767
if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam))
27582768
return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex,
2759-
NTTP->getDepth());
2769+
NewDepth);
27602770
llvm_unreachable("Unhandled template parameter types");
27612771
}
27622772

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

29123036
auto NewTemplateArgument = Context.getCanonicalTemplateArgument(
@@ -2923,7 +3047,8 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
29233047
// TemplateArgsForBuildingFPrime.
29243048
Args.addOuterTemplateArguments(TemplateArgsForBuildingFPrime);
29253049
NamedDecl *NewParam = transformTemplateParameter(
2926-
SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size());
3050+
SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size(),
3051+
getTemplateDepth(TP));
29273052
FPrimeTemplateParams.push_back(NewParam);
29283053

29293054
assert(TemplateArgsForBuildingFPrime[FTemplateParamIdx].isNull() &&
@@ -2978,8 +3103,8 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
29783103
Sema::CodeSynthesisContext::BuildingDeductionGuides)) {
29793104
auto *GG = cast<CXXDeductionGuideDecl>(FPrime);
29803105

2981-
Expr *RequiresClause =
2982-
transformRequireClause(SemaRef, F, TemplateArgsForBuildingFPrime);
3106+
Expr *RequiresClause = transformRequireClause(
3107+
SemaRef, F, AliasTemplate, DeduceResults);
29833108

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