https://github.com/cjdb created https://github.com/llvm/llvm-project/pull/238456

This provides a canonical way to determine a type's destructor information for 
both the standard library and projects using Clang as a library.

**Alternatives considered**

* **Make `EvaluateUnaryTypeTrait` a public function.** Not chosen because it 
isn't clear whether consumers can obtain obtain `TypeSourceInfo`. This 
alternative is more viable if Clang provides a way for users to source this 
information without difficulty.
* **Combine `GetDestructorFormat` and `IsDestructorNothrow`.** Not chosen 
because trivial destructors can be marked as potentially-throwing, so we'd need 
to add `DestructorFormat::TrivialThrowing` and 
`DestructorFormat::NonTrivialThrowing`. Checking that the destructor is 
non-throwing non-throwing is also orthogonal to checking that that the type is 
destructible. Checking this property only when necessary is also a separation 
of concerns.
* **Split `GetDestructorFormat` into a Boolean function for each trait.** This 
approach requires either a non-trivial amount of code duplication, or creating 
helpers that don't fit together very well. It also limits what a user can do, 
since Boolean function only returns one bit of information at a time (we can 
use `GetDestructorFormat` in a switch-statement, for example).
* **Replace `NotAvailable` with more nuanced enumerators (e.g. `Deleted`, 
`Private`, etc.).** Not chosen because there isn't a clear use-case for this at 
the moment, and because that functionality can be queried in other ways.

>From 058eab4922b0811166e4c71046d4fb8d722c9029 Mon Sep 17 00:00:00 2001
From: Christopher Di Bella <[email protected]>
Date: Tue, 3 Nov 2026 01:39:02 +0000
Subject: [PATCH] Moves destructor type-trait deduction to a public function

