Skip to content

Commit

Permalink
[Clang][Sema] Allow elaborated-type-specifiers that declare member cl…
Browse files Browse the repository at this point in the history
…ass 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.
  • Loading branch information
sdkrystian authored Jan 30, 2024
1 parent 6251b6b commit a0d266d
Show file tree
Hide file tree
Showing 12 changed files with 138 additions and 31 deletions.
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ Improvements to Clang's time-trace

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

Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
3 changes: 1 addition & 2 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -7148,8 +7148,7 @@ def warn_standalone_specifier : Warning<"'%0' ignored on this declaration">,
def ext_standalone_specifier : ExtWarn<"'%0' is not permitted on a declaration "
"of a type">, InGroup<MissingDeclarations>;
def err_standalone_class_nested_name_specifier : Error<
"forward declaration of %select{class|struct|interface|union|enum|enum class|enum struct}0 cannot "
"have a nested name specifier">;
"forward declaration of %0 cannot have a nested name specifier">;
def err_typecheck_sclass_func : Error<"illegal storage class on function">;
def err_static_block_func : Error<
"function declared in block scope cannot have 'static' storage class">;
Expand Down
46 changes: 23 additions & 23 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5207,25 +5207,6 @@ Decl *Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS,
return ActOnFriendTypeDecl(S, DS, TemplateParams);
}

const CXXScopeSpec &SS = DS.getTypeSpecScope();
bool IsExplicitSpecialization =
!TemplateParams.empty() && TemplateParams.back()->size() == 0;
if (Tag && SS.isNotEmpty() && !Tag->isCompleteDefinition() &&
!IsExplicitInstantiation && !IsExplicitSpecialization &&
!isa<ClassTemplatePartialSpecializationDecl>(Tag)) {
// Per C++ [dcl.type.elab]p1, a class declaration cannot have a
// nested-name-specifier unless it is an explicit instantiation
// or an explicit specialization.
//
// FIXME: We allow class template partial specializations here too, per the
// obvious intent of DR1819.
//
// Per C++ [dcl.enum]p1, an opaque-enum-declaration can't either.
Diag(SS.getBeginLoc(), diag::err_standalone_class_nested_name_specifier)
<< GetDiagnosticTypeSpecifierID(DS) << SS.getRange();
return nullptr;
}

// Track whether this decl-specifier declares anything.
bool DeclaresAnything = true;

