Skip to content

[Clang] Fix various bugs in alias CTAD transform #132061

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 5 commits into from
Mar 22, 2025
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
19 changes: 17 additions & 2 deletions clang/lib/Sema/SemaTemplateDeductionGuide.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1072,12 +1072,27 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
AliasRhsTemplateArgs, TDeduceInfo, DeduceResults,
/*NumberOfArgumentsMustMatch=*/false);

static std::function<bool(const TemplateArgument &TA)> IsNonDeducedArgument =
[](const TemplateArgument &TA) {
// The following cases indicate the template argument is non-deducible:
// 1. The result is null. E.g. When it comes from a default template
// argument that doesn't appear in the alias declaration.
// 2. The template parameter is a pack and that cannot be deduced from
// the arguments within the alias declaration.
// Non-deducible template parameters will persist in the transformed
// deduction guide.
return TA.isNull() ||
(TA.getKind() == TemplateArgument::Pack &&
llvm::any_of(TA.pack_elements(), IsNonDeducedArgument));
};

SmallVector<TemplateArgument> DeducedArgs;
SmallVector<unsigned> NonDeducedTemplateParamsInFIndex;
// !!NOTE: DeduceResults respects the sequence of template parameters of
// the deduction guide f.
for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) {
if (const auto &D = DeduceResults[Index]; !D.isNull()) // Deduced
const auto &D = DeduceResults[Index];
if (!IsNonDeducedArgument(D))
DeducedArgs.push_back(D);
else
NonDeducedTemplateParamsInFIndex.push_back(Index);
Expand Down Expand Up @@ -1141,7 +1156,7 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef,
Args.addOuterTemplateArguments(TransformedDeducedAliasArgs);
for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) {
const auto &D = DeduceResults[Index];
if (D.isNull()) {
if (IsNonDeducedArgument(D)) {
// 2): Non-deduced template parameters would be substituted later.
continue;
}
Expand Down
46 changes: 34 additions & 12 deletions clang/lib/Sema/SemaTemplateInstantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,16 @@ std::optional<TemplateDeductionInfo *> Sema::isSFINAEContext() const {
return std::nullopt;
}

static TemplateArgument
getPackSubstitutedTemplateArgument(Sema &S, TemplateArgument Arg) {
Copy link
Contributor

Choose a reason for hiding this comment

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

getSubstitutedTemplateArgumentPattern would be a better name. Please add a comment too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is pre-existing - do I have to rename that?

assert(S.ArgumentPackSubstitutionIndex >= 0);
assert(S.ArgumentPackSubstitutionIndex < (int)Arg.pack_size());
Arg = Arg.pack_begin()[S.ArgumentPackSubstitutionIndex];
if (Arg.isPackExpansion())
Arg = Arg.getPackExpansionPattern();
return Arg;
}

//===----------------------------------------------------------------------===/
// Template Instantiation for Types
//===----------------------------------------------------------------------===/
Expand Down Expand Up @@ -1467,11 +1477,13 @@ namespace {
}
}

static TemplateArgument
TemplateArgument
Copy link
Contributor

Choose a reason for hiding this comment

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

static is gone

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is intended because we need to access SemaRef.ArgumentPackSubstitutionIndex

getTemplateArgumentPackPatternForRewrite(const TemplateArgument &TA) {
if (TA.getKind() != TemplateArgument::Pack)
return TA;
assert(TA.pack_size() == 1 &&
if (SemaRef.ArgumentPackSubstitutionIndex != -1)
return getPackSubstitutedTemplateArgument(SemaRef, TA);
assert(TA.pack_size() == 1 && TA.pack_begin()->isPackExpansion() &&
"unexpected pack arguments in template rewrite");
TemplateArgument Arg = *TA.pack_begin();
if (Arg.isPackExpansion())
Expand Down Expand Up @@ -1630,6 +1642,9 @@ namespace {
std::vector<TemplateArgument> TArgs;
switch (Arg.getKind()) {
case TemplateArgument::Pack:
assert(SemaRef.CodeSynthesisContexts.empty() ||
SemaRef.CodeSynthesisContexts.back().Kind ==
Sema::CodeSynthesisContext::BuildingDeductionGuides);
// Literally rewrite the template argument pack, instead of unpacking
// it.
for (auto &pack : Arg.getPackAsArray()) {
Expand All @@ -1650,6 +1665,23 @@ namespace {
return inherited::TransformTemplateArgument(Input, Output, Uneval);
}

std::optional<unsigned> ComputeSizeOfPackExprWithoutSubstitution(
ArrayRef<TemplateArgument> PackArgs) {
// Don't do this when rewriting template parameters for CTAD:
// 1) The heuristic needs the unpacked Subst* nodes to figure out the
// expanded size, but this never applies since Subst* nodes are not
// created in rewrite scenarios.
//
// 2) The heuristic substitutes into the pattern with pack expansion
// suppressed, which does not meet the requirements for argument
// rewriting when template arguments include a non-pack matching against
// a pack, particularly when rewriting an alias CTAD.
if (TemplateArgs.isRewrite())
return std::nullopt;

return inherited::ComputeSizeOfPackExprWithoutSubstitution(PackArgs);
}

template<typename Fn>
QualType TransformFunctionProtoType(TypeLocBuilder &TLB,
FunctionProtoTypeLoc TL,
Expand Down Expand Up @@ -1869,16 +1901,6 @@ bool TemplateInstantiator::AlreadyTransformed(QualType T) {
return true;
}

static TemplateArgument
getPackSubstitutedTemplateArgument(Sema &S, TemplateArgument Arg) {
assert(S.ArgumentPackSubstitutionIndex >= 0);
assert(S.ArgumentPackSubstitutionIndex < (int)Arg.pack_size());
Arg = Arg.pack_begin()[S.ArgumentPackSubstitutionIndex];
if (Arg.isPackExpansion())
Arg = Arg.getPackExpansionPattern();
return Arg;
}

Decl *TemplateInstantiator::TransformDecl(SourceLocation Loc, Decl *D) {
if (!D)
return nullptr;
Expand Down
84 changes: 48 additions & 36 deletions clang/lib/Sema/TreeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -3660,6 +3660,9 @@ class TreeTransform {
return SemaRef.BuildCXXNoexceptExpr(Range.getBegin(), Arg, Range.getEnd());
}

std::optional<unsigned>
ComputeSizeOfPackExprWithoutSubstitution(ArrayRef<TemplateArgument> PackArgs);

/// Build a new expression to compute the length of a parameter pack.
ExprResult RebuildSizeOfPackExpr(SourceLocation OperatorLoc, NamedDecl *Pack,
SourceLocation PackLoc,
Expand Down Expand Up @@ -16028,6 +16031,49 @@ TreeTransform<Derived>::TransformPackExpansionExpr(PackExpansionExpr *E) {
E->getNumExpansions());
}

template <typename Derived>
std::optional<unsigned>
TreeTransform<Derived>::ComputeSizeOfPackExprWithoutSubstitution(
ArrayRef<TemplateArgument> PackArgs) {
std::optional<unsigned> Result = 0;
for (const TemplateArgument &Arg : PackArgs) {
if (!Arg.isPackExpansion()) {
Result = *Result + 1;
continue;
}

TemplateArgumentLoc ArgLoc;
InventTemplateArgumentLoc(Arg, ArgLoc);

// Find the pattern of the pack expansion.
SourceLocation Ellipsis;
std::optional<unsigned> OrigNumExpansions;
TemplateArgumentLoc Pattern =
getSema().getTemplateArgumentPackExpansionPattern(ArgLoc, Ellipsis,
OrigNumExpansions);

// Substitute under the pack expansion. Do not expand the pack (yet).
TemplateArgumentLoc OutPattern;
Sema::ArgumentPackSubstitutionIndexRAII SubstIndex(getSema(), -1);
if (getDerived().TransformTemplateArgument(Pattern, OutPattern,
/*Uneval*/ true))
return true;

// See if we can determine the number of arguments from the result.
std::optional<unsigned> NumExpansions =
getSema().getFullyPackExpandedSize(OutPattern.getArgument());
if (!NumExpansions) {
// No: we must be in an alias template expansion, and we're going to
// need to actually expand the packs.
Result = std::nullopt;
break;
}

Result = *Result + *NumExpansions;
}
return Result;
}

template<typename Derived>
ExprResult
TreeTransform<Derived>::TransformSizeOfPackExpr(SizeOfPackExpr *E) {
Expand Down Expand Up @@ -16093,42 +16139,8 @@ TreeTransform<Derived>::TransformSizeOfPackExpr(SizeOfPackExpr *E) {
}

// Try to compute the result without performing a partial substitution.
std::optional<unsigned> Result = 0;
for (const TemplateArgument &Arg : PackArgs) {
if (!Arg.isPackExpansion()) {
Result = *Result + 1;
continue;
}

TemplateArgumentLoc ArgLoc;
InventTemplateArgumentLoc(Arg, ArgLoc);

// Find the pattern of the pack expansion.
SourceLocation Ellipsis;
std::optional<unsigned> OrigNumExpansions;
TemplateArgumentLoc Pattern =
getSema().getTemplateArgumentPackExpansionPattern(ArgLoc, Ellipsis,
OrigNumExpansions);

// Substitute under the pack expansion. Do not expand the pack (yet).
TemplateArgumentLoc OutPattern;
Sema::ArgumentPackSubstitutionIndexRAII SubstIndex(getSema(), -1);
if (getDerived().TransformTemplateArgument(Pattern, OutPattern,
/*Uneval*/ true))
return true;

// See if we can determine the number of arguments from the result.
std::optional<unsigned> NumExpansions =
getSema().getFullyPackExpandedSize(OutPattern.getArgument());
if (!NumExpansions) {
// No: we must be in an alias template expansion, and we're going to need
// to actually expand the packs.
Result = std::nullopt;
break;
}

Result = *Result + *NumExpansions;
}
std::optional<unsigned> Result =
getDerived().ComputeSizeOfPackExprWithoutSubstitution(PackArgs);

// Common case: we could determine the number of expansions without
// substituting.
Expand Down
132 changes: 131 additions & 1 deletion clang/test/SemaCXX/ctad.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// RUN: %clang_cc1 -fsyntax-only -verify -Wno-unused-value -std=c++20 %s
// expected-no-diagnostics

namespace GH64347 {

Expand All @@ -17,3 +16,134 @@ void k() {
}

} // namespace GH64347

namespace GH123591 {


template < typename... _Types >
struct variant {
template <int N = sizeof...(_Types)>
variant(_Types...);
};

template <class T>
using AstNode = variant<T, T, T>;

AstNode tree(42, 43, 44);

}

namespace GH123591_2 {

template <int>
using enable_if_t = char;

template < typename... Types >
struct variant {
template < enable_if_t<sizeof...(Types)>>
variant();
};

template <int>
using AstNode = variant<>;
// expected-note@-1 {{couldn't infer template argument ''}} \
// expected-note@-1 2{{implicit deduction guide declared as}} \
// expected-note@-1 {{candidate function template not viable}}


AstNode tree; // expected-error {{no viable constructor or deduction guide}}

}

namespace GH127539 {

template <class...>
struct A {
template <class... ArgTs>
A(ArgTs...) {}
};

template <class... ArgTs>
A(ArgTs...) -> A<typename ArgTs::value_type...>;

template <class... Ts>
using AA = A<Ts..., Ts...>;

AA a{};

}

namespace GH129077 {

using size_t = decltype(sizeof(0));

struct index_type
{
size_t value{~0ull};
index_type() = default;
constexpr index_type(size_t i) noexcept : value(i) {}
};

template <index_type... Extents>
struct extents
{
constexpr extents(decltype(Extents)...) noexcept {}
};

template <class... Extents>
extents(Extents...) -> extents<(requires { Extents::value; } ? Extents{} : ~0ull)...>;

template <index_type... Index>
using index = extents<Index...>;

int main()
{
extents i{0,0};
auto j = extents<64,{}>({}, 42);

index k{0,0};
auto l = index<64,{}>({}, 42);

return 0;
}

}

namespace GH129620 {

template <class... Ts>
struct A {
constexpr A(Ts...) {}
};

template <class... Ts>
using Foo = A<Ts...>;

template <class T>
using Bar = Foo<T, T>;

Bar a{0, 0};

}

namespace GH129998 {

struct converible_to_one {
constexpr operator int() const noexcept { return 1; }
};

template <int... Extents>
struct class_template {
class_template() = default;
constexpr class_template(auto&&...) noexcept {}
};

template <class... Extents>
class_template(Extents...) -> class_template<(true ? 0 : +Extents{})...>;

template <int... Extents>
using alias_template = class_template<Extents...>;

alias_template var2{converible_to_one{}, 2};

}
Loading