-
Notifications
You must be signed in to change notification settings - Fork 14.4k
[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
Conversation
@llvm/pr-subscribers-clang Author: Samarth Narang (snarang181) ChangesExpands on #141911 Full diff: https://github.com/llvm/llvm-project/pull/145044.diff 4 Files Affected:
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}}
+}
|
2660f0f
to
ef3da9b
Compare
There was a problem hiding this 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.
ef3da9b
to
b128769
Compare
Thank you for reviewing, @AaronBallman. Requesting a re-review. |
There was a problem hiding this 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
There was a problem hiding this 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
b128769
to
93c89fc
Compare
Expands on #141911