-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[clang] CTAD: build aggregate deduction guides for alias templates. #85904
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2754,23 +2754,42 @@ bool hasDeclaredDeductionGuides(DeclarationName Name, DeclContext *DC) { | |
return false; | ||
} | ||
|
||
// Build deduction guides for a type alias template. | ||
void DeclareImplicitDeductionGuidesForTypeAlias( | ||
Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate, SourceLocation Loc) { | ||
if (AliasTemplate->isInvalidDecl()) | ||
return; | ||
auto &Context = SemaRef.Context; | ||
// FIXME: if there is an explicit deduction guide after the first use of the | ||
// type alias usage, we will not cover this explicit deduction guide. fix this | ||
// case. | ||
if (hasDeclaredDeductionGuides( | ||
Context.DeclarationNames.getCXXDeductionGuideName(AliasTemplate), | ||
AliasTemplate->getDeclContext())) | ||
return; | ||
NamedDecl *transformTemplateParameter(Sema &SemaRef, DeclContext *DC, | ||
NamedDecl *TemplateParam, | ||
MultiLevelTemplateArgumentList &Args, | ||
unsigned NewIndex) { | ||
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam)) | ||
return transformTemplateTypeParam(SemaRef, DC, TTP, Args, TTP->getDepth(), | ||
NewIndex); | ||
if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam)) | ||
return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex, | ||
TTP->getDepth()); | ||
if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam)) | ||
return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex, | ||
NTTP->getDepth()); | ||
llvm_unreachable("Unhandled template parameter types"); | ||
} | ||
|
||
Expr *transformRequireClause(Sema &SemaRef, FunctionTemplateDecl *FTD, | ||
llvm::ArrayRef<TemplateArgument> TransformedArgs) { | ||
Expr *RC = FTD->getTemplateParameters()->getRequiresClause(); | ||
if (!RC) | ||
return nullptr; | ||
MultiLevelTemplateArgumentList Args; | ||
Args.setKind(TemplateSubstitutionKind::Rewrite); | ||
Args.addOuterTemplateArguments(TransformedArgs); | ||
ExprResult E = SemaRef.SubstExpr(RC, Args); | ||
if (E.isInvalid()) | ||
return nullptr; | ||
return E.getAs<Expr>(); | ||
} | ||
|
||
std::pair<TemplateDecl *, llvm::ArrayRef<TemplateArgument>> | ||
getRHSTemplateDeclAndArgs(Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate) { | ||
// Unwrap the sugared ElaboratedType. | ||
auto RhsType = AliasTemplate->getTemplatedDecl() | ||
->getUnderlyingType() | ||
.getSingleStepDesugaredType(Context); | ||
.getSingleStepDesugaredType(SemaRef.Context); | ||
TemplateDecl *Template = nullptr; | ||
llvm::ArrayRef<TemplateArgument> AliasRhsTemplateArgs; | ||
if (const auto *TST = RhsType->getAs<TemplateSpecializationType>()) { | ||
|
@@ -2791,6 +2810,24 @@ void DeclareImplicitDeductionGuidesForTypeAlias( | |
} else { | ||
assert(false && "unhandled RHS type of the alias"); | ||
} | ||
return {Template, AliasRhsTemplateArgs}; | ||
} | ||
|
||
// Build deduction guides for a type alias template. | ||
void DeclareImplicitDeductionGuidesForTypeAlias( | ||
Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate, SourceLocation Loc) { | ||
if (AliasTemplate->isInvalidDecl()) | ||
return; | ||
auto &Context = SemaRef.Context; | ||
// FIXME: if there is an explicit deduction guide after the first use of the | ||
// type alias usage, we will not cover this explicit deduction guide. fix this | ||
// case. | ||
if (hasDeclaredDeductionGuides( | ||
Context.DeclarationNames.getCXXDeductionGuideName(AliasTemplate), | ||
AliasTemplate->getDeclContext())) | ||
return; | ||
auto [Template, AliasRhsTemplateArgs] = | ||
getRHSTemplateDeclAndArgs(SemaRef, AliasTemplate); | ||
if (!Template) | ||
return; | ||
DeclarationNameInfo NameInfo( | ||
|
@@ -2803,6 +2840,13 @@ void DeclareImplicitDeductionGuidesForTypeAlias( | |
FunctionTemplateDecl *F = dyn_cast<FunctionTemplateDecl>(G); | ||
if (!F) | ||
continue; | ||
// The **aggregate** deduction guides are handled in a different code path | ||
// (DeclareImplicitDeductionGuideFromInitList), which involves the tricky | ||
// cache. | ||
if (cast<CXXDeductionGuideDecl>(F->getTemplatedDecl()) | ||
->getDeductionCandidateKind() == DeductionCandidate::Aggregate) | ||
continue; | ||
|
||
auto RType = F->getTemplatedDecl()->getReturnType(); | ||
// The (trailing) return type of the deduction guide. | ||
const TemplateSpecializationType *FReturnType = | ||
|
@@ -2885,21 +2929,6 @@ void DeclareImplicitDeductionGuidesForTypeAlias( | |
// parameters, used for building `TemplateArgsForBuildingFPrime`. | ||
SmallVector<TemplateArgument, 16> TransformedDeducedAliasArgs( | ||
AliasTemplate->getTemplateParameters()->size()); | ||
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. I see this was just moved... I'd still love to see us figure out WHEN this could be nullptr. I fear this is a case where we are going to 'miss' something if we add a new template param type. 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. It is reasonable that some of it elements will be nullptr, because not all template parameters of the alias template will be rewritten.
For this particular case, we have covered it in |
||
auto TransformTemplateParameter = | ||
[&SemaRef](DeclContext *DC, NamedDecl *TemplateParam, | ||
MultiLevelTemplateArgumentList &Args, | ||
unsigned NewIndex) -> NamedDecl * { | ||
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam)) | ||
return transformTemplateTypeParam(SemaRef, DC, TTP, Args, | ||
TTP->getDepth(), NewIndex); | ||
if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam)) | ||
return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex, | ||
TTP->getDepth()); | ||
if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam)) | ||
return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex, | ||
NTTP->getDepth()); | ||
return nullptr; | ||
}; | ||
|
||
for (unsigned AliasTemplateParamIdx : DeducedAliasTemplateParams) { | ||
auto *TP = AliasTemplate->getTemplateParameters()->getParam( | ||
|
@@ -2909,9 +2938,9 @@ void DeclareImplicitDeductionGuidesForTypeAlias( | |
MultiLevelTemplateArgumentList Args; | ||
Args.setKind(TemplateSubstitutionKind::Rewrite); | ||
Args.addOuterTemplateArguments(TransformedDeducedAliasArgs); | ||
NamedDecl *NewParam = | ||
TransformTemplateParameter(AliasTemplate->getDeclContext(), TP, Args, | ||
/*NewIndex*/ FPrimeTemplateParams.size()); | ||
NamedDecl *NewParam = transformTemplateParameter( | ||
SemaRef, AliasTemplate->getDeclContext(), TP, Args, | ||
/*NewIndex*/ FPrimeTemplateParams.size()); | ||
FPrimeTemplateParams.push_back(NewParam); | ||
|
||
auto NewTemplateArgument = Context.getCanonicalTemplateArgument( | ||
|
@@ -2927,8 +2956,8 @@ void DeclareImplicitDeductionGuidesForTypeAlias( | |
// We take a shortcut here, it is ok to reuse the | ||
// TemplateArgsForBuildingFPrime. | ||
Args.addOuterTemplateArguments(TemplateArgsForBuildingFPrime); | ||
NamedDecl *NewParam = TransformTemplateParameter( | ||
F->getDeclContext(), TP, Args, FPrimeTemplateParams.size()); | ||
NamedDecl *NewParam = transformTemplateParameter( | ||
SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size()); | ||
FPrimeTemplateParams.push_back(NewParam); | ||
|
||
assert(TemplateArgsForBuildingFPrime[FTemplateParamIdx].isNull() && | ||
|
@@ -2938,16 +2967,8 @@ void DeclareImplicitDeductionGuidesForTypeAlias( | |
Context.getInjectedTemplateArg(NewParam)); | ||
} | ||
// Substitute new template parameters into requires-clause if present. | ||
Expr *RequiresClause = nullptr; | ||
if (Expr *InnerRC = F->getTemplateParameters()->getRequiresClause()) { | ||
MultiLevelTemplateArgumentList Args; | ||
Args.setKind(TemplateSubstitutionKind::Rewrite); | ||
Args.addOuterTemplateArguments(TemplateArgsForBuildingFPrime); | ||
ExprResult E = SemaRef.SubstExpr(InnerRC, Args); | ||
if (E.isInvalid()) | ||
return; | ||
RequiresClause = E.getAs<Expr>(); | ||
} | ||
Expr *RequiresClause = | ||
transformRequireClause(SemaRef, F, TemplateArgsForBuildingFPrime); | ||
// FIXME: implement the is_deducible constraint per C++ | ||
// [over.match.class.deduct]p3.3: | ||
// ... and a constraint that is satisfied if and only if the arguments | ||
|
@@ -3013,11 +3034,102 @@ void DeclareImplicitDeductionGuidesForTypeAlias( | |
} | ||
} | ||
|
||
// Build an aggregate deduction guide for a type alias template. | ||
FunctionTemplateDecl *DeclareAggregateDeductionGuideForTypeAlias( | ||
Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate, | ||
MutableArrayRef<QualType> ParamTypes, SourceLocation Loc) { | ||
TemplateDecl *RHSTemplate = | ||
getRHSTemplateDeclAndArgs(SemaRef, AliasTemplate).first; | ||
if (!RHSTemplate) | ||
return nullptr; | ||
auto *RHSDeductionGuide = SemaRef.DeclareAggregateDeductionGuideFromInitList( | ||
RHSTemplate, ParamTypes, Loc); | ||
if (!RHSDeductionGuide) | ||
return nullptr; | ||
|
||
LocalInstantiationScope Scope(SemaRef); | ||
|
||
// Build a new template parameter list for the synthesized aggregate deduction | ||
// guide by transforming the one from RHSDeductionGuide. | ||
SmallVector<NamedDecl *> TransformedTemplateParams; | ||
// Template args that refer to the rebuilt template parameters. | ||
// All template arguments must be initialized in advance. | ||
SmallVector<TemplateArgument> TransformedTemplateArgs( | ||
RHSDeductionGuide->getTemplateParameters()->size()); | ||
for (auto *TP : *RHSDeductionGuide->getTemplateParameters()) { | ||
// Rebuild any internal references to earlier parameters and reindex as | ||
// we go. | ||
MultiLevelTemplateArgumentList Args; | ||
Args.setKind(TemplateSubstitutionKind::Rewrite); | ||
Args.addOuterTemplateArguments(TransformedTemplateArgs); | ||
NamedDecl *NewParam = transformTemplateParameter( | ||
SemaRef, AliasTemplate->getDeclContext(), TP, Args, | ||
/*NewIndex=*/TransformedTemplateParams.size()); | ||
|
||
TransformedTemplateArgs[TransformedTemplateParams.size()] = | ||
SemaRef.Context.getCanonicalTemplateArgument( | ||
SemaRef.Context.getInjectedTemplateArg(NewParam)); | ||
TransformedTemplateParams.push_back(NewParam); | ||
} | ||
// FIXME: implement the is_deducible constraint per C++ | ||
// [over.match.class.deduct]p3.3. | ||
Expr *TransformedRequiresClause = transformRequireClause( | ||
SemaRef, RHSDeductionGuide, TransformedTemplateArgs); | ||
auto *TransformedTemplateParameterList = TemplateParameterList::Create( | ||
SemaRef.Context, AliasTemplate->getTemplateParameters()->getTemplateLoc(), | ||
AliasTemplate->getTemplateParameters()->getLAngleLoc(), | ||
TransformedTemplateParams, | ||
AliasTemplate->getTemplateParameters()->getRAngleLoc(), | ||
TransformedRequiresClause); | ||
auto *TransformedTemplateArgList = TemplateArgumentList::CreateCopy( | ||
SemaRef.Context, TransformedTemplateArgs); | ||
|
||
if (auto *TransformedDeductionGuide = SemaRef.InstantiateFunctionDeclaration( | ||
RHSDeductionGuide, TransformedTemplateArgList, | ||
AliasTemplate->getLocation(), | ||
Sema::CodeSynthesisContext::BuildingDeductionGuides)) { | ||
auto *GD = | ||
llvm::dyn_cast<clang::CXXDeductionGuideDecl>(TransformedDeductionGuide); | ||
FunctionTemplateDecl *Result = buildDeductionGuide( | ||
SemaRef, AliasTemplate, TransformedTemplateParameterList, | ||
GD->getCorrespondingConstructor(), GD->getExplicitSpecifier(), | ||
GD->getTypeSourceInfo(), AliasTemplate->getBeginLoc(), | ||
AliasTemplate->getLocation(), AliasTemplate->getEndLoc(), | ||
GD->isImplicit()); | ||
cast<CXXDeductionGuideDecl>(Result->getTemplatedDecl()) | ||
->setDeductionCandidateKind(DeductionCandidate::Aggregate); | ||
return Result; | ||
} | ||
return nullptr; | ||
} | ||
|
||
} // namespace | ||
|
||
FunctionTemplateDecl *Sema::DeclareImplicitDeductionGuideFromInitList( | ||
FunctionTemplateDecl *Sema::DeclareAggregateDeductionGuideFromInitList( | ||
TemplateDecl *Template, MutableArrayRef<QualType> ParamTypes, | ||
SourceLocation Loc) { | ||
llvm::FoldingSetNodeID ID; | ||
ID.AddPointer(Template); | ||
for (auto &T : ParamTypes) | ||
T.getCanonicalType().Profile(ID); | ||
unsigned Hash = ID.ComputeHash(); | ||
|
||
auto Found = AggregateDeductionCandidates.find(Hash); | ||
if (Found != AggregateDeductionCandidates.end()) { | ||
CXXDeductionGuideDecl *GD = Found->getSecond(); | ||
return GD->getDescribedFunctionTemplate(); | ||
} | ||
|
||
if (auto *AliasTemplate = llvm::dyn_cast<TypeAliasTemplateDecl>(Template)) { | ||
if (auto *FTD = DeclareAggregateDeductionGuideForTypeAlias( | ||
*this, AliasTemplate, ParamTypes, Loc)) { | ||
auto *GD = cast<CXXDeductionGuideDecl>(FTD->getTemplatedDecl()); | ||
GD->setDeductionCandidateKind(DeductionCandidate::Aggregate); | ||
AggregateDeductionCandidates[Hash] = GD; | ||
return FTD; | ||
} | ||
} | ||
|
||
if (CXXRecordDecl *DefRecord = | ||
cast<CXXRecordDecl>(Template->getTemplatedDecl())->getDefinition()) { | ||
if (TemplateDecl *DescribedTemplate = | ||
|
@@ -3050,10 +3162,13 @@ FunctionTemplateDecl *Sema::DeclareImplicitDeductionGuideFromInitList( | |
Transform.NestedPattern ? Transform.NestedPattern : Transform.Template; | ||
ContextRAII SavedContext(*this, Pattern->getTemplatedDecl()); | ||
|
||
auto *DG = cast<FunctionTemplateDecl>( | ||
auto *FTD = cast<FunctionTemplateDecl>( | ||
Transform.buildSimpleDeductionGuide(ParamTypes)); | ||
SavedContext.pop(); | ||
return DG; | ||
auto *GD = cast<CXXDeductionGuideDecl>(FTD->getTemplatedDecl()); | ||
GD->setDeductionCandidateKind(DeductionCandidate::Aggregate); | ||
AggregateDeductionCandidates[Hash] = GD; | ||
return FTD; | ||
} | ||
|
||
void Sema::DeclareImplicitDeductionGuides(TemplateDecl *Template, | ||
|
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.
What needs to happen to fix this? This is a decent sized issue. Is this covered under UB (of so, can we diagnose that?), or does it have well defined rules?
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.
The behavior depends on where we see the first usage that triggers alias CTAD in the TU. The basic rule is "well-defined":
Bar
), we will generate all deduction guides for theBar
.Bar
, we reuse these generated deduction guides above;If there are some explicit deduction guides after the first usage, they are not covered and are not added to the overload candidate sets.
Regarding the fix, I haven't put much thought into it yet. A naive solution would be that we cache these results (underlying deduction guides -> alias deduction guides), and check that if we have generated the alias deduction guides.
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.
What about a diagnostic on an explicit deduction guide used after first use? If they are not covered/added to the candidate set, we should diagnose that it isn't valid (then, anything AFTER the error where we generate them 'wrong' is irrelevant anyway).
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.
Emitting a diagnostic is an option, but I think it's suboptimal, and it is not trivial to detect such cases. I prefer to fix the issue directly rather than implementing a diagnostic that would eventually be removed.
Do you consider this case to be critical for the clang trunk at the moment? I believe we should fix it before the next clang release, and we still have some time to do so.
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.
WOULD we remove said diagnostic? It seems that:
If there are some explicit deduction guides after the first usage, they are not covered and are not added to the overload candidate sets.
means that adding an explicit deduction guide after first use effectively 'does nothing'.
That said, I'm ok having us do it (or whatever we do next) in a followup patch.