Skip to content

[Clang] Implement diagnostics for why is_empty is false #145044

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 25, 2025

Conversation

snarang181
Copy link
Contributor

Expands on #141911

@snarang181 snarang181 marked this pull request as ready for review June 20, 2025 14:53
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jun 20, 2025
@snarang181 snarang181 requested a review from cor3ntin June 20, 2025 14:53
@snarang181 snarang181 self-assigned this Jun 20, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 20, 2025

@llvm/pr-subscribers-clang

Author: Samarth Narang (snarang181)

Changes

Expands on #141911


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

4 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+7-1)
  • (modified) clang/lib/Sema/SemaTypeTraits.cpp (+66)
  • (modified) clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp (+44)
  • (modified) clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp (+58)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 34b798a09c216..ec1969a4fd10b 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@-1 {{'int &' is not trivially copyable}} \
 // expected-note@-1 {{because it is a reference type}}
 
+static_assert(!std::is_empty<int>::value);
+
+static_assert(std::is_empty<int&>::value);
+// expected-error-re@-1 {{static assertion failed due to requirement 'std::{{.*}}is_empty<int &>::value'}} \
+// expected-note@-1 {{'int &' is not empty}} \
+// expected-note@-1 {{because it is a reference type}}
+static_assert(std::is_empty_v<int&>);
+// expected-error@-1 {{static assertion failed due to requirement 'std::is_empty_v<int &>'}} \
+// expected-note@-1 {{'int &' is not empty}} \
+// expected-note@-1 {{because it is a reference type}}
+
+
 
 namespace test_namespace {
     using namespace std;
@@ -119,6 +154,15 @@ namespace test_namespace {
     // expected-error@-1 {{static assertion failed due to requirement 'is_trivially_copyable_v<int &>'}} \
     // expected-note@-1 {{'int &' is not trivially copyable}} \
     // expected-note@-1 {{because it is a reference type}}
+
+    static_assert(is_empty<int&>::value);
+    // expected-error-re@-1 {{static assertion failed due to requirement '{{.*}}is_empty<int &>::value'}} \
+    // expected-note@-1 {{'int &' is not empty}} \
+    // expected-note@-1 {{because it is a reference type}} 
+    static_assert(is_empty_v<int&>);
+    // expected-error@-1 {{static assertion failed due to requirement 'is_empty_v<int &>'}} \
+    // expected-note@-1 {{'int &' is not empty}} \
+    // expected-note@-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@-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@-1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::A)'}} \
+    // expected-note@-1 {{'A' is not empty}} \
+    // expected-note@-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@-1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::R)'}} \
+    // expected-note@-1 {{'R' is not empty}} \
+    // expected-note@-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@-1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::VirtualFunc)'}} \
+    // expected-note@-1 {{'VirtualFunc' is not empty}} \
+    // expected-note@-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@-1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::VB)'}} \
+    // expected-note@-1 {{'VB' is not empty}} \
+    // expected-note@-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@-1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::Derived)'}} \
+    // expected-note@-1 {{'Derived' is not empty}} \
+    // expected-note@-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@-1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::Multi)'}} \
+    // expected-note@-1 {{'Multi' is not empty}} \
+    // expected-note@-1 {{because it has a non-static data member 'z' of type 'int'}} \
+    // expected-note@-1 {{because it has a virtual function 'g'}} \
+    // expected-note@-1 {{because it has a base class 'Base' that is not empty}} \
+    // expected-note@-1 {{because it has a virtual base class 'EB'}} \
+    // expected-note@#e-Multi {{'Multi' defined here}}
+}

Copilot

This comment was marked as off-topic.

@snarang181 snarang181 force-pushed the clang-explain-is-empty branch from 2660f0f to ef3da9b Compare June 22, 2025 01:05
Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this! Please be sure to add a release note to clang/docs/ReleaseNotes.rst so users know about the improvement.

@snarang181 snarang181 force-pushed the clang-explain-is-empty branch from ef3da9b to b128769 Compare June 25, 2025 03:51
@snarang181 snarang181 requested a review from AaronBallman June 25, 2025 03:51
@snarang181
Copy link
Contributor Author

Thank you for reviewing, @AaronBallman. Requesting a re-review.

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am happy with that if @AaronBallman is

Add tests for various cases of 'is_empty' evaluating to false
// and ensure that the diagnostics are correct.
Add info about diagnostic in ReleaseNotes.rst
Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Revert info in ReleaseNotes.rst
@snarang181 snarang181 force-pushed the clang-explain-is-empty branch from b128769 to 93c89fc Compare June 25, 2025 13:19
@snarang181 snarang181 merged commit b8e8ff3 into llvm:main Jun 25, 2025
8 checks passed
@snarang181 snarang181 deleted the clang-explain-is-empty branch June 26, 2025 10:16
anthonyhatran pushed a commit to anthonyhatran/llvm-project that referenced this pull request Jun 26, 2025
rlavaee pushed a commit to rlavaee/llvm-project that referenced this pull request Jul 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants