Skip to content
This repository has been archived by the owner on Jan 20, 2024. It is now read-only.

Commit

Permalink
[Clang][Sema] Diagnose unexpanded packs in the template argument list…
Browse files Browse the repository at this point in the history
…s of function template specializations (#76677)

This diagnoses unexpanded packs in the _unqualified-id_ of a function
template specialization's _declarator-id_. For example:
```cpp
template<typename... Ts>
struct A
{
    template<typename U>
    void f();

    template<>
    void f<Ts>(); // error: explicit specialization contains unexpanded parameter pack 'Ts'
};
```

I moved the handling of template-id's so it happens right after we
determine whether we are declaring a function template/function template
specialization so diagnostics are issued in lexical order.
  • Loading branch information
sdkrystian authored Jan 3, 2024
1 parent 7c963fd commit 7fbc1de
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 46 deletions.
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ Improvements to Clang's diagnostics
- Clang now diagnoses definitions of friend function specializations, e.g. ``friend void f<>(int) {}``.
- Clang now diagnoses narrowing conversions involving const references.
(`#63151: <https://github.com/llvm/llvm-project/issues/63151>`_).
- Clang now diagnoses unexpanded packs within the template argument lists of function template specializations.


Improvements to Clang's time-trace
Expand Down
89 changes: 43 additions & 46 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9900,15 +9900,15 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
// Match up the template parameter lists with the scope specifier, then
// determine whether we have a template or a template specialization.
bool Invalid = false;
TemplateIdAnnotation *TemplateId =
D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId
? D.getName().TemplateId
: nullptr;
TemplateParameterList *TemplateParams =
MatchTemplateParametersToScopeSpecifier(
D.getDeclSpec().getBeginLoc(), D.getIdentifierLoc(),
D.getCXXScopeSpec(),
D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId
? D.getName().TemplateId
: nullptr,
TemplateParamLists, isFriend, isMemberSpecialization,
Invalid);
D.getCXXScopeSpec(), TemplateId, TemplateParamLists, isFriend,
isMemberSpecialization, Invalid);
if (TemplateParams) {
// Check that we can declare a template here.
if (CheckTemplateDeclScope(S, TemplateParams))
Expand All @@ -9921,6 +9921,11 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
if (Name.getNameKind() == DeclarationName::CXXDestructorName) {
Diag(NewFD->getLocation(), diag::err_destructor_template);
NewFD->setInvalidDecl();
// Function template with explicit template arguments.
} else if (TemplateId) {
Diag(D.getIdentifierLoc(), diag::err_function_template_partial_spec)
<< SourceRange(TemplateId->LAngleLoc, TemplateId->RAngleLoc);
NewFD->setInvalidDecl();
}

// If we're adding a template to a dependent context, we may need to
Expand Down Expand Up @@ -9973,6 +9978,11 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
<< FixItHint::CreateRemoval(RemoveRange)
<< FixItHint::CreateInsertion(InsertLoc, "<>");
Invalid = true;

// Recover by faking up an empty template argument list.
HasExplicitTemplateArgs = true;
TemplateArgs.setLAngleLoc(InsertLoc);
TemplateArgs.setRAngleLoc(InsertLoc);
}
}
} else {
Expand All @@ -9986,6 +9996,33 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
if (TemplateParamLists.size() > 0)
// For source fidelity, store all the template param lists.
NewFD->setTemplateParameterListsInfo(Context, TemplateParamLists);

// "friend void foo<>(int);" is an implicit specialization decl.
if (isFriend && TemplateId)
isFunctionTemplateSpecialization = true;
}

