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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ Improvements to Clang's diagnostics
#GH69470, #GH59391, #GH58172, #GH46215, #GH45915, #GH45891, #GH44490,
#GH36703, #GH32903, #GH23312, #GH69874.


Improvements to Clang's time-trace
----------------------------------

Expand Down
7 changes: 6 additions & 1 deletion clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -1787,6 +1788,10 @@ 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}|"
"%NonEmptyBase{has a base class %1 that is not empty}|"
"%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}|"
Expand Down
72 changes: 72 additions & 0 deletions clang/lib/Sema/SemaTypeTraits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -2313,6 +2314,74 @@ 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 (const auto *Field : D->fields()) {
if (Field->isZeroLengthBitField())
continue;
if (Field->isBitField()) {
S.Diag(Loc, diag::note_unsatisfied_trait_reason)
<< diag::TraitNotSatisfiedReason::NonZeroLengthField << Field
<< Field->getSourceRange();
continue;
}
S.Diag(Loc, diag::note_unsatisfied_trait_reason)
<< diag::TraitNotSatisfiedReason::NonEmptyMember << Field
<< Field->getType() << Field->getSourceRange();
}

// Virtual functions.
for (const auto *M : D->methods()) {
if (M->isVirtual()) {
S.Diag(Loc, diag::note_unsatisfied_trait_reason)
<< diag::TraitNotSatisfiedReason::VirtualFunction << M
<< M->getSourceRange();
break;
}
}

// Virtual bases and non-empty bases.
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::VBase << 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())
Expand All @@ -2336,6 +2405,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;
}
Expand Down
44 changes: 44 additions & 0 deletions clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

This comment was marked as off-topic.

static constexpr bool value = __is_empty(T);
};
template <typename T>
constexpr bool is_empty_v = __is_empty(T);
#endif

#ifdef STD2
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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

}
Expand All @@ -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);
Expand Down Expand Up @@ -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}}
}


Expand Down
75 changes: 75 additions & 0 deletions clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -559,3 +559,78 @@ 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 '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 '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}}

}