Skip to content

[Clang] Substitute for the type aliases inside of a CTAD guide #94740

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 9 commits into from
Jul 10, 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
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,7 @@ Bug Fixes to C++ Support
evaluated to an integer. (#GH96670).
- Fixed a bug where references to lambda capture inside a ``noexcept`` specifier were not correctly
instantiated. (#GH95735).
- Fixed a CTAD substitution bug involving type aliases that reference outer template parameters. (#GH94614).

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Sema/Template.h
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ enum class TemplateSubstitutionKind : char {
VarTemplateSpecializationDecl *PrevDecl = nullptr);

Decl *InstantiateTypedefNameDecl(TypedefNameDecl *D, bool IsTypeAlias);
Decl *InstantiateTypeAliasTemplateDecl(TypeAliasTemplateDecl *D);
ClassTemplatePartialSpecializationDecl *
InstantiateClassTemplatePartialSpecialization(
ClassTemplateDecl *ClassTemplate,
Expand Down
108 changes: 100 additions & 8 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2172,23 +2172,110 @@ namespace {
class ExtractTypeForDeductionGuide
: public TreeTransform<ExtractTypeForDeductionGuide> {
llvm::SmallVectorImpl<TypedefNameDecl *> &MaterializedTypedefs;
ClassTemplateDecl *NestedPattern;
const MultiLevelTemplateArgumentList *OuterInstantiationArgs;
std::optional<TemplateDeclInstantiator> TypedefNameInstantiator;

public:
typedef TreeTransform<ExtractTypeForDeductionGuide> Base;
ExtractTypeForDeductionGuide(
Sema &SemaRef,
llvm::SmallVectorImpl<TypedefNameDecl *> &MaterializedTypedefs)
: Base(SemaRef), MaterializedTypedefs(MaterializedTypedefs) {}
llvm::SmallVectorImpl<TypedefNameDecl *> &MaterializedTypedefs,
ClassTemplateDecl *NestedPattern,
const MultiLevelTemplateArgumentList *OuterInstantiationArgs)
: Base(SemaRef), MaterializedTypedefs(MaterializedTypedefs),
NestedPattern(NestedPattern),
OuterInstantiationArgs(OuterInstantiationArgs) {
if (OuterInstantiationArgs)
TypedefNameInstantiator.emplace(
SemaRef, SemaRef.getASTContext().getTranslationUnitDecl(),
*OuterInstantiationArgs);
}

TypeSourceInfo *transform(TypeSourceInfo *TSI) { return TransformType(TSI); }

/// Returns true if it's safe to substitute \p Typedef with
/// \p OuterInstantiationArgs.
bool mightReferToOuterTemplateParameters(TypedefNameDecl *Typedef) {
if (!NestedPattern)
return false;

static auto WalkUp = [](DeclContext *DC, DeclContext *TargetDC) {
if (DC->Equals(TargetDC))
return true;
while (DC->isRecord()) {
if (DC->Equals(TargetDC))
return true;
DC = DC->getParent();
}
return false;
};

if (WalkUp(Typedef->getDeclContext(), NestedPattern->getTemplatedDecl()))
return true;
if (WalkUp(NestedPattern->getTemplatedDecl(), Typedef->getDeclContext()))
return true;
return false;
}

QualType
RebuildTemplateSpecializationType(TemplateName Template,
SourceLocation TemplateNameLoc,
TemplateArgumentListInfo &TemplateArgs) {
if (!OuterInstantiationArgs ||
!isa_and_present<TypeAliasTemplateDecl>(Template.getAsTemplateDecl()))
return Base::RebuildTemplateSpecializationType(Template, TemplateNameLoc,
TemplateArgs);

auto *TATD = cast<TypeAliasTemplateDecl>(Template.getAsTemplateDecl());
auto *Pattern = TATD;
while (Pattern->getInstantiatedFromMemberTemplate())
Pattern = Pattern->getInstantiatedFromMemberTemplate();
if (!mightReferToOuterTemplateParameters(Pattern->getTemplatedDecl()))
return Base::RebuildTemplateSpecializationType(Template, TemplateNameLoc,
TemplateArgs);

Decl *NewD =
TypedefNameInstantiator->InstantiateTypeAliasTemplateDecl(TATD);
if (!NewD)
return QualType();

auto *NewTATD = cast<TypeAliasTemplateDecl>(NewD);
MaterializedTypedefs.push_back(NewTATD->getTemplatedDecl());

return Base::RebuildTemplateSpecializationType(
TemplateName(NewTATD), TemplateNameLoc, TemplateArgs);
}

QualType TransformTypedefType(TypeLocBuilder &TLB, TypedefTypeLoc TL) {
ASTContext &Context = SemaRef.getASTContext();
TypedefNameDecl *OrigDecl = TL.getTypedefNameDecl();
TypedefNameDecl *Decl = OrigDecl;
// Transform the underlying type of the typedef and clone the Decl only if
// the typedef has a dependent context.
if (OrigDecl->getDeclContext()->isDependentContext()) {
bool InDependentContext = OrigDecl->getDeclContext()->isDependentContext();

// A typedef/alias Decl within the NestedPattern may reference the outer
// template parameters. They're substituted with corresponding instantiation
// arguments here and in RebuildTemplateSpecializationType() above.
// Otherwise, we would have a CTAD guide with "dangling" template
// parameters.
// For example,
// template <class T> struct Outer {
// using Alias = S<T>;
// template <class U> struct Inner {
// Inner(Alias);
// };
// };
if (OuterInstantiationArgs && InDependentContext &&
TL.getTypePtr()->isInstantiationDependentType()) {
Decl = cast_if_present<TypedefNameDecl>(
TypedefNameInstantiator->InstantiateTypedefNameDecl(
OrigDecl, /*IsTypeAlias=*/isa<TypeAliasDecl>(OrigDecl)));
if (!Decl)
return QualType();
MaterializedTypedefs.push_back(Decl);
} else if (InDependentContext) {
TypeLocBuilder InnerTLB;
QualType Transformed =
TransformType(InnerTLB, OrigDecl->getTypeSourceInfo()->getTypeLoc());
Expand Down Expand Up @@ -2535,16 +2622,18 @@ struct ConvertConstructorToDeductionGuideTransform {
// defined outside of the surrounding class template. That is T in the
// above example.
if (NestedPattern) {
NewParam = transformFunctionTypeParam(NewParam, OuterInstantiationArgs,
MaterializedTypedefs);
NewParam = transformFunctionTypeParam(
NewParam, OuterInstantiationArgs, MaterializedTypedefs,
/*TransformingOuterPatterns=*/true);
if (!NewParam)
return QualType();
}
// Then, transform all the references to template parameters that are
// defined at the class template and the constructor. In this example,
// they're U and V, respectively.
NewParam =
transformFunctionTypeParam(NewParam, Args, MaterializedTypedefs);
transformFunctionTypeParam(NewParam, Args, MaterializedTypedefs,
/*TransformingOuterPatterns=*/false);
if (!NewParam)
return QualType();
ParamTypes.push_back(NewParam->getType());
Expand Down Expand Up @@ -2588,7 +2677,8 @@ struct ConvertConstructorToDeductionGuideTransform {

ParmVarDecl *transformFunctionTypeParam(
ParmVarDecl *OldParam, MultiLevelTemplateArgumentList &Args,
llvm::SmallVectorImpl<TypedefNameDecl *> &MaterializedTypedefs) {
llvm::SmallVectorImpl<TypedefNameDecl *> &MaterializedTypedefs,
bool TransformingOuterPatterns) {
TypeSourceInfo *OldDI = OldParam->getTypeSourceInfo();
TypeSourceInfo *NewDI;
if (auto PackTL = OldDI->getTypeLoc().getAs<PackExpansionTypeLoc>()) {
Expand All @@ -2611,7 +2701,9 @@ struct ConvertConstructorToDeductionGuideTransform {
// members of the current instantiations with the definitions of those
// typedefs, avoiding triggering instantiation of the deduced type during
// deduction.
NewDI = ExtractTypeForDeductionGuide(SemaRef, MaterializedTypedefs)
NewDI = ExtractTypeForDeductionGuide(
SemaRef, MaterializedTypedefs, NestedPattern,
TransformingOuterPatterns ? &Args : nullptr)
.transform(NewDI);

// Resolving a wording defect, we also inherit default arguments from the
Expand Down
13 changes: 10 additions & 3 deletions clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1096,8 +1096,8 @@ Decl *TemplateDeclInstantiator::VisitTypeAliasDecl(TypeAliasDecl *D) {
return Typedef;
}

Decl *
TemplateDeclInstantiator::VisitTypeAliasTemplateDecl(TypeAliasTemplateDecl *D) {
Decl *TemplateDeclInstantiator::InstantiateTypeAliasTemplateDecl(
TypeAliasTemplateDecl *D) {
// Create a local instantiation scope for this type alias template, which
// will contain the instantiations of the template parameters.
LocalInstantiationScope Scope(SemaRef);
Expand Down Expand Up @@ -1143,7 +1143,14 @@ TemplateDeclInstantiator::VisitTypeAliasTemplateDecl(TypeAliasTemplateDecl *D) {
if (!PrevAliasTemplate)
Inst->setInstantiatedFromMemberTemplate(D);

Owner->addDecl(Inst);
return Inst;
}

Decl *
TemplateDeclInstantiator::VisitTypeAliasTemplateDecl(TypeAliasTemplateDecl *D) {
Decl *Inst = InstantiateTypeAliasTemplateDecl(D);
if (Inst)
Owner->addDecl(Inst);

return Inst;
}
Expand Down
74 changes: 73 additions & 1 deletion clang/test/SemaTemplate/nested-deduction-guides.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// RUN: %clang_cc1 -std=c++17 -verify %s
// expected-no-diagnostics

template<typename T> struct A {
template<typename U> struct B {
Expand All @@ -16,3 +15,76 @@ using T = A<void>::B<int>;

using Copy = decltype(copy);
using Copy = A<void>::B<int>;

namespace GH94614 {

template <class, class> struct S {};
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wonder whether it is feasible to add an ast-dump test for this issue, I find seeing and verifying the shape of AST deduction guide is clearer. However, the ast-dump doesn't seem to dump much information about the type of the function parameter decl, which is the information we want to verify.

Copy link
Contributor Author

@zyn0217 zyn0217 Jun 29, 2024

Choose a reason for hiding this comment

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

Yeah, and I've even tried #pragma clang __debug dump param, which doesn't work either...

I was expecting we could dump the TypeLoc through some command lines, but unfortunately, this isn't the case.


struct trouble_1 {
} constexpr t1;
struct trouble_2 {
} constexpr t2;
struct trouble_3 {
} constexpr t3;
struct trouble_4 {
} constexpr t4;
struct trouble_5 {
} constexpr t5;
struct trouble_6 {
} constexpr t6;
struct trouble_7 {
} constexpr t7;
struct trouble_8 {
} constexpr t8;
struct trouble_9 {
} constexpr t9;

template <class U, class... T> struct Unrelated {
using Trouble = S<U, T...>;

template <class... V> using Trouble2 = S<V..., T...>;
};

template <class T, class U> struct Outer {
using Trouble = S<U, T>;

template <class V> using Trouble2 = S<V, T>;

template <class V> using Trouble3 = S<U, T>;

template <class V> struct Inner {
template <class W> struct Paranoid {
using Trouble4 = S<W, T>;

template <class... X> using Trouble5 = S<X..., T>;
};

Inner(trouble_1, V v, Trouble trouble) {}
Inner(trouble_2, V v, Trouble2<V> trouble) {}
Inner(trouble_3, V v, Trouble3<V> trouble) {}
Inner(trouble_4, V v, typename Unrelated<U, T>::template Trouble2<V> trouble) {}
Inner(trouble_5, V v, typename Unrelated<U, T>::Trouble trouble) {}
Inner(trouble_6, V v, typename Unrelated<V, T>::Trouble trouble) {}
Inner(trouble_7, V v, typename Paranoid<V>::Trouble4 trouble) {}
Inner(trouble_8, V v, typename Paranoid<V>::template Trouble5<V> trouble) {}
template <class W>
Inner(trouble_9, V v, W w, typename Paranoid<V>::template Trouble5<W> trouble) {}
};
};

S<int, char> s;

Outer<char, int>::Inner _1(t1, 42, s);
Outer<char, int>::Inner _2(t2, 42, s);
Outer<char, int>::Inner _3(t3, 42, s);
Outer<char, int>::Inner _4(t4, 42, s);
Outer<char, int>::Inner _5(t5, 42, s);
Outer<char, int>::Inner _6(t6, 42, s);
Outer<char, int>::Inner _7(t7, 42, s);
Outer<char, int>::Inner _8(t8, 42, s);
Outer<char, int>::Inner _9(t9, 42, 24, s);

// Make sure we don't accidentally inject the TypedefNameDecl into the TU.
Trouble should_not_be_in_the_tu_decl; // expected-error {{unknown type name 'Trouble'}}

} // namespace GH94614
Loading