Skip to content

Commit

Permalink
Reland "[clang][Sema] Use original template pattern when declaring im…
Browse files Browse the repository at this point in the history
…plicit deduction guides for nested template classes" (#73087)

Reland of f418319 with proper handling
of template constructors

When a nested template is instantiated, the template pattern of the
inner class is not copied into the outer class
ClassTemplateSpecializationDecl. The specialization contains a
ClassTemplateDecl with an empty record that points to the original
template pattern instead.

As a result, when looking up the constructors of the inner class, no
results are returned. This patch finds the original template pattern and
uses that for the lookup instead.

Based on CWG2471 we must also substitute the known outer template
arguments when creating deduction guides for the inner class.

Changes from last iteration:

1. In template constructors, arguments are first rewritten to depth - 1
relative to the constructor as compared to depth 0 originally. These
arguments are needed for substitution into constraint expressions.
2. Outer arguments are then applied with the template instantiator to
produce a template argument at depth zero for use in the deduction
guide. This substitution does not evaluate constraints, which preserves
constraint arguments at the correct depth for later evaluation.
3. Tests are added that cover template constructors within nested
deduction guides for all special substitution cases.
4. Computation of the template pattern and outer instantiation arguments
are pulled into the constructor of
`ConvertConstructorToDeductionGuideTransform`.
  • Loading branch information
antangelo committed Nov 25, 2023
1 parent 75a9ed4 commit bee78b8
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 6 deletions.
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,11 @@ Bug Fixes to C++ Support
declaration definition. Fixes:
(`#61763 <https://github.com/llvm/llvm-project/issues/61763>`_)

- Fix a bug where implicit deduction guides are not correctly generated for nested template
classes. Fixes:
(`#46200 <https://github.com/llvm/llvm-project/issues/46200>`_)
(`#57812 <https://github.com/llvm/llvm-project/issues/57812>`_)

