https://github.com/snarang181 updated https://github.com/llvm/llvm-project/pull/145044
>From 29de582dd07ceb89ead7ecf24be6c19004edd46e Mon Sep 17 00:00:00 2001 From: Samarth Narang <snar...@umass.edu> Date: Fri, 20 Jun 2025 10:51:04 -0400 Subject: [PATCH 1/3] 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 bf59bbb87fd8a..1c71ca2448f1c 100644 --- a/clang/lib/Sema/SemaTypeTraits.cpp +++ b/clang/lib/Sema/SemaTypeTraits.cpp @@ -1958,6 +1958,7 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) { .Case("is_replaceable", TypeTrait::UTT_IsReplaceable) .Case("is_trivially_copyable", TypeTrait::UTT_IsTriviallyCopyable) .Case("is_assignable", TypeTrait::BTT_IsAssignable) + .Case("is_empty", TypeTrait::UTT_IsEmpty) .Default(std::nullopt); } @@ -2313,6 +2314,68 @@ static void DiagnoseNonAssignableReason(Sema &SemaRef, SourceLocation Loc, 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()) @@ -2336,6 +2399,9 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) { case BTT_IsAssignable: DiagnoseNonAssignableReason(*this, E->getBeginLoc(), Args[0], Args[1]); 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 33b92326aec21..89fbad0c5b9b8 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp @@ -28,6 +28,13 @@ struct is_assignable { template <typename T, typename U> constexpr bool is_assignable_v = __is_assignable(T, U); + +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 @@ -63,6 +70,15 @@ using is_assignable = __details_is_assignable<T, U>; template <typename T, typename U> constexpr bool is_assignable_v = __is_assignable(T, U); + +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 @@ -101,6 +117,13 @@ using is_assignable = __details_is_assignable<T, U>; template <typename T, typename U> constexpr bool is_assignable_v = is_assignable<T, U>::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 } @@ -127,6 +150,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}} + + static_assert(std::is_assignable<int&, int>::value); static_assert(std::is_assignable<int&, void>::value); @@ -162,6 +197,15 @@ namespace test_namespace { static_assert(is_assignable_v<int&, void>); // expected-error@-1 {{static assertion failed due to requirement 'is_assignable_v<int &, void>'}} \ // expected-error@-1 {{assigning to 'int' from incompatible type 'void'}} + + 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 975174353a2a1..bdcb56d6fcc01 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp @@ -559,3 +559,61 @@ static_assert(__is_assignable(C1, C1)); // expected-note@#ama-C1 {{implicitly declared private here}} \ // expected-note@#a-C1 {{'C1' 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}} +} >From aecedc927ba79f41c301c090f1e515d428c6a4d2 Mon Sep 17 00:00:00 2001 From: Samarth Narang <snar...@umass.edu> Date: Tue, 24 Jun 2025 23:50:15 -0400 Subject: [PATCH 2/3] 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 1c71ca2448f1c..64b7c1ad5c028 100644 --- a/clang/lib/Sema/SemaTypeTraits.cpp +++ b/clang/lib/Sema/SemaTypeTraits.cpp @@ -2317,32 +2317,38 @@ static void DiagnoseNonAssignableReason(Sema &SemaRef, SourceLocation Loc, 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 bdcb56d6fcc01..f1624f0a6e553 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp @@ -591,7 +591,7 @@ namespace is_empty_tests { 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@-1 {{because it has a virtual base 'EB'}} \ // expected-note@#e-VB {{'VB' defined here}} // Non-empty base class. @@ -614,6 +614,23 @@ namespace is_empty_tests { // 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@-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@-1 {{static assertion failed due to requirement '__is_empty(is_empty_tests::DependentBitField<2>)'}} \ + // expected-note@-1 {{'DependentBitField<2>' is not empty}} \ + // expected-note@-1 {{because it field '' is a non-zero-length bit-field}} \ + // expected-note@#e-DependentBitField {{'DependentBitField<2>' defined here}} + } >From 93c89fc7e3131c9262e0c6620d0cb22f2aab1983 Mon Sep 17 00:00:00 2001 From: Samarth Narang <snar...@umass.edu> Date: Wed, 25 Jun 2025 09:18:46 -0400 Subject: [PATCH 3/3] Minor change in tablegen Revert info in ReleaseNotes.rst --- clang/docs/ReleaseNotes.rst | 6 +----- clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 +- clang/lib/Sema/SemaTypeTraits.cpp | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 05efdd0b1ce10..357dcc1e451b5 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -644,11 +644,7 @@ 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 1c3d7d50e8689..6eba0619883d3 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1791,7 +1791,7 @@ def note_unsatisfied_trait_reason "%NonEmptyMember{has a non-static data member %1 of type %2}|" "%VirtualFunction{has a virtual function %1}|" "%NonEmptyBase{has a base class %1 that is not empty}|" - "%ZeroLengthField{field %1 is a non-zero-length bit-field}|" + "%NonZeroLengthField{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 64b7c1ad5c028..c8a764d19c3d5 100644 --- a/clang/lib/Sema/SemaTypeTraits.cpp +++ b/clang/lib/Sema/SemaTypeTraits.cpp @@ -2322,7 +2322,7 @@ static void DiagnoseIsEmptyReason(Sema &S, SourceLocation Loc, continue; if (Field->isBitField()) { S.Diag(Loc, diag::note_unsatisfied_trait_reason) - << diag::TraitNotSatisfiedReason::ZeroLengthField << Field + << diag::TraitNotSatisfiedReason::NonZeroLengthField << Field << Field->getSourceRange(); continue; } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits