[clang] [clang] Treat templates referenced in unevaluated operands as used for -Wunused-template (PR #234168)

via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 23 01:38:03 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Aditya Medhane (flash1729)

<details>
<summary>Changes</summary>

`ShouldRemoveFromUnused` only counts a function or variable template as used when some specialization is odr-used or has had its definition instantiated. A specialization that is implicitly instantiated because it was named in an unevaluated operand (decltype, sizeof, noexcept, a requires-expression) is only marked referenced and never gets a body, so the template was diagnosed as unused even though it determined the meaning of that expression.

Treat a referenced implicit specialization as a use of its template.

Fixes #<!-- -->218429

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


4 Files Affected:

- (modified) clang/docs/ReleaseNotes.md (+4) 
- (modified) clang/lib/Sema/Sema.cpp (+15-4) 
- (modified) clang/test/Misc/diag-unused-source-ranges.cpp (+3-3) 
- (modified) clang/test/SemaCXX/warn-unused-filescoped.cpp (+29-1) 


``````````diff
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index e6b71ba2a0b565..f8c8713fce8c4d 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -422,6 +422,10 @@ features cannot lower the translation-unit ABI level;
 - Fixed spurious `-Wimplicit-void-ptr-cast` warnings in C for parenthesized
   null pointer macros such as `(NULL)`. (#GH171874)
 
+- `-Wunused-template` no longer warns about a function or variable template
+  whose only uses are in unevaluated operands such as `decltype`, `sizeof`,
+  `noexcept` or a requires-expression. (#GH218429)
+
 - `-Wfortify-source` now diagnoses when `strlcat`, `__builtin_strlcat`, `strlcpy`, or
   `__builtin_strlcpy` is called with a size argument larger than the destination buffer.
 
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 9abab22e3809f3..b798ac73b19ade 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -908,10 +908,17 @@ static bool ShouldRemoveFromUnused(Sema *SemaRef, const DeclaratorDecl *D) {
 
   if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
     // If this is a function template and none of its specializations is used,
-    // we should warn.
+    // we should warn. A specialization that was implicitly instantiated only
+    // because it was named in an unevaluated operand (decltype, sizeof,
+    // noexcept, a requires-expression) is referenced but never odr-used and
+    // never gets a body; the template still determined the meaning of that
+    // expression, so treat it as a use.
     if (FunctionTemplateDecl *Template = FD->getDescribedFunctionTemplate())
       for (const auto *Spec : Template->specializations())
-        if (ShouldRemoveFromUnused(SemaRef, Spec))
+        if (ShouldRemoveFromUnused(SemaRef, Spec) ||
+            (Spec->getTemplateSpecializationKind() ==
+                 TSK_ImplicitInstantiation &&
+             Spec->isReferenced()))
           return true;
 
     // UnusedFileScopedDecls stores the first declaration.
@@ -939,9 +946,13 @@ static bool ShouldRemoveFromUnused(Sema *SemaRef, const DeclaratorDecl *D) {
 
     if (VarTemplateDecl *Template = VD->getDescribedVarTemplate())
       // If this is a variable template and none of its specializations is used,
-      // we should warn.
+      // we should warn. As for function templates above, a specialization that
+      // is only referenced from an unevaluated operand counts as a use.
       for (const auto *Spec : Template->specializations())
-        if (ShouldRemoveFromUnused(SemaRef, Spec))
+        if (ShouldRemoveFromUnused(SemaRef, Spec) ||
+            (Spec->getTemplateSpecializationKind() ==
+                 TSK_ImplicitInstantiation &&
+             Spec->isReferenced()))
           return true;
 
     // UnusedFileScopedDecls stores the first declaration.
diff --git a/clang/test/Misc/diag-unused-source-ranges.cpp b/clang/test/Misc/diag-unused-source-ranges.cpp
index 6b7fd7d263278c..71afe6660845e3 100644
--- a/clang/test/Misc/diag-unused-source-ranges.cpp
+++ b/clang/test/Misc/diag-unused-source-ranges.cpp
@@ -22,9 +22,9 @@ class A {
   // CHECK-NEXT:   void func() {}
   // CHECK-NEXT:        ^~~~{{$}}
     void func() {}
-  // CHECK:      :{[[@LINE+3]]:32-[[@LINE+3]]:37}: warning: unused function template
-  // CHECK-NEXT:   void templ(T) {}
-  // CHECK-NEXT:        ^~~~~{{$}}
+  // templ(0.0) in foo() references templ<double>, which counts as a use even
+  // though foo() is never instantiated; the template must not be diagnosed.
+  // CHECK-NOT:    warning: unused function template 'templ'
     template <typename T> void templ(T) {}
   // CHECK:      :{[[@LINE+3]]:22-[[@LINE+3]]:32}: warning: member function 'templ<int>' is not needed
   // CHECK-NEXT:   void templ<int>(int) {}
diff --git a/clang/test/SemaCXX/warn-unused-filescoped.cpp b/clang/test/SemaCXX/warn-unused-filescoped.cpp
index be8d350855c078..3a86528d134d5c 100644
--- a/clang/test/SemaCXX/warn-unused-filescoped.cpp
+++ b/clang/test/SemaCXX/warn-unused-filescoped.cpp
@@ -1,6 +1,7 @@
 // RUN: %clang_cc1 -fsyntax-only -verify -Wunused -Wunused-template -Wunused-member-function -Wno-unused-local-typedefs \
 // RUN:            -Wno-c++11-extensions -Wno-c++14-extensions -std=c++98 %s
 // RUN: %clang_cc1 -fsyntax-only -verify -Wunused -Wunused-template -Wunused-member-function -Wno-unused-local-typedefs -std=c++14 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -Wunused -Wunused-template -Wunused-member-function -Wno-unused-local-typedefs -std=c++20 %s
 
 #ifdef HEADER
 
@@ -131,7 +132,9 @@ namespace test4 {
 
 namespace rdar8733476 {
 static void foo() {}                         // expected-warning {{function 'foo' is not needed and will not be emitted}}
-template <typename T> static void foo_t() {} // expected-warning {{unused function template 'foo_t'}}
+// foo_t<void> below is referenced from the never-instantiated bar<>, which
+// counts as a use of the template (see GH218429 at the end of this file).
+template <typename T> static void foo_t() {}
 template <> void foo_t<int>() {}             // expected-warning {{function 'foo_t<int>' is not needed and will not be emitted}}
 
 template <int>
@@ -236,4 +239,29 @@ constexpr int constexpr4() { return 2; }
 #endif
 }
 
+namespace GH218429 {
+// A template whose only uses are in unevaluated operands is still a use: the
+// template determined the meaning of the expression even though no
+// specialization was odr-used or had its definition instantiated.
+#if __cplusplus >= 201103L
+template <typename T> static T in_decltype(T t) { return t; }
+using A = decltype(in_decltype(1));
+template <typename T> static T in_sizeof(T t) { return t; }
+static_assert(sizeof(in_sizeof(1)) == sizeof(int), "");
+template <typename T> static T in_noexcept(T t) { return t; }
+static_assert(!noexcept(in_noexcept(1)), "");
+template <typename T> static T never_named(T t) { return t; } // expected-warning {{unused function template 'never_named'}}
+#endif
+#if __cplusplus >= 201402L
+template <typename T> static T var_in_decltype = T{};
+using D = decltype(var_in_decltype<int>);
+template <typename T> static T var_never_named = T{}; // expected-warning {{unused variable template 'var_never_named'}}
+#endif
+#if __cplusplus >= 202002L
+template <typename T> static void in_requires(T) {}
+template <typename T> static constexpr bool satisfied = requires { in_requires(T{}); };
+static_assert(satisfied<int>);
+#endif
+}
+
 #endif

``````````

</details>


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


More information about the cfe-commits mailing list