Skip to content

Commit 1f2c2c0

Browse files
sdkrystianronlieb
authored andcommitted
[Clang][Sema] Diagnose variable template explicit specializations with 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
1 parent 65fd8ab commit 1f2c2c0

File tree

18 files changed

+234
-224
lines changed

18 files changed

+234
-224
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5423,9 +5423,6 @@ def err_not_class_template_specialization : Error<
54235423
"parameter}0">;
54245424
def ext_explicit_specialization_storage_class : ExtWarn<
54255425
"explicit specialization cannot have a storage class">, InGroup<ExplicitSpecializationStorageClass>;
5426-
def err_explicit_specialization_inconsistent_storage_class : Error<
5427-
"explicit specialization has extraneous, inconsistent storage class "
5428-
"'%select{none|extern|static|__private_extern__|auto|register}0'">;
54295426
def err_dependent_function_template_spec_no_match : Error<
54305427
"no candidate function template was found for dependent"
54315428
" %select{member|friend}0 function template specialization">;

clang/lib/Parse/ParseDeclCXX.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3317,7 +3317,8 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
33173317
DeclSpec::SCS_static &&
33183318
DeclaratorInfo.getDeclSpec().getStorageClassSpec() !=
33193319
DeclSpec::SCS_typedef &&
3320-
!DS.isFriendSpecified()) {
3320+
!DS.isFriendSpecified() &&
3321+
TemplateInfo.Kind == ParsedTemplateInfo::NonTemplate) {
33213322
// It's a default member initializer.
33223323
if (BitfieldSize.get())
33233324
Diag(Tok, getLangOpts().CPlusPlus20
@@ -3416,7 +3417,7 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
34163417
} else if (ThisDecl)
34173418
Actions.AddInitializerToDecl(ThisDecl, Init.get(),
34183419
EqualLoc.isInvalid());
3419-
} else if (ThisDecl && DS.getStorageClassSpec() == DeclSpec::SCS_static)
3420+
} else if (ThisDecl && DeclaratorInfo.isStaticMember())
34203421
// No initializer.
34213422
Actions.ActOnUninitializedDecl(ThisDecl);
34223423