Expand Down Expand Up @@ -17222,10 +17203,29 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
// for non-C++ cases.
if (TemplateParameterLists.size() > 0 ||
(SS.isNotEmpty() && TUK != TUK_Reference)) {
if (TemplateParameterList *TemplateParams =
MatchTemplateParametersToScopeSpecifier(
KWLoc, NameLoc, SS, nullptr, TemplateParameterLists,
TUK == TUK_Friend, isMemberSpecialization, Invalid)) {
TemplateParameterList *TemplateParams =
MatchTemplateParametersToScopeSpecifier(
KWLoc, NameLoc, SS, nullptr, TemplateParameterLists,
TUK == TUK_Friend, isMemberSpecialization, Invalid);

// C++23 [dcl.type.elab] p2:
// If an 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: [...]
// C++23 [dcl.enum] p1:
// If the enum-head-name of an opaque-enum-declaration contains a
// nested-name-specifier, the declaration shall be an explicit
// specialization.
//
// FIXME: Class template partial specializations can be forward declared
// per CWG2213, but the resolution failed to allow qualified forward
// declarations. This is almost certainly unintentional, so we allow them.
if (TUK == TUK_Declaration && SS.isNotEmpty() && !isMemberSpecialization)
Diag(SS.getBeginLoc(), diag::err_standalone_class_nested_name_specifier)
<< TypeWithKeyword::getTagTypeKindName(Kind) << SS.getRange();

if (TemplateParams) {
if (Kind == TagTypeKind::Enum) {
Diag(KWLoc, diag::err_enum_template);
return true;
Expand Down
1 change: 1 addition & 0 deletions clang/test/CXX/class.access/p4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ namespace test21 {
template <class T> class A<T>::Inner {};
class B {
template <class T> class A<T>::Inner; // expected-error{{non-friend class member 'Inner' cannot have a qualified name}}
// expected-error@-1{{forward declaration of class cannot have a nested name specifier}}
};

void test() {
Expand Down
30 changes: 30 additions & 0 deletions clang/test/CXX/dcl.dcl/dcl.enum/p1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// RUN: %clang_cc1 -verify %s -std=c++11

template<typename T>
struct S0 {
enum E0 : int;

enum class E1;
};

struct S3 {
enum E2 : int;

enum class E3;
};

template<typename T>
enum S0<T>::E0 : int; // expected-error{{cannot have a nested name specifier}}

template<>
enum S0<int>::E0 : int;

template<typename T>
enum class S0<T>::E1; // expected-error{{cannot have a nested name specifier}}

template<>
enum class S0<int>::E1;

enum S3::E2 : int; // expected-error{{cannot have a nested name specifier}}

enum class S3::E3; // expected-error{{cannot have a nested name specifier}}
66 changes: 65 additions & 1 deletion clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.type.elab/p1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,75 @@ template<> struct N::B<int>;
template struct N::B<float>;

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

template<typename T> struct D::A; // expected-error {{cannot have a nested name specifier}}
template<typename T> struct D::A<T*>; // FIXME: This is technically ill-formed, but that's not the intent.
template<> struct D::A<int>;
template struct D::A<float>;

namespace qualified_decl {
template<typename T>
struct S0 {
struct S1;

template<typename U>
struct S2;

enum E0 : int;

enum class E1;
};

struct S3 {
struct S4;

template<typename T>
struct S5;

enum E2 : int;

enum class E3;
};

template<typename T>
struct S0<T>::S1; // expected-error{{cannot have a nested name specifier}}

template<>
struct S0<int>::S1;

template<typename T>
template<typename U>
struct S0<T>::S2; // expected-error{{cannot have a nested name specifier}}

template<typename T>
template<typename U>
struct S0<T>::S2<U*>;

template<>
template<>
struct S0<int>::S2<bool>;

template<>
template<typename U>
struct S0<int>::S2;

struct S3::S4; // expected-error{{cannot have a nested name specifier}}

template<typename T>
struct S3::S5; // expected-error{{cannot have a nested name specifier}}

struct S3::S4 f0();
enum S0<long>::E0 f1();
enum S0<long>::E1 f2();
enum S3::E2 f3();
enum S3::E3 f4();

using A0 = struct S3::S4;
using A1 = enum S0<long>::E0;
using A2 = enum S0<long>::E1;
using A3 = enum S3::E2;
using A4 = enum S3::E3;
}
1 change: 0 additions & 1 deletion clang/test/CXX/drs/dr16xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ namespace dr1638 { // dr1638: 3.1

enum class A<unsigned>::E;
// since-cxx11-error@-1 {{template specialization requires 'template<>'}}
// since-cxx11-error@-2 {{forward declaration of enum class cannot have a nested name specifier}}
template enum class A<unsigned>::E;
// since-cxx11-error@-1 {{enumerations cannot be explicitly instantiated}}
enum class A<unsigned>::E *e;
Expand Down
4 changes: 4 additions & 0 deletions clang/test/CXX/module/module.interface/p2-2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct X {
};

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

Expand All @@ -28,10 +29,13 @@ export struct Y {
};

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

export {
template <typename T> struct X<T>::iterator; // expected-error {{cannot export 'iterator' as it is not at namespace scope}}
// expected-error@-1 {{forward declaration of struct cannot have a nested name specifier}}
struct Y::iterator; // expected-error {{cannot export 'iterator' as it is not at namespace scope}}
// expected-error@-1 {{forward declaration of struct cannot have a nested name specifier}}
}
2 changes: 2 additions & 0 deletions clang/test/SemaCXX/enum-scoped.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ namespace test5 {
namespace test6 {
enum A : unsigned;
struct A::a; // expected-error {{incomplete type 'test6::A' named in nested name specifier}}
// expected-error@-1{{forward declaration of struct cannot have a nested name specifier}}
enum A::b; // expected-error {{incomplete type 'test6::A' named in nested name specifier}}
// expected-error@-1{{forward declaration of enum cannot have a nested name specifier}}
int A::c; // expected-error {{incomplete type 'test6::A' named in nested name specifier}}
void A::d(); // expected-error {{incomplete type 'test6::A' named in nested name specifier}}
void test() {
Expand Down
11 changes: 7 additions & 4 deletions clang/test/SemaCXX/nested-name-spec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ A::C c1;
struct A::C c2;
struct S : public A::C {};
struct A::undef; // expected-error {{no struct named 'undef' in namespace 'A'}}

// expected-error@-1 {{forward declaration of struct cannot have a nested name specifier}}
namespace A2 {
typedef int INT;
struct RC;
Expand Down Expand Up @@ -280,9 +280,11 @@ template<typename T>
struct A {
protected:
struct B;
struct B::C; // expected-error {{requires a template parameter list}} \
// expected-error {{no struct named 'C'}} \
// expected-error{{non-friend class member 'C' cannot have a qualified name}}
struct B::C;
// expected-error@-1 {{requires a template parameter list}}
// expected-error@-2 {{no struct named 'C'}}
// expected-error@-3 {{non-friend class member 'C' cannot have a qualified name}}
// expected-error@-4 {{forward declaration of struct cannot have a nested name specifier}}
};

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

namespace PR13033 {
Expand Down
2 changes: 2 additions & 0 deletions clang/test/SemaTemplate/elaborated-type-specifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace PR6915 {
template<typename T>
struct DeclOrDef {
enum T::foo; // expected-error{{nested name specifier for a declaration cannot depend on a template parameter}}
// expected-error@-1{{forward declaration of enum cannot have a nested name specifier}}
enum T::bar { // expected-error{{nested name specifier for a declaration cannot depend on a template parameter}}
value
};
Expand All @@ -31,6 +32,7 @@ struct DeclOrDef {
namespace PR6649 {
template <typename T> struct foo {
class T::bar; // expected-error{{nested name specifier for a declaration cannot depend on a template parameter}}
// expected-error@-1{{forward declaration of class cannot have a nested name specifier}}
class T::bar { int x; }; // expected-error{{nested name specifier for a declaration cannot depend on a template parameter}}
};
}
Expand Down
1 change: 1 addition & 0 deletions clang/test/SemaTemplate/qualified-id.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ namespace PR12291 {
template <typename V>
template <typename W>
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}}
// expected-error@-1{{forward declaration of class cannot have a nested name specifier}}
};
}

0 comments on commit a0d266d

Please sign in to comment.