[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