clang/lib/Sema/DeclSpec.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ bool Declarator::isDeclarationOfFunction() const {
418418
bool Declarator::isStaticMember() {
419419
assert(getContext() == DeclaratorContext::Member);
420420
return getDeclSpec().getStorageClassSpec() == DeclSpec::SCS_static ||
421+
(!isDeclarationOfFunction() && !getTemplateParameterLists().empty()) ||
421422
(getName().getKind() == UnqualifiedIdKind::IK_OperatorFunctionId &&
422423
CXXMethodDecl::isStaticOverloadedOperator(
423424
getName().OperatorFunctionId.Operator));

clang/lib/Sema/SemaDecl.cpp

Lines changed: 134 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -7494,89 +7494,16 @@ NamedDecl *Sema::ActOnVariableDeclarator(
74947494
NTCUC_AutoVar, NTCUK_Destruct);
74957495
} else {
74967496
bool Invalid = false;
7497-
7498-
if (DC->isRecord() && !CurContext->isRecord()) {
7499-
// This is an out-of-line definition of a static data member.
7500-
switch (SC) {
7501-
case SC_None:
7502-
break;
7503-
case SC_Static:
7504-
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
7505-
diag::err_static_out_of_line)
7506-
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
7507-
break;
7508-
case SC_Auto:
7509-
case SC_Register:
7510-
case SC_Extern:
7511-
// [dcl.stc] p2: The auto or register specifiers shall be applied only
7512-
// to names of variables declared in a block or to function parameters.
7513-
// [dcl.stc] p6: The extern specifier cannot be used in the declaration
7514-
// of class members
7515-
7516-
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
7517-
diag::err_storage_class_for_static_member)
7518-
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
7519-
break;
7520-
case SC_PrivateExtern:
7521-
llvm_unreachable("C storage class in c++!");
7522-
}
7523-
}
7524-
7525-
if (SC == SC_Static && CurContext->isRecord()) {
7526-
if (const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(DC)) {
7527-
// Walk up the enclosing DeclContexts to check for any that are
7528-
// incompatible with static data members.
7529-
const DeclContext *FunctionOrMethod = nullptr;
7530-
const CXXRecordDecl *AnonStruct = nullptr;
7531-
for (DeclContext *Ctxt = DC; Ctxt; Ctxt = Ctxt->getParent()) {
7532-
if (Ctxt->isFunctionOrMethod()) {
7533-
FunctionOrMethod = Ctxt;
7534-
break;
7535-
}
7536-
const CXXRecordDecl *ParentDecl = dyn_cast<CXXRecordDecl>(Ctxt);
7537-
if (ParentDecl && !ParentDecl->getDeclName()) {
7538-
AnonStruct = ParentDecl;
7539-
break;
7540-
}
7541-
}
7542-
if (FunctionOrMethod) {
7543-
// C++ [class.static.data]p5: A local class shall not have static data
7544-
// members.
7545-
Diag(D.getIdentifierLoc(),
7546-
diag::err_static_data_member_not_allowed_in_local_class)
7547-
<< Name << RD->getDeclName()
7548-
<< llvm::to_underlying(RD->getTagKind());
7549-
} else if (AnonStruct) {
7550-
// C++ [class.static.data]p4: Unnamed classes and classes contained
7551-
// directly or indirectly within unnamed classes shall not contain
7552-
// static data members.
7553-
Diag(D.getIdentifierLoc(),
7554-
diag::err_static_data_member_not_allowed_in_anon_struct)
7555-
<< Name << llvm::to_underlying(AnonStruct->getTagKind());
7556-
Invalid = true;
7557-
} else if (RD->isUnion()) {
7558-
// C++98 [class.union]p1: If a union contains a static data member,
7559-
// the program is ill-formed. C++11 drops this restriction.
7560-
Diag(D.getIdentifierLoc(),
7561-
getLangOpts().CPlusPlus11
7562-
? diag::warn_cxx98_compat_static_data_member_in_union
7563-
: diag::ext_static_data_member_in_union) << Name;
7564-
}
7565-
}
7566-
}
7567-
75687497
// Match up the template parameter lists with the scope specifier, then
75697498
// determine whether we have a template or a template specialization.
7570-
bool InvalidScope = false;
75717499
TemplateParams = MatchTemplateParametersToScopeSpecifier(
75727500
D.getDeclSpec().getBeginLoc(), D.getIdentifierLoc(),
75737501
D.getCXXScopeSpec(),
75747502
D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId
75757503
? D.getName().TemplateId
75767504
: nullptr,
75777505
TemplateParamLists,
7578-
/*never a friend*/ false, IsMemberSpecialization, InvalidScope);
7579-
Invalid |= InvalidScope;
7506+
/*never a friend*/ false, IsMemberSpecialization, Invalid);
75807507

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

