-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[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
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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( | ||
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()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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>(); | ||
|
@@ -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( | ||
|
@@ -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() && | ||
|
@@ -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: | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.