[clang] [Clang][Sema] Allow elaborated-type-specifiers that declare member class template explict specializations (PR #78720)

via cfe-commits cfe-commits at lists.llvm.org
Fri Jan 19 07:01:58 PST 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-modules

Author: Krystian Stasiowski (sdkrystian)

<details>
<summary>Changes</summary>

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.

---
Full diff: https://github.com/llvm/llvm-project/pull/78720.diff


12 Files Affected:

- (modified) clang/docs/ReleaseNotes.rst (+2) 
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+1-2) 
- (modified) clang/lib/Sema/SemaDecl.cpp (+23-21) 
- (modified) clang/test/CXX/class.access/p4.cpp (+1) 
- (added) clang/test/CXX/dcl.dcl/dcl.enum/p1.cpp (+30) 
- (modified) clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.type.elab/p1.cpp (+65-1) 
- (modified) clang/test/CXX/drs/dr16xx.cpp (-1) 
- (modified) clang/test/CXX/module/module.interface/p2-2.cpp (+4) 
- (modified) clang/test/SemaCXX/enum-scoped.cpp (+2) 
- (modified) clang/test/SemaCXX/nested-name-spec.cpp (+7-4) 
- (modified) clang/test/SemaTemplate/elaborated-type-specifier.cpp (+2) 
- (modified) clang/test/SemaTemplate/qualified-id.cpp (+1) 


``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 392f694065a242b..a8a9f974a716041 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -821,6 +821,8 @@ Bug Fixes in This Version
 - Fix an issue with missing symbol definitions when the first coroutine
   statement appears in a discarded ``if constexpr`` branch.
   Fixes (`#78290 <https://github.com/llvm/llvm-project/issues/78290>`_)
+- 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
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 03b0122d1c08f75..e93f0281977ad0c 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7129,8 +7129,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">;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index cbb8ed8ce34d1bd..d7475f994825cda 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -5206,25 +5206,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;
 
@@ -17181,10 +17162,31 @@ 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 =
+    TemplateParameterList *TemplateParams =
             MatchTemplateParametersToScopeSpecifier(
                 KWLoc, NameLoc, SS, nullptr, TemplateParameterLists,
-                TUK == TUK_Friend, isMemberSpecialization, Invalid)) {
+                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();
+      // Invalid = true;
+    }
+
+    if (TemplateParams) {
       if (Kind == TagTypeKind::Enum) {
         Diag(KWLoc, diag::err_enum_template);
         return true;
diff --git a/clang/test/CXX/class.access/p4.cpp b/clang/test/CXX/class.access/p4.cpp
index fef5b7aa88726ea..ca98c9f90bd89e7 100644
--- a/clang/test/CXX/class.access/p4.cpp
+++ b/clang/test/CXX/class.access/p4.cpp
@@ -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 at -1{{forward declaration of class cannot have a nested name specifier}}
   };
 
   void test() {
diff --git a/clang/test/CXX/dcl.dcl/dcl.enum/p1.cpp b/clang/test/CXX/dcl.dcl/dcl.enum/p1.cpp
new file mode 100644
index 000000000000000..d97859f96ff91c7
--- /dev/null
+++ b/clang/test/CXX/dcl.dcl/dcl.enum/p1.cpp
@@ -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}}
diff --git a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.type.elab/p1.cpp b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.type.elab/p1.cpp
index e3982fd6a8386fb..e2408cdc5a7a760 100644
--- a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.type.elab/p1.cpp
+++ b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.type.elab/p1.cpp
@@ -16,7 +16,7 @@ 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>;
 
@@ -24,3 +24,67 @@ template<typename T> struct D::A; // expected-error {{cannot have a nested name
 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;
+}
diff --git a/clang/test/CXX/drs/dr16xx.cpp b/clang/test/CXX/drs/dr16xx.cpp
index 1be4d2ab3ebe886..6ce77fbba7ceec4 100644
--- a/clang/test/CXX/drs/dr16xx.cpp
+++ b/clang/test/CXX/drs/dr16xx.cpp
@@ -99,7 +99,6 @@ namespace dr1638 { // dr1638: 3.1
 
   enum class A<unsigned>::E;
   // since-cxx11-error at -1 {{template specialization requires 'template<>'}}
-  // since-cxx11-error at -2 {{forward declaration of enum class cannot have a nested name specifier}}
   template enum class A<unsigned>::E;
   // since-cxx11-error at -1 {{enumerations cannot be explicitly instantiated}}
   enum class A<unsigned>::E *e;
diff --git a/clang/test/CXX/module/module.interface/p2-2.cpp b/clang/test/CXX/module/module.interface/p2-2.cpp
index 04904a8d86f2792..8607ea47ac1ce30 100644
--- a/clang/test/CXX/module/module.interface/p2-2.cpp
+++ b/clang/test/CXX/module/module.interface/p2-2.cpp
@@ -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 at -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}}
 
@@ -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 at -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 at -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 at -1 {{forward declaration of struct cannot have a nested name specifier}}
 }
diff --git a/clang/test/SemaCXX/enum-scoped.cpp b/clang/test/SemaCXX/enum-scoped.cpp
index 2bfe7203367503b..a4da0607d74ae5b 100644
--- a/clang/test/SemaCXX/enum-scoped.cpp
+++ b/clang/test/SemaCXX/enum-scoped.cpp
@@ -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 at -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 at -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() {
diff --git a/clang/test/SemaCXX/nested-name-spec.cpp b/clang/test/SemaCXX/nested-name-spec.cpp
index 161c5e7fdf67af1..920ef42bc156468 100644
--- a/clang/test/SemaCXX/nested-name-spec.cpp
+++ b/clang/test/SemaCXX/nested-name-spec.cpp
@@ -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 at -1 {{forward declaration of struct cannot have a nested name specifier}}
 namespace A2 {
   typedef int INT;
   struct RC;
@@ -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 at -1 {{requires a template parameter list}}
+  // expected-error at -2 {{no struct named 'C'}}
+  // expected-error at -3 {{non-friend class member 'C' cannot have a qualified name}}
+  // expected-error at -4 {{forward declaration of struct cannot have a nested name specifier}}
 };
 
 template<typename T>
@@ -292,6 +294,7 @@ struct A2 {
 };
 template <typename T>
 struct A2<T>::B::C; // expected-error {{no struct named 'C'}}
+                    // expected-error at -1 {{forward declaration of struct cannot have a nested name specifier}}
 }
 
 namespace PR13033 {
diff --git a/clang/test/SemaTemplate/elaborated-type-specifier.cpp b/clang/test/SemaTemplate/elaborated-type-specifier.cpp
index 514c5f2d57f4aa8..27b3f36ee14dd8e 100644
--- a/clang/test/SemaTemplate/elaborated-type-specifier.cpp
+++ b/clang/test/SemaTemplate/elaborated-type-specifier.cpp
@@ -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 at -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 
   };
@@ -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 at -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}}
   };
 }
diff --git a/clang/test/SemaTemplate/qualified-id.cpp b/clang/test/SemaTemplate/qualified-id.cpp
index 64dff1ce235359a..883d3d330d14820 100644
--- a/clang/test/SemaTemplate/qualified-id.cpp
+++ b/clang/test/SemaTemplate/qualified-id.cpp
@@ -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 at -1{{forward declaration of class cannot have a nested name specifier}}
   };
 }

``````````

</details>


https://github.com/llvm/llvm-project/pull/78720


More information about the cfe-commits mailing list