Skip to content

Conversation

@AnushaK6
Copy link

@AnushaK6 AnushaK6 commented Nov 7, 2025

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

@github-actions
Copy link

github-actions bot commented Nov 7, 2025

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Nov 7, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 7, 2025

@llvm/pr-subscribers-backend-risc-v
@llvm/pr-subscribers-llvm-globalisel
@llvm/pr-subscribers-backend-aarch64

@llvm/pr-subscribers-clang

Author: None (AnushaK6)

Changes

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

Full diff: https://github.com/llvm/llvm-project/pull/166967.diff

4 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+4-1)
  • (modified) clang/lib/Sema/SemaTypeTraits.cpp (+64)
  • (modified) clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp (+76)
  • (modified) clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp (+49)
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..fef47dfc2cc51 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,66 @@ 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 +2950,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@-1 {{because it is a reference type}} \
 // expected-note@-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@-1 {{static assertion failed due to requirement 'std::{{.*}}is_destructible<void>::value'}} \
+// expected-note@-1 {{'void' is not destructible}} \
+// expected-note@-1 {{because it is a cv void type}}
+
+static_assert(std::is_destructible_v<void>);
+// expected-error@-1 {{static assertion failed due to requirement 'std::is_destructible_v<void>'}} \
+// expected-note@-1 {{'void' is not destructible}} \
+// expected-note@-1 {{because it is a cv void type}}
+
 
 namespace test_namespace {
     using namespace std;
@@ -473,6 +514,17 @@ namespace test_namespace {
     // expected-note@-1 {{'int &' is not abstract}} \
     // expected-note@-1 {{because it is a reference type}} \
     // expected-note@-1 {{because it is not a struct or class type}}
+
+    static_assert(is_destructible<void>::value);
+    // expected-error-re@-1 {{static assertion failed due to requirement '{{.*}}is_destructible<void>::value'}} \
+    // expected-note@-1 {{'void' is not destructible}} \
+    // expected-note@-1 {{because it is a cv void type}}
+
+    static_assert(is_destructible_v<void>);
+    // expected-error@-1 {{static assertion failed due to requirement 'is_destructible_v<void>'}} \
+    // expected-note@-1 {{'void' is not destructible}} \
+    // expected-note@-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@-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@-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@-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@-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@-1 {{incomplete type 'Incomplete' used in type trait expression}}
+
+static_assert(__is_destructible(void));
+// expected-error@-1 {{static assertion failed due to requirement '__is_destructible(void)'}} \
+// expected-note@-1 {{'void' is not destructible}} \
+// expected-note@-1 {{because it is a cv void type}}
+
+using F = void();
+static_assert(__is_destructible(F));
+// expected-error@-1 {{static assertion failed due to requirement '__is_destructible(void ())'}} \
+// expected-note@-1 {{'void ()' is not destructible}} \
+// expected-note@-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@-1 {{static assertion failed due to requirement '__is_destructible(destructible::DeletedDtor)'}} \
+// expected-note@-1 {{'destructible::DeletedDtor' is not destructible}} \
+// expected-note@-1 {{because it has a deleted destructor}}
+
+struct PrivateDtor { // #d-PrivateDtor
+private:
+  ~PrivateDtor(); // #d-PrivateDtor-dtor
+};
+static_assert(__is_destructible(PrivateDtor));
+// expected-error@-1 {{static assertion failed due to requirement '__is_destructible(destructible::PrivateDtor)'}} \
+// expected-note@-1 {{'destructible::PrivateDtor' is not destructible}} \
+// expected-note@-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@-1 {{static assertion failed due to requirement '__is_destructible(destructible::DerivedFromInaccessible)'}} \
+// expected-note@-1 {{'destructible::DerivedFromInaccessible' is not destructible}} \
+// expected-note@-1 {{because it has a deleted destructor}}
+
+}

@lenary
Copy link
Member

lenary commented Nov 10, 2025

This seems to have some GISel changes that aren't relevant?

@AnushaK6 AnushaK6 closed this by deleting the head repository Nov 10, 2025
@AnushaK6
Copy link
Author

Thank you for catching that! I was working on a different issue and accidentally pushed those commits to the same branch.
Sincere apologies for the oversight.

I've raised a new PR with the correct changes for this issue here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:AArch64 backend:RISC-V clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category llvm:globalisel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants