Skip to content

Commit 5c55f96

Browse files
authored
[Clang] Don't assume unexpanded PackExpansions' size when expanding packs (#120380)
CheckParameterPacksForExpansion() previously assumed that template arguments don't include PackExpansion types when attempting another pack expansion (i.e. when NumExpansions is present). However, this assumption doesn't hold for type aliases, whose substitution might involve unexpanded packs. This can lead to incorrect diagnostics during substitution because the pack size is not yet determined. To address this, this patch calculates the minimum pack size (ignoring unexpanded PackExpansionTypes) and compares it to the previously expanded size. If the minimum pack size is smaller, then there's still a chance for future substitution to expand it to a correct size, so we don't diagnose it too eagerly. Fixes #61415 Fixes #32252 Fixes #17042
1 parent 2c782ab commit 5c55f96

File tree

4 files changed

+113
-8
lines changed

4 files changed

+113
-8
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,7 @@ Bug Fixes to C++ Support
850850
- Clang no longer rejects deleting a pointer of incomplete enumeration type. (#GH99278)
851851
- Fixed recognition of ``std::initializer_list`` when it's surrounded with ``extern "C++"`` and exported
852852
out of a module (which is the case e.g. in MSVC's implementation of ``std`` module). (#GH118218)
853+
- Fixed a pack expansion issue in checking unexpanded parameter sizes. (#GH17042)
853854

854855
Bug Fixes to AST Handling
855856
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5863,10 +5863,10 @@ def err_pack_expansion_without_parameter_packs : Error<
58635863
"pack expansion does not contain any unexpanded parameter packs">;
58645864
def err_pack_expansion_length_conflict : Error<
58655865
"pack expansion contains parameter packs %0 and %1 that have different "
5866-
"lengths (%2 vs. %3)">;
5866+
"lengths (%2 vs. %select{|at least }3%4))">;
58675867
def err_pack_expansion_length_conflict_multilevel : Error<
58685868
"pack expansion contains parameter pack %0 that has a different "
5869-
"length (%1 vs. %2) from outer parameter packs">;
5869+
"length (%1 vs. %select{|at least }2%3) from outer parameter packs">;
58705870
def err_pack_expansion_length_conflict_partial : Error<
58715871
"pack expansion contains parameter pack %0 that has a different "
58725872
"length (at least %1 vs. %2) from outer parameter packs">;

clang/lib/Sema/SemaTemplateVariadic.cpp

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ bool Sema::CheckParameterPacksForExpansion(
780780
}
781781

782782
// Determine the size of this argument pack.
783-
unsigned NewPackSize;
783+
unsigned NewPackSize, PendingPackExpansionSize = 0;
784784
if (IsVarDeclPack) {
785785
// Figure out whether we're instantiating to an argument pack or not.
786786
typedef LocalInstantiationScope::DeclArgumentPack DeclArgumentPack;
@@ -808,7 +808,25 @@ bool Sema::CheckParameterPacksForExpansion(
808808
}
809809

810810
// Determine the size of the argument pack.
811-
NewPackSize = TemplateArgs(Depth, Index).pack_size();
811+
ArrayRef<TemplateArgument> Pack =
812+
TemplateArgs(Depth, Index).getPackAsArray();
813+
NewPackSize = Pack.size();
814+
PendingPackExpansionSize =
815+
llvm::count_if(Pack, [](const TemplateArgument &TA) {
816+
if (!TA.isPackExpansion())
817+
return false;
818+
819+
if (TA.getKind() == TemplateArgument::Type)
820+
return !TA.getAsType()
821+
->getAs<PackExpansionType>()
822+
->getNumExpansions();
823+
824+
if (TA.getKind() == TemplateArgument::Expression)
825+
return !cast<PackExpansionExpr>(TA.getAsExpr())
826+
->getNumExpansions();
827+
828+
return !TA.getNumTemplateExpansions();
829+
});
812830
}
813831

814832
// C++0x [temp.arg.explicit]p9:
@@ -831,7 +849,7 @@ bool Sema::CheckParameterPacksForExpansion(
831849
}
832850

833851
if (!NumExpansions) {
834-
// The is the first pack we've seen for which we have an argument.
852+
// This is the first pack we've seen for which we have an argument.
835853
// Record it.
836854
NumExpansions = NewPackSize;
837855
FirstPack.first = Name;
@@ -841,17 +859,44 @@ bool Sema::CheckParameterPacksForExpansion(
841859
}
842860

843861
if (NewPackSize != *NumExpansions) {
862+
// In some cases, we might be handling packs with unexpanded template
863+
// arguments. For example, this can occur when substituting into a type
864+
// alias declaration that uses its injected template parameters as
865+
// arguments:
866+
//
867+
// template <class... Outer> struct S {
868+
// template <class... Inner> using Alias = S<void(Outer, Inner)...>;
869+
// };
870+
//
871+
// Consider an instantiation attempt like 'S<int>::Alias<Pack...>', where
872+
// Pack comes from another template parameter. 'S<int>' is first
873+
// instantiated, expanding the outer pack 'Outer' to <int>. The alias
874+
// declaration is accordingly substituted, leaving the template arguments
875+
// as unexpanded
876+
// '<Pack...>'.
877+
//
878+
// Since we have no idea of the size of '<Pack...>' until its expansion,
879+
// we shouldn't assume its pack size for validation. However if we are
880+
// certain that there are extra arguments beyond unexpanded packs, in
881+
// which case the pack size is already larger than the previous expansion,
882+
// we can complain that before instantiation.
883+
unsigned LeastNewPackSize = NewPackSize - PendingPackExpansionSize;
884+
if (PendingPackExpansionSize && LeastNewPackSize <= *NumExpansions) {
885+
ShouldExpand = false;
886+
continue;
887+
}
844888
// C++0x [temp.variadic]p5:
845889
// All of the parameter packs expanded by a pack expansion shall have
846890
// the same number of arguments specified.
847891
if (HaveFirstPack)
848892
Diag(EllipsisLoc, diag::err_pack_expansion_length_conflict)
849-
<< FirstPack.first << Name << *NumExpansions << NewPackSize
893+
<< FirstPack.first << Name << *NumExpansions
894+
<< (LeastNewPackSize != NewPackSize) << LeastNewPackSize
850895
<< SourceRange(FirstPack.second) << SourceRange(ParmPack.second);
851896
else
852897
Diag(EllipsisLoc, diag::err_pack_expansion_length_conflict_multilevel)
853-
<< Name << *NumExpansions << NewPackSize
854-
<< SourceRange(ParmPack.second);
898+
<< Name << *NumExpansions << (LeastNewPackSize != NewPackSize)
899+
<< LeastNewPackSize << SourceRange(ParmPack.second);
855900
return true;
856901
}
857902
}

clang/test/SemaTemplate/pack-deduction.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,62 @@ constexpr auto baz(Int<foo<T>(T())>... x) -> int { return 1; }
199199

200200
static_assert(baz<Int<1>, Int<2>, Int<3>>(Int<10>(), Int<10>(), Int<10>()) == 1, "");
201201
}
202+
203+
namespace GH17042 {
204+
205+
template <class... Ts> struct X {
206+
template <class... Us> using Y = X<void(Ts, Us)...>; // #GH17042_Y
207+
};
208+
209+
template <class... T>
210+
using any_pairs_list = X<int, int>::Y<T...>; // #any_pairs_list
211+
212+
template <class... T>
213+
using any_pairs_list_2 = X<int, int>::Y<>;
214+
// expected-error@#GH17042_Y {{different length (2 vs. 0)}} \
215+
// expected-note@-1 {{requested here}}
216+
217+
template <class A, class B, class... P>
218+
using any_pairs_list_3 = X<int, int>::Y<A, B, P...>; // #any_pairs_list_3
219+
220+
template <class A, class B, class C, class... P>
221+
using any_pairs_list_4 = X<int, int>::Y<A, B, C, P...>;
222+
// expected-error@#GH17042_Y {{different length (2 vs. at least 3)}} \
223+
// expected-note@-1 {{requested here}}
224+
225+
static_assert(__is_same(any_pairs_list<char, char>, X<void(int, char), void(int, char)>), "");
226+
227+
static_assert(!__is_same(any_pairs_list<char, char, char>, X<void(int, char), void(int, char)>), "");
228+
// expected-error@#GH17042_Y {{different length (2 vs. 3)}} \
229+
// expected-note@#any_pairs_list {{requested here}} \
230+
// expected-note@-1 {{requested here}}
231+
232+
static_assert(__is_same(any_pairs_list_3<char, char>, X<void(int, char), void(int, char)>), "");
233+
234+
static_assert(!__is_same(any_pairs_list_3<char, char, float>, X<void(int, char), void(int, char)>), "");
235+
// expected-error@#GH17042_Y {{different length (2 vs. 3)}} \
236+
// expected-note@#any_pairs_list_3 {{requested here}} \
237+
// expected-note@-1 {{requested here}}
238+
239+
namespace TemplateTemplateParameters {
240+
template <class... T> struct C {};
241+
242+
template <class T, template <class> class... Args1> struct Ttp {
243+
template <template <class> class... Args2>
244+
using B = C<void(Args1<T>, Args2<T>)...>;
245+
};
246+
template <class> struct D {};
247+
248+
template <template <class> class... Args>
249+
using Alias = Ttp<int, D, D>::B<Args...>;
250+
}
251+
252+
namespace NTTP {
253+
template <int... Args1> struct Nttp {
254+
template <int... Args2> using B = Nttp<(Args1 + Args2)...>;
255+
};
256+
257+
template <int... Args> using Alias = Nttp<1, 2, 3>::B<Args...>;
258+
}
259+
260+
}

0 commit comments

Comments
 (0)