Skip to content

Commit 9a88aa0

Browse files
authored
[Clang][Sema] Diagnose variable template explicit specializations with storage-class-specifiers (#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.
1 parent 35a2b60 commit 9a88aa0

File tree

20 files changed

+526
-233
lines changed

20 files changed

+526
-233
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,7 @@ Bug Fixes to C++ Support
865865
- Fixed a failed assertion when attempting to convert an integer representing the difference
866866
between the addresses of two labels (a GNU extension) to a pointer within a constant expression. (#GH95366).
867867
- Fix immediate escalation bugs in the presence of dependent call arguments. (#GH94935)
868+
- Clang now diagnoses explicit specializations with storage class specifiers in all contexts.
868869

869870

870871
Bug Fixes to AST Handling

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5360,9 +5360,6 @@ def err_not_class_template_specialization : Error<
53605360
"parameter}0">;
53615361
def ext_explicit_specialization_storage_class : ExtWarn<
53625362
"explicit specialization cannot have a storage class">;
5363-
def err_explicit_specialization_inconsistent_storage_class : Error<
5364-
"explicit specialization has extraneous, inconsistent storage class "
5365-
"'%select{none|extern|static|__private_extern__|auto|register}0'">;
53665363
def err_dependent_function_template_spec_no_match : Error<
53675364
"no candidate function template was found for dependent"
53685365
" %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
@@ -3162,7 +3162,8 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
31623162
DeclSpec::SCS_static &&
31633163
DeclaratorInfo.getDeclSpec().getStorageClassSpec() !=
31643164
DeclSpec::SCS_typedef &&
3165-
!DS.isFriendSpecified()) {
3165+
!DS.isFriendSpecified() &&
3166+
TemplateInfo.Kind == ParsedTemplateInfo::NonTemplate) {
31663167
// It's a default member initializer.
31673168
if (BitfieldSize.get())
31683169
Diag(Tok, getLangOpts().CPlusPlus20
@@ -3261,7 +3262,7 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
32613262
} else if (ThisDecl)
32623263
Actions.AddInitializerToDecl(ThisDecl, Init.get(),
32633264
EqualLoc.isInvalid());
3264-
} else if (ThisDecl && DS.getStorageClassSpec() == DeclSpec::SCS_static)
3265+
} else if (ThisDecl && DeclaratorInfo.isStaticMember())
32653266
// No initializer.
32663267
Actions.ActOnUninitializedDecl(ThisDecl);
32673268

clang/lib/Sema/DeclSpec.cpp

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

clang/lib/Sema/SemaDecl.cpp

Lines changed: 134 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -7609,89 +7609,16 @@ NamedDecl *Sema::ActOnVariableDeclarator(
76097609
NTCUC_AutoVar, NTCUK_Destruct);
76107610
} else {
76117611
bool Invalid = false;
7612-
7613-
if (DC->isRecord() && !CurContext->isRecord()) {
7614-
// This is an out-of-line definition of a static data member.
7615-
switch (SC) {
7616-
case SC_None:
7617-
break;
7618-
case SC_Static:
7619-
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
7620-
diag::err_static_out_of_line)
7621-
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
7622-
break;
7623-
case SC_Auto:
7624-
case SC_Register:
7625-
case SC_Extern:
7626-
// [dcl.stc] p2: The auto or register specifiers shall be applied only
7627-
// to names of variables declared in a block or to function parameters.
7628-
// [dcl.stc] p6: The extern specifier cannot be used in the declaration
7629-
// of class members
7630-
7631-
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
7632-
diag::err_storage_class_for_static_member)
7633-
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
7634-
break;
7635-
case SC_PrivateExtern:
7636-
llvm_unreachable("C storage class in c++!");
7637-
}
7638-
}
7639-
7640-
if (SC == SC_Static && CurContext->isRecord()) {
7641-
if (const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(DC)) {
7642-
// Walk up the enclosing DeclContexts to check for any that are
7643-
// incompatible with static data members.
7644-
const DeclContext *FunctionOrMethod = nullptr;
7645-
const CXXRecordDecl *AnonStruct = nullptr;
7646-
for (DeclContext *Ctxt = DC; Ctxt; Ctxt = Ctxt->getParent()) {
7647-
if (Ctxt->isFunctionOrMethod()) {
7648-
FunctionOrMethod = Ctxt;
7649-
break;
7650-
}
7651-
const CXXRecordDecl *ParentDecl = dyn_cast<CXXRecordDecl>(Ctxt);
7652-
if (ParentDecl && !ParentDecl->getDeclName()) {
7653-
AnonStruct = ParentDecl;
7654-
break;
7655-
}
7656-
}
7657-
if (FunctionOrMethod) {
7658-
// C++ [class.static.data]p5: A local class shall not have static data
7659-
// members.
7660-
Diag(D.getIdentifierLoc(),
7661-
diag::err_static_data_member_not_allowed_in_local_class)
7662-
<< Name << RD->getDeclName()
7663-
<< llvm::to_underlying(RD->getTagKind());
7664-
} else if (AnonStruct) {
7665-
// C++ [class.static.data]p4: Unnamed classes and classes contained
7666-
// directly or indirectly within unnamed classes shall not contain
7667-
// static data members.
7668-
Diag(D.getIdentifierLoc(),
7669-
diag::err_static_data_member_not_allowed_in_anon_struct)
7670-
<< Name << llvm::to_underlying(AnonStruct->getTagKind());
7671-
Invalid = true;
7672-
} else if (RD->isUnion()) {
7673-
// C++98 [class.union]p1: If a union contains a static data member,
7674-
// the program is ill-formed. C++11 drops this restriction.
7675-
Diag(D.getIdentifierLoc(),
7676-
getLangOpts().CPlusPlus11
7677-
? diag::warn_cxx98_compat_static_data_member_in_union
7678-
: diag::ext_static_data_member_in_union) << Name;
7679-
}
7680-
}
7681-
}
7682-
76837612
// Match up the template parameter lists with the scope specifier, then
76847613
// determine whether we have a template or a template specialization.
7685-
bool InvalidScope = false;
76867614
TemplateParams = MatchTemplateParametersToScopeSpecifier(
76877615
D.getDeclSpec().getBeginLoc(), D.getIdentifierLoc(),
76887616
D.getCXXScopeSpec(),
76897617
D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId
76907618
? D.getName().TemplateId
76917619
: nullptr,
76927620
TemplateParamLists,
7693-
/*never a friend*/ false, IsMemberSpecialization, InvalidScope);
7694-
Invalid |= InvalidScope;
7621+
/*never a friend*/ false, IsMemberSpecialization, Invalid);
76957622

