Skip to content

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

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 1 commit into from
Mar 10, 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
7 changes: 5 additions & 2 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ related warnings within the method body.
``__attribute__((model("large")))`` on non-TLS globals in x86-64 compilations.
This forces the global to be considered small or large in regards to the
x86-64 code model, regardless of the code model specified for the compilation.
- Clang now emits a warning ``-Wreserved-init-priority`` instead of a hard error
when ``__attribute__((init_priority(n)))`` is used with values of n in the
- Clang now emits a warning ``-Wreserved-init-priority`` instead of a hard error
when ``__attribute__((init_priority(n)))`` is used with values of n in the
reserved range [0, 100]. The warning will be treated as an error by default.

- There is a new ``format_matches`` attribute to complement the existing
Expand Down Expand Up @@ -294,6 +294,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
Expand Down
8 changes: 5 additions & 3 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -11363,14 +11363,16 @@ class Sema final : public SemaBase {

/// 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
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8165,7 +8165,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
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
36 changes: 24 additions & 12 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -2066,7 +2077,7 @@ DeclResult Sema::CheckClassTemplate(
SemanticContext->isDependentContext())
? TPC_ClassTemplateMember
: TUK == TagUseKind::Friend ? TPC_FriendClassTemplate
: TPC_ClassTemplate,
: TPC_Other,
SkipBody))
Invalid = true;

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
16 changes: 9 additions & 7 deletions clang/lib/Sema/SemaTemplateDeduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
17 changes: 7 additions & 10 deletions clang/test/SemaTemplate/cwg2398.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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

Expand Down
3 changes: 2 additions & 1 deletion clang/test/SemaTemplate/temp_arg_template_p0522.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions clang/unittests/AST/DeclPrinterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down