- Diagnose use of a variable-length array in a coroutine. The design of
coroutines is such that it is not possible to support VLA use. Fixes:
(`#65858 <https://github.com/llvm/llvm-project/issues/65858>`_)
Expand Down
66 changes: 60 additions & 6 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2250,10 +2250,24 @@ class ExtractTypeForDeductionGuide
struct ConvertConstructorToDeductionGuideTransform {
ConvertConstructorToDeductionGuideTransform(Sema &S,
ClassTemplateDecl *Template)
: SemaRef(S), Template(Template) {}
: SemaRef(S), Template(Template) {
// If the template is nested, then we need to use the original
// pattern to iterate over the constructors.
ClassTemplateDecl *Pattern = Template;
while (Pattern->getInstantiatedFromMemberTemplate()) {
if (Pattern->isMemberSpecialization())
break;
Pattern = Pattern->getInstantiatedFromMemberTemplate();
NestedPattern = Pattern;
}

if (NestedPattern)
OuterInstantiationArgs = SemaRef.getTemplateInstantiationArgs(Template);
}

Sema &SemaRef;
ClassTemplateDecl *Template;
ClassTemplateDecl *NestedPattern = nullptr;

DeclContext *DC = Template->getDeclContext();
CXXRecordDecl *Primary = Template->getTemplatedDecl();
Expand All @@ -2266,6 +2280,10 @@ struct ConvertConstructorToDeductionGuideTransform {
// depth-0 template parameters.
unsigned Depth1IndexAdjustment = Template->getTemplateParameters()->size();

// Instantiation arguments for the outermost depth-1 templates
// when the template is nested
MultiLevelTemplateArgumentList OuterInstantiationArgs;

/// Transform a constructor declaration into a deduction guide.
NamedDecl *transformConstructor(FunctionTemplateDecl *FTD,
CXXConstructorDecl *CD) {
Expand All @@ -2284,21 +2302,43 @@ struct ConvertConstructorToDeductionGuideTransform {
if (FTD) {
TemplateParameterList *InnerParams = FTD->getTemplateParameters();
SmallVector<NamedDecl *, 16> AllParams;
SmallVector<TemplateArgument, 16> Depth1Args;
AllParams.reserve(TemplateParams->size() + InnerParams->size());
AllParams.insert(AllParams.begin(),
TemplateParams->begin(), TemplateParams->end());
SubstArgs.reserve(InnerParams->size());
Depth1Args.reserve(InnerParams->size());

// Later template parameters could refer to earlier ones, so build up
// a list of substituted template arguments as we go.
for (NamedDecl *Param : *InnerParams) {
MultiLevelTemplateArgumentList Args;
Args.setKind(TemplateSubstitutionKind::Rewrite);
Args.addOuterTemplateArguments(SubstArgs);
Args.addOuterTemplateArguments(Depth1Args);
Args.addOuterRetainedLevel();
if (NestedPattern)
Args.addOuterRetainedLevels(NestedPattern->getTemplateDepth());
NamedDecl *NewParam = transformTemplateParameter(Param, Args);
if (!NewParam)
return nullptr;

// Constraints require that we substitute depth-1 arguments
// to match depths when substituted for evaluation later
Depth1Args.push_back(SemaRef.Context.getCanonicalTemplateArgument(
SemaRef.Context.getInjectedTemplateArg(NewParam)));

if (NestedPattern) {
TemplateDeclInstantiator Instantiator(SemaRef, DC,
OuterInstantiationArgs);
Instantiator.setEvaluateConstraints(false);
SemaRef.runWithSufficientStackSpace(NewParam->getLocation(), [&] {
NewParam = cast<NamedDecl>(Instantiator.Visit(NewParam));
});
}

assert(NewParam->getTemplateDepth() == 0 &&
"Unexpected template parameter depth");

AllParams.push_back(NewParam);
SubstArgs.push_back(SemaRef.Context.getCanonicalTemplateArgument(
SemaRef.Context.getInjectedTemplateArg(NewParam)));
Expand All @@ -2309,8 +2349,10 @@ struct ConvertConstructorToDeductionGuideTransform {
if (Expr *InnerRC = InnerParams->getRequiresClause()) {
MultiLevelTemplateArgumentList Args;
Args.setKind(TemplateSubstitutionKind::Rewrite);
Args.addOuterTemplateArguments(SubstArgs);
Args.addOuterTemplateArguments(Depth1Args);
Args.addOuterRetainedLevel();
if (NestedPattern)
Args.addOuterRetainedLevels(NestedPattern->getTemplateDepth());
ExprResult E = SemaRef.SubstExpr(InnerRC, Args);
if (E.isInvalid())
return nullptr;
Expand All @@ -2333,6 +2375,9 @@ struct ConvertConstructorToDeductionGuideTransform {
Args.addOuterRetainedLevel();
}

if (NestedPattern)
Args.addOuterRetainedLevels(NestedPattern->getTemplateDepth());

FunctionProtoTypeLoc FPTL = CD->getTypeSourceInfo()->getTypeLoc()
.getAsAdjusted<FunctionProtoTypeLoc>();
assert(FPTL && "no prototype for constructor declaration");
Expand Down Expand Up @@ -2394,7 +2439,7 @@ struct ConvertConstructorToDeductionGuideTransform {
// substitute it directly.
auto *NewTTP = TemplateTypeParmDecl::Create(
SemaRef.Context, DC, TTP->getBeginLoc(), TTP->getLocation(),
/*Depth*/ 0, Depth1IndexAdjustment + TTP->getIndex(),
TTP->getDepth() - 1, Depth1IndexAdjustment + TTP->getIndex(),
TTP->getIdentifier(), TTP->wasDeclaredWithTypename(),
TTP->isParameterPack(), TTP->hasTypeConstraint(),
TTP->isExpandedParameterPack()
Expand Down Expand Up @@ -2429,7 +2474,8 @@ struct ConvertConstructorToDeductionGuideTransform {
// the index of the parameter once it's done.
auto *NewParam =
cast<TemplateParmDecl>(SemaRef.SubstDecl(OldParam, DC, Args));
assert(NewParam->getDepth() == 0 && "unexpected template param depth");
assert(NewParam->getDepth() == OldParam->getDepth() - 1 &&
"unexpected template param depth");
NewParam->setPosition(NewParam->getPosition() + Depth1IndexAdjustment);
return NewParam;
}
Expand All @@ -2446,6 +2492,9 @@ struct ConvertConstructorToDeductionGuideTransform {
for (auto *OldParam : TL.getParams()) {
ParmVarDecl *NewParam =
transformFunctionTypeParam(OldParam, Args, MaterializedTypedefs);
if (NestedPattern && NewParam)
NewParam = transformFunctionTypeParam(NewParam, OuterInstantiationArgs,
MaterializedTypedefs);
if (!NewParam)
return QualType();
ParamTypes.push_back(NewParam->getType());
Expand Down Expand Up @@ -2655,9 +2704,12 @@ void Sema::DeclareImplicitDeductionGuides(TemplateDecl *Template,
// FIXME: Skip constructors for which deduction must necessarily fail (those
// for which some class template parameter without a default argument never
// appears in a deduced context).
ClassTemplateDecl *Pattern =
Transform.NestedPattern ? Transform.NestedPattern : Transform.Template;
ContextRAII SavedContext(*this, Pattern->getTemplatedDecl());
llvm::SmallPtrSet<NamedDecl *, 8> ProcessedCtors;
bool AddedAny = false;
for (NamedDecl *D : LookupConstructors(Transform.Primary)) {
for (NamedDecl *D : LookupConstructors(Pattern->getTemplatedDecl())) {
D = D->getUnderlyingDecl();
if (D->isInvalidDecl() || D->isImplicit())
continue;
Expand Down Expand Up @@ -2703,6 +2755,8 @@ void Sema::DeclareImplicitDeductionGuides(TemplateDecl *Template,
Transform.buildSimpleDeductionGuide(Transform.DeducedType))
->getTemplatedDecl())
->setDeductionCandidateKind(DeductionCandidate::Copy);

SavedContext.pop();
}

/// Diagnose the presence of a default template argument on a
Expand Down
5 changes: 5 additions & 0 deletions clang/test/SemaTemplate/nested-deduction-guides.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
template<typename T> struct A {
template<typename U> struct B {
B(...);
B(const B &) = default;
};
template<typename U> B(U) -> B<U>;
};
A<void>::B b = 123;
A<void>::B copy = b;

using T = decltype(b);
using T = A<void>::B<int>;

using Copy = decltype(copy);
using Copy = A<void>::B<int>;
60 changes: 60 additions & 0 deletions clang/test/SemaTemplate/nested-implicit-deduction-guides.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// RUN: %clang_cc1 -std=c++20 -verify %s
// expected-no-diagnostics

template<class T> struct S {
template<class U> struct N {
N(T) {}
N(T, U) {}
template<class V> N(V, U) {}
};
};

S<int>::N x{"a", 1};
using T = decltype(x);
using T = S<int>::N<int>;

template<class X> struct default_ftd_argument {
template<class Y> struct B {
template<class W = X, class Z = Y, class V = Z, int I = 0> B(Y);
};
};

default_ftd_argument<int>::B default_arg("a");
using DefaultArg = decltype(default_arg);
using DefaultArg = default_ftd_argument<int>::B<const char *>;

template<bool> struct test;
template<class X> struct non_type_param {
template<class Y> struct B {
B(Y);
template<class Z, test<Z::value> = 0> B(Z);
};
};

non_type_param<int>::B ntp = 5;
using NonTypeParam = decltype(ntp);
using NonTypeParam = non_type_param<int>::B<int>;

template<typename A, typename T>
concept C = (sizeof(T) == sizeof(A));

template<class X> struct concepts {
template<class Y> struct B {
template<class K = X, C<K> Z> B(Y, Z);
};
};

concepts<int>::B cc(1, 3);
using Concepts = decltype(cc);
using Concepts = concepts<int>::B<int>;

template<class X> struct requires_clause {
template<class Y> struct B {
template<class Z> requires (sizeof(Z) == sizeof(X))
B(Y, Z);
};
};

requires_clause<int>::B req(1, 2);
using RC = decltype(req);
using RC = requires_clause<int>::B<int>;

0 comments on commit bee78b8

Please sign in to comment.