76967623
if (TemplateParams) {
76977624
if (!TemplateParams->size() &&
@@ -7734,6 +7661,102 @@ NamedDecl *Sema::ActOnVariableDeclarator(
77347661
"should have a 'template<>' for this decl");
77357662
}
77367663

7664+
bool IsExplicitSpecialization =
7665+
IsVariableTemplateSpecialization && !IsPartialSpecialization;
7666+
7667+
// C++ [temp.expl.spec]p2:
7668+
// The declaration in an explicit-specialization shall not be an
7669+
// export-declaration. An explicit specialization shall not use a
7670+
// storage-class-specifier other than thread_local.
7671+
//
7672+
// We use the storage-class-specifier from DeclSpec because we may have
7673+
// added implicit 'extern' for declarations with __declspec(dllimport)!
7674+
if (SCSpec != DeclSpec::SCS_unspecified &&
7675+
(IsExplicitSpecialization || IsMemberSpecialization)) {
7676+
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
7677+
diag::ext_explicit_specialization_storage_class)
7678+
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
7679+
}
7680+
7681+
if (CurContext->isRecord()) {
7682+
if (SC == SC_Static) {
7683+
if (const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(DC)) {
7684+
// Walk up the enclosing DeclContexts to check for any that are
7685+
// incompatible with static data members.
7686+
const DeclContext *FunctionOrMethod = nullptr;
7687+
const CXXRecordDecl *AnonStruct = nullptr;
7688+
for (DeclContext *Ctxt = DC; Ctxt; Ctxt = Ctxt->getParent()) {
7689+
if (Ctxt->isFunctionOrMethod()) {
7690+
FunctionOrMethod = Ctxt;
7691+
break;
7692+
}
7693+
const CXXRecordDecl *ParentDecl = dyn_cast<CXXRecordDecl>(Ctxt);
7694+
if (ParentDecl && !ParentDecl->getDeclName()) {
7695+
AnonStruct = ParentDecl;
7696+
break;
7697+
}
7698+
}
7699+
if (FunctionOrMethod) {
7700+
// C++ [class.static.data]p5: A local class shall not have static
7701+
// data members.
7702+
Diag(D.getIdentifierLoc(),
7703+
diag::err_static_data_member_not_allowed_in_local_class)
7704+
<< Name << RD->getDeclName()
7705+
<< llvm::to_underlying(RD->getTagKind());
7706+
} else if (AnonStruct) {
7707+
// C++ [class.static.data]p4: Unnamed classes and classes contained
7708+
// directly or indirectly within unnamed classes shall not contain
7709+
// static data members.
7710+
Diag(D.getIdentifierLoc(),
7711+
diag::err_static_data_member_not_allowed_in_anon_struct)
7712+
<< Name << llvm::to_underlying(AnonStruct->getTagKind());
7713+
Invalid = true;
7714+
} else if (RD->isUnion()) {
7715+
// C++98 [class.union]p1: If a union contains a static data member,
7716+
// the program is ill-formed. C++11 drops this restriction.
7717+
Diag(D.getIdentifierLoc(),
7718+
getLangOpts().CPlusPlus11
7719+
? diag::warn_cxx98_compat_static_data_member_in_union
7720+
: diag::ext_static_data_member_in_union)
7721+
<< Name;
7722+
}
7723+
}
7724+
} else if (IsVariableTemplate || IsPartialSpecialization) {
7725+
// There is no such thing as a member field template.
7726+
Diag(D.getIdentifierLoc(), diag::err_template_member)
7727+
<< II << TemplateParams->getSourceRange();
7728+
// Recover by pretending this is a static data member template.
7729+
SC = SC_Static;
7730+
}
7731+
} else if (DC->isRecord()) {
7732+
// This is an out-of-line definition of a static data member.
7733+
switch (SC) {
7734+
case SC_None:
7735+
break;
7736+
case SC_Static:
7737+
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
7738+
diag::err_static_out_of_line)
7739+
<< FixItHint::CreateRemoval(
7740+
D.getDeclSpec().getStorageClassSpecLoc());
7741+
break;
7742+
case SC_Auto:
7743+
case SC_Register:
7744+
case SC_Extern:
7745+
// [dcl.stc] p2: The auto or register specifiers shall be applied only
7746+
// to names of variables declared in a block or to function parameters.
7747+
// [dcl.stc] p6: The extern specifier cannot be used in the declaration
7748+
// of class members
7749+
7750+
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
7751+
diag::err_storage_class_for_static_member)
7752+
<< FixItHint::CreateRemoval(
7753+
D.getDeclSpec().getStorageClassSpecLoc());
7754+
break;
7755+
case SC_PrivateExtern:
7756+
llvm_unreachable("C storage class in c++!");
7757+
}
7758+
}
7759+
77377760
if (IsVariableTemplateSpecialization) {
77387761
SourceLocation TemplateKWLoc =
77397762
TemplateParamLists.size() > 0
@@ -7779,8 +7802,6 @@ NamedDecl *Sema::ActOnVariableDeclarator(
77797802
// the variable (matching the scope specifier), store them.
77807803
// An explicit variable template specialization does not own any template
77817804
// parameter lists.
7782-
bool IsExplicitSpecialization =
7783-
IsVariableTemplateSpecialization && !IsPartialSpecialization;
77847805
unsigned VDTemplateParamLists =
77857806
(TemplateParams && !IsExplicitSpecialization) ? 1 : 0;
77867807
if (TemplateParamLists.size() > VDTemplateParamLists)
@@ -10210,25 +10231,45 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
1021010231
NewFD->setImplicitlyInline(ImplicitInlineCXX20);
1021110232
}
1021210233

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

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

10223-
// MSVC permits the use of a 'static' storage specifier on an out-of-line
10224-
// member function template declaration and class member template
10225-
// declaration (MSVC versions before 2015), warn about this.
10226-
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
10227-
((!getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2015) &&
10228-
cast<CXXRecordDecl>(DC)->getDescribedClassTemplate()) ||
10229-
(getLangOpts().MSVCCompat && NewFD->getDescribedFunctionTemplate()))
10230-
? diag::ext_static_out_of_line : diag::err_static_out_of_line)
10231-
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
10257+
// Complain about the 'static' specifier if it's on an out-of-line
10258+
// member function definition.
10259+
10260+
// MSVC permits the use of a 'static' storage specifier on an
10261+
// out-of-line member function template declaration and class member
10262+
// template declaration (MSVC versions before 2015), warn about this.
10263+
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
10264+
((!getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2015) &&
10265+
cast<CXXRecordDecl>(DC)->getDescribedClassTemplate()) ||
10266+
(getLangOpts().MSVCCompat &&
10267+
NewFD->getDescribedFunctionTemplate()))
10268+
? diag::ext_static_out_of_line
10269+
: diag::err_static_out_of_line)
10270+
<< FixItHint::CreateRemoval(
10271+
D.getDeclSpec().getStorageClassSpecLoc());
10272+
}
1023210273
}
1023310274