7555+
bool IsExplicitSpecialization =
7556+
IsVariableTemplateSpecialization && !IsPartialSpecialization;
7557+
7558+
// C++ [temp.expl.spec]p2:
7559+
// The declaration in an explicit-specialization shall not be an
7560+
// export-declaration. An explicit specialization shall not use a
7561+
// storage-class-specifier other than thread_local.
7562+
//
7563+
// We use the storage-class-specifier from DeclSpec because we may have
7564+
// added implicit 'extern' for declarations with __declspec(dllimport)!
7565+
if (SCSpec != DeclSpec::SCS_unspecified &&
7566+
(IsExplicitSpecialization || IsMemberSpecialization)) {
7567+
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
7568+
diag::ext_explicit_specialization_storage_class)
7569+
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
7570+
}
7571+
7572+
if (CurContext->isRecord()) {
7573+
if (SC == SC_Static) {
7574+
if (const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(DC)) {
7575+
// Walk up the enclosing DeclContexts to check for any that are
7576+
// incompatible with static data members.
7577+
const DeclContext *FunctionOrMethod = nullptr;
7578+
const CXXRecordDecl *AnonStruct = nullptr;
7579+
for (DeclContext *Ctxt = DC; Ctxt; Ctxt = Ctxt->getParent()) {
7580+
if (Ctxt->isFunctionOrMethod()) {
7581+
FunctionOrMethod = Ctxt;
7582+
break;
7583+
}
7584+
const CXXRecordDecl *ParentDecl = dyn_cast<CXXRecordDecl>(Ctxt);
7585+
if (ParentDecl && !ParentDecl->getDeclName()) {
7586+
AnonStruct = ParentDecl;
7587+
break;
7588+
}
7589+
}
7590+
if (FunctionOrMethod) {
7591+
// C++ [class.static.data]p5: A local class shall not have static
7592+
// data members.
7593+
Diag(D.getIdentifierLoc(),
7594+
diag::err_static_data_member_not_allowed_in_local_class)
7595+
<< Name << RD->getDeclName()
7596+
<< llvm::to_underlying(RD->getTagKind());
7597+
} else if (AnonStruct) {
7598+
// C++ [class.static.data]p4: Unnamed classes and classes contained
7599+
// directly or indirectly within unnamed classes shall not contain
7600+
// static data members.
7601+
Diag(D.getIdentifierLoc(),
7602+
diag::err_static_data_member_not_allowed_in_anon_struct)
7603+
<< Name << llvm::to_underlying(AnonStruct->getTagKind());
7604+
Invalid = true;
7605+
} else if (RD->isUnion()) {
7606+
// C++98 [class.union]p1: If a union contains a static data member,
7607+
// the program is ill-formed. C++11 drops this restriction.
7608+
Diag(D.getIdentifierLoc(),
7609+
getLangOpts().CPlusPlus11
7610+
? diag::warn_cxx98_compat_static_data_member_in_union
7611+
: diag::ext_static_data_member_in_union)
7612+
<< Name;
7613+
}
7614+
}
7615+
} else if (IsVariableTemplate || IsPartialSpecialization) {
7616+
// There is no such thing as a member field template.
7617+
Diag(D.getIdentifierLoc(), diag::err_template_member)
7618+
<< II << TemplateParams->getSourceRange();
7619+
// Recover by pretending this is a static data member template.
7620+
SC = SC_Static;
7621+
}
7622+
} else if (DC->isRecord()) {
7623+
// This is an out-of-line definition of a static data member.
7624+
switch (SC) {
7625+
case SC_None:
7626+
break;
7627+
case SC_Static:
7628+
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
7629+
diag::err_static_out_of_line)
7630+
<< FixItHint::CreateRemoval(
7631+
D.getDeclSpec().getStorageClassSpecLoc());
7632+
break;
7633+
case SC_Auto:
7634+
case SC_Register:
7635+
case SC_Extern:
7636+
// [dcl.stc] p2: The auto or register specifiers shall be applied only
7637+
// to names of variables declared in a block or to function parameters.
7638+
// [dcl.stc] p6: The extern specifier cannot be used in the declaration
7639+
// of class members
7640+
7641+
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
7642+
diag::err_storage_class_for_static_member)
7643+
<< FixItHint::CreateRemoval(
7644+
D.getDeclSpec().getStorageClassSpecLoc());
7645+
break;
7646+
case SC_PrivateExtern:
7647+
llvm_unreachable("C storage class in c++!");
7648+
}
7649+
}
7650+
76287651
if (IsVariableTemplateSpecialization) {
76297652
SourceLocation TemplateKWLoc =
76307653
TemplateParamLists.size() > 0
@@ -7670,8 +7693,6 @@ NamedDecl *Sema::ActOnVariableDeclarator(
76707693
// the variable (matching the scope specifier), store them.
76717694
// An explicit variable template specialization does not own any template
76727695
// parameter lists.
7673-
bool IsExplicitSpecialization =
7674-
IsVariableTemplateSpecialization && !IsPartialSpecialization;
76757696
unsigned VDTemplateParamLists =
76767697
(TemplateParams && !IsExplicitSpecialization) ? 1 : 0;
76777698
if (TemplateParamLists.size() > VDTemplateParamLists)
@@ -10073,25 +10094,45 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
1007310094
NewFD->setImplicitlyInline(ImplicitInlineCXX20);
1007410095
}
1007510096

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

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

10086-
// MSVC permits the use of a 'static' storage specifier on an out-of-line
10087-
// member function template declaration and class member template
10088-
// declaration (MSVC versions before 2015), warn about this.
10089-
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
10090-
((!getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2015) &&
10091-
cast<CXXRecordDecl>(DC)->getDescribedClassTemplate()) ||
10092-
(getLangOpts().MSVCCompat && NewFD->getDescribedFunctionTemplate()))
10093-
? diag::ext_static_out_of_line : diag::err_static_out_of_line)
10094-
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
10120+
// Complain about the 'static' specifier if it's on an out-of-line
10121+
// member function definition.
10122+
10123+
// MSVC permits the use of a 'static' storage specifier on an
10124+
// out-of-line member function template declaration and class member
10125+
// template declaration (MSVC versions before 2015), warn about this.
10126+
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
10127+
((!getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2015) &&
10128+
cast<CXXRecordDecl>(DC)->getDescribedClassTemplate()) ||
10129+
(getLangOpts().MSVCCompat &&
10130+
NewFD->getDescribedFunctionTemplate()))
10131+
? diag::ext_static_out_of_line
10132+
: diag::err_static_out_of_line)
10133+
<< FixItHint::CreateRemoval(
10134+
D.getDeclSpec().getStorageClassSpecLoc());
10135+
}
1009510136
}
1009610137

1009710138
// C++11 [except.spec]p15:
@@ -10459,27 +10500,6 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
1045910500
Previous))
1046010501
NewFD->setInvalidDecl();
1046110502
}
10462-
10463-
// C++ [dcl.stc]p1:
10464-
// A storage-class-specifier shall not be specified in an explicit
10465-
// specialization (14.7.3)
10466-
// FIXME: We should be checking this for dependent specializations.
10467-
FunctionTemplateSpecializationInfo *Info =
10468-
NewFD->getTemplateSpecializationInfo();
10469-
if (Info && SC != SC_None) {
10470-
if (SC != Info->getTemplate()->getTemplatedDecl()->getStorageClass())
10471-
Diag(NewFD->getLocation(),
10472-
diag::err_explicit_specialization_inconsistent_storage_class)
10473-
<< SC
10474-
<< FixItHint::CreateRemoval(
10475-
D.getDeclSpec().getStorageClassSpecLoc());
10476-
10477-
else
10478-
Diag(NewFD->getLocation(),
10479-
diag::ext_explicit_specialization_storage_class)
10480-
<< FixItHint::CreateRemoval(
10481-
D.getDeclSpec().getStorageClassSpecLoc());
10482-
}
1048310503
} else if (isMemberSpecialization && !FunctionTemplate) {
1048410504
if (CheckMemberSpecialization(NewFD, Previous))
1048510505
NewFD->setInvalidDecl();

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3413,9 +3413,9 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
34133413
break;
34143414
}
34153415

3416-
bool isInstField = ((DS.getStorageClassSpec() == DeclSpec::SCS_unspecified ||
3417-
DS.getStorageClassSpec() == DeclSpec::SCS_mutable) &&
3418-
!isFunc);
3416+
bool isInstField = (DS.getStorageClassSpec() == DeclSpec::SCS_unspecified ||
3417+
DS.getStorageClassSpec() == DeclSpec::SCS_mutable) &&
3418+
!isFunc && TemplateParameterLists.empty();
34193419

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

34663466
IdentifierInfo *II = Name.getAsIdentifierInfo();
3467-
3468-
// Member field could not be with "template" keyword.
3469-
// So TemplateParameterLists should be empty in this case.
3470-
if (TemplateParameterLists.size()) {
3471-
TemplateParameterList* TemplateParams = TemplateParameterLists[0];
3472-
if (TemplateParams->size()) {
3473-
// There is no such thing as a member field template.
3474-
Diag(D.getIdentifierLoc(), diag::err_template_member)
3475-
<< II
3476-
<< SourceRange(TemplateParams->getTemplateLoc(),
3477-
TemplateParams->getRAngleLoc());
3478-
} else {
3479-
// There is an extraneous 'template<>' for this member.
3480-
Diag(TemplateParams->getTemplateLoc(),
3481-
diag::err_template_member_noparams)
3482-
<< II
3483-
<< SourceRange(TemplateParams->getTemplateLoc(),
3484-
TemplateParams->getRAngleLoc());
3485-
}
3486-
return nullptr;
3487-
}
3488-
34893467
if (D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId) {
34903468
Diag(D.getIdentifierLoc(), diag::err_member_with_template_arguments)
34913469
<< II

clang/test/CXX/dcl.dcl/dcl.spec/dcl.stc/p1.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ template<typename T> void f(T) {}
77
template<typename T> static void g(T) {}
88

99

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

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

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

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

0 commit comments

Comments
 (0)