This provides a canonical way to determine a type's destructor
information for both the standard library and projects using Clang as a
library.
---
 clang/include/clang/Sema/Sema.h   |  32 +++++++++
 clang/lib/Sema/SemaTypeTraits.cpp | 115 ++++++++++++++++++------------
 2 files changed, 102 insertions(+), 45 deletions(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index ba215e7e18d7ba..c863e6cdf3406b 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -16057,6 +16057,38 @@ class Sema final : public SemaBase {
   ///@}
 };
 
+enum class DestructorFormat {
+  // SMF is deleted, private, protected, or otherwise inaccessible.
+  NotAvailable,
+
+  // SMF is trivial.
+  Trivial,
+
+  // SMF is non-trivial and nothrow.
+  NonTrivial,
+};
+
+/// Returns whether a type is trivially-destructible, non-trivially
+/// destructible, or cannot be destroyed in the current context.
+///
+/// It is possible for a type to have a trivial destructor without being
+/// trivially-destructible. For example, `struct S { ~S() = delete; };`
+/// has a trivial destructor: its destructor is non-virtual, all of its
+/// bases have trivial destructors, and all of its members are trivially
+/// destructible. `S` isn't trivially-destructible because its destructor
+/// has been deleted. Similarly, `class C { ~C() = default; }` isn't
+/// trivially-destructible because `~C()`---although trivial for the
+/// same reasons---is private.
+///
+/// This would ideally be placed in `QualType`, but libClangBasic
+/// doesn't include `Sema`.
+DestructorFormat GetDestructorFormat(Sema &S, const ASTContext &C,
+                                     SourceLocation Loc, QualType T);
+
+/// Returns whether a type's destructor is nothrow.
+bool IsDestructorNoThrow(Sema &S, const ASTContext &C, SourceLocation Loc,
+                         QualType T);
+
 DeductionFailureInfo
 MakeDeductionFailureInfo(ASTContext &Context, TemplateDeductionResult TDK,
                          sema::TemplateDeductionInfo &Info);
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp 
b/clang/lib/Sema/SemaTypeTraits.cpp
index cb30ff9fd02da9..91328c09750e8d 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -653,6 +653,67 @@ static ComparisonCategoryResult EvaluateTypeOrder(Sema &S, 
QualType LHS,
                     : ComparisonCategoryResult::Less;
 }
 
+DestructorFormat clang::GetDestructorFormat(Sema &S, const ASTContext &C,
+                                            SourceLocation Loc, QualType T) {
+  // C++14 [meta.unary.prop]:
+  //   For reference types, is_destructible<T>::value is true.
+  if (T->isReferenceType())
+    return DestructorFormat::Trivial;
+
+  // Objective-C++ ARC: autorelease types don't require destruction.
+  if (T->isObjCLifetimeType() &&
+      T.getObjCLifetime() == Qualifiers::OCL_Autoreleasing)
+    return DestructorFormat::Trivial;
+
+  // C++14 [meta.unary.prop]:
+  //   For incomplete types and function types, is_destructible<T>::value is
+  //   false.
+  if (T->isIncompleteType() || T->isFunctionType())
+    return DestructorFormat::NotAvailable;
+
+  DestructorFormat Result = DestructorFormat::Trivial;
+
+  // A type that requires destruction (via a non-trivial destructor or ARC
+  // lifetime semantics) is not trivially-destructible.
+  if (T.isDestructedType())
+    Result = DestructorFormat::NonTrivial;
+
+  // C++14 [meta.unary.prop]:
+  //   For object types and given U equal to remove_all_extents_t<T>, if the
+  //   expression std::declval<U&>().~U() is well-formed when treated as an
+  //   unevaluated operand (Clause 5), then is_destructible<T>::value is true
+  auto *RD = C.getBaseElementType(T)->getAsCXXRecordDecl();
+  if (!RD)
+    return Result;
+
+  CXXDestructorDecl *Destructor = S.LookupDestructor(RD);
+  if (!Destructor)
+    return DestructorFormat::NotAvailable;
+
+  //  C++14 [dcl.fct.def.delete]p2:
+  //    A program that refers to a deleted function implicitly or
+  //    explicitly, other than to declare it, is ill-formed.
+  if (Destructor->isDeleted())
+    return DestructorFormat::NotAvailable;
+
+  if (C.getLangOpts().AccessControl && Destructor->getAccess() != AS_public)
+    return DestructorFormat::NotAvailable;
+
+  return Result;
+}
+
+bool clang::IsDestructorNoThrow(Sema &S, const ASTContext &C,
+                                SourceLocation Loc, QualType T) {
+  auto *RD = C.getBaseElementType(T)->getAsCXXRecordDecl();
+  if (!RD)
+    return true;
+
+  CXXDestructorDecl *Destructor = S.LookupDestructor(RD);
+  auto *CPT = Destructor->getType()->castAs<FunctionProtoType>();
+  CPT = S.ResolveExceptionSpec(Loc, CPT);
+  return CPT && CPT->isNothrow();
+}
+
 static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
                                    SourceLocation KeyLoc,
                                    TypeSourceInfo *TInfo) {
@@ -878,52 +939,16 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait 
UTT,
              !RD->hasNonTrivialCopyAssignment();
     return false;
   case UTT_IsDestructible:
+    return clang::GetDestructorFormat(Self, C, KeyLoc, T) !=
+           DestructorFormat::NotAvailable;
   case UTT_IsTriviallyDestructible:
-  case UTT_IsNothrowDestructible:
-    // C++14 [meta.unary.prop]:
-    //   For reference types, is_destructible<T>::value is true.
-    if (T->isReferenceType())
-      return true;
-
-    // Objective-C++ ARC: autorelease types don't require destruction.
-    if (T->isObjCLifetimeType() &&
-        T.getObjCLifetime() == Qualifiers::OCL_Autoreleasing)
-      return true;
-
-    // C++14 [meta.unary.prop]:
-    //   For incomplete types and function types, is_destructible<T>::value is
-    //   false.
-    if (T->isIncompleteType() || T->isFunctionType())
-      return false;
-
-    // A type that requires destruction (via a non-trivial destructor or ARC
-    // lifetime semantics) is not trivially-destructible.
-    if (UTT == UTT_IsTriviallyDestructible && T.isDestructedType())
-      return false;
-
-    // C++14 [meta.unary.prop]:
-    //   For object types and given U equal to remove_all_extents_t<T>, if the
-    //   expression std::declval<U&>().~U() is well-formed when treated as an
-    //   unevaluated operand (Clause 5), then is_destructible<T>::value is true
-    if (auto *RD = C.getBaseElementType(T)->getAsCXXRecordDecl()) {
-      CXXDestructorDecl *Destructor = Self.LookupDestructor(RD);
-      if (!Destructor)
-        return false;
-      //  C++14 [dcl.fct.def.delete]p2:
-      //    A program that refers to a deleted function implicitly or
-      //    explicitly, other than to declare it, is ill-formed.
-      if (Destructor->isDeleted())
-        return false;
-      if (C.getLangOpts().AccessControl && Destructor->getAccess() != 
AS_public)
-        return false;
-      if (UTT == UTT_IsNothrowDestructible) {
-        auto *CPT = Destructor->getType()->castAs<FunctionProtoType>();
-        CPT = Self.ResolveExceptionSpec(KeyLoc, CPT);
-        if (!CPT || !CPT->isNothrow())
-          return false;
-      }
-    }
-    return true;
+    return clang::GetDestructorFormat(Self, C, KeyLoc, T) ==
+           DestructorFormat::Trivial;
+  case UTT_IsNothrowDestructible: {
+    auto Format = clang::GetDestructorFormat(Self, C, KeyLoc, T);
+    return Format != DestructorFormat::NotAvailable &&
+           clang::IsDestructorNoThrow(Self, C, KeyLoc, T);
+  }
 
   case UTT_HasTrivialDestructor:
     // http://gcc.gnu.org/onlinedocs/gcc/Type-Traits.html

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to