1023410275
// C++11 [except.spec]p15:
@@ -10596,27 +10637,6 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
1059610637
Previous))
1059710638
NewFD->setInvalidDecl();
1059810639
}
10599-
10600-
// C++ [dcl.stc]p1:
10601-
// A storage-class-specifier shall not be specified in an explicit
10602-
// specialization (14.7.3)
10603-
// FIXME: We should be checking this for dependent specializations.
10604-
FunctionTemplateSpecializationInfo *Info =
10605-
NewFD->getTemplateSpecializationInfo();
10606-
if (Info && SC != SC_None) {
10607-
if (SC != Info->getTemplate()->getTemplatedDecl()->getStorageClass())
10608-
Diag(NewFD->getLocation(),
10609-
diag::err_explicit_specialization_inconsistent_storage_class)
10610-
<< SC
10611-
<< FixItHint::CreateRemoval(
10612-
D.getDeclSpec().getStorageClassSpecLoc());
10613-
10614-
else
10615-
Diag(NewFD->getLocation(),
10616-
diag::ext_explicit_specialization_storage_class)
10617-
<< FixItHint::CreateRemoval(
10618-
D.getDeclSpec().getStorageClassSpecLoc());
10619-
}
1062010640
} else if (isMemberSpecialization && isa<CXXMethodDecl>(NewFD)) {
1062110641
if (CheckMemberSpecialization(NewFD, Previous))
1062210642
NewFD->setInvalidDecl();

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3508,9 +3508,9 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
35083508
break;
35093509
}
35103510

