https://github.com/snarang181 created https://github.com/llvm/llvm-project/pull/145044
Expands on https://github.com/llvm/llvm-project/issues/141911 >From 2660f0f3b2096640a52e0d2375c566994645d304 Mon Sep 17 00:00:00 2001 From: Samarth Narang <snar...@umass.edu> Date: Fri, 20 Jun 2025 10:51:04 -0400 Subject: [PATCH] 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 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}} +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits