[clang] [Clang][Sema] Diagnose explicit specializations with object parameters that do not match their primary template (PR #89300)

via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 18 13:25:08 PDT 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Krystian Stasiowski (sdkrystian)

<details>
<summary>Changes</summary>

According to [[dcl.fct] p6](https://eel.is/c++draft/dcl.fct#<!-- -->6) (with the resolution for [CWG2846](https://cplusplus.github.io/CWG/issues/2846.html) applied):
> An explicit-object-parameter-declaration shall appear only as the first _parameter-declaration_ of a _parameter-declaration-list_ of either:
> - a declaration of a member function or member function template, or
> - an explicit instantiation or explicit specialization of a templated member function, or
> - a _lambda-declarator_.

This patch diagnoses explicit specializations declared with an object parameter that does not match the primary template. For example:
```cpp
struct A 
{
    template<typename T>
    void f(this T);

    template<typename T>
    void g(T);

    template<typename T>
    static void h(T);
};

template<>
void A::f(int); // error: an explicit specialization of an explicit object member function must have an explicit object parameter

template<>
void A::g(this int); // error: an explicit specialization of an implicit object member function cannot have an explicit object parameter

template<>
void A::h(this int); // error: an explicit specialization of a static member function cannot have an explicit object parameter
```

Since the presence/absence of an explicit object parameter is not taken into consideration during template argument deduction, the selected primary template (after partial ordering) _can_ have a different object parameter than the explicit specialization. We therefore do not diagnose the mismatch until the primary function template has been selected. 

Note: This still needs a release note + more tests (for friend specializations). I plan to add diagnostics for explicit instantiations in this patch as well, I just ran out of time today :)

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


3 Files Affected:

- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+4) 
- (modified) clang/lib/Sema/SemaTemplate.cpp (+38-4) 
- (modified) clang/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p6-cxx23.cpp (+142) 


``````````diff
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a9f4143c6b375e..4c172c63245c7b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7487,6 +7487,10 @@ def err_explicit_object_parameter_mutable: Error<
 def err_invalid_explicit_object_type_in_lambda: Error<
   "invalid explicit object parameter type %0 in lambda with capture; "
   "the type must be the same as, or derived from, the lambda">;
+def err_explicit_object_spec_mismatch: Error<
+  "%select{an explicit|a friend function}0 specialization %select{of|naming}0 "
+  "%select{a static|an implicit object|an explicit object}1 member function "
+  "%select{cannot|cannot|must}1 have an explicit object parameter">;
 
 def err_ref_qualifier_overload : Error<
   "cannot overload a member function %select{without a ref-qualifier|with "
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index f4b6e1ceb6f023..69b4317f02c339 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -10238,6 +10238,44 @@ bool Sema::CheckFunctionTemplateSpecialization(
       !ResolveExceptionSpec(FD->getLocation(), SpecializationFPT))
     return true;
 
+  // If this is a friend declaration, then we're not really declaring
+  // an explicit specialization.
+  bool isFriend = (FD->getFriendObjectKind() != Decl::FOK_None);
+
+  // C++23 [dcl.fct]p6 (with CWG2846 applied):
+  //   [...] An explicit-object-parameter-declaration shall appear
+  //   only as the first parameter-declaration of a parameter-declaration-list
+  //   of either one of:
+  //   - a member-declarator that declares declaration of a member function or
+  //     member function template, or
+  //   - an explicit instantiation or explicit specialization of a templated
+  //     member function, or
+  //   - a lambda-declarator.
+  //
+  // If the primary template is an explicit object member function, then
+  // the specialization must have an explicit object parameter. Likewise,
+  // if the primary template is an implicit object member function,
+  // static member function, or non-member function, then the specialization
+  // cannot have an explicit object parameter.
+  FunctionDecl *Primary =
+      Specialization->getPrimaryTemplate()->getTemplatedDecl();
+  if (FD->hasCXXExplicitFunctionObjectParameter() !=
+      Primary->hasCXXExplicitFunctionObjectParameter()) {
+    Diag(FD->getLocation(), diag::err_explicit_object_spec_mismatch)
+        << isFriend
+        << (Primary->isStatic()
+                ? 0
+                : Specialization->hasCXXExplicitFunctionObjectParameter() + 1)
+        << (FD->hasCXXExplicitFunctionObjectParameter()
+                ? FD->getParamDecl(0)->getSourceRange()
+                : SourceRange());
+    Diag(Primary->getLocation(), diag::note_specialized_decl)
+        << (Primary->hasCXXExplicitFunctionObjectParameter()
+                ? Primary->getParamDecl(0)->getSourceRange()
+                : SourceRange());
+    return true;
+  }
+
   FunctionTemplateSpecializationInfo *SpecInfo
     = Specialization->getTemplateSpecializationInfo();
   assert(SpecInfo && "Function template specialization info missing?");
