Skip to content

[clang] CTAD alias: fix the transformation for the require-clause expr #90961

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 134 additions & 14 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2758,31 +2758,149 @@ bool hasDeclaredDeductionGuides(DeclarationName Name, DeclContext *DC) {
return false;
}

unsigned getTemplateParameterDepth(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());
return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex, NewDepth);
if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam))
return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex,
NTTP->getDepth());
return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex, 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(),
getTemplateParameterDepth(TP) + AdjustDepth);

auto NewTemplateArgument = Context.getCanonicalTemplateArgument(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to canonicalize the argument here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not needed, and I think avoiding the use of canonical types can lead to a more optimal AST, see #79798. We've already used this pattern in the ConvertConstructorToDeductionGuideTransform, and the current implementation here simply follows it.

However, addressing this issue is not the primary focus of this PR. It might be more appropriate to address it in a separate patch.

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()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto. Do we want to use the “primary” template after possibly multiple transformations?

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>();
Expand Down Expand Up @@ -2920,7 +3038,8 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
Args.addOuterTemplateArguments(TransformedDeducedAliasArgs);
NamedDecl *NewParam = transformTemplateParameter(
SemaRef, AliasTemplate->getDeclContext(), TP, Args,
/*NewIndex=*/FPrimeTemplateParams.size());
/*NewIndex=*/FPrimeTemplateParams.size(),
getTemplateParameterDepth(TP));
FPrimeTemplateParams.push_back(NewParam);

auto NewTemplateArgument = Context.getCanonicalTemplateArgument(
Expand All @@ -2937,7 +3056,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(),
getTemplateParameterDepth(TP));
FPrimeTemplateParams.push_back(NewParam);

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

Expr *RequiresClause =
transformRequireClause(SemaRef, F, TemplateArgsForBuildingFPrime);
transformRequireClause(SemaRef, F, AliasTemplate, DeduceResults);

// FIXME: implement the is_deducible constraint per C++
// [over.match.class.deduct]p3.3:
Expand Down
40 changes: 40 additions & 0 deletions clang/test/AST/ast-dump-ctad-alias.cpp
Original file line number Diff line number Diff line change
@@ -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'
68 changes: 68 additions & 0 deletions clang/test/SemaCXX/cxx20-ctad-type-alias.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading