[clang] [Clang] Implement diagnostics for why is_empty is false (PR #145044)
Samarth Narang via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 24 20:51:27 PDT 2025
https://github.com/snarang181 updated https://github.com/llvm/llvm-project/pull/145044
>From c56a2afe9e3ad22807c30ef69da41af6f325d3a5 Mon Sep 17 00:00:00 2001
From: Samarth Narang <snarang at umass.edu>
Date: Fri, 20 Jun 2025 10:51:04 -0400
Subject: [PATCH 1/2] Explain why 'is_empty' evaluates to false Add tests for
various cases of 'is_empty' evaluating to false // and ensure that the
diagnostics are correct.
---
.../clang/Basic/DiagnosticSemaKinds.td | 8 ++-
clang/lib/Sema/SemaTypeTraits.cpp | 66 +++++++++++++++++++
.../type-traits-unsatisfied-diags-std.cpp | 44 +++++++++++++
.../SemaCXX/type-traits-unsatisfied-diags.cpp | 58 ++++++++++++++++
4 files changed, 175 insertions(+), 1 deletion(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 9392cbb39c021..f8b733063f10b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1767,7 +1767,8 @@ def note_unsatisfied_trait
: Note<"%0 is not %enum_select<TraitName>{"
"%TriviallyRelocatable{trivially relocatable}|"
"%Replaceable{replaceable}|"
- "%TriviallyCopyable{trivially copyable}"
+ "%TriviallyCopyable{trivially copyable}|"
+ "%Empty{empty}"
"}1">;
def note_unsatisfied_trait_reason
@@ -1787,6 +1788,11 @@ def note_unsatisfied_trait_reason
"%NonReplaceableField{has a non-replaceable member %1 of type %2}|"
"%NTCBase{has a non-trivially-copyable base %1}|"
"%NTCField{has a non-trivially-copyable member %1 of type %2}|"
+ "%NonEmptyMember{has a non-static data member %1 of type %2}|"
+ "%VirtualFunction{has a virtual function %1}|"
+ "%VirtualBase{has a virtual base class %1}|"
+ "%NonEmptyBase{has a base class %1 that is not empty}|"
+ "%ZeroLengthField{field %1 is a non-zero-length bit-field}|"
"%DeletedDtr{has a %select{deleted|user-provided}1 destructor}|"
"%UserProvidedCtr{has a user provided %select{copy|move}1 "
"constructor}|"
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index 4dbb2450857e0..b387721f1a54a 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -1956,6 +1956,7 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) {
TypeTrait::UTT_IsCppTriviallyRelocatable)
.Case("is_replaceable", TypeTrait::UTT_IsReplaceable)
.Case("is_trivially_copyable", TypeTrait::UTT_IsTriviallyCopyable)
+ .Case("is_empty", TypeTrait::UTT_IsEmpty)
.Default(std::nullopt);
}
@@ -2285,6 +2286,68 @@ static void DiagnoseNonTriviallyCopyableReason(Sema &SemaRef,
SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
}
+static void DiagnoseIsEmptyReason(Sema &S, SourceLocation Loc,
+ const CXXRecordDecl *D) {
+ // Non-static data members (ignore zero-width bit‐fields).
+ for (auto *Field : D->fields()) {
+ if (Field->isBitField() && Field->getBitWidthValue() == 0)
+ continue;
+ S.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NonEmptyMember << Field
+ << Field->getType() << Field->getSourceRange();
+ }
+
+ // Virtual functions.
+ for (auto *M : D->methods()) {
+ if (M->isVirtual()) {
+ S.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::VirtualFunction << M->getDeclName()
+ << M->getSourceRange();
+ break;
+ }
+ }
+
+ // Virtual bases and non-empty bases.
+ for (auto &B : D->bases()) {
+ auto *BR = B.getType()->getAsCXXRecordDecl();
+ if (!BR || BR->isInvalidDecl())
+ continue;
+ if (B.isVirtual()) {
+ S.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::VirtualBase << B.getType()
+ << B.getSourceRange();
+ }
+ if (!BR->isEmpty()) {
+ S.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NonEmptyBase << B.getType()
+ << B.getSourceRange();
+ }
+ }
+}
+
+static void DiagnoseIsEmptyReason(Sema &S, SourceLocation Loc, QualType T) {
+ // Emit primary "not empty" diagnostic.
+ S.Diag(Loc, diag::note_unsatisfied_trait) << T << diag::TraitName::Empty;
+
+ // While diagnosing is_empty<T>, we want to look at the actual type, not a
+ // reference or an array of it. So we need to massage the QualType param to
+ // strip refs and arrays.
+ if (T->isReferenceType())
+ S.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::Ref;
+ T = T.getNonReferenceType();
+
+ if (auto *AT = S.Context.getAsArrayType(T))
+ T = AT->getElementType();
+
+ if (auto *D = T->getAsCXXRecordDecl()) {
+ if (D->hasDefinition()) {
+ DiagnoseIsEmptyReason(S, Loc, D);
+ S.Diag(D->getLocation(), diag::note_defined_here) << D;
+ }
+ }
+}
+
void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
E = E->IgnoreParenImpCasts();
if (E->containsErrors())
@@ -2305,6 +2368,9 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
case UTT_IsTriviallyCopyable:
DiagnoseNonTriviallyCopyableReason(*this, E->getBeginLoc(), Args[0]);
break;
+ case UTT_IsEmpty:
+ DiagnoseIsEmptyReason(*this, E->getBeginLoc(), Args[0]);
+ break;
default:
break;
}
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
index 329b611110c1d..7f8ebac10c6c4 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
@@ -20,6 +20,13 @@ struct is_trivially_copyable {
template <typename T>
constexpr bool is_trivially_copyable_v = __is_trivially_copyable(T);
+
+template <typename T>
+struct is_empty {
+ static constexpr bool value = __is_empty(T);
+};
+template <typename T>
+constexpr bool is_empty_v = __is_empty(T);
#endif
#ifdef STD2
@@ -44,6 +51,15 @@ using is_trivially_copyable = __details_is_trivially_copyable<T>;
template <typename T>
constexpr bool is_trivially_copyable_v = __is_trivially_copyable(T);
+
+template <typename T>
+struct __details_is_empty {
+ static constexpr bool value = __is_empty(T);
+};
+template <typename T>
+using is_empty = __details_is_empty<T>;
+template <typename T>
+constexpr bool is_empty_v = __is_empty(T);
#endif
@@ -73,6 +89,13 @@ using is_trivially_copyable = __details_is_trivially_copyable<T>;
template <typename T>
constexpr bool is_trivially_copyable_v = is_trivially_copyable<T>::value;
+
+template <typename T>
+struct __details_is_empty : bool_constant<__is_empty(T)> {};
+template <typename T>
+using is_empty = __details_is_empty<T>;
+template <typename T>
+constexpr bool is_empty_v = is_empty<T>::value;
#endif
}
@@ -99,6 +122,18 @@ static_assert(std::is_trivially_copyable_v<int&>);
// expected-note at -1 {{'int &' is not trivially copyable}} \
// expected-note at -1 {{because it is a reference type}}
+static_assert(!std::is_empty<int>::value);
+
+static_assert(std::is_empty<int&>::value);
+// expected-error-re at -1 {{static assertion failed due to requirement 'std::{{.*}}is_empty<int &>::value'}} \
+// expected-note at -1 {{'int &' is not empty}} \
+// expected-note at -1 {{because it is a reference type}}
+static_assert(std::is_empty_v<int&>);
+// expected-error at -1 {{static assertion failed due to requirement 'std::is_empty_v<int &>'}} \
+// expected-note at -1 {{'int &' is not empty}} \
+// expected-note at -1 {{because it is a reference type}}
+
+
namespace test_namespace {
using namespace std;
@@ -119,6 +154,15 @@ namespace test_namespace {
// expected-error at -1 {{static assertion failed due to requirement 'is_trivially_copyable_v<int &>'}} \
// expected-note at -1 {{'int &' is not trivially copyable}} \
// expected-note at -1 {{because it is a reference type}}
+
+ static_assert(is_empty<int&>::value);
+ // expected-error-re at -1 {{static assertion failed due to requirement '{{.*}}is_empty<int &>::value'}} \
+ // expected-note at -1 {{'int &' is not empty}} \
+ // expected-note at -1 {{because it is a reference type}}
+ static_assert(is_empty_v<int&>);
+ // expected-error at -1 {{static assertion failed due to requirement 'is_empty_v<int &>'}} \
+ // expected-note at -1 {{'int &' is not empty}} \
+ // expected-note at -1 {{because it is a reference type}}
}
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
index 5210354a66d43..1d97ee719b6c6 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
@@ -488,3 +488,61 @@ static_assert(__is_trivially_copyable(S12));
// expected-note at -1 {{'S12' is not trivially copyable}} \
// expected-note@#tc-S12 {{'S12' defined here}}
}
+
+namespace is_empty_tests {
+ // Non-static data member.
+ struct A { int x; }; // #e-A
+ static_assert(__is_empty(A));
+ // expected-error at -1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::A)'}} \
+ // expected-note at -1 {{'A' is not empty}} \
+ // expected-note at -1 {{because it has a non-static data member 'x' of type 'int'}} \
+ // expected-note@#e-A {{'A' defined here}}
+
+ // Reference member.
+ struct R {int &r; }; // #e-R
+ static_assert(__is_empty(R));
+ // expected-error at -1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::R)'}} \
+ // expected-note at -1 {{'R' is not empty}} \
+ // expected-note at -1 {{because it has a non-static data member 'r' of type 'int &'}} \
+ // expected-note@#e-R {{'R' defined here}}
+
+ // Virtual function.
+ struct VirtualFunc {virtual void f(); }; // #e-VirtualFunc
+ static_assert(__is_empty(VirtualFunc));
+ // expected-error at -1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::VirtualFunc)'}} \
+ // expected-note at -1 {{'VirtualFunc' is not empty}} \
+ // expected-note at -1 {{because it has a virtual function 'f'}} \
+ // expected-note@#e-VirtualFunc {{'VirtualFunc' defined here}}
+
+ // Virtual base class.
+ struct EB {};
+ struct VB: virtual EB {}; // #e-VB
+ static_assert(__is_empty(VB));
+ // expected-error at -1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::VB)'}} \
+ // expected-note at -1 {{'VB' is not empty}} \
+ // expected-note at -1 {{because it has a virtual base class 'EB'}} \
+ // expected-note@#e-VB {{'VB' defined here}}
+
+ // Non-empty base class.
+ struct Base { int b; }; // #e-Base
+ struct Derived : Base {}; // #e-Derived
+ static_assert(__is_empty(Derived));
+ // expected-error at -1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::Derived)'}} \
+ // expected-note at -1 {{'Derived' is not empty}} \
+ // expected-note at -1 {{because it has a base class 'Base' that is not empty}} \
+ // expected-note@#e-Derived {{'Derived' defined here}}
+
+ // Combination of the above.
+ struct Multi : Base, virtual EB { // #e-Multi
+ int z;
+ virtual void g();
+ };
+ static_assert(__is_empty(Multi));
+ // expected-error at -1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::Multi)'}} \
+ // expected-note at -1 {{'Multi' is not empty}} \
+ // expected-note at -1 {{because it has a non-static data member 'z' of type 'int'}} \
+ // expected-note at -1 {{because it has a virtual function 'g'}} \
+ // expected-note at -1 {{because it has a base class 'Base' that is not empty}} \
+ // expected-note at -1 {{because it has a virtual base class 'EB'}} \
+ // expected-note@#e-Multi {{'Multi' defined here}}
+}
>From b128769296b12b03f25e89da017782a1b30405b7 Mon Sep 17 00:00:00 2001
From: Samarth Narang <snarang at umass.edu>
Date: Tue, 24 Jun 2025 23:50:15 -0400
Subject: [PATCH 2/2] Address PR comments Add info about diagnostic in
ReleaseNotes.rst
---
clang/docs/ReleaseNotes.rst | 4 ++++
.../clang/Basic/DiagnosticSemaKinds.td | 1 -
clang/lib/Sema/SemaTypeTraits.cpp | 20 +++++++++++-------
.../SemaCXX/type-traits-unsatisfied-diags.cpp | 21 +++++++++++++++++--
4 files changed, 36 insertions(+), 10 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index e1fe22393eebb..05efdd0b1ce10 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -644,6 +644,10 @@ Improvements to Clang's diagnostics
#GH69470, #GH59391, #GH58172, #GH46215, #GH45915, #GH45891, #GH44490,
#GH36703, #GH32903, #GH23312, #GH69874.
+- Clang now gives pinpointed diagnostics for std::is_empty<T> failures—emitting a “not empty” error
+ plus “because…” notes for non-static members, virtual functions/bases, non-empty bases, and non-zero bit-fields
+ (including dependent widths) while correctly ignoring zero-width bit-fields.
+
Improvements to Clang's time-trace
----------------------------------
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f8b733063f10b..1c3d7d50e8689 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1790,7 +1790,6 @@ def note_unsatisfied_trait_reason
"%NTCField{has a non-trivially-copyable member %1 of type %2}|"
"%NonEmptyMember{has a non-static data member %1 of type %2}|"
"%VirtualFunction{has a virtual function %1}|"
- "%VirtualBase{has a virtual base class %1}|"
"%NonEmptyBase{has a base class %1 that is not empty}|"
"%ZeroLengthField{field %1 is a non-zero-length bit-field}|"
"%DeletedDtr{has a %select{deleted|user-provided}1 destructor}|"
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index b387721f1a54a..c40c3066b3c11 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -2289,32 +2289,38 @@ static void DiagnoseNonTriviallyCopyableReason(Sema &SemaRef,
static void DiagnoseIsEmptyReason(Sema &S, SourceLocation Loc,
const CXXRecordDecl *D) {
// Non-static data members (ignore zero-width bit‐fields).
- for (auto *Field : D->fields()) {
- if (Field->isBitField() && Field->getBitWidthValue() == 0)
+ for (const auto *Field : D->fields()) {
+ if (Field->isZeroLengthBitField())
continue;
+ if (Field->isBitField()) {
+ S.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::ZeroLengthField << Field
+ << Field->getSourceRange();
+ continue;
+ }
S.Diag(Loc, diag::note_unsatisfied_trait_reason)
<< diag::TraitNotSatisfiedReason::NonEmptyMember << Field
<< Field->getType() << Field->getSourceRange();
}
// Virtual functions.
- for (auto *M : D->methods()) {
+ for (const auto *M : D->methods()) {
if (M->isVirtual()) {
S.Diag(Loc, diag::note_unsatisfied_trait_reason)
- << diag::TraitNotSatisfiedReason::VirtualFunction << M->getDeclName()
+ << diag::TraitNotSatisfiedReason::VirtualFunction << M
<< M->getSourceRange();
break;
}
}
// Virtual bases and non-empty bases.
- for (auto &B : D->bases()) {
- auto *BR = B.getType()->getAsCXXRecordDecl();
+ for (const auto &B : D->bases()) {
+ const auto *BR = B.getType()->getAsCXXRecordDecl();
if (!BR || BR->isInvalidDecl())
continue;
if (B.isVirtual()) {
S.Diag(Loc, diag::note_unsatisfied_trait_reason)
- << diag::TraitNotSatisfiedReason::VirtualBase << B.getType()
+ << diag::TraitNotSatisfiedReason::VBase << B.getType()
<< B.getSourceRange();
}
if (!BR->isEmpty()) {
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
index 1d97ee719b6c6..f8105816e22cc 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
@@ -520,7 +520,7 @@ namespace is_empty_tests {
static_assert(__is_empty(VB));
// expected-error at -1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::VB)'}} \
// expected-note at -1 {{'VB' is not empty}} \
- // expected-note at -1 {{because it has a virtual base class 'EB'}} \
+ // expected-note at -1 {{because it has a virtual base 'EB'}} \
// expected-note@#e-VB {{'VB' defined here}}
// Non-empty base class.
@@ -543,6 +543,23 @@ namespace is_empty_tests {
// expected-note at -1 {{because it has a non-static data member 'z' of type 'int'}} \
// expected-note at -1 {{because it has a virtual function 'g'}} \
// expected-note at -1 {{because it has a base class 'Base' that is not empty}} \
- // expected-note at -1 {{because it has a virtual base class 'EB'}} \
+ // expected-note at -1 {{because it has a virtual base 'EB'}} \
// expected-note@#e-Multi {{'Multi' defined here}}
+
+ // Zero-width bit-field.
+ struct BitField { int : 0; }; // #e-BitField
+ static_assert(__is_empty(BitField)); // no diagnostics
+
+ // Dependent bit-field width.
+ template <int N>
+ struct DependentBitField { int : N; }; // #e-DependentBitField
+
+ static_assert(__is_empty(DependentBitField<0>)); // no diagnostics
+
+ static_assert(__is_empty(DependentBitField<2>));
+ // expected-error at -1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::DependentBitField<2>)'}} \
+ // expected-note at -1 {{'DependentBitField<2>' is not empty}} \
+ // expected-note at -1 {{because it field '' is a non-zero-length bit-field}} \
+ // expected-note@#e-DependentBitField {{'DependentBitField<2>' defined here}}
+
}
More information about the cfe-commits
mailing list