[llvm-branch-commits] [clang] [clang] CTAD alias: fix the transformation for the require-clause expr (PR #90961)
Haojian Wu via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Tue May 7 06:15:26 PDT 2024
https://github.com/hokein updated https://github.com/llvm/llvm-project/pull/90961
>From 5b70158f8eff30125c0bf0d675bab62497ac620f Mon Sep 17 00:00:00 2001
From: Haojian Wu <hokein.wu at gmail.com>
Date: Fri, 3 May 2024 13:54:06 +0200
Subject: [PATCH 1/2] [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.
---
clang/lib/Sema/SemaTemplate.cpp | 147 +++++++++++++++++--
clang/test/AST/ast-dump-ctad-alias.cpp | 40 +++++
clang/test/SemaCXX/cxx20-ctad-type-alias.cpp | 68 +++++++++
3 files changed, 242 insertions(+), 13 deletions(-)
create mode 100644 clang/test/AST/ast-dump-ctad-alias.cpp
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 7f49acc36769e..1de0cfac8f1f7 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -2744,31 +2744,151 @@ bool hasDeclaredDeductionGuides(DeclarationName Name, DeclContext *DC) {
return false;
}
+unsigned getTemplateDepth(NamedDecl *TemplateParam) {
+ if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam))
+ return TTP->getDepth();
+ if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam))
+ return TTP->getDepth();
+ if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam))
+ return NTTP->getDepth();
+ llvm_unreachable("Unhandled template parameter types");
+}
+
NamedDecl *transformTemplateParameter(Sema &SemaRef, DeclContext *DC,
NamedDecl *TemplateParam,
MultiLevelTemplateArgumentList &Args,
- unsigned NewIndex) {
+ unsigned NewIndex, unsigned NewDepth) {
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam))
- return transformTemplateTypeParam(SemaRef, DC, TTP, Args, TTP->getDepth(),
+ return transformTemplateTypeParam(SemaRef, DC, TTP, Args, NewDepth,
NewIndex);
if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam))
return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex,
- TTP->getDepth());
+ NewDepth);
if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam))
return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex,
- NTTP->getDepth());
+ NewDepth);
llvm_unreachable("Unhandled template parameter types");
}
-Expr *transformRequireClause(Sema &SemaRef, FunctionTemplateDecl *FTD,
- llvm::ArrayRef<TemplateArgument> TransformedArgs) {
- Expr *RC = FTD->getTemplateParameters()->getRequiresClause();
+// Transform the require-clause of F if any.
+// The return result is expected to be the require-clause for the synthesized
+// alias deduction guide.
+Expr *transformRequireClause(
+ Sema &SemaRef, FunctionTemplateDecl *F,
+ TypeAliasTemplateDecl *AliasTemplate,
+ ArrayRef<DeducedTemplateArgument> DeduceResults) {
+ Expr *RC = F->getTemplateParameters()->getRequiresClause();
if (!RC)
return nullptr;
+
+ auto &Context = SemaRef.Context;
+ LocalInstantiationScope Scope(SemaRef);
+
+ // 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:
+ //
+ // 1) In the transformed require-clause, occurrences of template parameters
+ // must use the "uninstantiated" depth;
+ // 2) When substituting on the require-clause expr of the underlying
+ // deduction guide, we must use the entire set of template argument lists;
+ //
+ // It's important to note that we're performing this transformation on an
+ // *instantiated* AliasTemplate.
+
+ // For 1), if the alias template is nested within a class template, we
+ // calcualte the 'uninstantiated' depth by adding the substitution level back.
+ unsigned AdjustDepth = 0;
+ if (auto *PrimaryTemplate = AliasTemplate->getInstantiatedFromMemberTemplate())
+ AdjustDepth = PrimaryTemplate->getTemplateDepth();
+
+ // We rebuild all template parameters with the uninstantiated depth, and
+ // build template arguments refer to them.
+ SmallVector<TemplateArgument> AdjustedAliasTemplateArgs;
+
+ for (auto *TP : *AliasTemplate->getTemplateParameters()) {
+ // Rebuild any internal references to earlier parameters and reindex
+ // as we go.
+ MultiLevelTemplateArgumentList Args;
+ Args.setKind(TemplateSubstitutionKind::Rewrite);
+ Args.addOuterTemplateArguments(AdjustedAliasTemplateArgs);
+ NamedDecl *NewParam = transformTemplateParameter(
+ SemaRef, AliasTemplate->getDeclContext(), TP, Args,
+ /*NewIndex=*/AdjustedAliasTemplateArgs.size(),
+ getTemplateDepth(TP) + AdjustDepth);
+
+ auto NewTemplateArgument = Context.getCanonicalTemplateArgument(
+ Context.getInjectedTemplateArg(NewParam));
+ AdjustedAliasTemplateArgs.push_back(NewTemplateArgument);
+ }
+ // Template arguments used to transform the template arguments in
+ // DeducedResults.
+ SmallVector<TemplateArgument> TemplateArgsForBuildingRC(
+ F->getTemplateParameters()->size());
+ // Transform the transformed template args
MultiLevelTemplateArgumentList Args;
Args.setKind(TemplateSubstitutionKind::Rewrite);
- Args.addOuterTemplateArguments(TransformedArgs);
- ExprResult E = SemaRef.SubstExpr(RC, Args);
+ Args.addOuterTemplateArguments(AdjustedAliasTemplateArgs);
+
+ for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) {
+ const auto &D = DeduceResults[Index];
+ if (D.isNull())
+ continue;
+ TemplateArgumentLoc Input =
+ SemaRef.getTrivialTemplateArgumentLoc(D, QualType(), SourceLocation{});
+ TemplateArgumentLoc Output;
+ if (!SemaRef.SubstTemplateArgument(Input, Args, Output)) {
+ assert(TemplateArgsForBuildingRC[Index].isNull() &&
+ "InstantiatedArgs must be null before setting");
+ TemplateArgsForBuildingRC[Index] = Output.getArgument();
+ }
+ }
+
+ // A list of template arguments for transforming the require-clause of F.
+ // It must contain the entire set of template argument lists.
+ MultiLevelTemplateArgumentList ArgsForBuildingRC;
+ ArgsForBuildingRC.setKind(clang::TemplateSubstitutionKind::Rewrite);
+ ArgsForBuildingRC.addOuterTemplateArguments(TemplateArgsForBuildingRC);
+ // For 2), if the underlying F is instantiated from a member template, we need
+ // the entire template argument list, as the constraint AST in the
+ // require-clause of F remains completely uninstantiated.
+ //
+ // For example:
+ // template <typename T> // depth 0
+ // struct Outer {
+ // template <typename U>
+ // struct Foo { Foo(U); };
+ //
+ // template <typename U> // depth 1
+ // requires C<U>
+ // Foo(U) -> Foo<int>;
+ // };
+ // template <typename U>
+ // using AFoo = Outer<int>::Foo<U>;
+ //
+ // In this scenario, the deduction guide for `Foo` inside `Outer<int>`:
+ // - The occurrence of U in the require-expression is [depth:1, index:0]
+ // - The occurrence of U in the function parameter is [depth:0, index:0]
+ // - The template parameter of U is [depth:0, index:0]
+ //
+ // We add the outer template arguments which is [int] to the multi-level arg
+ // list to ensure that the occurrence U in `C<U>` will be replaced with int
+ // during the substitution.
+ if (F->getInstantiatedFromMemberTemplate()) {
+ auto OuterLevelArgs = SemaRef.getTemplateInstantiationArgs(
+ F, F->getLexicalDeclContext(),
+ /*Final=*/false, /*Innermost=*/std::nullopt,
+ /*RelativeToPrimary=*/true,
+ /*Pattern=*/nullptr,
+ /*ForConstraintInstantiation=*/true);
+ for (auto It : OuterLevelArgs)
+ ArgsForBuildingRC.addOuterTemplateArguments(It.Args);
+ }
+
+ ExprResult E = SemaRef.SubstExpr(RC, ArgsForBuildingRC);
if (E.isInvalid())
return nullptr;
return E.getAs<Expr>();
@@ -2906,7 +3026,7 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
Args.addOuterTemplateArguments(TransformedDeducedAliasArgs);
NamedDecl *NewParam = transformTemplateParameter(
SemaRef, AliasTemplate->getDeclContext(), TP, Args,
- /*NewIndex=*/FPrimeTemplateParams.size());
+ /*NewIndex=*/FPrimeTemplateParams.size(), getTemplateDepth(TP));
FPrimeTemplateParams.push_back(NewParam);
auto NewTemplateArgument = Context.getCanonicalTemplateArgument(
@@ -2923,7 +3043,8 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
// TemplateArgsForBuildingFPrime.
Args.addOuterTemplateArguments(TemplateArgsForBuildingFPrime);
NamedDecl *NewParam = transformTemplateParameter(
- SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size());
+ SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size(),
+ getTemplateDepth(TP));
FPrimeTemplateParams.push_back(NewParam);
assert(TemplateArgsForBuildingFPrime[FTemplateParamIdx].isNull() &&
@@ -2978,8 +3099,8 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
Sema::CodeSynthesisContext::BuildingDeductionGuides)) {
auto *GG = cast<CXXDeductionGuideDecl>(FPrime);
- Expr *RequiresClause =
- transformRequireClause(SemaRef, F, TemplateArgsForBuildingFPrime);
+ Expr *RequiresClause = transformRequireClause(
+ SemaRef, F, AliasTemplate, DeduceResults);
// FIXME: implement the is_deducible constraint per C++
// [over.match.class.deduct]p3.3:
diff --git a/clang/test/AST/ast-dump-ctad-alias.cpp b/clang/test/AST/ast-dump-ctad-alias.cpp
new file mode 100644
index 0000000000000..423c3454ccb73
--- /dev/null
+++ b/clang/test/AST/ast-dump-ctad-alias.cpp
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++2a -ast-dump %s | FileCheck -strict-whitespace %s
+
+template <typename, typename>
+constexpr bool Concept = true;
+template<typename T> // depth 0
+struct Out {
+ template<typename U> // depth 1
+ struct Inner {
+ U t;
+ };
+
+ template<typename V> // depth1
+ requires Concept<T, V>
+ Inner(V) -> Inner<V>;
+};
+
+template <typename X>
+struct Out2 {
+ template<typename Y> // depth1
+ using AInner = Out<int>::Inner<Y>;
+};
+Out2<double>::AInner t(1.0);
+
+// Verify that the require-clause of alias deduction guide is transformed correctly:
+// - Occurrence T should be replaced with `int`;
+// - Occurrence V should be replaced with the Y with depth 1
+//
+// CHECK: | `-FunctionTemplateDecl {{.*}} <deduction guide for AInner>
+// CHECK-NEXT: | |-TemplateTypeParmDecl {{.*}} typename depth 0 index 0 Y
+// CHECK-NEXT: | |-UnresolvedLookupExpr {{.*}} '<dependent type>' lvalue (no ADL) = 'Concept'
+// CHECK-NEXT: | | |-TemplateArgument type 'int'
+// CHECK-NEXT: | | | `-BuiltinType {{.*}} 'int'
+// CHECK-NEXT: | | `-TemplateArgument type 'type-parameter-1-0'
+// CHECK-NEXT: | | `-TemplateTypeParmType {{.*}} 'type-parameter-1-0' dependent depth 1 index 0
+// CHECK-NEXT: | |-CXXDeductionGuideDecl {{.*}} <deduction guide for AInner> 'auto (type-parameter-0-0) -> Inner<type-parameter-0-0>'
+// CHECK-NEXT: | | `-ParmVarDecl {{.*}} 'type-parameter-0-0'
+// CHECK-NEXT: | `-CXXDeductionGuideDecl {{.*}} used <deduction guide for AInner> 'auto (double) -> Inner<double>' implicit_instantiation
+// CHECK-NEXT: | |-TemplateArgument type 'double'
+// CHECK-NEXT: | | `-BuiltinType {{.*}} 'double'
+// CHECK-NEXT: | `-ParmVarDecl {{.*}} 'double'
diff --git a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp
index e8b4383f53c5e..4c5595e409f28 100644
--- a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp
+++ b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp
@@ -321,3 +321,71 @@ AG ag(1.0);
// choosen.
static_assert(__is_same(decltype(ag.t1), int));
} // namespace test23
+
+// GH90177
+// verify that the transformed require-clause of the alias deduction gudie has
+// the right depth info.
+namespace test24 {
+class Forward;
+class Key {};
+
+template <typename D>
+constexpr bool C = sizeof(D);
+
+// Case1: the alias template and the underlying deduction guide are in the same
+// scope.
+template <typename T>
+struct Case1 {
+ template <typename U>
+ struct Foo {
+ Foo(U);
+ };
+
+ template <typename V>
+ requires (C<V>)
+ Foo(V) -> Foo<V>;
+
+ template <typename Y>
+ using Alias = Foo<Y>;
+};
+// The require-clause should be evaluated on the type Key.
+Case1<Forward>::Alias t2 = Key();
+
+
+// Case2: the alias template and underlying deduction guide are in different
+// scope.
+template <typename T>
+struct Foo {
+ Foo(T);
+};
+template <typename U>
+requires (C<U>)
+Foo(U) -> Foo<U>;
+
+template <typename T>
+struct Case2 {
+ template <typename Y>
+ using Alias = Foo<Y>;
+};
+// The require-caluse should be evaluated on the type Key.
+Case2<Forward>::Alias t1 = Key();
+
+// Case3: crashes on the constexpr evaluator due to the mixed-up depth in
+// require-expr.
+template <class T1>
+struct A1 {
+ template<class T2>
+ struct A2 {
+ template <class T3>
+ struct Foo {
+ Foo(T3);
+ };
+ template <class T3>
+ requires C<T3>
+ Foo(T3) -> Foo<T3>;
+ };
+};
+template <typename U>
+using AFoo = A1<int>::A2<int>::Foo<U>;
+AFoo case3(1);
+} // namespace test24
>From cec1ca85fc4251aa1b8a7b71654ce17a5c1a56ad Mon Sep 17 00:00:00 2001
From: Haojian Wu <hokein.wu at gmail.com>
Date: Tue, 7 May 2024 15:04:43 +0200
Subject: [PATCH 2/2] rename getTemplateDepth => getTemplateParameterDepth
---
clang/lib/Sema/SemaTemplate.cpp | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 1de0cfac8f1f7..d010c945e5362 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -2744,7 +2744,7 @@ bool hasDeclaredDeductionGuides(DeclarationName Name, DeclContext *DC) {
return false;
}
-unsigned getTemplateDepth(NamedDecl *TemplateParam) {
+unsigned getTemplateParameterDepth(NamedDecl *TemplateParam) {
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam))
return TTP->getDepth();
if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam))
@@ -2804,7 +2804,7 @@ Expr *transformRequireClause(
unsigned AdjustDepth = 0;
if (auto *PrimaryTemplate = AliasTemplate->getInstantiatedFromMemberTemplate())
AdjustDepth = PrimaryTemplate->getTemplateDepth();
-
+
// We rebuild all template parameters with the uninstantiated depth, and
// build template arguments refer to them.
SmallVector<TemplateArgument> AdjustedAliasTemplateArgs;
@@ -2818,7 +2818,7 @@ Expr *transformRequireClause(
NamedDecl *NewParam = transformTemplateParameter(
SemaRef, AliasTemplate->getDeclContext(), TP, Args,
/*NewIndex=*/AdjustedAliasTemplateArgs.size(),
- getTemplateDepth(TP) + AdjustDepth);
+ getTemplateParameterDepth(TP) + AdjustDepth);
auto NewTemplateArgument = Context.getCanonicalTemplateArgument(
Context.getInjectedTemplateArg(NewParam));
@@ -3026,7 +3026,7 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
Args.addOuterTemplateArguments(TransformedDeducedAliasArgs);
NamedDecl *NewParam = transformTemplateParameter(
SemaRef, AliasTemplate->getDeclContext(), TP, Args,
- /*NewIndex=*/FPrimeTemplateParams.size(), getTemplateDepth(TP));
+ /*NewIndex=*/FPrimeTemplateParams.size(), getTemplateParameterDepth(TP));
FPrimeTemplateParams.push_back(NewParam);
auto NewTemplateArgument = Context.getCanonicalTemplateArgument(
@@ -3044,7 +3044,7 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
Args.addOuterTemplateArguments(TemplateArgsForBuildingFPrime);
NamedDecl *NewParam = transformTemplateParameter(
SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size(),
- getTemplateDepth(TP));
+ getTemplateParameterDepth(TP));
FPrimeTemplateParams.push_back(NewParam);
assert(TemplateArgsForBuildingFPrime[FTemplateParamIdx].isNull() &&
More information about the llvm-branch-commits
mailing list