[clang] [Clang][Sema] Diagnose variable template explicit specializations with storage-class-specifiers (PR #93873)

Krystian Stasiowski via cfe-commits cfe-commits at lists.llvm.org
Thu May 30 13:25:08 PDT 2024


https://github.com/sdkrystian created https://github.com/llvm/llvm-project/pull/93873

According to [[temp.expl.spec] p2](http://eel.is/c++draft/temp.expl.spec#2):
> 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.
    ```cpp
    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.
    ```cpp
    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.
    ```cpp
    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.


>From b3e6fde2b128560f4ecbd9623a25ed301db19942 Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 28 May 2024 07:42:06 -0400
Subject: [PATCH 1/6] [FOLD]

---
 clang/lib/Sema/SemaDecl.cpp    | 8 ++++++++
 clang/lib/Sema/SemaDeclCXX.cpp | 6 ++++--
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 2a87b26f17a2b..6a0187a4e7ad5 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7716,6 +7716,14 @@ NamedDecl *Sema::ActOnVariableDeclarator(
                    ? diag::warn_cxx11_compat_variable_template
                    : diag::ext_variable_template);
         }
+
+        if (CurContext->isRecord() && SC != SC_Static && (IsVariableTemplate || IsPartialSpecialization)) {
+          // There is no such thing as a member field template.
+          Diag(D.getIdentifierLoc(), diag::err_template_member)
+              << II << TemplateParams->getSourceRange();
+          // Recover by pretending this is a static data member template.
+          SC = SC_Static;
+        }
       }
     } else {
       // Check that we can declare a member specialization here.
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 8ab429e2a136e..7077797bf6504 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -3489,9 +3489,9 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
     break;
   }
 
-  bool isInstField = ((DS.getStorageClassSpec() == DeclSpec::SCS_unspecified ||
+  bool isInstField = (DS.getStorageClassSpec() == DeclSpec::SCS_unspecified ||
                        DS.getStorageClassSpec() == DeclSpec::SCS_mutable) &&
-                      !isFunc);
+                      !isFunc && TemplateParameterLists.empty();
 
   if (DS.hasConstexprSpecifier() && isInstField) {
     SemaDiagnosticBuilder B =
@@ -3541,6 +3541,7 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
 
     IdentifierInfo *II = Name.getAsIdentifierInfo();
 
+    #if 0
     // Member field could not be with "template" keyword.
     // So TemplateParameterLists should be empty in this case.
     if (TemplateParameterLists.size()) {
@@ -3561,6 +3562,7 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
       }
       return nullptr;
     }
+    #endif
 
     if (D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId) {
       Diag(D.getIdentifierLoc(), diag::err_member_with_template_arguments)

>From 351d16fe996909d072e44d2c670e8ea3c9a2a294 Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 28 May 2024 10:29:19 -0400
Subject: [PATCH 2/6] [FOLD]

---
 clang/lib/Parse/ParseDeclCXX.cpp              |   3 +-
 clang/lib/Sema/DeclSpec.cpp                   |   1 +
 clang/lib/Sema/SemaDecl.cpp                   | 216 ++++++++--------
 clang/test/CXX/drs/cwg7xx.cpp                 |   2 +-
 .../CXX/temp/temp.spec/temp.expl.spec/p17.cpp |  12 +-
 .../temp/temp.spec/temp.expl.spec/p2-20.cpp   | 243 ++++++++++++++++++
 .../cxx1y-variable-templates_in_class.cpp     |   4 +-
 7 files changed, 370 insertions(+), 111 deletions(-)
 create mode 100644 clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-20.cpp

diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 9a4a777f575b2..13f10cb60f94a 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -3162,7 +3162,8 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
                      DeclSpec::SCS_static &&
                  DeclaratorInfo.getDeclSpec().getStorageClassSpec() !=
                      DeclSpec::SCS_typedef &&
-                 !DS.isFriendSpecified()) {
+                 !DS.isFriendSpecified() &&
+                 TemplateInfo.Kind == ParsedTemplateInfo::NonTemplate) {
         // It's a default member initializer.
         if (BitfieldSize.get())
           Diag(Tok, getLangOpts().CPlusPlus20
diff --git a/clang/lib/Sema/DeclSpec.cpp b/clang/lib/Sema/DeclSpec.cpp
index 60e8189025700..96c90a60b9682 100644
--- a/clang/lib/Sema/DeclSpec.cpp
+++ b/clang/lib/Sema/DeclSpec.cpp
@@ -416,6 +416,7 @@ bool Declarator::isDeclarationOfFunction() const {
 bool Declarator::isStaticMember() {
   assert(getContext() == DeclaratorContext::Member);
   return getDeclSpec().getStorageClassSpec() == DeclSpec::SCS_static ||
+         (!isDeclarationOfFunction() && !getTemplateParameterLists().empty()) ||
          (getName().getKind() == UnqualifiedIdKind::IK_OperatorFunctionId &&
           CXXMethodDecl::isStaticOverloadedOperator(
               getName().OperatorFunctionId.Operator));
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 6a0187a4e7ad5..c7aa038718e15 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7602,80 +7602,8 @@ NamedDecl *Sema::ActOnVariableDeclarator(
                             NTCUC_AutoVar, NTCUK_Destruct);
   } else {
     bool Invalid = false;
-
-    if (DC->isRecord() && !CurContext->isRecord()) {
-      // This is an out-of-line definition of a static data member.
-      switch (SC) {
-      case SC_None:
-        break;
-      case SC_Static:
-        Diag(D.getDeclSpec().getStorageClassSpecLoc(),
-             diag::err_static_out_of_line)
-          << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
-        break;
-      case SC_Auto:
-      case SC_Register:
-      case SC_Extern:
-        // [dcl.stc] p2: The auto or register specifiers shall be applied only
-        // to names of variables declared in a block or to function parameters.
-        // [dcl.stc] p6: The extern specifier cannot be used in the declaration
-        // of class members
-
-        Diag(D.getDeclSpec().getStorageClassSpecLoc(),
-             diag::err_storage_class_for_static_member)
-          << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
-        break;
-      case SC_PrivateExtern:
-        llvm_unreachable("C storage class in c++!");
-      }
-    }
-
-    if (SC == SC_Static && CurContext->isRecord()) {
-      if (const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(DC)) {
-        // Walk up the enclosing DeclContexts to check for any that are
-        // incompatible with static data members.
-        const DeclContext *FunctionOrMethod = nullptr;
-        const CXXRecordDecl *AnonStruct = nullptr;
-        for (DeclContext *Ctxt = DC; Ctxt; Ctxt = Ctxt->getParent()) {
-          if (Ctxt->isFunctionOrMethod()) {
-            FunctionOrMethod = Ctxt;
-            break;
-          }
-          const CXXRecordDecl *ParentDecl = dyn_cast<CXXRecordDecl>(Ctxt);
-          if (ParentDecl && !ParentDecl->getDeclName()) {
-            AnonStruct = ParentDecl;
-            break;
-          }
-        }
-        if (FunctionOrMethod) {
-          // C++ [class.static.data]p5: A local class shall not have static data
-          // members.
-          Diag(D.getIdentifierLoc(),
-               diag::err_static_data_member_not_allowed_in_local_class)
-              << Name << RD->getDeclName()
-              << llvm::to_underlying(RD->getTagKind());
-        } else if (AnonStruct) {
-          // C++ [class.static.data]p4: Unnamed classes and classes contained
-          // directly or indirectly within unnamed classes shall not contain
-          // static data members.
-          Diag(D.getIdentifierLoc(),
-               diag::err_static_data_member_not_allowed_in_anon_struct)
-              << Name << llvm::to_underlying(AnonStruct->getTagKind());
-          Invalid = true;
-        } else if (RD->isUnion()) {
-          // C++98 [class.union]p1: If a union contains a static data member,
-          // the program is ill-formed. C++11 drops this restriction.
-          Diag(D.getIdentifierLoc(),
-               getLangOpts().CPlusPlus11
-                 ? diag::warn_cxx98_compat_static_data_member_in_union
-                 : diag::ext_static_data_member_in_union) << Name;
-        }
-      }
-    }
-
     // Match up the template parameter lists with the scope specifier, then
     // determine whether we have a template or a template specialization.
-    bool InvalidScope = false;
     TemplateParams = MatchTemplateParametersToScopeSpecifier(
         D.getDeclSpec().getBeginLoc(), D.getIdentifierLoc(),
         D.getCXXScopeSpec(),
@@ -7683,8 +7611,7 @@ NamedDecl *Sema::ActOnVariableDeclarator(
             ? D.getName().TemplateId
             : nullptr,
         TemplateParamLists,
-        /*never a friend*/ false, IsMemberSpecialization, InvalidScope);
-    Invalid |= InvalidScope;
+        /*never a friend*/ false, IsMemberSpecialization, Invalid);
 
     if (TemplateParams) {
       if (!TemplateParams->size() &&
@@ -7716,14 +7643,6 @@ NamedDecl *Sema::ActOnVariableDeclarator(
                    ? diag::warn_cxx11_compat_variable_template
                    : diag::ext_variable_template);
         }
-
-        if (CurContext->isRecord() && SC != SC_Static && (IsVariableTemplate || IsPartialSpecialization)) {
-          // There is no such thing as a member field template.
-          Diag(D.getIdentifierLoc(), diag::err_template_member)
-              << II << TemplateParams->getSourceRange();
-          // Recover by pretending this is a static data member template.
-          SC = SC_Static;
-        }
       }
     } else {
       // Check that we can declare a member specialization here.
@@ -7735,6 +7654,88 @@ NamedDecl *Sema::ActOnVariableDeclarator(
              "should have a 'template<>' for this decl");
     }
 
+    if (SC != SC_None && ((IsVariableTemplateSpecialization && !IsPartialSpecialization) || IsMemberSpecialization)) {
+      Diag(D.getDeclSpec().getStorageClassSpecLoc(),
+             diag::ext_explicit_specialization_storage_class)
+          << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
+    }
+
+    if (CurContext->isRecord()) {
+      if (SC == SC_Static) {
+        if (const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(DC)) {
+          // Walk up the enclosing DeclContexts to check for any that are
+          // incompatible with static data members.
+          const DeclContext *FunctionOrMethod = nullptr;
+          const CXXRecordDecl *AnonStruct = nullptr;
+          for (DeclContext *Ctxt = DC; Ctxt; Ctxt = Ctxt->getParent()) {
+            if (Ctxt->isFunctionOrMethod()) {
+              FunctionOrMethod = Ctxt;
+              break;
+            }
+            const CXXRecordDecl *ParentDecl = dyn_cast<CXXRecordDecl>(Ctxt);
+            if (ParentDecl && !ParentDecl->getDeclName()) {
+              AnonStruct = ParentDecl;
+              break;
+            }
+          }
+          if (FunctionOrMethod) {
+            // C++ [class.static.data]p5: A local class shall not have static data
+            // members.
+            Diag(D.getIdentifierLoc(),
+                 diag::err_static_data_member_not_allowed_in_local_class)
+                << Name << RD->getDeclName()
+                << llvm::to_underlying(RD->getTagKind());
+          } else if (AnonStruct) {
+            // C++ [class.static.data]p4: Unnamed classes and classes contained
+            // directly or indirectly within unnamed classes shall not contain
+            // static data members.
+            Diag(D.getIdentifierLoc(),
+                 diag::err_static_data_member_not_allowed_in_anon_struct)
+                << Name << llvm::to_underlying(AnonStruct->getTagKind());
+            Invalid = true;
+          } else if (RD->isUnion()) {
+            // C++98 [class.union]p1: If a union contains a static data member,
+            // the program is ill-formed. C++11 drops this restriction.
+            Diag(D.getIdentifierLoc(),
+                 getLangOpts().CPlusPlus11
+                   ? diag::warn_cxx98_compat_static_data_member_in_union
+                   : diag::ext_static_data_member_in_union) << Name;
+          }
+        }
+      } else if (IsVariableTemplate || IsPartialSpecialization) {
+        // There is no such thing as a member field template.
+        Diag(D.getIdentifierLoc(), diag::err_template_member)
+            << II << TemplateParams->getSourceRange();
+        // Recover by pretending this is a static data member template.
+        SC = SC_Static;
+      }
+    } else if (DC->isRecord()) {
+      // This is an out-of-line definition of a static data member.
+      switch (SC) {
+      case SC_None:
+        break;
+      case SC_Static:
+        Diag(D.getDeclSpec().getStorageClassSpecLoc(),
+             diag::err_static_out_of_line)
+          << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
+        break;
+      case SC_Auto:
+      case SC_Register:
+      case SC_Extern:
+        // [dcl.stc] p2: The auto or register specifiers shall be applied only
+        // to names of variables declared in a block or to function parameters.
+        // [dcl.stc] p6: The extern specifier cannot be used in the declaration
+        // of class members
+
+        Diag(D.getDeclSpec().getStorageClassSpecLoc(),
+             diag::err_storage_class_for_static_member)
+          << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
+        break;
+      case SC_PrivateExtern:
+        llvm_unreachable("C storage class in c++!");
+      }
+    }
+
     if (IsVariableTemplateSpecialization) {
       SourceLocation TemplateKWLoc =
           TemplateParamLists.size() > 0
@@ -10211,26 +10212,37 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
       NewFD->setImplicitlyInline(ImplicitInlineCXX20);
     }
 
-    if (SC == SC_Static && isa<CXXMethodDecl>(NewFD) &&
-        !CurContext->isRecord()) {
-      // C++ [class.static]p1:
-      //   A data or function member of a class may be declared static
-      //   in a class definition, in which case it is a static member of
-      //   the class.
-
-      // Complain about the 'static' specifier if it's on an out-of-line
-      // member function definition.
-
-      // MSVC permits the use of a 'static' storage specifier on an out-of-line
-      // member function template declaration and class member template
-      // declaration (MSVC versions before 2015), warn about this.
-      Diag(D.getDeclSpec().getStorageClassSpecLoc(),
-           ((!getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2015) &&
-             cast<CXXRecordDecl>(DC)->getDescribedClassTemplate()) ||
-           (getLangOpts().MSVCCompat && NewFD->getDescribedFunctionTemplate()))
-           ? diag::ext_static_out_of_line : diag::err_static_out_of_line)
-        << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
+    if (!isFriend && SC != SC_None) {
+      if (isFunctionTemplateSpecialization || isMemberSpecialization) {
+        Diag(D.getDeclSpec().getStorageClassSpecLoc(),
+               diag::ext_explicit_specialization_storage_class)
+            << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
+      } else if (SC == SC_Static && !CurContext->isRecord() && DC->isRecord()) {
+        assert(isa<CXXMethodDecl>(NewFD) && "Out-of-line member function should be a CXXMethodDecl");
+        // C++ [class.static]p1:
+        //   A data or function member of a class may be declared static
+        //   in a class definition, in which case it is a static member of
+        //   the class.
+
+        // Complain about the 'static' specifier if it's on an out-of-line
+        // member function definition.
+
+        // MSVC permits the use of a 'static' storage specifier on an out-of-line
+        // member function template declaration and class member template
+        // declaration (MSVC versions before 2015), warn about this.
+        Diag(D.getDeclSpec().getStorageClassSpecLoc(),
+             ((!getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2015) &&
+               cast<CXXRecordDecl>(DC)->getDescribedClassTemplate()) ||
+             (getLangOpts().MSVCCompat && NewFD->getDescribedFunctionTemplate()))
+             ? diag::ext_static_out_of_line : diag::err_static_out_of_line)
+          << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
+      }
+    }
+    #if 0
+    if (SC != SC_None && !isFriend && ) {
+    } else if (SC == SC_Static && isa<CXXMethodDecl>(NewFD) && !CurContext->isRecord()) {
     }
+    #endif
 
     // C++11 [except.spec]p15:
     //   A deallocation function with no exception-specification is treated
@@ -10598,6 +10610,7 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
           NewFD->setInvalidDecl();
       }
 
+      #if 0
       // C++ [dcl.stc]p1:
       //   A storage-class-specifier shall not be specified in an explicit
       //   specialization (14.7.3)
@@ -10618,6 +10631,7 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
             << FixItHint::CreateRemoval(
                                       D.getDeclSpec().getStorageClassSpecLoc());
       }
+      #endif
     } else if (isMemberSpecialization && isa<CXXMethodDecl>(NewFD)) {
       if (CheckMemberSpecialization(NewFD, Previous))
           NewFD->setInvalidDecl();
diff --git a/clang/test/CXX/drs/cwg7xx.cpp b/clang/test/CXX/drs/cwg7xx.cpp
index 0300dae08d6d3..334c616dd35ae 100644
--- a/clang/test/CXX/drs/cwg7xx.cpp
+++ b/clang/test/CXX/drs/cwg7xx.cpp
@@ -277,7 +277,7 @@ namespace cwg727 { // cwg727: partial
     // cxx98-11-error at -1 {{variable templates are a C++14 extension}}
     // cxx98-14-error at -2 {{inline variables are a C++17 extension}}
     template<> static inline int v2<T>; // #cwg727-v2-T
-    // cxx98-14-error at -1 {{inline variables are a C++17 extension}} 
+    // cxx98-14-error at -1 {{inline variables are a C++17 extension}}
     template<> static inline int v2<U>;
     // cxx98-14-error at -1 {{inline variables are a C++17 extension}}
     // expected-error at -2 {{duplicate member 'v2'}}
diff --git a/clang/test/CXX/temp/temp.spec/temp.expl.spec/p17.cpp b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p17.cpp
index 1b039627a1d3a..c91dbb7bb8c03 100644
--- a/clang/test/CXX/temp/temp.spec/temp.expl.spec/p17.cpp
+++ b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p17.cpp
@@ -1,12 +1,12 @@
 // RUN: %clang_cc1 -fsyntax-only -verify %s
-template<class T1> 
+template<class T1>
 class A {
   template<class T2> class B {
     void mf();
   };
 };
 
-template<> template<> class A<int>::B<double>; 
+template<> template<> class A<int>::B<double>;
 template<> template<> void A<char>::B<char>::mf();
 
 template<> void A<char>::B<int>::mf(); // expected-error{{requires 'template<>'}}
@@ -17,15 +17,15 @@ namespace test1 {
     static int bar;
   };
   typedef A<int> AA;
-  
-  template <> int AA::foo = 0; 
+
+  template <> int AA::foo = 0;
   int AA::bar = 1; // expected-error {{template specialization requires 'template<>'}}
   int A<float>::bar = 2; // expected-error {{template specialization requires 'template<>'}}
 
-  template <> class A<double> { 
+  template <> class A<double> {
   public:
     static int foo;
-    static int bar;    
+    static int bar;
   };
 
   typedef A<double> AB;
diff --git a/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-20.cpp b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-20.cpp
new file mode 100644
index 0000000000000..0acc4361c6b62
--- /dev/null
+++ b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-20.cpp
@@ -0,0 +1,243 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s
+
+struct A {
+  static int x;
+
+  static int y;
+
+  static void f();
+
+  static void g();
+};
+
+int A::x = 0;
+
+static int A::y = 0; // expected-error {{'static' can only be specified inside the class definition}}
+
+void A::f() { }
+
+static void A::g() { } // expected-error {{'static' can only be specified inside the class definition}}
+
+struct B {
+  template<typename T>
+  static int x;
+
+  template<typename T>
+  static int y;
+
+  template<typename T>
+  int z; // expected-error {{member 'z' declared as a template}}
+
+  template<typename T>
+  static int x<T*>;
+
+  template<typename T>
+  static int y<T*>;
+
+  template<typename T>
+  int x<T**>; // expected-error {{member 'x' declared as a template}}
+
+  template<>
+  int x<short>;
+
+  template<>
+  static int x<long>; // expected-warning {{explicit specialization cannot have a storage class}}
+
+  template<typename T>
+  static void f();
+
+  template<typename T>
+  static void g();
+
+  template<>
+  void f<short>();
+
+  template<>
+  static void f<long>(); // expected-warning {{explicit specialization cannot have a storage class}}
+};
+
+template<typename T>
+int B::x = 0;
+
+template<typename T>
+static int B::y = 0; // expected-error {{'static' can only be specified inside the class definition}}
+
+template<typename T>
+int B::x<T*> = 0;
+
+template<typename T>
+static int B::y<T*> = 0; // expected-error {{'static' can only be specified inside the class definition}}
+
+template<typename T>
+int B::x<T***>;
+
+template<typename T>
+static int B::y<T***>; // expected-error {{'static' can only be specified inside the class definition}}
+
+template<>
+int B::x<unsigned>;
+
+template<>
+static int B::y<unsigned>; // expected-warning {{explicit specialization cannot have a storage class}}
+                           // expected-error at -1 {{'static' can only be specified inside the class definition}}
+
+template<typename T>
+void B::f() { }
+
+template<typename T>
+static void B::g() { } // expected-error {{'static' can only be specified inside the class definition}}
+
+template<>
+void B::f<unsigned>();
+
+template<>
+static void B::g<unsigned>(); // expected-warning {{explicit specialization cannot have a storage class}}
+
+
+template<typename T>
+struct C {
+  static int x;
+
+  static int y;
+
+  static void f();
+
+  static void g();
+};
+
+template<typename T>
+int C<T>::x = 0;
+
+template<typename T>
+static int C<T>::y = 0; // expected-error {{'static' can only be specified inside the class definition}}
+
+template<typename T>
+void C<T>::f() { }
+
+template<typename T>
+static void C<T>::g() { } // expected-warning {{'static' can only be specified inside the class definition}}
+
+template<>
+int C<int>::x = 0;
+
+template<>
+static int C<int>::y = 0; // expected-warning {{explicit specialization cannot have a storage class}}
+                          // expected-error at -1 {{'static' can only be specified inside the class definition}}
+
+template<>
+void C<int>::f();
+
+template<>
+static void C<int>::g(); // expected-warning {{explicit specialization cannot have a storage class}}
+
+template<typename T>
+struct D {
+  template<typename U>
+  static int x;
+
+  template<typename U>
+  static int y;
+
+  template<typename U>
+  int z; // expected-error {{member 'z' declared as a template}}
+
+  template<typename U>
+  static int x<U*>;
+
+  template<typename U>
+  static int y<U*>;
+
+  template<typename U>
+  int x<U**>; // expected-error {{member 'x' declared as a template}}
+
+  template<>
+  int x<short>;
+
+  template<>
+  static int x<long>; // expected-warning {{explicit specialization cannot have a storage class}}
+
+  template<typename U>
+  static void f();
+
+  template<typename U>
+  static void g();
+
+  template<>
+  void f<short>();
+
+  template<>
+  static void f<long>(); // expected-warning {{explicit specialization cannot have a storage class}}
+};
+
+template<typename T>
+template<typename U>
+int D<T>::x = 0;
+
+template<typename T>
+template<typename U>
+static int D<T>::y = 0; // expected-error {{'static' can only be specified inside the class definition}}
+
+template<typename T>
+template<typename U>
+int D<T>::x<U*> = 0;
+
+template<typename T>
+template<typename U>
+static int D<T>::y<U*> = 0; // expected-error {{'static' can only be specified inside the class definition}}
+
+template<typename T>
+template<typename U>
+int D<T>::x<U***>;
+
+template<typename T>
+template<typename U>
+static int D<T>::y<U***>; // expected-error {{'static' can only be specified inside the class definition}}
+
+template<>
+template<typename U>
+int D<int>::x;
+
+template<>
+template<typename U>
+static int D<int>::y; // expected-warning {{explicit specialization cannot have a storage class}}
+                      // expected-error at -1 {{'static' can only be specified inside the class definition}}
+template<>
+template<typename U>
+int D<int>::x<U****>;
+
+template<>
+template<typename U>
+static int D<int>::y<U****>; // expected-warning {{explicit specialization cannot have a storage class}}
+                             // expected-error at -1 {{'static' can only be specified inside the class definition}}
+template<>
+template<>
+int D<int>::x<unsigned>;
+
+template<>
+template<>
+static int D<int>::y<unsigned>; // expected-warning {{explicit specialization cannot have a storage class}}
+                                // expected-error at -1 {{'static' can only be specified inside the class definition}}
+
+template<typename T>
+template<typename U>
+void D<T>::f() { }
+
+template<typename T>
+template<typename U>
+static void D<T>::g() { } // expected-warning {{'static' can only be specified inside the class definition}}
+
+template<>
+template<typename U>
+void D<int>::f();
+
+template<>
+template<typename U>
+static void D<int>::g(); // expected-warning {{explicit specialization cannot have a storage class}}
+
+template<>
+template<>
+void D<int>::f<unsigned>();
+
+template<>
+template<>
+static void D<int>::g<unsigned>(); // expected-warning {{explicit specialization cannot have a storage class}}
diff --git a/clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp b/clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp
index af121a8b75d51..75d8832181067 100644
--- a/clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp
+++ b/clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp
@@ -352,7 +352,7 @@ namespace ns2 {
   };
   template<class T> template<class U, T N, U M> T&& A<T>::Var = T(N + M);
   int *AV = &A<int>().Var<char, 5, 'A'>;
-  
+
 } //end ns2
 } // end ns member_access_is_ok
 
@@ -372,7 +372,7 @@ struct Something
     }
 };
 
-int main() { 
+int main() {
     Something<Value>{}.foo();
     return 0;
 }

>From 8ed32139d54b9c9543410ff8f0503a969e482521 Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 28 May 2024 10:56:08 -0400
Subject: [PATCH 3/6] [FOLD]

---
 .../temp/temp.spec/temp.expl.spec/p2-20.cpp   | 30 +++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-20.cpp b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-20.cpp
index 0acc4361c6b62..f55a4231b2153 100644
--- a/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-20.cpp
+++ b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-20.cpp
@@ -1,5 +1,35 @@
 // RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s
 
+template<typename T>
+int x;
+
+template<typename T>
+static int x<T*>;
+
+template<>
+static int x<int>; // expected-warning {{explicit specialization cannot have a storage class}}
+
+template<typename T>
+extern int y;
+
+template<typename T>
+static int y<T*>;
+
+template<>
+static int y<int>; // expected-warning {{explicit specialization cannot have a storage class}}
+
+template<typename T>
+void f();
+
+template<>
+static void f<int>(); // expected-warning {{explicit specialization cannot have a storage class}}
+
+template<typename T>
+extern void g();
+
+template<>
+static void g<int>(); // expected-warning {{explicit specialization cannot have a storage class}}
+
 struct A {
   static int x;
 

>From 4372df3fe5322e7cb31d5ca825db7e8981aec9f8 Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 28 May 2024 14:18:26 -0400
Subject: [PATCH 4/6] [FOLD]

---
 clang/lib/Parse/ParseDeclCXX.cpp | 2 +-
 clang/lib/Sema/SemaDecl.cpp      | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 13f10cb60f94a..d02548f6441f9 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -3262,7 +3262,7 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
       } else if (ThisDecl)
         Actions.AddInitializerToDecl(ThisDecl, Init.get(),
                                      EqualLoc.isInvalid());
-    } else if (ThisDecl && DS.getStorageClassSpec() == DeclSpec::SCS_static)
+    } else if (ThisDecl && DeclaratorInfo.isStaticMember())
       // No initializer.
       Actions.ActOnUninitializedDecl(ThisDecl);
 
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index c7aa038718e15..74cdcf4a09804 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7654,7 +7654,7 @@ NamedDecl *Sema::ActOnVariableDeclarator(
              "should have a 'template<>' for this decl");
     }
 
-    if (SC != SC_None && ((IsVariableTemplateSpecialization && !IsPartialSpecialization) || IsMemberSpecialization)) {
+    if (SCSpec != DeclSpec::SCS_unspecified && ((IsVariableTemplateSpecialization && !IsPartialSpecialization) || IsMemberSpecialization)) {
       Diag(D.getDeclSpec().getStorageClassSpecLoc(),
              diag::ext_explicit_specialization_storage_class)
           << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());

>From 107f8f7de89079a5efa30a9177993c4b51f58da3 Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 28 May 2024 14:18:36 -0400
Subject: [PATCH 5/6] [FOLD] update tests

---
 .../test/CXX/dcl.dcl/dcl.spec/dcl.stc/p1.cpp  |  5 +-
 clang/test/CXX/drs/cwg7xx.cpp                 | 22 +++----
 .../test/CXX/temp/temp.decls/temp.mem/p2.cpp  |  2 +-
 .../CXX/temp/temp.decls/temp.variadic/p5.cpp  |  2 +-
 .../CXX/temp/temp.spec/temp.expl.spec/p17.cpp |  3 +-
 .../temp/temp.spec/temp.expl.spec/p2-0x.cpp   | 59 ++++++++++---------
 .../test/Modules/Inputs/redecl-templates/a.h  |  2 +-
 clang/test/Modules/redecl-templates.cpp       |  3 +-
 clang/test/PCH/cxx-templates.h                | 12 ++--
 clang/test/PCH/cxx1y-variable-templates.cpp   | 24 +++++---
 .../cxx1y-variable-templates_in_class.cpp     | 26 ++++----
 .../explicit-specialization-member.cpp        |  8 +--
 clang/test/SemaTemplate/nested-template.cpp   | 14 ++---
 13 files changed, 100 insertions(+), 82 deletions(-)

diff --git a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.stc/p1.cpp b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.stc/p1.cpp
index cbb439ef5fecd..f6b5d2487e73d 100644
--- a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.stc/p1.cpp
+++ b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.stc/p1.cpp
@@ -7,7 +7,7 @@ template<typename T> void f(T) {}
 template<typename T> static void g(T) {}
 
 
-template<> static void f<int>(int); // expected-error{{explicit specialization has extraneous, inconsistent storage class 'static'}}
+template<> static void f<int>(int); // expected-warning{{explicit specialization cannot have a storage class}}
 template static void f<float>(float); // expected-error{{explicit instantiation cannot have a storage class}}
 
 template<> void f<double>(double);
@@ -29,4 +29,5 @@ int X<T>::value = 17;
 
 template static int X<int>::value; // expected-error{{explicit instantiation cannot have a storage class}}
 
-template<> static int X<float>::value; // expected-error{{'static' can only be specified inside the class definition}}
+template<> static int X<float>::value; // expected-warning{{explicit specialization cannot have a storage class}}
+                                       // expected-error at -1{{'static' can only be specified inside the class definition}}
diff --git a/clang/test/CXX/drs/cwg7xx.cpp b/clang/test/CXX/drs/cwg7xx.cpp
index 334c616dd35ae..6d93e2948dadb 100644
--- a/clang/test/CXX/drs/cwg7xx.cpp
+++ b/clang/test/CXX/drs/cwg7xx.cpp
@@ -80,7 +80,7 @@ namespace cwg727 { // cwg727: partial
 
     template<> struct C<int>;
     template<> void f<int>();
-    template<> static int N<int>;
+    template<> int N<int>;
 
     template<typename T> struct C<T*>;
     template<typename T> static int N<T*>;
@@ -91,7 +91,7 @@ namespace cwg727 { // cwg727: partial
       //   expected-note@#cwg727-C {{explicitly specialized declaration is here}}
       template<> void f<float>();
       // expected-error at -1 {{no function template matches function template specialization 'f'}}
-      template<> static int N<float>;
+      template<> int N<float>;
       // expected-error at -1 {{variable template specialization of 'N' not in class 'A' or an enclosing namespace}}
       //   expected-note@#cwg727-N {{explicitly specialized declaration is here}}
 
@@ -109,7 +109,7 @@ namespace cwg727 { // cwg727: partial
       template<> void A::f<double>();
       // expected-error at -1 {{o function template matches function template specialization 'f'}}
       // expected-error at -2 {{non-friend class member 'f' cannot have a qualified name}}
-      template<> static int A::N<double>;
+      template<> int A::N<double>;
       // expected-error at -1 {{non-friend class member 'N' cannot have a qualified name}}
       // expected-error at -2 {{variable template specialization of 'N' not in class 'A' or an enclosing namespace}}
       //   expected-note@#cwg727-N {{explicitly specialized declaration is here}}
@@ -166,7 +166,7 @@ namespace cwg727 { // cwg727: partial
 
     template<> struct C<int> {};
     template<> void f<int>() {}
-    template<> static const int N<int>;
+    template<> const int N<int>;
 
     template<typename T> struct C<T*> {};
     template<typename T> static const int N<T*>;
@@ -208,18 +208,18 @@ namespace cwg727 { // cwg727: partial
 #if __cplusplus >= 201402L
     template<int> struct B {
       template<int> static const int u = 1;
-      template<> static const int u<0> = 2; // #cwg727-u0
+      template<> const int u<0> = 2; // #cwg727-u0
 
       // Note that in C++17 onwards, these are implicitly inline, and so the
       // initializer of v<0> is not instantiated with the declaration. In
       // C++14, v<0> is a non-defining declaration and its initializer is
       // instantiated with the class.
       template<int> static constexpr int v = 1;
-      template<> static constexpr int v<0> = 2; // #cwg727-v0
+      template<> constexpr int v<0> = 2; // #cwg727-v0
 
       template<int> static const inline int w = 1;
       // cxx14-error at -1 {{inline variables are a C++17 extension}}
-      template<> static const inline int w<0> = 2;
+      template<> const inline int w<0> = 2;
       // cxx14-error at -1 {{inline variables are a C++17 extension}}
     };
 
@@ -267,8 +267,8 @@ namespace cwg727 { // cwg727: partial
 
     template<typename> static int v1;
     // cxx98-11-error at -1 {{variable templates are a C++14 extension}}
-    template<> static int v1<T>; // #cwg727-v1-T
-    template<> static int v1<U>;
+    template<> int v1<T>; // #cwg727-v1-T
+    template<> int v1<U>;
     // expected-error at -1 {{duplicate member 'v1'}}
     //   expected-note@#cwg727-Collision-int-int {{in instantiation of template class 'cwg727::Collision<int, int>' requested here}}
     //   expected-note@#cwg727-v1-T {{previous}}
@@ -276,9 +276,9 @@ namespace cwg727 { // cwg727: partial
     template<typename> static inline int v2;
     // cxx98-11-error at -1 {{variable templates are a C++14 extension}}
     // cxx98-14-error at -2 {{inline variables are a C++17 extension}}
-    template<> static inline int v2<T>; // #cwg727-v2-T
+    template<> inline int v2<T>; // #cwg727-v2-T
     // cxx98-14-error at -1 {{inline variables are a C++17 extension}}
-    template<> static inline int v2<U>;
+    template<> inline int v2<U>;
     // cxx98-14-error at -1 {{inline variables are a C++17 extension}}
     // expected-error at -2 {{duplicate member 'v2'}}
     //   expected-note@#cwg727-v2-T {{previous declaration is here}}
diff --git a/clang/test/CXX/temp/temp.decls/temp.mem/p2.cpp b/clang/test/CXX/temp/temp.decls/temp.mem/p2.cpp
index feeb362e34b40..2a74429bd7290 100644
--- a/clang/test/CXX/temp/temp.decls/temp.mem/p2.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.mem/p2.cpp
@@ -9,6 +9,6 @@ void fun() {
     template <typename> void baz() {}   // expected-error{{templates cannot be declared inside of a local class}}
     template <typename> void qux();     // expected-error{{templates cannot be declared inside of a local class}}
     template <typename> using corge = int; // expected-error{{templates cannot be declared inside of a local class}}
-    template <typename T> static T grault; // expected-error{{static data member}} expected-error{{templates cannot be declared inside of a local class}}
+    template <typename T> static T grault; // expected-error{{templates cannot be declared inside of a local class}}
   };
 }
diff --git a/clang/test/CXX/temp/temp.decls/temp.variadic/p5.cpp b/clang/test/CXX/temp/temp.decls/temp.variadic/p5.cpp
index 3c500c2c4dc4a..16e668e971a21 100644
--- a/clang/test/CXX/temp/temp.decls/temp.variadic/p5.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.variadic/p5.cpp
@@ -408,7 +408,7 @@ namespace Specializations {
     template<typename... Us>
     constexpr static int InnerVar = 0;
     template<>
-    constexpr static int InnerVar<Ts> = 0; // expected-error{{explicit specialization contains unexpanded parameter pack 'Ts'}}
+    constexpr int InnerVar<Ts> = 0; // expected-error{{explicit specialization contains unexpanded parameter pack 'Ts'}}
     template<typename U>
     constexpr static int InnerVar<U, Ts> = 0; // expected-error{{partial specialization contains unexpanded parameter pack 'Ts'}}
 #endif
diff --git a/clang/test/CXX/temp/temp.spec/temp.expl.spec/p17.cpp b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p17.cpp
index c91dbb7bb8c03..904058e71a7b8 100644
--- a/clang/test/CXX/temp/temp.spec/temp.expl.spec/p17.cpp
+++ b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p17.cpp
@@ -40,7 +40,8 @@ struct S {
   int j<int>; // expected-error  {{member 'j' cannot have template arguments}}
 
   static int k<12>; // expected-error {{template specialization requires 'template<>'}} \
-                       expected-error{{no variable template matches specialization}}
+                       expected-error {{no variable template matches specialization}} \
+                       expected-warning {{explicit specialization cannot have a storage class}}
   void f<12>();     // expected-error {{template specialization requires 'template<>'}} \
                     // expected-error {{no function template matches function template specialization 'f'}}
 };
diff --git a/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-0x.cpp b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-0x.cpp
index c29646dd94559..80fae707de0c4 100644
--- a/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-0x.cpp
+++ b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-0x.cpp
@@ -24,7 +24,7 @@ namespace N0 {
   void test_f0(NonDefaultConstructible NDC) {
     f0(NDC);
   }
-  
+
   template<> void f0(int);
   template<> void f0(long);
 }
@@ -39,34 +39,34 @@ template<> void N0::f0(double) { }
 
 struct X1 {
   template<typename T> void f(T);
-  
+
   template<> void f(int); // OK (DR727)
 };
 
 //     -- class template
 namespace N0 {
-  
+
 template<typename T>
 struct X0 { // expected-note {{here}}
   static T member;
-  
+
   void f1(T t) {
     t = 17;
   }
-  
+
   struct Inner : public T { }; // expected-note 2{{here}}
-  
+
   template<typename U>
   struct InnerTemplate : public T { }; // expected-note 1{{explicitly specialized}} \
    // expected-error{{base specifier}}
-  
+
   template<typename U>
   void ft1(T t, U u);
 };
 
 }
 
-template<typename T> 
+template<typename T>
 template<typename U>
 void N0::X0<T>::ft1(T t, U u) {
   t = u;
@@ -85,35 +85,36 @@ namespace N0 {
   template<> struct X0<volatile void>;
 }
 
-template<> struct N0::X0<volatile void> { 
+template<> struct N0::X0<volatile void> {
   void f1(void *);
 };
 
 //     -- variable template [C++1y]
 namespace N0 {
 template<typename T> int v0; // expected-note 4{{explicitly specialized declaration is here}}
-template<> extern int v0<char[1]>;
-template<> extern int v0<char[2]>;
-template<> extern int v0<char[5]>;
-template<> extern int v0<char[6]>;
+template<> int v0<char[1]>; // expected-note {{previous definition is here}}
+template<> int v0<char[2]>;
+template<> int v0<char[5]>; // expected-note {{previous definition is here}}
+template<> int v0<char[6]>;
 }
 using N0::v0;
 
 template<typename T> int v1; // expected-note 4{{explicitly specialized declaration is here}}
-template<> extern int v1<char[3]>;
-template<> extern int v1<char[4]>;
-template<> extern int v1<char[7]>;
-template<> extern int v1<char[8]>;
+template<> int v1<char[3]>; // expected-note {{previous definition is here}}
+template<> int v1<char[4]>; // expected-note {{previous definition is here}}
+template<> int v1<char[7]>; // expected-note {{previous definition is here}}
+template<> int v1<char[8]>;
 
 template<> int N0::v0<int[1]>;
 template<> int v0<int[2]>;
 template<> int ::v1<int[3]>; // expected-warning {{extra qualification}}
 template<> int v1<int[4]>;
 
-template<> int N0::v0<char[1]>;
+template<> int N0::v0<char[1]>; // expected-error {{redefinition of 'v0<char[1]>'}}
 template<> int v0<char[2]>;
 template<> int ::v1<char[3]>; // expected-warning {{extra qualification}}
-template<> int v1<char[4]>;
+                              // expected-error at -1 {{redefinition of 'v1<char[3]>'}}
+template<> int v1<char[4]>; // expected-error {{redefinition of 'v1<char[4]>'}}
 
 namespace N1 {
 template<> int N0::v0<int[5]>; // expected-error {{not in a namespace enclosing 'N0'}}
@@ -122,8 +123,10 @@ template<> int ::v1<int[7]>; // expected-error {{must occur at global scope}}
 template<> int v1<int[8]>; // expected-error {{must occur at global scope}}
 
 template<> int N0::v0<char[5]>; // expected-error {{not in a namespace enclosing 'N0'}}
+                                // expected-error at -1 {{redefinition of 'v0<char[5]>'}}
 template<> int v0<char[6]>; // expected-error {{not in a namespace enclosing 'N0'}}
 template<> int ::v1<char[7]>; // expected-error {{must occur at global scope}}
+                              // expected-error at -1 {{redefinition of 'v1<char[7]>'}}
 template<> int v1<char[8]>; // expected-error {{must occur at global scope}}
 }
 
@@ -147,13 +150,13 @@ void test_x0_cvvoid(N0::X0<const volatile void*> x0, const volatile void *cvp) {
 //     -- static data member of a class template
 namespace N0 {
   // This actually tests p15; the following is a declaration, not a definition.
-  template<> 
+  template<>
   NonDefaultConstructible X0<NonDefaultConstructible>::member;
-  
+
   template<> long X0<long>::member = 17;
 
   template<> float X0<float>::member;
-  
+
   template<> double X0<double>::member;
 }
 
@@ -171,7 +174,7 @@ namespace N1 {
 
 //    -- member class of a class template
 namespace N0 {
-  
+
   template<>
   struct X0<void*>::Inner { };
 
@@ -213,7 +216,7 @@ namespace N0 {
   template<>
   template<>
   struct X0<void*>::InnerTemplate<int> { };
-  
+
   template<> template<>
   struct X0<int>::InnerTemplate<int>; // expected-note{{forward declaration}}
 
@@ -245,7 +248,7 @@ namespace N0 {
   template<>
   template<>
   void X0<void*>::ft1(void*, const void*) { }
-  
+
   template<> template<>
   void X0<void*>::ft1(void *, int);
 
@@ -279,7 +282,7 @@ namespace has_inline_namespaces {
   inline namespace inner {
     template<class T> void f(T&);
 
-    template<class T> 
+    template<class T>
     struct X0 {
       struct MemberClass;
 
@@ -330,10 +333,10 @@ template<> struct has_inline_namespaces::X0<X4>::MemberClass { };
 
 template<> void has_inline_namespaces::X0<X4>::mem_func();
 
-template<> template<typename T> 
+template<> template<typename T>
 struct has_inline_namespaces::X0<X4>::MemberClassTemplate { };
 
-template<> template<typename T> 
+template<> template<typename T>
 void has_inline_namespaces::X0<X4>::mem_func_template(T&) { }
 
 template<> int has_inline_namespaces::X0<X4>::value = 13;
diff --git a/clang/test/Modules/Inputs/redecl-templates/a.h b/clang/test/Modules/Inputs/redecl-templates/a.h
index fd25fcf0768d2..205483fc01f12 100644
--- a/clang/test/Modules/Inputs/redecl-templates/a.h
+++ b/clang/test/Modules/Inputs/redecl-templates/a.h
@@ -5,4 +5,4 @@ template<int N> constexpr void f();
 template<> constexpr void f<1>();
 
 template<int N> extern int v;
-template<> extern int v<1>;
+template<> int v<1>;
diff --git a/clang/test/Modules/redecl-templates.cpp b/clang/test/Modules/redecl-templates.cpp
index ee42dc9c6a84c..37e3738f6307f 100644
--- a/clang/test/Modules/redecl-templates.cpp
+++ b/clang/test/Modules/redecl-templates.cpp
@@ -1,7 +1,6 @@
 // RUN: rm -rf %t
 // RUN: %clang_cc1 -x c++ -I %S/Inputs/redecl-templates %s -verify -std=c++14
 // RUN: %clang_cc1 -x c++ -fmodules -fimplicit-module-maps -fmodules-cache-path=%t -I %S/Inputs/redecl-templates %s -verify -std=c++14
-// expected-no-diagnostics
 
 template<int N> struct A {};
 template<int N> using X = A<N>;
@@ -29,4 +28,4 @@ int &x = w<1>;
 // instantiation of this specialization.
 template<> struct A<1> {};
 template<> constexpr void f<1>() {}
-template<> int v<1>;
+template<> int v<1>; // expected-error{{redefinition of 'v<1>'}}
diff --git a/clang/test/PCH/cxx-templates.h b/clang/test/PCH/cxx-templates.h
index 95d684e4a92db..8927fd55dcb83 100644
--- a/clang/test/PCH/cxx-templates.h
+++ b/clang/test/PCH/cxx-templates.h
@@ -49,7 +49,7 @@ struct Dep {
     int y = T::template my_templf<int>(0);
     ovl(y);
   }
-  
+
   void ovl(int);
   void ovl(float);
 };
@@ -67,7 +67,7 @@ template <class T> class UseBase {
 
 template <class T> class UseA : public UseBase<T> {
   using UseBase<T>::foo;
-  using typename UseBase<T>::bar; 
+  using typename UseBase<T>::bar;
 };
 
 template <class T> class Sub : public UseBase<int> { };
@@ -95,7 +95,7 @@ template<> bool isInt<8>(int x) {
 template<typename _CharT>
 int __copy_streambufs_eof(_CharT);
 
-class basic_streambuf 
+class basic_streambuf
 {
   void m() { }
   friend int __copy_streambufs_eof<>(int);
@@ -174,7 +174,7 @@ struct S7<int[N]> : S6<const int[N]> { };
 namespace ZeroLengthExplicitTemplateArgs {
   template<typename T> void h();
 
-  struct Y { 
+  struct Y {
     template<typename T> void f();
   };
 
@@ -417,11 +417,11 @@ namespace ClassScopeExplicitSpecializations {
   template<int> struct B {
     template<typename> static const int v = 1;
     template<typename T> static const int v<T*> = 2;
-    template<> static const int v<int> = 3;
+    template<> const int v<int> = 3;
 
     template<typename> static constexpr int w = 1;
     template<typename T> static constexpr int w<T*> = 2;
-    template<> static constexpr int w<int> = 3;
+    template<> constexpr int w<int> = 3;
   };
 
   template<> template<typename> constexpr int B<0>::v = 4;
diff --git a/clang/test/PCH/cxx1y-variable-templates.cpp b/clang/test/PCH/cxx1y-variable-templates.cpp
index faa9b3df22c1c..9063b6ee86938 100644
--- a/clang/test/PCH/cxx1y-variable-templates.cpp
+++ b/clang/test/PCH/cxx1y-variable-templates.cpp
@@ -67,11 +67,15 @@ namespace spec {
 
 namespace spec_join1 {
   template<typename T> T va = T(10);
-  template<> extern float va<float>;
+#ifdef ERROR
+  template<> float va<float>; // expected-note {{previous definition is here}}
+#endif
   extern template int va<int>;
 
   template<typename T> T vb = T(10);
-  template<> extern float vb<float>;
+#ifdef ERROR
+  template<> float vb<float>; // expected-note {{previous definition is here}}
+#endif
 
   template<typename T> T vc = T(10);
 
@@ -102,15 +106,19 @@ namespace join {
 
 namespace spec_join1 {
   template<typename T> extern T va;
-  template<> float va<float> = 1.5;
+#ifdef ERROR
+  template<> float va<float> = 1.5; // expected-error {{redefinition of 'va<float>'}}
+#endif
   extern template int va<int>;
-  
-  template<> float vb<float> = 1.5;
+
+#ifdef ERROR
+  template<> float vb<float> = 1.5; // expected-error {{redefinition of 'vb<float>'}}
+#endif
   template int vb<int>;
 
   template<> float vc<float> = 1.5;
   template int vc<int>;
-  
+
   template<typename T> extern T vd;
   template<typename T> T* vd<T*> = new T();
 }
@@ -123,9 +131,9 @@ namespace spec_join1 {
 template int var0a<int>;
 float fvara = var0a<float>;
 
-template<typename T> extern T var0a; 
+template<typename T> extern T var0a;
 
-template<typename T> T var0b = T(); 
+template<typename T> T var0b = T();
 template int var0b<int>;
 float fvarb = var0b<float>;
 
diff --git a/clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp b/clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp
index 75d8832181067..01123e17cb88e 100644
--- a/clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp
+++ b/clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp
@@ -15,8 +15,14 @@ class A {
   template<typename T> static CONST T right<T,int> = 5;
   template<typename T> CONST int right<int,T>;  // expected-error {{member 'right' declared as a template}}
   template<typename T> CONST float right<float,T> = 5;  // expected-error {{member 'right' declared as a template}}
-  template<> static CONST int right<int,int> = 7;
-  template<> static CONST float right<float,int>;
+#ifdef PRECXX11
+                                                        // expected-warning at -2 {{in-class initializer for static data member of type 'const float' is a GNU extension}}
+#else
+                                                        // expected-error at -4 {{in-class initializer for static data member of type 'const float' requires 'constexpr' specifier}}
+                                                        // expected-note at -5 {{add 'constexpr'}}
+#endif
+  template<> CONST int right<int,int> = 7;
+  template<> CONST float right<float,int>;
   template static CONST int right<int,int>;     // expected-error {{expected '<' after 'template'}}
 };
 
@@ -155,16 +161,16 @@ namespace non_const_init {
 #ifndef PRECXX11
 namespace constexpred {
   class A {
-    template<typename T> constexpr T wrong;           // expected-error {{member 'wrong' declared as a template}} \
-                                                      // expected-error {{non-static data member cannot be constexpr; did you intend to make it const?}}
-    template<typename T> constexpr T wrong_init = 5;      // expected-error {{non-static data member cannot be constexpr; did you intend to make it static?}}
+    template<typename T> constexpr T wrong;           // expected-error {{member 'wrong' declared as a template}}
+                                                      // expected-error at -1 {{declaration of constexpr static data member 'wrong' requires an initializer}}
+    template<typename T> constexpr T wrong_init = 5;  // expected-error {{member 'wrong_init' declared as a template}}
     template<typename T, typename T0> static constexpr T right = T(100);
     template<typename T> static constexpr T right<T,int> = 5;
-    template<typename T> constexpr int right<int,T>;  // expected-error {{member 'right' declared as a template}} \
-                                                      // expected-error {{non-static data member cannot be constexpr; did you intend to make it const?}}
-    template<typename T> constexpr float right<float,T> = 5;  // expected-error {{non-static data member cannot be constexpr; did you intend to make it static?}}
-    template<> static constexpr int right<int,int> = 7;
-    template <> static constexpr float right<float, int>; // expected-error {{declaration of constexpr static data member 'right<float, int>' requires an initializer}}
+    template<typename T> constexpr int right<int,T>;         // expected-error {{member 'right' declared as a template}}
+                                                             // expected-error at -1 {{declaration of constexpr static data member 'right<int, T>' requires an initializer}}
+    template<typename T> constexpr float right<float,T> = 5; // expected-error {{member 'right' declared as a template}}
+    template<> constexpr int right<int,int> = 7;
+    template<> constexpr float right<float, int>; // expected-error {{declaration of constexpr static data member 'right<float, int>' requires an initializer}}
     template static constexpr int right<int,int>;     // expected-error {{expected '<' after 'template'}}
   };
 }
diff --git a/clang/test/SemaTemplate/explicit-specialization-member.cpp b/clang/test/SemaTemplate/explicit-specialization-member.cpp
index 5dc8118556d42..c406fb3e21d40 100644
--- a/clang/test/SemaTemplate/explicit-specialization-member.cpp
+++ b/clang/test/SemaTemplate/explicit-specialization-member.cpp
@@ -2,7 +2,7 @@
 template<typename T>
 struct X0 {
   typedef T* type;
-  
+
   void f0(T);
   void f1(type);
 };
@@ -71,13 +71,13 @@ namespace PR41607 {
     };
 
     template<typename...> static int a;
-    template<> static constexpr int a<> = N;
+    template<> constexpr int a<> = N;
 
     template<typename...> static inline int b;
-    template<> static inline constexpr int b<> = N;
+    template<> inline constexpr int b<> = N;
 
     template<typename...> static constexpr int f();
-    template<> static constexpr int f() {
+    template<> constexpr int f() {
       return N;
     }
   };
diff --git a/clang/test/SemaTemplate/nested-template.cpp b/clang/test/SemaTemplate/nested-template.cpp
index 5bd388d4dff3d..a6ede8e99037e 100644
--- a/clang/test/SemaTemplate/nested-template.cpp
+++ b/clang/test/SemaTemplate/nested-template.cpp
@@ -3,7 +3,7 @@ class A;
 
 class S {
 public:
-   template<typename T> struct A { 
+   template<typename T> struct A {
      struct Nested {
        typedef T type;
      };
@@ -17,15 +17,15 @@ template<typename T>
 struct Outer {
   template<typename U>
   class Inner0;
-  
+
   template<typename U>
   class Inner1 {
     struct ReallyInner;
-    
+
     T foo(U);
     template<typename V> T bar(V);
     template<typename V> T* bar(V);
-    
+
     static T value1;
     static U value2;
   };
@@ -47,7 +47,7 @@ template<typename X>
 template<typename Y>
 struct Outer<X>::Inner1<Y>::ReallyInner {
   static Y value3;
-  
+
   void g(X, Y);
 };
 
@@ -130,10 +130,10 @@ namespace PR10896 {
   public:
     void foo() {}
   private:
-	
+
     template<typename T>
     T SomeField; // expected-error {{member 'SomeField' declared as a template}}
-    template<> int SomeField2; // expected-error {{extraneous 'template<>' in declaration of member 'SomeField2'}}
+    template<> int SomeField2; // expected-error {{extraneous 'template<>' in declaration of variable 'SomeField2'}}
   };
 
   void g() {

>From e870dc22b8ff3ff57fab42619922cef7c69631fb Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Wed, 29 May 2024 13:01:18 -0400
Subject: [PATCH 6/6] [FOLD]

---
 clang/lib/Sema/SemaDecl.cpp                   | 94 +++++++++----------
 clang/lib/Sema/SemaDeclCXX.cpp                |  8 +-
 .../temp/temp.spec/temp.expl.spec/p2-20.cpp   |  7 +-
 clang/test/Modules/redecl-templates.cpp       |  5 +-
 4 files changed, 58 insertions(+), 56 deletions(-)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 74cdcf4a09804..cc36387c0e332 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7654,9 +7654,20 @@ NamedDecl *Sema::ActOnVariableDeclarator(
              "should have a 'template<>' for this decl");
     }
 
-    if (SCSpec != DeclSpec::SCS_unspecified && ((IsVariableTemplateSpecialization && !IsPartialSpecialization) || IsMemberSpecialization)) {
+    bool IsExplicitSpecialization =
+        IsVariableTemplateSpecialization && !IsPartialSpecialization;
+
+    // C++ [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.
+    //
+    // We use the storage-class-specifier from DeclSpec because we may have
+    // added implicit 'extern' for declarations with __declspec(dllimport)!
+    if (SCSpec != DeclSpec::SCS_unspecified &&
+        (IsExplicitSpecialization || IsMemberSpecialization)) {
       Diag(D.getDeclSpec().getStorageClassSpecLoc(),
-             diag::ext_explicit_specialization_storage_class)
+           diag::ext_explicit_specialization_storage_class)
           << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
     }
 
@@ -7679,8 +7690,8 @@ NamedDecl *Sema::ActOnVariableDeclarator(
             }
           }
           if (FunctionOrMethod) {
-            // C++ [class.static.data]p5: A local class shall not have static data
-            // members.
+            // C++ [class.static.data]p5: A local class shall not have static
+            // data members.
             Diag(D.getIdentifierLoc(),
                  diag::err_static_data_member_not_allowed_in_local_class)
                 << Name << RD->getDeclName()
@@ -7698,8 +7709,9 @@ NamedDecl *Sema::ActOnVariableDeclarator(
             // the program is ill-formed. C++11 drops this restriction.
             Diag(D.getIdentifierLoc(),
                  getLangOpts().CPlusPlus11
-                   ? diag::warn_cxx98_compat_static_data_member_in_union
-                   : diag::ext_static_data_member_in_union) << Name;
+                     ? diag::warn_cxx98_compat_static_data_member_in_union
+                     : diag::ext_static_data_member_in_union)
+                << Name;
           }
         }
       } else if (IsVariableTemplate || IsPartialSpecialization) {
@@ -7717,7 +7729,8 @@ NamedDecl *Sema::ActOnVariableDeclarator(
       case SC_Static:
         Diag(D.getDeclSpec().getStorageClassSpecLoc(),
              diag::err_static_out_of_line)
-          << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
+            << FixItHint::CreateRemoval(
+                   D.getDeclSpec().getStorageClassSpecLoc());
         break;
       case SC_Auto:
       case SC_Register:
@@ -7729,7 +7742,8 @@ NamedDecl *Sema::ActOnVariableDeclarator(
 
         Diag(D.getDeclSpec().getStorageClassSpecLoc(),
              diag::err_storage_class_for_static_member)
-          << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
+            << FixItHint::CreateRemoval(
+                   D.getDeclSpec().getStorageClassSpecLoc());
         break;
       case SC_PrivateExtern:
         llvm_unreachable("C storage class in c++!");
@@ -7781,8 +7795,6 @@ NamedDecl *Sema::ActOnVariableDeclarator(
     // the variable (matching the scope specifier), store them.
     // An explicit variable template specialization does not own any template
     // parameter lists.
-    bool IsExplicitSpecialization =
-        IsVariableTemplateSpecialization && !IsPartialSpecialization;
     unsigned VDTemplateParamLists =
         (TemplateParams && !IsExplicitSpecialization) ? 1 : 0;
     if (TemplateParamLists.size() > VDTemplateParamLists)
@@ -10213,12 +10225,23 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
     }
 
     if (!isFriend && SC != SC_None) {
+      // C++ [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.
+      //
+      // We diagnose friend declarations with storage-class-specifiers
+      // elsewhere.
       if (isFunctionTemplateSpecialization || isMemberSpecialization) {
         Diag(D.getDeclSpec().getStorageClassSpecLoc(),
-               diag::ext_explicit_specialization_storage_class)
-            << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
-      } else if (SC == SC_Static && !CurContext->isRecord() && DC->isRecord()) {
-        assert(isa<CXXMethodDecl>(NewFD) && "Out-of-line member function should be a CXXMethodDecl");
+             diag::ext_explicit_specialization_storage_class)
+            << FixItHint::CreateRemoval(
+                   D.getDeclSpec().getStorageClassSpecLoc());
+      }
+
+      if (SC == SC_Static && !CurContext->isRecord() && DC->isRecord()) {
+        assert(isa<CXXMethodDecl>(NewFD) &&
+               "Out-of-line member function should be a CXXMethodDecl");
         // C++ [class.static]p1:
         //   A data or function member of a class may be declared static
         //   in a class definition, in which case it is a static member of
@@ -10227,22 +10250,20 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
         // Complain about the 'static' specifier if it's on an out-of-line
         // member function definition.
 
-        // MSVC permits the use of a 'static' storage specifier on an out-of-line
-        // member function template declaration and class member template
-        // declaration (MSVC versions before 2015), warn about this.
+        // MSVC permits the use of a 'static' storage specifier on an
+        // out-of-line member function template declaration and class member
+        // template declaration (MSVC versions before 2015), warn about this.
         Diag(D.getDeclSpec().getStorageClassSpecLoc(),
              ((!getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2015) &&
                cast<CXXRecordDecl>(DC)->getDescribedClassTemplate()) ||
-             (getLangOpts().MSVCCompat && NewFD->getDescribedFunctionTemplate()))
-             ? diag::ext_static_out_of_line : diag::err_static_out_of_line)
-          << FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
+              (getLangOpts().MSVCCompat &&
+               NewFD->getDescribedFunctionTemplate()))
+                 ? diag::ext_static_out_of_line
+                 : diag::err_static_out_of_line)
+            << FixItHint::CreateRemoval(
+                   D.getDeclSpec().getStorageClassSpecLoc());
       }
     }
-    #if 0
-    if (SC != SC_None && !isFriend && ) {
-    } else if (SC == SC_Static && isa<CXXMethodDecl>(NewFD) && !CurContext->isRecord()) {
-    }
-    #endif
 
     // C++11 [except.spec]p15:
     //   A deallocation function with no exception-specification is treated
@@ -10609,29 +10630,6 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
                                                 Previous))
           NewFD->setInvalidDecl();
       }
-
-      #if 0
-      // C++ [dcl.stc]p1:
-      //   A storage-class-specifier shall not be specified in an explicit
-      //   specialization (14.7.3)
-      // FIXME: We should be checking this for dependent specializations.
-      FunctionTemplateSpecializationInfo *Info =
-          NewFD->getTemplateSpecializationInfo();
-      if (Info && SC != SC_None) {
-        if (SC != Info->getTemplate()->getTemplatedDecl()->getStorageClass())
-          Diag(NewFD->getLocation(),
-               diag::err_explicit_specialization_inconsistent_storage_class)
-            << SC
-            << FixItHint::CreateRemoval(
-                                      D.getDeclSpec().getStorageClassSpecLoc());
-
-        else
-          Diag(NewFD->getLocation(),
-               diag::ext_explicit_specialization_storage_class)
-            << FixItHint::CreateRemoval(
-                                      D.getDeclSpec().getStorageClassSpecLoc());
-      }
-      #endif
     } else if (isMemberSpecialization && isa<CXXMethodDecl>(NewFD)) {
       if (CheckMemberSpecialization(NewFD, Previous))
           NewFD->setInvalidDecl();
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 7077797bf6504..55284d638efb1 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -3490,8 +3490,8 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
   }
 
   bool isInstField = (DS.getStorageClassSpec() == DeclSpec::SCS_unspecified ||
-                       DS.getStorageClassSpec() == DeclSpec::SCS_mutable) &&
-                      !isFunc && TemplateParameterLists.empty();
+                      DS.getStorageClassSpec() == DeclSpec::SCS_mutable) &&
+                     !isFunc && TemplateParameterLists.empty();
 
   if (DS.hasConstexprSpecifier() && isInstField) {
     SemaDiagnosticBuilder B =
@@ -3541,7 +3541,7 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
 
     IdentifierInfo *II = Name.getAsIdentifierInfo();
 
-    #if 0
+#if 0
     // Member field could not be with "template" keyword.
     // So TemplateParameterLists should be empty in this case.
     if (TemplateParameterLists.size()) {
@@ -3562,7 +3562,7 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
       }
       return nullptr;
     }
-    #endif
+#endif
 
     if (D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId) {
       Diag(D.getIdentifierLoc(), diag::err_member_with_template_arguments)
diff --git a/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-20.cpp b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-20.cpp
index f55a4231b2153..884c119556265 100644
--- a/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-20.cpp
+++ b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p2-20.cpp
@@ -122,7 +122,7 @@ void B::f<unsigned>();
 
 template<>
 static void B::g<unsigned>(); // expected-warning {{explicit specialization cannot have a storage class}}
-
+                              // expected-error at -1 {{'static' can only be specified inside the class definition}}
 
 template<typename T>
 struct C {
@@ -159,7 +159,7 @@ void C<int>::f();
 
 template<>
 static void C<int>::g(); // expected-warning {{explicit specialization cannot have a storage class}}
-
+                         // expected-error at -1 {{'static' can only be specified inside the class definition}}
 template<typename T>
 struct D {
   template<typename U>
@@ -263,7 +263,7 @@ void D<int>::f();
 template<>
 template<typename U>
 static void D<int>::g(); // expected-warning {{explicit specialization cannot have a storage class}}
-
+                         // expected-error at -1 {{'static' can only be specified inside the class definition}}
 template<>
 template<>
 void D<int>::f<unsigned>();
@@ -271,3 +271,4 @@ void D<int>::f<unsigned>();
 template<>
 template<>
 static void D<int>::g<unsigned>(); // expected-warning {{explicit specialization cannot have a storage class}}
+                                   // expected-error at -1 {{'static' can only be specified inside the class definition}}
\ No newline at end of file
diff --git a/clang/test/Modules/redecl-templates.cpp b/clang/test/Modules/redecl-templates.cpp
index 37e3738f6307f..3ebafb53dc3bc 100644
--- a/clang/test/Modules/redecl-templates.cpp
+++ b/clang/test/Modules/redecl-templates.cpp
@@ -28,4 +28,7 @@ int &x = w<1>;
 // instantiation of this specialization.
 template<> struct A<1> {};
 template<> constexpr void f<1>() {}
-template<> int v<1>; // expected-error{{redefinition of 'v<1>'}}
+// Variable template explicit specializations are always definitions unless they
+// are static data members declared without an initializer.
+template<> int v<1>; // expected-error {{redefinition of 'v<1>'}}
+                     // expected-note at Inputs/redecl-templates/a.h:8 {{previous definition is here}}



More information about the cfe-commits mailing list