[clang] [Clang] Add diagnostic reasoning for unsatisfied is_destructible trait (PR #166967)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Nov 7 09:14:49 PST 2025
https://github.com/AnushaK6 created https://github.com/llvm/llvm-project/pull/166967
This patch adds diagnostic reasoning for the std::is_destructible type trait in Clang’s Sema diagnostics.
It enables detailed “unsatisfied trait” messages when a type fails destructibility checks (e.g., void, function types, deleted/private destructors, incomplete types, etc).
Changes:
- Added DiagnoseNonDestructibleReason() to SemaTypeTraits.cpp
- Integrated UTT_IsDestructible handling in Sema::DiagnoseTypeTraitDetails
- Updated diagnostic notes in DiagnosticSemaKinds.td
Added new tests to:
- clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
- clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
>From 14182fb64adde6e86a96f7a6ea0c22749124b827 Mon Sep 17 00:00:00 2001
From: AnushaK6 <anusha.k1300 at gmail.com>
Date: Fri, 7 Nov 2025 22:33:04 +0530
Subject: [PATCH 1/2] Add diagnostic reasoning for unsatisfied is_destructible
trait
---
.../clang/Basic/DiagnosticSemaKinds.td | 5 +-
clang/lib/Sema/SemaTypeTraits.cpp | 62 +++++++++++++++
.../type-traits-unsatisfied-diags-std.cpp | 76 +++++++++++++++++++
.../SemaCXX/type-traits-unsatisfied-diags.cpp | 49 ++++++++++++
4 files changed, 191 insertions(+), 1 deletion(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 4e369be0bbb92..ee357936e3a87 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1776,7 +1776,8 @@ def note_unsatisfied_trait
"%StandardLayout{standard-layout}|"
"%Aggregate{aggregate}|"
"%Final{final}|"
- "%Abstract{abstract}"
+ "%Abstract{abstract}|"
+ "%Destructible{destructible}"
"}1">;
def note_unsatisfied_trait_reason
@@ -1808,6 +1809,7 @@ def note_unsatisfied_trait_reason
"%NonStandardLayoutMember{has a non-standard-layout member %1 of type %2}|"
"%IndirectBaseWithFields{has an indirect base %1 with data members}|"
"%DeletedDtr{has a %select{deleted|user-provided}1 destructor}|"
+ "%InaccessibleDtr{has a %select{private|protected}1 destructor}|"
"%UserProvidedCtr{has a user provided %select{copy|move}1 "
"constructor}|"
"%UserDeclaredCtr{has a user-declared constructor}|"
@@ -1823,6 +1825,7 @@ def note_unsatisfied_trait_reason
"%FunctionType{is a function type}|"
"%CVVoidType{is a cv void type}|"
"%IncompleteArrayType{is an incomplete array type}|"
+ "%IncompleteType{is an incomplete type}|"
"%PrivateProtectedDirectDataMember{has a %select{private|protected}1 direct data member}|"
"%PrivateProtectedDirectBase{has a %select{private|protected}1 direct base}|"
"%NotClassOrUnion{is not a class or union type}|"
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index 38877967af05e..e9b8032733efc 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -2028,6 +2028,7 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) {
.Case("is_constructible", TypeTrait::TT_IsConstructible)
.Case("is_final", TypeTrait::UTT_IsFinal)
.Case("is_abstract", TypeTrait::UTT_IsAbstract)
+ .Case("is_destructible", TypeTrait::UTT_IsDestructible)
.Default(std::nullopt);
}
@@ -2399,6 +2400,64 @@ static void DiagnoseNonConstructibleReason(
SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
}
+static void DiagnoseNonDestructibleReason(
+ Sema &SemaRef, SourceLocation Loc,
+ QualType T) {
+
+ QualType CoreT = T.getCanonicalType();
+ if (const ArrayType *AT = SemaRef.Context.getAsArrayType(CoreT))
+ CoreT = AT->getElementType();
+
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait) << CoreT << diag::TraitName::Destructible;
+
+
+ if (CoreT->isFunctionType()){
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) << diag::TraitNotSatisfiedReason::FunctionType;
+ return;
+ }
+
+ if(CoreT->isVoidType()){
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) << diag::TraitNotSatisfiedReason::CVVoidType;
+ return;
+ }
+
+ if (CoreT->isIncompleteType()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) << diag::TraitNotSatisfiedReason::IncompleteType;
+ return;
+ }
+
+ const CXXRecordDecl *RD = CoreT->getAsCXXRecordDecl();
+ if (!RD || RD->isInvalidDecl())
+ return;
+
+ const CXXRecordDecl *Def = RD->getDefinition();
+ if (!Def) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::IncompleteType;
+ return;
+ }
+
+ CXXDestructorDecl *Dtor = Def->getDestructor();
+ if (!Dtor)
+ return;
+
+ if (Dtor->isDeleted()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::DeletedDtr << 0
+ << Dtor->getSourceRange();
+ return;
+ }
+
+ AccessSpecifier AS = Dtor->getAccess();
+ if (AS == AS_private || AS == AS_protected) {
+ unsigned Select = (AS == AS_private) ? 0 : 1;
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::InaccessibleDtr << Select
+ << Dtor->getSourceRange();
+ return;
+ }
+}
+
static void DiagnoseNonTriviallyCopyableReason(Sema &SemaRef,
SourceLocation Loc, QualType T) {
SemaRef.Diag(Loc, diag::note_unsatisfied_trait)
@@ -2889,6 +2948,9 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
case TT_IsConstructible:
DiagnoseNonConstructibleReason(*this, E->getBeginLoc(), Args);
break;
+ case UTT_IsDestructible:
+ DiagnoseNonDestructibleReason(*this, E->getBeginLoc(), Args[0]);
+ break;
case UTT_IsAggregate:
DiagnoseNonAggregateReason(*this, E->getBeginLoc(), Args[0]);
break;
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
index 3e03a79275232..3e02fe8f10f56 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
@@ -73,6 +73,15 @@ struct is_abstract {
template <typename T>
constexpr bool is_abstract_v = __is_abstract(T);
+template <typename T>
+struct is_destructible {
+ static constexpr bool value = __is_destructible(T);
+};
+
+template <typename T>
+constexpr bool is_destructible_v = __is_destructible(T);
+
+
#endif
#ifdef STD2
@@ -167,6 +176,17 @@ using is_abstract = __details_is_abstract<T>;
template <typename T>
constexpr bool is_abstract_v = __is_abstract(T);
+template <typename T>
+struct __details_is_destructible {
+ static constexpr bool value = __is_destructible(T);
+};
+
+template <typename T>
+using is_destructible = __details_is_destructible<T>;
+
+template <typename T>
+constexpr bool is_destructible_v = __is_destructible(T);
+
#endif
@@ -252,6 +272,15 @@ using is_abstract = __details_is_abstract<T>;
template <typename T>
constexpr bool is_abstract_v = is_abstract<T>::value;
+template <typename T>
+struct __details_is_destructible : bool_constant<__is_destructible(T)> {};
+
+template <typename T>
+using is_destructible = __details_is_destructible<T>;
+
+template <typename T>
+constexpr bool is_destructible_v = is_destructible<T>::value;
+
#endif
}
@@ -374,6 +403,18 @@ static_assert(std::is_abstract_v<int&>);
// expected-note at -1 {{because it is a reference type}} \
// expected-note at -1 {{because it is not a struct or class type}}
+static_assert(std::is_destructible<int>::value);
+
+static_assert(std::is_destructible<void>::value);
+// expected-error-re at -1 {{static assertion failed due to requirement 'std::{{.*}}is_destructible<void>::value'}} \
+// expected-note at -1 {{'void' is not destructible}} \
+// expected-note at -1 {{because it is a cv void type}}
+
+static_assert(std::is_destructible_v<void>);
+// expected-error at -1 {{static assertion failed due to requirement 'std::is_destructible_v<void>'}} \
+// expected-note at -1 {{'void' is not destructible}} \
+// expected-note at -1 {{because it is a cv void type}}
+
namespace test_namespace {
using namespace std;
@@ -473,6 +514,17 @@ namespace test_namespace {
// expected-note at -1 {{'int &' is not abstract}} \
// expected-note at -1 {{because it is a reference type}} \
// expected-note at -1 {{because it is not a struct or class type}}
+
+ static_assert(is_destructible<void>::value);
+ // expected-error-re at -1 {{static assertion failed due to requirement '{{.*}}is_destructible<void>::value'}} \
+ // expected-note at -1 {{'void' is not destructible}} \
+ // expected-note at -1 {{because it is a cv void type}}
+
+ static_assert(is_destructible_v<void>);
+ // expected-error at -1 {{static assertion failed due to requirement 'is_destructible_v<void>'}} \
+ // expected-note at -1 {{'void' is not destructible}} \
+ // expected-note at -1 {{because it is a cv void type}}
+
}
@@ -518,6 +570,15 @@ concept C5 = std::is_aggregate_v<T>; // #concept10
template <C5 T> void g5(); // #cand10
+template <typename T>
+requires std::is_destructible<T>::value void f6(); // #cand11
+
+template <typename T>
+concept C6 = std::is_destructible_v<T>; // #concept11
+
+template <C6 T> void g6(); // #cand12
+
+
void test() {
f<int&>();
// expected-error at -1 {{no matching function for call to 'f'}} \
@@ -589,6 +650,21 @@ void test() {
// expected-note@#concept10 {{because 'std::is_aggregate_v<void>' evaluated to false}} \
// expected-note@#concept10 {{'void' is not aggregate}} \
// expected-note@#concept10 {{because it is a cv void type}}
+
+ f6<void>();
+ // expected-error at -1 {{no matching function for call to 'f6'}} \
+ // expected-note@#cand11 {{candidate template ignored: constraints not satisfied [with T = void]}} \
+ // expected-note-re@#cand11 {{because '{{.*}}is_destructible<void>::value' evaluated to false}} \
+ // expected-note@#cand11 {{'void' is not destructible}} \
+ // expected-note@#cand11 {{because it is a cv void type}}
+
+ g6<void>();
+ // expected-error at -1 {{no matching function for call to 'g6'}} \
+ // expected-note@#cand12 {{candidate template ignored: constraints not satisfied [with T = void]}} \
+ // expected-note@#cand12 {{because 'void' does not satisfy 'C6'}} \
+ // expected-note@#concept11 {{because 'std::is_destructible_v<void>' evaluated to false}} \
+ // expected-note@#concept11 {{'void' is not destructible}} \
+ // expected-note@#concept11 {{because it is a cv void type}}
}
}
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
index 22740418f09f5..858a5cc24868f 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
@@ -1052,3 +1052,52 @@ static_assert(__is_abstract(U));
// expected-note at -1 {{because it is not a struct or class type}}
}
+namespace destructible {
+
+struct Incomplete; // expected-note {{forward declaration of 'destructible::Incomplete'}}
+static_assert(__is_destructible(Incomplete));
+// expected-error at -1 {{incomplete type 'Incomplete' used in type trait expression}}
+
+static_assert(__is_destructible(void));
+// expected-error at -1 {{static assertion failed due to requirement '__is_destructible(void)'}} \
+// expected-note at -1 {{'void' is not destructible}} \
+// expected-note at -1 {{because it is a cv void type}}
+
+using F = void();
+static_assert(__is_destructible(F));
+// expected-error at -1 {{static assertion failed due to requirement '__is_destructible(void ())'}} \
+// expected-note at -1 {{'void ()' is not destructible}} \
+// expected-note at -1 {{because it is a function type}}
+
+using Ref = int&;
+static_assert(__is_destructible(Ref)); // no diagnostics (true)
+
+struct DeletedDtor { // #d-DeletedDtor
+ ~DeletedDtor() = delete;
+};
+static_assert(__is_destructible(DeletedDtor));
+// expected-error at -1 {{static assertion failed due to requirement '__is_destructible(destructible::DeletedDtor)'}} \
+// expected-note at -1 {{'destructible::DeletedDtor' is not destructible}} \
+// expected-note at -1 {{because it has a deleted destructor}}
+
+struct PrivateDtor { // #d-PrivateDtor
+private:
+ ~PrivateDtor(); // #d-PrivateDtor-dtor
+};
+static_assert(__is_destructible(PrivateDtor));
+// expected-error at -1 {{static assertion failed due to requirement '__is_destructible(destructible::PrivateDtor)'}} \
+// expected-note at -1 {{'destructible::PrivateDtor' is not destructible}} \
+// expected-note at -1 {{because it has a private destructor}}
+
+struct BaseInaccessible { // #d-BaseInacc
+private:
+ ~BaseInaccessible(); // #d-BaseInacc-dtor
+};
+
+struct DerivedFromInaccessible : BaseInaccessible {}; // #d-DerivedInacc
+static_assert(__is_destructible(DerivedFromInaccessible));
+// expected-error at -1 {{static assertion failed due to requirement '__is_destructible(destructible::DerivedFromInaccessible)'}} \
+// expected-note at -1 {{'destructible::DerivedFromInaccessible' is not destructible}} \
+// expected-note at -1 {{because it has a deleted destructor}}
+
+}
>From 5b5bdbfeafceedec79b173bf8e1d317818d7127d Mon Sep 17 00:00:00 2001
From: AnushaK6 <anusha.k1300 at gmail.com>
Date: Fri, 7 Nov 2025 22:34:51 +0530
Subject: [PATCH 2/2] [NFC] Format code using clang-format
---
clang/lib/Sema/SemaTypeTraits.cpp | 26 ++++++++++++++------------
1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index e9b8032733efc..fef47dfc2cc51 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -2400,29 +2400,31 @@ static void DiagnoseNonConstructibleReason(
SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
}
-static void DiagnoseNonDestructibleReason(
- Sema &SemaRef, SourceLocation Loc,
- QualType T) {
+static void DiagnoseNonDestructibleReason(Sema &SemaRef, SourceLocation Loc,
+ QualType T) {
QualType CoreT = T.getCanonicalType();
if (const ArrayType *AT = SemaRef.Context.getAsArrayType(CoreT))
CoreT = AT->getElementType();
- SemaRef.Diag(Loc, diag::note_unsatisfied_trait) << CoreT << diag::TraitName::Destructible;
-
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait)
+ << CoreT << diag::TraitName::Destructible;
- if (CoreT->isFunctionType()){
- SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) << diag::TraitNotSatisfiedReason::FunctionType;
+ if (CoreT->isFunctionType()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::FunctionType;
return;
}
-
- if(CoreT->isVoidType()){
- SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) << diag::TraitNotSatisfiedReason::CVVoidType;
+
+ if (CoreT->isVoidType()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::CVVoidType;
return;
}
if (CoreT->isIncompleteType()) {
- SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) << diag::TraitNotSatisfiedReason::IncompleteType;
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::IncompleteType;
return;
}
@@ -2433,7 +2435,7 @@ static void DiagnoseNonDestructibleReason(
const CXXRecordDecl *Def = RD->getDefinition();
if (!Def) {
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
- << diag::TraitNotSatisfiedReason::IncompleteType;
+ << diag::TraitNotSatisfiedReason::IncompleteType;
return;
}
More information about the cfe-commits
mailing list