// If this is a function template specialization and the unqualified-id of
// the declarator-id is a template-id, convert the template argument list
// into our AST format and check for unexpanded packs.
if (isFunctionTemplateSpecialization && TemplateId) {
HasExplicitTemplateArgs = true;

TemplateArgs.setLAngleLoc(TemplateId->LAngleLoc);
TemplateArgs.setRAngleLoc(TemplateId->RAngleLoc);
ASTTemplateArgsPtr TemplateArgsPtr(TemplateId->getTemplateArgs(),
TemplateId->NumArgs);
translateTemplateArguments(TemplateArgsPtr, TemplateArgs);

// FIXME: Should we check for unexpanded packs if this was an (invalid)
// declaration of a function template partial specialization? Should we
// consider the unexpanded pack context to be a partial specialization?
for (const TemplateArgumentLoc &ArgLoc : TemplateArgs.arguments()) {
if (DiagnoseUnexpandedParameterPack(
ArgLoc, isFriend ? UPPC_FriendDeclaration
: UPPC_ExplicitSpecialization))
NewFD->setInvalidDecl();
}
}

if (Invalid) {
Expand Down Expand Up @@ -10438,46 +10475,6 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
diag::ext_operator_new_delete_declared_inline)
<< NewFD->getDeclName();

// If the declarator is a template-id, translate the parser's template
// argument list into our AST format.
if (D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId) {
TemplateIdAnnotation *TemplateId = D.getName().TemplateId;
TemplateArgs.setLAngleLoc(TemplateId->LAngleLoc);
TemplateArgs.setRAngleLoc(TemplateId->RAngleLoc);
ASTTemplateArgsPtr TemplateArgsPtr(TemplateId->getTemplateArgs(),
TemplateId->NumArgs);
translateTemplateArguments(TemplateArgsPtr,
TemplateArgs);

HasExplicitTemplateArgs = true;

if (NewFD->isInvalidDecl()) {
HasExplicitTemplateArgs = false;
} else if (FunctionTemplate) {
// Function template with explicit template arguments.
Diag(D.getIdentifierLoc(), diag::err_function_template_partial_spec)
<< SourceRange(TemplateId->LAngleLoc, TemplateId->RAngleLoc);

HasExplicitTemplateArgs = false;
} else if (isFriend) {
// "friend void foo<>(int);" is an implicit specialization decl.
isFunctionTemplateSpecialization = true;
} else {
assert(isFunctionTemplateSpecialization &&
"should have a 'template<>' for this decl");
}
} else if (isFriend && isFunctionTemplateSpecialization) {
// This combination is only possible in a recovery case; the user
// wrote something like:
// template <> friend void foo(int);
// which we're recovering from as if the user had written:
// friend void foo<>(int);
// Go ahead and fake up a template id.
HasExplicitTemplateArgs = true;
TemplateArgs.setLAngleLoc(D.getIdentifierLoc());
TemplateArgs.setRAngleLoc(D.getIdentifierLoc());
}

// We do not add HD attributes to specializations here because
// they may have different constexpr-ness compared to their
// templates and, after maybeAddCUDAHostDeviceAttrs() is applied,
Expand Down
12 changes: 12 additions & 0 deletions clang/test/CXX/temp/temp.decls/temp.variadic/p5.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,11 @@ namespace Specializations {
template<typename... Ts>
struct PrimaryClass<Ts>; // expected-error{{partial specialization contains unexpanded parameter pack 'Ts'}}

template<typename T, typename... Ts>
void PrimaryFunction();
template<typename T, typename... Ts>
void PrimaryFunction<Ts>(); // expected-error{{function template partial specialization is not allowed}}

#if __cplusplus >= 201402L
template<typename T, typename... Ts>
constexpr int PrimaryVar = 0;
Expand All @@ -392,6 +397,13 @@ namespace Specializations {
template<typename U>
struct InnerClass<U, Ts>; // expected-error{{partial specialization contains unexpanded parameter pack 'Ts'}}

template<typename... Us>
void InnerFunction();
template<>
void InnerFunction<Ts>(); // expected-error{{explicit specialization contains unexpanded parameter pack 'Ts'}}

friend void PrimaryFunction<Ts>(); // expected-error{{friend declaration contains unexpanded parameter pack 'Ts'}}

#if __cplusplus >= 201402L
template<typename... Us>
constexpr static int InnerVar = 0;
Expand Down

0 comments on commit 7fbc1de

Please sign in to comment.