3511-
bool isInstField = ((DS.getStorageClassSpec() == DeclSpec::SCS_unspecified ||
3512-
DS.getStorageClassSpec() == DeclSpec::SCS_mutable) &&
3513-
!isFunc);
3511+
bool isInstField = (DS.getStorageClassSpec() == DeclSpec::SCS_unspecified ||
3512+
DS.getStorageClassSpec() == DeclSpec::SCS_mutable) &&
3513+
!isFunc && TemplateParameterLists.empty();
35143514

35153515
if (DS.hasConstexprSpecifier() && isInstField) {
35163516
SemaDiagnosticBuilder B =
@@ -3559,28 +3559,6 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
35593559
}
35603560

35613561
IdentifierInfo *II = Name.getAsIdentifierInfo();
3562-
3563-
// Member field could not be with "template" keyword.
3564-
// So TemplateParameterLists should be empty in this case.
3565-
if (TemplateParameterLists.size()) {
3566-
TemplateParameterList* TemplateParams = TemplateParameterLists[0];
3567-
if (TemplateParams->size()) {
3568-
// There is no such thing as a member field template.
3569-
Diag(D.getIdentifierLoc(), diag::err_template_member)
3570-
<< II
3571-
<< SourceRange(TemplateParams->getTemplateLoc(),
3572-
TemplateParams->getRAngleLoc());
3573-
} else {
3574-
// There is an extraneous 'template<>' for this member.
3575-
Diag(TemplateParams->getTemplateLoc(),
3576-
diag::err_template_member_noparams)
3577-
<< II
3578-
<< SourceRange(TemplateParams->getTemplateLoc(),
3579-
TemplateParams->getRAngleLoc());
3580-
}
3581-
return nullptr;
3582-
}
3583-
35843562
if (D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId) {
35853563
Diag(D.getIdentifierLoc(), diag::err_member_with_template_arguments)
35863564
<< 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)