Skip to content

Commit

Permalink
[Clang][Sema] Diagnose variable template explicit specializations wit…
Browse files Browse the repository at this point in the history
…h storage-class-specifiers (llvm#93873)

According to [temp.expl.spec] p2:
> The declaration in an _explicit-specialization_ shall not be an
_export-declaration_. An explicit specialization shall not use a
_storage-class-specifier_ other than `thread_local`.

Clang partially implements this, but a number of issues exist:
1. We don't diagnose class scope explicit specializations of variable
templates with _storage-class-specifiers_, e.g.
    ```
    struct A
    {
        template<typename T>
        static constexpr int x = 0;

        template<>
static constexpr int x<void> = 1; // ill-formed, but clang accepts
    };
    ````
2. We incorrectly reject class scope explicit specializations of
variable templates when `static` is not used, e.g.
    ```
    struct A
    {
        template<typename T>
        static constexpr int x = 0;

        template<>
constexpr int x<void> = 1; // error: non-static data member cannot be
constexpr; did you intend to make it static?
    };
    ````
3. We don't diagnose dependent class scope explicit specializations of
function templates with storage class specifiers, e.g.
    ```
    template<typename T>
    struct A
    {
        template<typename U>
        static void f();

        template<>
        static void f<int>(); // ill-formed, but clang accepts
    };
    ````

This patch addresses these issues as follows:
- # 1 is fixed by issuing a diagnostic when an explicit
specialization of a variable template has storage class specifier
- # 2 is fixed by considering any non-function declaration with any
template parameter lists at class scope to be a static data member. This
also allows for better error recovery (it's more likely the user
intended to declare a variable template than a "field template").
- # 3 is fixed by checking whether a function template explicit
specialization has a storage class specifier even when the primary
template is not yet known.

One thing to note is that it would be far simpler to diagnose this when
parsing the _decl-specifier-seq_, but such an implementation would
necessitate a refactor of `ParsedTemplateInfo` which I believe to be
outside the scope of this patch.

(cherry-picked from commit 9a88aa0)

Change-Id: I83a3158e50af6550db031c7b45d772bc9ceda487
  • Loading branch information
sdkrystian authored and ronlieb committed Sep 23, 2024
1 parent 65fd8ab commit 1f2c2c0
Show file tree
Hide file tree
Showing 18 changed files with 234 additions and 224 deletions.
3 changes: 0 additions & 3 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -5423,9 +5423,6 @@ def err_not_class_template_specialization : Error<
"parameter}0">;
def ext_explicit_specialization_storage_class : ExtWarn<
"explicit specialization cannot have a storage class">, InGroup<ExplicitSpecializationStorageClass>;
def err_explicit_specialization_inconsistent_storage_class : Error<
"explicit specialization has extraneous, inconsistent storage class "
"'%select{none|extern|static|__private_extern__|auto|register}0'">;
def err_dependent_function_template_spec_no_match : Error<
"no candidate function template was found for dependent"
" %select{member|friend}0 function template specialization">;
Expand Down
5 changes: 3 additions & 2 deletions clang/lib/Parse/ParseDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3317,7 +3317,8 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
DeclSpec::SCS_static &&
DeclaratorInfo.getDeclSpec().getStorageClassSpec() !=
DeclSpec::SCS_typedef &&
!DS.isFriendSpecified()) {
!DS.isFriendSpecified() &&
TemplateInfo.Kind == ParsedTemplateInfo::NonTemplate) {
// It's a default member initializer.
if (BitfieldSize.get())
Diag(Tok, getLangOpts().CPlusPlus20
Expand Down Expand Up @@ -3416,7 +3417,7 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
} else if (ThisDecl)
Actions.AddInitializerToDecl(ThisDecl, Init.get(),
EqualLoc.isInvalid());
} else if (ThisDecl && DS.getStorageClassSpec() == DeclSpec::SCS_static)
} else if (ThisDecl && DeclaratorInfo.isStaticMember())
// No initializer.
Actions.ActOnUninitializedDecl(ThisDecl);

Expand Down
1 change: 1 addition & 0 deletions clang/lib/Sema/DeclSpec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ bool Declarator::isDeclarationOfFunction() const {
bool Declarator::isStaticMember() {
assert(getContext() == DeclaratorContext::Member);
return getDeclSpec().getStorageClassSpec() == DeclSpec::SCS_static ||
(!isDeclarationOfFunction() && !getTemplateParameterLists().empty()) ||
(getName().getKind() == UnqualifiedIdKind::IK_OperatorFunctionId &&
CXXMethodDecl::isStaticOverloadedOperator(
getName().OperatorFunctionId.Operator));
Expand Down
248 changes: 134 additions & 114 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7494,89 +7494,16 @@ NamedDecl *Sema::ActOnVariableDeclarator(
NTCUC_AutoVar, NTCUK_Destruct);
} else {
bool Invalid = false;

if (DC->isRecord() && !CurContext->isRecord()) {
// This is an out-of-line definition of a static data member.
switch (SC) {
case SC_None:
break;
case SC_Static:
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
diag::err_static_out_of_line)
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
break;
case SC_Auto:
case SC_Register:
case SC_Extern:
// [dcl.stc] p2: The auto or register specifiers shall be applied only
// to names of variables declared in a block or to function parameters.
// [dcl.stc] p6: The extern specifier cannot be used in the declaration
// of class members

Diag(D.getDeclSpec().getStorageClassSpecLoc(),
diag::err_storage_class_for_static_member)
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
break;
case SC_PrivateExtern:
llvm_unreachable("C storage class in c++!");
}
}

if (SC == SC_Static && CurContext->isRecord()) {
if (const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(DC)) {
// Walk up the enclosing DeclContexts to check for any that are
// incompatible with static data members.
const DeclContext *FunctionOrMethod = nullptr;
const CXXRecordDecl *AnonStruct = nullptr;
for (DeclContext *Ctxt = DC; Ctxt; Ctxt = Ctxt->getParent()) {
if (Ctxt->isFunctionOrMethod()) {
FunctionOrMethod = Ctxt;
break;
}
const CXXRecordDecl *ParentDecl = dyn_cast<CXXRecordDecl>(Ctxt);
if (ParentDecl && !ParentDecl->getDeclName()) {
AnonStruct = ParentDecl;
break;
}
}
if (FunctionOrMethod) {
// C++ [class.static.data]p5: A local class shall not have static data
// members.
Diag(D.getIdentifierLoc(),
diag::err_static_data_member_not_allowed_in_local_class)
<< Name << RD->getDeclName()
<< llvm::to_underlying(RD->getTagKind());
} else if (AnonStruct) {
// C++ [class.static.data]p4: Unnamed classes and classes contained
// directly or indirectly within unnamed classes shall not contain
// static data members.
Diag(D.getIdentifierLoc(),
diag::err_static_data_member_not_allowed_in_anon_struct)
<< Name << llvm::to_underlying(AnonStruct->getTagKind());
Invalid = true;
} else if (RD->isUnion()) {
// C++98 [class.union]p1: If a union contains a static data member,
// the program is ill-formed. C++11 drops this restriction.
Diag(D.getIdentifierLoc(),
getLangOpts().CPlusPlus11
? diag::warn_cxx98_compat_static_data_member_in_union
: diag::ext_static_data_member_in_union) << Name;
}
}
}

// Match up the template parameter lists with the scope specifier, then
// determine whether we have a template or a template specialization.
bool InvalidScope = false;
TemplateParams = MatchTemplateParametersToScopeSpecifier(
D.getDeclSpec().getBeginLoc(), D.getIdentifierLoc(),
D.getCXXScopeSpec(),
D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId
? D.getName().TemplateId
: nullptr,
TemplateParamLists,
/*never a friend*/ false, IsMemberSpecialization, InvalidScope);
Invalid |= InvalidScope;
/*never a friend*/ false, IsMemberSpecialization, Invalid);

if (TemplateParams) {
if (DC->isDependentContext()) {
Expand Down Expand Up @@ -7625,6 +7552,102 @@ NamedDecl *Sema::ActOnVariableDeclarator(
"should have a 'template<>' for this decl");
}

bool IsExplicitSpecialization =
IsVariableTemplateSpecialization && !IsPartialSpecialization;

// C++ [temp.expl.spec]p2:
// The declaration in an explicit-specialization shall not be an
// export-declaration. An explicit specialization shall not use a
// storage-class-specifier other than thread_local.
//
// We use the storage-class-specifier from DeclSpec because we may have
// added implicit 'extern' for declarations with __declspec(dllimport)!
if (SCSpec != DeclSpec::SCS_unspecified &&
(IsExplicitSpecialization || IsMemberSpecialization)) {
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
diag::ext_explicit_specialization_storage_class)
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
}

if (CurContext->isRecord()) {
if (SC == SC_Static) {
if (const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(DC)) {
// Walk up the enclosing DeclContexts to check for any that are
// incompatible with static data members.
const DeclContext *FunctionOrMethod = nullptr;
const CXXRecordDecl *AnonStruct = nullptr;
for (DeclContext *Ctxt = DC; Ctxt; Ctxt = Ctxt->getParent()) {
if (Ctxt->isFunctionOrMethod()) {
FunctionOrMethod = Ctxt;
break;
}
const CXXRecordDecl *ParentDecl = dyn_cast<CXXRecordDecl>(Ctxt);
if (ParentDecl && !ParentDecl->getDeclName()) {
AnonStruct = ParentDecl;
break;
}
}
if (FunctionOrMethod) {
// C++ [class.static.data]p5: A local class shall not have static
// data members.
Diag(D.getIdentifierLoc(),
diag::err_static_data_member_not_allowed_in_local_class)
<< Name << RD->getDeclName()
<< llvm::to_underlying(RD->getTagKind());
} else if (AnonStruct) {
// C++ [class.static.data]p4: Unnamed classes and classes contained
// directly or indirectly within unnamed classes shall not contain
// static data members.
Diag(D.getIdentifierLoc(),
diag::err_static_data_member_not_allowed_in_anon_struct)
<< Name << llvm::to_underlying(AnonStruct->getTagKind());
Invalid = true;
} else if (RD->isUnion()) {
// C++98 [class.union]p1: If a union contains a static data member,
// the program is ill-formed. C++11 drops this restriction.
Diag(D.getIdentifierLoc(),
getLangOpts().CPlusPlus11
? diag::warn_cxx98_compat_static_data_member_in_union
: diag::ext_static_data_member_in_union)
<< Name;
}
}
} else if (IsVariableTemplate || IsPartialSpecialization) {
// There is no such thing as a member field template.
Diag(D.getIdentifierLoc(), diag::err_template_member)
<< II << TemplateParams->getSourceRange();
// Recover by pretending this is a static data member template.
SC = SC_Static;
}
} else if (DC->isRecord()) {
// This is an out-of-line definition of a static data member.
switch (SC) {
case SC_None:
break;
case SC_Static:
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
diag::err_static_out_of_line)
<< FixItHint::CreateRemoval(
D.getDeclSpec().getStorageClassSpecLoc());
break;
case SC_Auto:
case SC_Register:
case SC_Extern:
// [dcl.stc] p2: The auto or register specifiers shall be applied only
// to names of variables declared in a block or to function parameters.
// [dcl.stc] p6: The extern specifier cannot be used in the declaration
// of class members

Diag(D.getDeclSpec().getStorageClassSpecLoc(),
diag::err_storage_class_for_static_member)
<< FixItHint::CreateRemoval(
D.getDeclSpec().getStorageClassSpecLoc());
break;
case SC_PrivateExtern:
llvm_unreachable("C storage class in c++!");
}
}

if (IsVariableTemplateSpecialization) {
SourceLocation TemplateKWLoc =
TemplateParamLists.size() > 0
Expand Down Expand Up @@ -7670,8 +7693,6 @@ NamedDecl *Sema::ActOnVariableDeclarator(
// the variable (matching the scope specifier), store them.
// An explicit variable template specialization does not own any template
// parameter lists.
bool IsExplicitSpecialization =
IsVariableTemplateSpecialization && !IsPartialSpecialization;
unsigned VDTemplateParamLists =
(TemplateParams && !IsExplicitSpecialization) ? 1 : 0;
if (TemplateParamLists.size() > VDTemplateParamLists)
Expand Down Expand Up @@ -10073,25 +10094,45 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
NewFD->setImplicitlyInline(ImplicitInlineCXX20);
}

if (SC == SC_Static && isa<CXXMethodDecl>(NewFD) &&
!CurContext->isRecord()) {
// C++ [class.static]p1:
// A data or function member of a class may be declared static
// in a class definition, in which case it is a static member of
// the class.
if (!isFriend && SC != SC_None) {
// C++ [temp.expl.spec]p2:
// The declaration in an explicit-specialization shall not be an
// export-declaration. An explicit specialization shall not use a
// storage-class-specifier other than thread_local.
//
// We diagnose friend declarations with storage-class-specifiers
// elsewhere.
if (isFunctionTemplateSpecialization || isMemberSpecialization) {
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
diag::ext_explicit_specialization_storage_class)
<< FixItHint::CreateRemoval(
D.getDeclSpec().getStorageClassSpecLoc());
}

// Complain about the 'static' specifier if it's on an out-of-line
// member function definition.
if (SC == SC_Static && !CurContext->isRecord() && DC->isRecord()) {
assert(isa<CXXMethodDecl>(NewFD) &&
"Out-of-line member function should be a CXXMethodDecl");
// C++ [class.static]p1:
// A data or function member of a class may be declared static
// in a class definition, in which case it is a static member of
// the class.

// MSVC permits the use of a 'static' storage specifier on an out-of-line
// member function template declaration and class member template
// declaration (MSVC versions before 2015), warn about this.
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
((!getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2015) &&
cast<CXXRecordDecl>(DC)->getDescribedClassTemplate()) ||
(getLangOpts().MSVCCompat && NewFD->getDescribedFunctionTemplate()))
? diag::ext_static_out_of_line : diag::err_static_out_of_line)
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
// Complain about the 'static' specifier if it's on an out-of-line
// member function definition.

// MSVC permits the use of a 'static' storage specifier on an
// out-of-line member function template declaration and class member
// template declaration (MSVC versions before 2015), warn about this.
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
((!getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2015) &&
cast<CXXRecordDecl>(DC)->getDescribedClassTemplate()) ||
(getLangOpts().MSVCCompat &&
NewFD->getDescribedFunctionTemplate()))
? diag::ext_static_out_of_line
: diag::err_static_out_of_line)
<< FixItHint::CreateRemoval(
D.getDeclSpec().getStorageClassSpecLoc());
}
}

// C++11 [except.spec]p15:
Expand Down Expand Up @@ -10459,27 +10500,6 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
Previous))
NewFD->setInvalidDecl();
}

// C++ [dcl.stc]p1:
// A storage-class-specifier shall not be specified in an explicit
// specialization (14.7.3)
// FIXME: We should be checking this for dependent specializations.
FunctionTemplateSpecializationInfo *Info =
NewFD->getTemplateSpecializationInfo();
if (Info && SC != SC_None) {
if (SC != Info->getTemplate()->getTemplatedDecl()->getStorageClass())
Diag(NewFD->getLocation(),
diag::err_explicit_specialization_inconsistent_storage_class)
<< SC
<< FixItHint::CreateRemoval(
D.getDeclSpec().getStorageClassSpecLoc());

else
Diag(NewFD->getLocation(),
diag::ext_explicit_specialization_storage_class)
<< FixItHint::CreateRemoval(
D.getDeclSpec().getStorageClassSpecLoc());
}
} else if (isMemberSpecialization && !FunctionTemplate) {
if (CheckMemberSpecialization(NewFD, Previous))
NewFD->setInvalidDecl();
Expand Down
28 changes: 3 additions & 25 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3413,9 +3413,9 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
break;
}

bool isInstField = ((DS.getStorageClassSpec() == DeclSpec::SCS_unspecified ||
DS.getStorageClassSpec() == DeclSpec::SCS_mutable) &&
!isFunc);
bool isInstField = (DS.getStorageClassSpec() == DeclSpec::SCS_unspecified ||
DS.getStorageClassSpec() == DeclSpec::SCS_mutable) &&
!isFunc && TemplateParameterLists.empty();

if (DS.hasConstexprSpecifier() && isInstField) {
SemaDiagnosticBuilder B =
Expand Down Expand Up @@ -3464,28 +3464,6 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
}

IdentifierInfo *II = Name.getAsIdentifierInfo();

// Member field could not be with "template" keyword.
// So TemplateParameterLists should be empty in this case.
if (TemplateParameterLists.size()) {
TemplateParameterList* TemplateParams = TemplateParameterLists[0];
if (TemplateParams->size()) {
// There is no such thing as a member field template.
Diag(D.getIdentifierLoc(), diag::err_template_member)
<< II
<< SourceRange(TemplateParams->getTemplateLoc(),
TemplateParams->getRAngleLoc());
} else {
// There is an extraneous 'template<>' for this member.
Diag(TemplateParams->getTemplateLoc(),
diag::err_template_member_noparams)
<< II
<< SourceRange(TemplateParams->getTemplateLoc(),
TemplateParams->getRAngleLoc());
}
return nullptr;
}

if (D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId) {
Diag(D.getIdentifierLoc(), diag::err_member_with_template_arguments)
<< II
Expand Down
5 changes: 3 additions & 2 deletions clang/test/CXX/dcl.dcl/dcl.spec/dcl.stc/p1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ template<typename T> void f(T) {}
template<typename T> static void g(T) {}


template<> static void f<int>(int); // expected-error{{explicit specialization has extraneous, inconsistent storage class 'static'}}
template<> static void f<int>(int); // expected-warning{{explicit specialization cannot have a storage class}}
template static void f<float>(float); // expected-error{{explicit instantiation cannot have a storage class}}

template<> void f<double>(double);
Expand All @@ -29,4 +29,5 @@ int X<T>::value = 17;

template static int X<int>::value; // expected-error{{explicit instantiation cannot have a storage class}}

template<> static int X<float>::value; // expected-error{{'static' can only be specified inside the class definition}}
template<> static int X<float>::value; // expected-warning{{explicit specialization cannot have a storage class}}
// expected-error@-1{{'static' can only be specified inside the class definition}}
Loading

0 comments on commit 1f2c2c0

Please sign in to comment.