Skip to content

Commit a0d266d

Browse files
authored
[Clang][Sema] Allow elaborated-type-specifiers that declare member class template explict specializations (llvm#78720)
According to [[dcl.type.elab] p2](http://eel.is/c++draft/dcl.type.elab#2): > If an [elaborated-type-specifier](http://eel.is/c++draft/dcl.type.elab#nt:elaborated-type-specifier) is the sole constituent of a declaration, the declaration is ill-formed unless it is an explicit specialization, an explicit instantiation or it has one of the following forms [...] Consider the following: ```cpp template<typename T> struct A { template<typename U> struct B; }; template<> template<typename U> struct A<int>::B; // #1 ``` The _elaborated-type-specifier_ at `#1` declares an explicit specialization (which is itself a template). We currently (incorrectly) reject this, and this PR fixes that. I moved the point at which _elaborated-type-specifiers_ with _nested-name-specifiers_ are diagnosed from `ParsedFreeStandingDeclSpec` to `ActOnTag` for two reasons: `ActOnTag` isn't called for explicit instantiations and partial/explicit specializations, and because it's where we determine if a member specialization is being declared. With respect to diagnostics, I am currently issuing the diagnostic without marking the declaration as invalid or returning early, which results in more diagnostics that I think is necessary. I would like feedback regarding what the "correct" behavior should be here.
1 parent 6251b6b commit a0d266d

File tree

12 files changed

+138
-31
lines changed

12 files changed

+138
-31
lines changed

clang/docs/ReleaseNotes.rst

+2
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ Improvements to Clang's time-trace
119119

120120
Bug Fixes in This Version
121121
-------------------------
122+
- Clang now accepts elaborated-type-specifiers that explicitly specialize
123+
a member class template for an implicit instantiation of a class template.
122124

123125
Bug Fixes to Compiler Builtins
124126
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/Basic/DiagnosticSemaKinds.td

+1-2
Original file line numberDiff line numberDiff line change
@@ -7148,8 +7148,7 @@ def warn_standalone_specifier : Warning<"'%0' ignored on this declaration">,
71487148
def ext_standalone_specifier : ExtWarn<"'%0' is not permitted on a declaration "
71497149
"of a type">, InGroup<MissingDeclarations>;
71507150
def err_standalone_class_nested_name_specifier : Error<
7151-
"forward declaration of %select{class|struct|interface|union|enum|enum class|enum struct}0 cannot "
7152-
"have a nested name specifier">;
7151+
"forward declaration of %0 cannot have a nested name specifier">;
71537152
def err_typecheck_sclass_func : Error<"illegal storage class on function">;
71547153
def err_static_block_func : Error<
71557154
"function declared in block scope cannot have 'static' storage class">;

clang/lib/Sema/SemaDecl.cpp

+23-23
Original file line numberDiff line numberDiff line change
@@ -5207,25 +5207,6 @@ Decl *Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS,
52075207
return ActOnFriendTypeDecl(S, DS, TemplateParams);
52085208
}
52095209

5210-
const CXXScopeSpec &SS = DS.getTypeSpecScope();
5211-
bool IsExplicitSpecialization =
5212-
!TemplateParams.empty() && TemplateParams.back()->size() == 0;
5213-
if (Tag && SS.isNotEmpty() && !Tag->isCompleteDefinition() &&
5214-
!IsExplicitInstantiation && !IsExplicitSpecialization &&
5215-
!isa<ClassTemplatePartialSpecializationDecl>(Tag)) {
5216-
// Per C++ [dcl.type.elab]p1, a class declaration cannot have a
5217-
// nested-name-specifier unless it is an explicit instantiation
5218-
// or an explicit specialization.
5219-
//
5220-
// FIXME: We allow class template partial specializations here too, per the
5221-
// obvious intent of DR1819.
5222-
//
5223-
// Per C++ [dcl.enum]p1, an opaque-enum-declaration can't either.
5224-
Diag(SS.getBeginLoc(), diag::err_standalone_class_nested_name_specifier)
5225-
<< GetDiagnosticTypeSpecifierID(DS) << SS.getRange();
5226-
return nullptr;
5227-
}
5228-
52295210
// Track whether this decl-specifier declares anything.
52305211
bool DeclaresAnything = true;
52315212

@@ -17222,10 +17203,29 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
1722217203
// for non-C++ cases.
1722317204
if (TemplateParameterLists.size() > 0 ||
1722417205
(SS.isNotEmpty() && TUK != TUK_Reference)) {
17225-
if (TemplateParameterList *TemplateParams =
17226-
MatchTemplateParametersToScopeSpecifier(
17227-
KWLoc, NameLoc, SS, nullptr, TemplateParameterLists,
17228-
TUK == TUK_Friend, isMemberSpecialization, Invalid)) {
17206+
TemplateParameterList *TemplateParams =
17207+
MatchTemplateParametersToScopeSpecifier(
17208+
KWLoc, NameLoc, SS, nullptr, TemplateParameterLists,
17209+
TUK == TUK_Friend, isMemberSpecialization, Invalid);
17210+
17211+
// C++23 [dcl.type.elab] p2:
17212+
// If an elaborated-type-specifier is the sole constituent of a
17213+
// declaration, the declaration is ill-formed unless it is an explicit
17214+
// specialization, an explicit instantiation or it has one of the
17215+
// following forms: [...]
17216+
// C++23 [dcl.enum] p1:
17217+
// If the enum-head-name of an opaque-enum-declaration contains a
17218+
// nested-name-specifier, the declaration shall be an explicit
17219+
// specialization.
17220+
//
17221+
// FIXME: Class template partial specializations can be forward declared
17222+
// per CWG2213, but the resolution failed to allow qualified forward
17223+
// declarations. This is almost certainly unintentional, so we allow them.
17224+
if (TUK == TUK_Declaration && SS.isNotEmpty() && !isMemberSpecialization)
17225+
Diag(SS.getBeginLoc(), diag::err_standalone_class_nested_name_specifier)
17226+
<< TypeWithKeyword::getTagTypeKindName(Kind) << SS.getRange();
17227+
17228+
if (TemplateParams) {
1722917229
if (Kind == TagTypeKind::Enum) {
1723017230
Diag(KWLoc, diag::err_enum_template);
1723117231
return true;

clang/test/CXX/class.access/p4.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,7 @@ namespace test21 {
611611
template <class T> class A<T>::Inner {};
612612
class B {
613613
template <class T> class A<T>::Inner; // expected-error{{non-friend class member 'Inner' cannot have a qualified name}}
614+
// expected-error@-1{{forward declaration of class cannot have a nested name specifier}}
614615
};
615616

616617
void test() {
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// RUN: %clang_cc1 -verify %s -std=c++11
2+
3+
template<typename T>
4+
struct S0 {
5+
enum E0 : int;
6+
7+
enum class E1;
8+
};
9+
10+
struct S3 {
11+
enum E2 : int;
12+
13+
enum class E3;
14+
};
15+
16+
template<typename T>
17+
enum S0<T>::E0 : int; // expected-error{{cannot have a nested name specifier}}
18+
19+
template<>
20+
enum S0<int>::E0 : int;
21+
22+
template<typename T>
23+
enum class S0<T>::E1; // expected-error{{cannot have a nested name specifier}}
24+
25+
template<>
26+
enum class S0<int>::E1;
27+
28+
enum S3::E2 : int; // expected-error{{cannot have a nested name specifier}}
29+
30+
enum class S3::E3; // expected-error{{cannot have a nested name specifier}}

clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.type.elab/p1.cpp

+65-1
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,75 @@ template<> struct N::B<int>;
1616
template struct N::B<float>;
1717

1818
template<typename T> struct C;
19-
template<typename T> struct C<T*>; // FIXME: This is technically ill-formed, but that's not the intent.
19+
template<typename T> struct C<T*>;
2020
template<> struct C<int>;
2121
template struct C<float>;
2222

2323
template<typename T> struct D::A; // expected-error {{cannot have a nested name specifier}}
2424
template<typename T> struct D::A<T*>; // FIXME: This is technically ill-formed, but that's not the intent.
2525
template<> struct D::A<int>;
2626
template struct D::A<float>;
27+
28+
namespace qualified_decl {
29+
template<typename T>
30+
struct S0 {
31+
struct S1;
32+
33+
template<typename U>
34+
struct S2;
35+
36+
enum E0 : int;
37+
38+
enum class E1;
39+
};
40+
41+
struct S3 {
42+
struct S4;
43+
44+
template<typename T>
45+
struct S5;
46+
47+
enum E2 : int;
48+
49+
enum class E3;
50+
};
51+
52+
template<typename T>
53+
struct S0<T>::S1; // expected-error{{cannot have a nested name specifier}}
54+
55+
template<>
56+
struct S0<int>::S1;
57+
58+
template<typename T>
59+
template<typename U>
60+
struct S0<T>::S2; // expected-error{{cannot have a nested name specifier}}
61+
62+
template<typename T>
63+
template<typename U>
64+
struct S0<T>::S2<U*>;
65+
66+
template<>
67+
template<>
68+
struct S0<int>::S2<bool>;
69+
70+
template<>
71+
template<typename U>
72+
struct S0<int>::S2;
73+
74+
struct S3::S4; // expected-error{{cannot have a nested name specifier}}
75+
76+
template<typename T>
77+
struct S3::S5; // expected-error{{cannot have a nested name specifier}}
78+
79+
struct S3::S4 f0();
80+
enum S0<long>::E0 f1();
81+
enum S0<long>::E1 f2();
82+
enum S3::E2 f3();
83+
enum S3::E3 f4();
84+
85+
using A0 = struct S3::S4;
86+
using A1 = enum S0<long>::E0;
87+
using A2 = enum S0<long>::E1;
88+
using A3 = enum S3::E2;
89+
using A4 = enum S3::E3;
90+
}

clang/test/CXX/drs/dr16xx.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ namespace dr1638 { // dr1638: 3.1
9999

100100
enum class A<unsigned>::E;
101101
// since-cxx11-error@-1 {{template specialization requires 'template<>'}}
102-
// since-cxx11-error@-2 {{forward declaration of enum class cannot have a nested name specifier}}
103102
template enum class A<unsigned>::E;
104103
// since-cxx11-error@-1 {{enumerations cannot be explicitly instantiated}}
105104
enum class A<unsigned>::E *e;

clang/test/CXX/module/module.interface/p2-2.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ struct X {
1515
};
1616

1717
export template <typename T> struct X<T>::iterator; // expected-error {{cannot export 'iterator' as it is not at namespace scope}}
18+
// expected-error@-1 {{forward declaration of struct cannot have a nested name specifier}}
1819
export template <typename T> void X<T>::foo(); // expected-error {{cannot export 'foo' as it is not at namespace scope}}
1920
export template <typename T> template <typename U> U X<T>::bar(); // expected-error {{cannot export 'bar' as it is not at namespace scope}}
2021

@@ -28,10 +29,13 @@ export struct Y {
2829
};
2930

3031
export struct Y::iterator; // expected-error {{cannot export 'iterator' as it is not at namespace scope}}
32+
// expected-error@-1 {{forward declaration of struct cannot have a nested name specifier}}
3133
export void Y::foo(); // expected-error {{cannot export 'foo' as it is not at namespace scope}}
3234
export template <typename U> U Y::bar(); // expected-error {{cannot export 'bar' as it is not at namespace scope}}
3335

3436
export {
3537
template <typename T> struct X<T>::iterator; // expected-error {{cannot export 'iterator' as it is not at namespace scope}}
38+
// expected-error@-1 {{forward declaration of struct cannot have a nested name specifier}}
3639
struct Y::iterator; // expected-error {{cannot export 'iterator' as it is not at namespace scope}}
40+
// expected-error@-1 {{forward declaration of struct cannot have a nested name specifier}}
3741
}

clang/test/SemaCXX/enum-scoped.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@ namespace test5 {
146146
namespace test6 {
147147
enum A : unsigned;
148148
struct A::a; // expected-error {{incomplete type 'test6::A' named in nested name specifier}}
149+
// expected-error@-1{{forward declaration of struct cannot have a nested name specifier}}
149150
enum A::b; // expected-error {{incomplete type 'test6::A' named in nested name specifier}}
151+
// expected-error@-1{{forward declaration of enum cannot have a nested name specifier}}
150152
int A::c; // expected-error {{incomplete type 'test6::A' named in nested name specifier}}
151153
void A::d(); // expected-error {{incomplete type 'test6::A' named in nested name specifier}}
152154
void test() {

clang/test/SemaCXX/nested-name-spec.cpp

+7-4
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ A::C c1;
6565
struct A::C c2;
6666
struct S : public A::C {};
6767
struct A::undef; // expected-error {{no struct named 'undef' in namespace 'A'}}
68-
68+
// expected-error@-1 {{forward declaration of struct cannot have a nested name specifier}}
6969
namespace A2 {
7070
typedef int INT;
7171
struct RC;
@@ -280,9 +280,11 @@ template<typename T>
280280
struct A {
281281
protected:
282282
struct B;
283-
struct B::C; // expected-error {{requires a template parameter list}} \
284-
// expected-error {{no struct named 'C'}} \
285-
// expected-error{{non-friend class member 'C' cannot have a qualified name}}
283+
struct B::C;
284+
// expected-error@-1 {{requires a template parameter list}}
285+
// expected-error@-2 {{no struct named 'C'}}
286+
// expected-error@-3 {{non-friend class member 'C' cannot have a qualified name}}
287+
// expected-error@-4 {{forward declaration of struct cannot have a nested name specifier}}
286288
};
287289

288290
template<typename T>
@@ -292,6 +294,7 @@ struct A2 {
292294
};
293295
template <typename T>
294296
struct A2<T>::B::C; // expected-error {{no struct named 'C'}}
297+
// expected-error@-1 {{forward declaration of struct cannot have a nested name specifier}}
295298
}
296299

297300
namespace PR13033 {

clang/test/SemaTemplate/elaborated-type-specifier.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace PR6915 {
2323
template<typename T>
2424
struct DeclOrDef {
2525
enum T::foo; // expected-error{{nested name specifier for a declaration cannot depend on a template parameter}}
26+
// expected-error@-1{{forward declaration of enum cannot have a nested name specifier}}
2627
enum T::bar { // expected-error{{nested name specifier for a declaration cannot depend on a template parameter}}
2728
value
2829
};
@@ -31,6 +32,7 @@ struct DeclOrDef {
3132
namespace PR6649 {
3233
template <typename T> struct foo {
3334
class T::bar; // expected-error{{nested name specifier for a declaration cannot depend on a template parameter}}
35+
// expected-error@-1{{forward declaration of class cannot have a nested name specifier}}
3436
class T::bar { int x; }; // expected-error{{nested name specifier for a declaration cannot depend on a template parameter}}
3537
};
3638
}

clang/test/SemaTemplate/qualified-id.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,6 @@ namespace PR12291 {
5252
template <typename V>
5353
template <typename W>
5454
class Outer2<V>::Inner; // expected-error{{nested name specifier 'Outer2<V>::' for declaration does not refer into a class, class template or class template partial specialization}}
55+
// expected-error@-1{{forward declaration of class cannot have a nested name specifier}}
5556
};
5657
}

0 commit comments

Comments
 (0)