[clang] [Clang] Add [[clang::diagnose_specializations]] (PR #101469)

Nikolas Klauser via cfe-commits cfe-commits at lists.llvm.org
Thu Aug 1 03:37:59 PDT 2024


https://github.com/philnik777 created https://github.com/llvm/llvm-project/pull/101469

This can be used to inform users when a template should not be specialized. For example, this is the case for the standard type traits (except for `common_type` and `common_reference`, which have more complicated rules).


>From 82ab798fc72c6de64ae527d96393f0dc67307e98 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Thu, 1 Aug 2024 12:30:22 +0200
Subject: [PATCH] [Clang] Add [[clang::diagnose_specializations]]

---
 clang/include/clang/Basic/Attr.td             | 13 ++++++-
 clang/include/clang/Basic/AttrDocs.td         | 14 ++++++--
 clang/include/clang/Basic/DiagnosticGroups.td |  1 +
 .../clang/Basic/DiagnosticSemaKinds.td        |  3 ++
 clang/lib/Sema/SemaDeclAttr.cpp               |  9 +++++
 clang/lib/Sema/SemaTemplate.cpp               |  6 ++++
 .../SemaCXX/attr-diagnose-specializations.cpp | 34 +++++++++++++++++++
 7 files changed, 76 insertions(+), 4 deletions(-)
 create mode 100644 clang/test/SemaCXX/attr-diagnose-specializations.cpp

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 8ac2079099c85..e074cc8b285a9 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -103,6 +103,9 @@ def NonParmVar : SubsetSubject<Var,
 def NonLocalVar : SubsetSubject<Var,
                                 [{!S->hasLocalStorage()}],
                                 "variables with non-local storage">;
+def VarTmpl : SubsetSubject<Var, [{S->getDescribedVarTemplate()}],
+                            "variable template">;
+
 def NonBitField : SubsetSubject<Field,
                                 [{!S->isBitField()}],
                                 "non-bit-field non-static data members">;
@@ -3327,6 +3330,14 @@ def DiagnoseIf : InheritableAttr {
   let Documentation = [DiagnoseIfDocs];
 }
 
+def DiagnoseSpecializations : InheritableAttr {
+  let Spellings = [Clang<"diagnose_specializations", /*AllowInC*/0>];
+  let Subjects = SubjectList<[ClassTmpl, VarTmpl]>;
+  let Documentation = [DiagnoseSpecializationsDocs];
+  let MeaningfulToClassTemplateDefinition = 1;
+  let TemplateDependent = 1;
+}
+
 def ArcWeakrefUnavailable : InheritableAttr {
   let Spellings = [Clang<"objc_arc_weak_reference_unavailable">];
   let Subjects = SubjectList<[ObjCInterface], ErrorDiag>;
@@ -4581,7 +4592,7 @@ def HLSLResource : InheritableAttr {
   let Spellings = [];
   let Subjects = SubjectList<[Struct]>;
   let LangOpts = [HLSL];
-  let Args = [    
+  let Args = [
     EnumArgument<
         "ResourceKind", "llvm::hlsl::ResourceKind",
         /*is_string=*/0,
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 94c284fc73158..4ca67a63714d4 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -975,6 +975,15 @@ Query for this feature with ``__has_attribute(diagnose_if)``.
   }];
 }
 
+def DiagnoseSpecializationsDocs : Documentation {
+  let Category = DocCatDecl;
+  let Content = [{
+``clang::diagnose_specializations`` can be appied to class templates which
+should not be specialized by users. This is primarily used to diagnose user
+specializations of standard library type traits.
+  }];
+}
+
 def PassObjectSizeDocs : Documentation {
   let Category = DocCatVariable; // Technically it's a parameter doc, but eh.
   let Heading = "pass_object_size, pass_dynamic_object_size";
@@ -7388,10 +7397,10 @@ def HLSLLoopHintDocs : Documentation {
   let Content = [{
 The ``[loop]`` directive allows loop optimization hints to be
 specified for the subsequent loop. The directive allows unrolling to
-be disabled and is not compatible with [unroll(x)]. 
+be disabled and is not compatible with [unroll(x)].
 
 Specifying the parameter, ``[loop]``, directs the
-unroller to not unroll the loop. 
+unroller to not unroll the loop.
 
 .. code-block:: hlsl
 
@@ -8306,4 +8315,3 @@ Declares that a function potentially allocates heap memory, and prevents any pot
 of ``nonallocating`` by the compiler.
   }];
 }
-
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 19c3f1e043349..d6f6111f70868 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -472,6 +472,7 @@ def ExpansionToDefined : DiagGroup<"expansion-to-defined">;
 def FlagEnum : DiagGroup<"flag-enum">;
 def IncrementBool : DiagGroup<"increment-bool", [DeprecatedIncrementBool]>;
 def InfiniteRecursion : DiagGroup<"infinite-recursion">;
+def InvalidSpecialization : DiagGroup<"invalid-specialization">;
 def PureVirtualCallFromCtorDtor: DiagGroup<"call-to-pure-virtual-from-ctor-dtor">;
 def GNUImaginaryConstant : DiagGroup<"gnu-imaginary-constant">;
 def IgnoredGCH : DiagGroup<"ignored-gch">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 581434d33c5c9..5972d630347ec 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -5407,6 +5407,9 @@ def note_dependent_function_template_spec_discard_reason : Note<
   "candidate ignored: %select{not a function template|"
   "not a member of the enclosing %select{class template|"
   "namespace; did you mean to explicitly qualify the specialization?}1}0">;
+def warn_diag_specialization : Warning<
+  "specializing a template which should not be specialized">,
+  DefaultError, InGroup<InvalidSpecialization>;
 
 // C++ class template specializations and out-of-line definitions
 def err_template_spec_needs_header : Error<
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 9011fa547638e..eb0a705fef044 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -1215,6 +1215,12 @@ static void handlePreferredName(Sema &S, Decl *D, const ParsedAttr &AL) {
         << TT->getDecl();
 }
 
+static void handleDiagnoseSpecializations(Sema &S, Decl *D,
+                                          const ParsedAttr &AL) {
+  D->getDescribedTemplate()->addAttr(
+      DiagnoseSpecializationsAttr::Create(S.Context, AL));
+}
+
 bool Sema::isValidPointerAttrType(QualType T, bool RefOkay) {
   if (RefOkay) {
     if (T->isReferenceType())
@@ -6700,6 +6706,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_PreferredName:
     handlePreferredName(S, D, AL);
     break;
+  case ParsedAttr::AT_DiagnoseSpecializations:
+    handleDiagnoseSpecializations(S, D, AL);
+    break;
   case ParsedAttr::AT_Section:
     handleSectionAttr(S, D, AL);
     break;
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index c22e329bef2b9..ddcc3272cff1f 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -3976,6 +3976,9 @@ DeclResult Sema::ActOnVarTemplateSpecialization(
              << IsPartialSpecialization;
   }
 
+  if (VarTemplate->hasAttr<DiagnoseSpecializationsAttr>())
+    Diag(TemplateNameLoc, diag::warn_diag_specialization);
+
   // Check for unexpanded parameter packs in any of the template arguments.
   for (unsigned I = 0, N = TemplateArgs.size(); I != N; ++I)
     if (DiagnoseUnexpandedParameterPack(TemplateArgs[I],
@@ -8085,6 +8088,9 @@ DeclResult Sema::ActOnClassTemplateSpecialization(
     return true;
   }
 
+  if (ClassTemplate->hasAttr<DiagnoseSpecializationsAttr>())
+    Diag(TemplateNameLoc, diag::warn_diag_specialization);
+
   bool isMemberSpecialization = false;
   bool isPartialSpecialization = false;
 
diff --git a/clang/test/SemaCXX/attr-diagnose-specializations.cpp b/clang/test/SemaCXX/attr-diagnose-specializations.cpp
new file mode 100644
index 0000000000000..bf9ea2c56c18a
--- /dev/null
+++ b/clang/test/SemaCXX/attr-diagnose-specializations.cpp
@@ -0,0 +1,34 @@
+// RUN: %clang_cc1 %s -verify
+
+#if !__has_cpp_attribute(clang::diagnose_specializations)
+#  error
+#endif
+
+struct [[clang::diagnose_specializations]] S {}; // expected-warning {{'diagnose_specializations' attribute only applies to class templates}}
+
+template <class T, class U>
+struct [[clang::diagnose_specializations]] is_same {
+  static constexpr bool value = __is_same(T, U);
+};
+
+template <>
+struct is_same<int, char> {}; // expected-error {{specializing a template which should not be specialized}}
+
+template <class>
+struct Template {};
+
+template <class T>
+struct is_same<Template<T>, Template <T>> {}; // expected-error {{specializing a template which should not be specialized}}
+
+bool test_instantiation1 = is_same<int, int>::value;
+
+template <class T, class U>
+[[clang::diagnose_specializations]] inline constexpr bool is_same_v = __is_same(T, U);
+
+template <>
+inline constexpr bool is_same_v<int, char> = false; // expected-error {{specializing a template which should not be specialized}}
+
+template <class T>
+inline constexpr bool is_same_v<Template <T>, Template <T>> = true; // expected-error {{specializing a template which should not be specialized}}
+
+bool test_instantiation2 = is_same_v<int, int>;



More information about the cfe-commits mailing list