@@ -10260,10 +10298,6 @@ bool Sema::CheckFunctionTemplateSpecialization(
   // FIXME: Check if the prior specialization has a point of instantiation.
   // If so, we have run afoul of .
 
-  // If this is a friend declaration, then we're not really declaring
-  // an explicit specialization.
-  bool isFriend = (FD->getFriendObjectKind() != Decl::FOK_None);
-
   // Check the scope of this explicit specialization.
   if (!isFriend &&
       CheckTemplateSpecializationScope(*this,
diff --git a/clang/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p6-cxx23.cpp b/clang/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p6-cxx23.cpp
index 9c1f30f81a0115..fcf52c022f2af6 100644
--- a/clang/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p6-cxx23.cpp
+++ b/clang/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p6-cxx23.cpp
@@ -5,3 +5,145 @@ auto x1 = requires (int, this int) { true; }; // expected-error {{a requires exp
 
 template<this auto> // expected-error {{expected template parameter}}
 void f(); // expected-error {{no function template matches function template specialization 'f'}}
+
+struct A {
+  template<typename T>
+  void f0(this T); // expected-note 2{{attempt to specialize declaration here}}
+
+  template<>
+  void f0(this short);
+
+  template<>
+  void f0(long); // expected-error {{an explicit specialization of an explicit object member function must have an explicit object parameter}}
+
+  template<typename T>
+  void g0(T); // expected-note 2{{attempt to specialize declaration here}}
+
+  template<>
+  void g0(short);
+
+  template<>
+  void g0(this long); // expected-error {{an explicit specialization of an implicit object member function cannot have an explicit object parameter}}
+
+  template<typename T>
+  static void h0(T); // expected-note 2{{attempt to specialize declaration here}}
+
+  template<>
+  void h0(short);
+
+  template<>
+  void h0(this long); // expected-error {{an explicit specialization of a static member function cannot have an explicit object parameter}}
+};
+
+template<>
+void A::f0(this signed);
+
+template<>
+void A::f0(unsigned); // expected-error {{an explicit specialization of an explicit object member function must have an explicit object parameter}}
+
+template<>
+void A::g0(signed);
+
+template<>
+void A::g0(this unsigned); // expected-error {{an explicit specialization of an implicit object member function cannot have an explicit object parameter}}
+
+template<>
+void A::h0(signed);
+
+template<>
+void A::h0(this unsigned); // expected-error {{an explicit specialization of a static member function cannot have an explicit object parameter}}
+
+template<typename T>
+struct B {
+  void f1(this int); // expected-note {{member declaration nearly matches}}
+
+  void g1(int); // expected-note {{member declaration nearly matches}}
+
+  static void h1(int); // expected-note {{member declaration nearly matches}}
+};
+
+template<>
+void B<short>::f1(this int);
+
+template<>
+void B<long>::f1(int); // expected-error {{out-of-line declaration of 'f1' does not match any declaration in 'B<long>'}}
+
+template<>
+void B<short>::g1(int);
+
+template<>
+void B<long>::g1(this int); // expected-error {{out-of-line declaration of 'g1' does not match any declaration in 'B<long>'}}
+
+template<>
+void B<short>::h1(int);
+
+template<>
+void B<long>::h1(this int); // expected-error {{out-of-line declaration of 'h1' does not match any declaration in 'B<long>'}}
+
+template<typename T>
+struct C {
+  template<typename U>
+  void f2(this U); // expected-note {{attempt to specialize declaration here}}
+
+  template<>
+  void f2(this short);
+
+  template<>
+  void f2(long); // expected-error {{an explicit specialization of an explicit object member function must have an explicit object parameter}}
+
+  template<typename U>
+  void g2(U); // expected-note {{attempt to specialize declaration here}}
+
+  template<>
+  void g2(short);
+
+  template<>
+  void g2(this long); // expected-error {{an explicit specialization of an implicit object member function cannot have an explicit object parameter}}
+
+  template<typename U>
+  static void h2(U); // expected-note {{attempt to specialize declaration here}}
+
+  template<>
+  void h2(short);
+
+  template<>
+  void h2(this long); // expected-error {{an explicit specialization of a static member function cannot have an explicit object parameter}}
+};
+
+template struct C<int>; // expected-note {{in instantiation of}}
+
+template<typename T>
+struct D {
+  template<typename U>
+  void f3(this U);
+
+  template<typename U>
+  void g3(U);
+
+  template<typename U>
+  static void h3(U);
+};
+
+template<>
+template<typename U>
+void D<short>::f3(this U);
+
+template<>
+template<typename U>
+void D<long>::f3(U); // expected-error {{out-of-line declaration of 'f3' does not match any declaration in 'D<long>'}}
+
+template<>
+template<typename U>
+void D<short>::g3(U);
+
+template<>
+template<typename U>
+void D<long>::g3(this U); // expected-error {{out-of-line declaration of 'g3' does not match any declaration in 'D<long>'}}
+
+template<>
+template<typename U>
+void D<short>::h3(U);
+
+template<>
+template<typename U>
+void D<long>::h3(this U); // expected-error {{out-of-line declaration of 'h3' does not match any declaration in 'D<long>'}}

``````````

</details>


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


More information about the cfe-commits mailing list