[clang] fix matching of nested template template parameters (#130447)

When checking the template template parameters of template template
parameters, the PartialOrdering context was not correctly propagated.

This also has a few drive-by fixes, such as checking the template
parameter lists of template template parameters, which was previously
missing and would have been it's own bug, but we need to fix it in order
to prevent crashes in error recovery in a simple way.

Fixes #130362
This commit is contained in:
Matheus Izvekov 2025-03-10 10:08:43 -03:00 committed by GitHub
parent 507e0c3b67
commit a1a6a83976
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 61 additions and 44 deletions

View File

@ -292,6 +292,9 @@ Bug Fixes to C++ Support
direct-list-initialized from an array is corrected to direct-initialization.
- Clang no longer crashes when a coroutine is declared ``[[noreturn]]``. (#GH127327)
- Clang now uses the parameter location for abbreviated function templates in ``extern "C"``. (#GH46386)
- Fixes matching of nested template template parameters. (#GH130362)
- Correctly diagnoses template template paramters which have a pack parameter
not in the last position.
- Clang now correctly parses ``if constexpr`` expressions in immediate function context. (#GH123524)
Bug Fixes to AST Handling

View File

@ -11356,14 +11356,16 @@ public:
/// The context in which we are checking a template parameter list.
enum TemplateParamListContext {
TPC_ClassTemplate,
TPC_VarTemplate,
// For this context, Class, Variable, TypeAlias, and non-pack Template
// Template Parameters are treated uniformly.
TPC_Other,
TPC_FunctionTemplate,
TPC_ClassTemplateMember,
TPC_FriendClassTemplate,
TPC_FriendFunctionTemplate,
TPC_FriendFunctionTemplateDefinition,
TPC_TypeAliasTemplate
TPC_TemplateTemplateParameterPack,
};
/// Checks the validity of a template parameter list, possibly

View File

@ -8154,7 +8154,7 @@ NamedDecl *Sema::ActOnVariableDeclarator(
(D.getCXXScopeSpec().isSet() && DC && DC->isRecord() &&
DC->isDependentContext())
? TPC_ClassTemplateMember
: TPC_VarTemplate))
: TPC_Other))
NewVD->setInvalidDecl();
// If we are providing an explicit specialization of a static variable

View File

@ -13586,7 +13586,7 @@ Decl *Sema::ActOnAliasDeclaration(Scope *S, AccessSpecifier AS,
// Merge any previous default template arguments into our parameters,
// and check the parameter list.
if (CheckTemplateParameterList(TemplateParams, OldTemplateParams,
TPC_TypeAliasTemplate))
TPC_Other))
return nullptr;
TypeAliasTemplateDecl *NewDecl =

View File

@ -1591,8 +1591,16 @@ NamedDecl *Sema::ActOnTemplateTemplateParameter(
assert(S->isTemplateParamScope() &&
"Template template parameter not in template parameter scope!");
// Construct the parameter object.
bool IsParameterPack = EllipsisLoc.isValid();
bool Invalid = false;
if (CheckTemplateParameterList(
Params,
/*OldParams=*/nullptr,
IsParameterPack ? TPC_TemplateTemplateParameterPack : TPC_Other))
Invalid = true;
// Construct the parameter object.
TemplateTemplateParmDecl *Param = TemplateTemplateParmDecl::Create(
Context, Context.getTranslationUnitDecl(),
NameLoc.isInvalid() ? TmpLoc : NameLoc, Depth, Position, IsParameterPack,
@ -1615,9 +1623,12 @@ NamedDecl *Sema::ActOnTemplateTemplateParameter(
if (Params->size() == 0) {
Diag(Param->getLocation(), diag::err_template_template_parm_no_parms)
<< SourceRange(Params->getLAngleLoc(), Params->getRAngleLoc());
Param->setInvalidDecl();
Invalid = true;
}
if (Invalid)
Param->setInvalidDecl();
// C++0x [temp.param]p9:
// A default template-argument may be specified for any kind of
// template-parameter that is not a template parameter pack.
@ -2066,7 +2077,7 @@ DeclResult Sema::CheckClassTemplate(
SemanticContext->isDependentContext())
? TPC_ClassTemplateMember
: TUK == TagUseKind::Friend ? TPC_FriendClassTemplate
: TPC_ClassTemplate,
: TPC_Other,
SkipBody))
Invalid = true;
@ -2208,9 +2219,8 @@ static bool DiagnoseDefaultTemplateArgument(Sema &S,
SourceLocation ParamLoc,
SourceRange DefArgRange) {
switch (TPC) {
case Sema::TPC_ClassTemplate:
case Sema::TPC_VarTemplate:
case Sema::TPC_TypeAliasTemplate:
case Sema::TPC_Other:
case Sema::TPC_TemplateTemplateParameterPack:
return false;
case Sema::TPC_FunctionTemplate:
@ -2383,8 +2393,11 @@ bool Sema::CheckTemplateParameterList(TemplateParameterList *NewParams,
MissingDefaultArg = true;
} else if (NonTypeTemplateParmDecl *NewNonTypeParm
= dyn_cast<NonTypeTemplateParmDecl>(*NewParam)) {
// Check for unexpanded parameter packs.
if (!NewNonTypeParm->isParameterPack() &&
// Check for unexpanded parameter packs, except in a template template
// parameter pack, as in those any unexpanded packs should be expanded
// along with the parameter itself.
if (TPC != TPC_TemplateTemplateParameterPack &&
!NewNonTypeParm->isParameterPack() &&
DiagnoseUnexpandedParameterPack(NewNonTypeParm->getLocation(),
NewNonTypeParm->getTypeSourceInfo(),
UPPC_NonTypeTemplateParameterType)) {
@ -2492,8 +2505,7 @@ bool Sema::CheckTemplateParameterList(TemplateParameterList *NewParams,
// If a template parameter of a primary class template or alias template
// is a template parameter pack, it shall be the last template parameter.
if (SawParameterPack && (NewParam + 1) != NewParamEnd &&
(TPC == TPC_ClassTemplate || TPC == TPC_VarTemplate ||
TPC == TPC_TypeAliasTemplate)) {
(TPC == TPC_Other || TPC == TPC_TemplateTemplateParameterPack)) {
Diag((*NewParam)->getLocation(),
diag::err_template_param_pack_must_be_last_template_parameter);
Invalid = true;
@ -2526,8 +2538,8 @@ bool Sema::CheckTemplateParameterList(TemplateParameterList *NewParams,
<< PrevModuleName;
Invalid = true;
} else if (MissingDefaultArg &&
(TPC == TPC_ClassTemplate || TPC == TPC_FriendClassTemplate ||
TPC == TPC_VarTemplate || TPC == TPC_TypeAliasTemplate)) {
(TPC == TPC_Other || TPC == TPC_TemplateTemplateParameterPack ||
TPC == TPC_FriendClassTemplate)) {
// C++ 23[temp.param]p14:
// If a template-parameter of a class template, variable template, or
// alias template has a default template argument, each subsequent

View File

@ -3427,9 +3427,9 @@ static TemplateDeductionResult FinishTemplateArgumentDeduction(
if (!P.isPackExpansion() && !A.isPackExpansion()) {
Info.Param =
makeTemplateParameter(Template->getTemplateParameters()->getParam(
(PsStack.empty() ? TemplateArgs.end()
: PsStack.front().begin()) -
TemplateArgs.begin()));
(AsStack.empty() ? CTAI.CanonicalConverted.end()
: AsStack.front().begin()) -
1 - CTAI.CanonicalConverted.begin()));
Info.FirstArg = P;
Info.SecondArg = A;
return TemplateDeductionResult::NonDeducedMismatch;
@ -6642,17 +6642,19 @@ bool Sema::isTemplateTemplateParameterAtLeastAsSpecializedAs(
TemplateDeductionResult TDK;
runWithSufficientStackSpace(Info.getLocation(), [&] {
TDK = ::FinishTemplateArgumentDeduction(
*this, AArg, /*IsPartialOrdering=*/true, PArgs, Deduced, Info);
TDK = ::FinishTemplateArgumentDeduction(*this, AArg, PartialOrdering, PArgs,
Deduced, Info);
});
switch (TDK) {
case TemplateDeductionResult::Success:
return true;
// It doesn't seem possible to get a non-deduced mismatch when partial
// ordering TTPs.
// ordering TTPs, except with an invalid template parameter list which has
// a parameter after a pack.
case TemplateDeductionResult::NonDeducedMismatch:
llvm_unreachable("Unexpected NonDeducedMismatch");
assert(PArg->isInvalidDecl() && "Unexpected NonDeducedMismatch");
return false;
// Substitution failures should have already been diagnosed.
case TemplateDeductionResult::AlreadyDiagnosed:

View File

@ -2176,7 +2176,7 @@ Decl *TemplateDeclInstantiator::VisitClassTemplateDecl(ClassTemplateDecl *D) {
// Do some additional validation, then merge default arguments
// from the existing declarations.
if (SemaRef.CheckTemplateParameterList(InstParams, PrevParams,
Sema::TPC_ClassTemplate))
Sema::TPC_Other))
return nullptr;
Inst->setAccess(PrevClassTemplate->getAccess());

View File

@ -602,6 +602,11 @@ namespace regression3 {
template <class...> class B {};
template struct A<B, Node<None>>;
} // namespace regression3
namespace GH130362 {
template <template <template <class... T1> class TT1> class TT2> struct A {};
template <template <class U1> class UU1> struct B {};
template struct A<B>;
} // namespace GH130362
namespace nttp_auto {
namespace t1 {
@ -610,24 +615,16 @@ namespace nttp_auto {
template struct A<B>;
} // namespace t1
namespace t2 {
// FIXME: Shouldn't accept parameters after a parameter pack.
template<template<auto... Va1, auto Va2> class> struct A {};
// expected-error@-1 {{deduced non-type template argument does not have the same type as the corresponding template parameter ('auto' vs 'int')}}
// expected-note@-2 {{previous template template parameter is here}}
// expected-error@-1 {{template parameter pack must be the last template parameter}}
template<int... Vi> struct B;
// expected-note@-1 {{template parameter is declared here}}
template struct A<B>;
// expected-note@-1 {{different template parameters}}
} // namespace t2
namespace t3 {
// FIXME: Shouldn't accept parameters after a parameter pack.
template<template<auto... Va1, auto... Va2> class> struct A {};
// expected-error@-1 {{deduced non-type template argument does not have the same type as the corresponding template parameter ('auto' vs 'int')}}
// expected-note@-2 {{previous template template parameter is here}}
// expected-error@-1 {{template parameter pack must be the last template parameter}}
template<int... Vi> struct B;
// expected-note@-1 {{template parameter is declared here}}
template struct A<B>;
// expected-note@-1 {{different template parameters}}
} // namespace t3
} // namespace nttp_auto

View File

@ -7,7 +7,8 @@
template<template<int> typename> struct Ti; // #Ti
template<template<int...> typename> struct TPi; // #TPi
template<template<int, int...> typename> struct TiPi;
template<template<int..., int...> typename> struct TPiPi; // FIXME: Why is this not ill-formed?
template<template<int..., int...> typename> struct TPiPi;
// expected-error@-1 {{template parameter pack must be the last template parameter}}
template<typename T, template<T> typename> struct tT0; // #tT0
template<template<typename T, T> typename> struct Tt0; // #Tt0

View File

@ -1196,21 +1196,21 @@ TEST(DeclPrinter, TestUnnamedTemplateParameters) {
}
TEST(DeclPrinter, TestUnnamedTemplateParametersPacks) {
ASSERT_TRUE(PrintedDeclCXX17Matches(
"template <typename ..., int ...,"
" template <typename ..., bool ...> class ...> void A();",
functionTemplateDecl(hasName("A")).bind("id"),
"template <typename ..., int ...,"
" template <typename ..., bool ...> class ...> void A()"));
ASSERT_TRUE(
PrintedDeclCXX17Matches("template <typename ..., int ...,"
" template <typename ...> class ...> void A();",
functionTemplateDecl(hasName("A")).bind("id"),
"template <typename ..., int ...,"
" template <typename ...> class ...> void A()"));
}
TEST(DeclPrinter, TestNamedTemplateParametersPacks) {
ASSERT_TRUE(PrintedDeclCXX17Matches(
"template <typename ...T, int ...I,"
" template <typename ...X, bool ...B> class ...Z> void A();",
" template <typename ...X> class ...Z> void A();",
functionTemplateDecl(hasName("A")).bind("id"),
"template <typename ...T, int ...I,"
" template <typename ...X, bool ...B> class ...Z> void A()"));
" template <typename ...X> class ...Z> void A()"));
}
TEST(DeclPrinter, TestTemplateTemplateParameterWrittenWithTypename) {