https://gcc.gnu.org/g:0629924777ea20d56d9ea40c3915eb0327a22ac7

commit r16-944-g0629924777ea20d56d9ea40c3915eb0327a22ac7
Author: Jason Merrill <ja...@redhat.com>
Date:   Wed May 28 11:42:00 2025 -0400

    c++: add __is_*destructible builtins [PR107600]
    
    Typically "does this class have a trivial destructor" is the wrong question
    to ask, we rather want "can I destroy this class trivially", thus the
    std::is_trivially_destructible standard trait.  Let's provide a builtin for
    it, and complain about asking whether a deleted destructor is trivial.
    
    Clang and MSVC also have these traits.
    
            PR c++/107600
    
    gcc/cp/ChangeLog:
    
            * cp-trait.def (IS_DESTRUCTIBLE, IS_NOTHROW_DESTRUCTIBLE)
            (IS_TRIVIALLY_DESTRUCTIBLE): New.
            * constraint.cc (diagnose_trait_expr): Explain them.
            * method.cc (destructible_expr): New.
            (is_xible_helper): Use it.
            * semantics.cc (finish_trait_expr): Handle new traits.
            (trait_expr_value): Likewise.  Complain about asking
            whether a deleted dtor is trivial.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/ext/is_destructible1.C: New test.

Diff:
---
 gcc/cp/constraint.cc                        |  9 +++++
 gcc/cp/method.cc                            | 15 ++++++++
 gcc/cp/semantics.cc                         | 20 ++++++++++
 gcc/testsuite/g++.dg/ext/is_destructible1.C | 60 +++++++++++++++++++++++++++++
 gcc/cp/cp-trait.def                         |  3 ++
 5 files changed, 107 insertions(+)

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 44fb086c6306..90625707043f 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -3100,6 +3100,9 @@ diagnose_trait_expr (tree expr, tree args)
     case CPTK_IS_CONVERTIBLE:
       inform (loc, "  %qT is not convertible from %qE", t2, t1);
       break;
+    case CPTK_IS_DESTRUCTIBLE:
+      inform (loc, "  %qT is not destructible", t1);
+      break;
     case CPTK_IS_EMPTY:
       inform (loc, "  %qT is not an empty class", t1);
       break;
@@ -3145,6 +3148,9 @@ diagnose_trait_expr (tree expr, tree args)
     case CPTK_IS_NOTHROW_CONVERTIBLE:
       inform (loc, "  %qT is not nothrow convertible from %qE", t2, t1);
       break;
+    case CPTK_IS_NOTHROW_DESTRUCTIBLE:
+      inform (loc, "  %qT is not nothrow destructible", t1);
+      break;
     case CPTK_IS_NOTHROW_INVOCABLE:
       if (!t2)
        inform (loc, "  %qT is not nothrow invocable", t1);
@@ -3194,6 +3200,9 @@ diagnose_trait_expr (tree expr, tree args)
     case CPTK_IS_TRIVIALLY_COPYABLE:
       inform (loc, "  %qT is not trivially copyable", t1);
       break;
+    case CPTK_IS_TRIVIALLY_DESTRUCTIBLE:
+      inform (loc, "  %qT is not trivially destructible", t1);
+      break;
     case CPTK_IS_UNBOUNDED_ARRAY:
       inform (loc, "  %qT is not an unbounded array", t1);
       break;
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index 092bae277875..3a675d9f8723 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -2330,6 +2330,19 @@ constructible_expr (tree to, tree from)
   return expr;
 }
 
+/* Return declval<T>().~T() treated as an unevaluated operand.  */
+
+static tree
+destructible_expr (tree to)
+{
+  cp_unevaluated cp_uneval_guard;
+  int flags = LOOKUP_NORMAL|LOOKUP_DESTRUCTOR;
+  to = build_trait_object (to);
+  tree r = build_delete (input_location, TREE_TYPE (to), to,
+                        sfk_complete_destructor, flags, 0, tf_none);
+  return r;
+}
+
 /* Returns a tree iff TO is assignable (if CODE is MODIFY_EXPR) or
    constructible (otherwise) from FROM, which is a single type for
    assignment or a list of types for construction.  */
@@ -2346,6 +2359,8 @@ is_xible_helper (enum tree_code code, tree to, tree from, 
bool trivial)
   tree expr;
   if (code == MODIFY_EXPR)
     expr = assignable_expr (to, from);
+  else if (code == BIT_NOT_EXPR)
+    expr = destructible_expr (to);
   else if (trivial && TREE_VEC_LENGTH (from) > 1
           && cxx_dialect < cxx20)
     return error_mark_node; // only 0- and 1-argument ctors can be trivial
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index ef4a668a4e4d..241f2730878b 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -13235,6 +13235,14 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree 
type2)
 
     case CPTK_HAS_TRIVIAL_DESTRUCTOR:
       type1 = strip_array_types (type1);
+      if (CLASS_TYPE_P (type1) && type_build_dtor_call (type1))
+       {
+         deferring_access_check_sentinel dacs (dk_no_check);
+         tree fn = get_dtor (type1, tf_none);
+         if (!fn && !seen_error ())
+           warning (0, "checking %qs for type %qT with a destructor that "
+                    "cannot be called", "__has_trivial_destructor", type1);
+       }
       return (trivial_type_p (type1) || type_code1 == REFERENCE_TYPE
              || (CLASS_TYPE_P (type1)
                  && TYPE_HAS_TRIVIAL_DESTRUCTOR (type1)));
@@ -13290,6 +13298,9 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree 
type2)
     case CPTK_IS_CONVERTIBLE:
       return is_convertible (type1, type2);
 
+    case CPTK_IS_DESTRUCTIBLE:
+      return is_xible (BIT_NOT_EXPR, type1, NULL_TREE);
+
     case CPTK_IS_EMPTY:
       return NON_UNION_CLASS_TYPE_P (type1) && CLASSTYPE_EMPTY_P (type1);
 
@@ -13329,6 +13340,9 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree 
type2)
     case CPTK_IS_NOTHROW_CONVERTIBLE:
       return is_nothrow_convertible (type1, type2);
 
+    case CPTK_IS_NOTHROW_DESTRUCTIBLE:
+      return is_nothrow_xible (BIT_NOT_EXPR, type1, NULL_TREE);
+
     case CPTK_IS_NOTHROW_INVOCABLE:
       return expr_noexcept_p (build_invoke (type1, type2, tf_none), tf_none);
 
@@ -13371,6 +13385,9 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree 
type2)
     case CPTK_IS_TRIVIALLY_COPYABLE:
       return trivially_copyable_p (type1);
 
+    case CPTK_IS_TRIVIALLY_DESTRUCTIBLE:
+      return is_trivially_xible (BIT_NOT_EXPR, type1, NULL_TREE);
+
     case CPTK_IS_UNBOUNDED_ARRAY:
       return array_of_unknown_bound_p (type1);
 
@@ -13543,6 +13560,9 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, 
tree type1, tree type2)
     case CPTK_HAS_NOTHROW_COPY:
     case CPTK_HAS_TRIVIAL_COPY:
     case CPTK_HAS_TRIVIAL_DESTRUCTOR:
+    case CPTK_IS_DESTRUCTIBLE:
+    case CPTK_IS_NOTHROW_DESTRUCTIBLE:
+    case CPTK_IS_TRIVIALLY_DESTRUCTIBLE:
       if (!check_trait_type (type1))
        return error_mark_node;
       break;
diff --git a/gcc/testsuite/g++.dg/ext/is_destructible1.C 
b/gcc/testsuite/g++.dg/ext/is_destructible1.C
new file mode 100644
index 000000000000..994c388263db
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/is_destructible1.C
@@ -0,0 +1,60 @@
+// PR c++/107600
+// { dg-do compile { target c++11 } }
+
+#define SA(X) static_assert ((X), #X)
+
+namespace N1 {
+  struct A { ~A() = delete; };
+
+  SA (!__is_destructible (A));
+  SA (!__is_nothrow_destructible (A));
+  SA (!__is_trivially_destructible (A));
+
+  bool b = __has_trivial_destructor (A); // { dg-message 
"has_trivial_destructor" }
+}
+
+namespace N2 {
+  struct A { protected: ~A() = default; };
+
+  SA (!__is_destructible (A));
+  SA (!__is_nothrow_destructible (A));
+  SA (!__is_trivially_destructible (A));
+  SA (__has_trivial_destructor (A));
+}
+
+namespace N3 {
+  struct A { ~A(); };
+
+  SA (__is_destructible (A));
+  SA (__is_nothrow_destructible (A));
+  SA (!__is_trivially_destructible (A));
+  SA (!__has_trivial_destructor (A));
+}
+
+namespace N4 {
+  struct A { ~A() noexcept (false); };
+
+  SA (__is_destructible (A));
+  SA (!__is_nothrow_destructible (A));
+  SA (!__is_trivially_destructible (A));
+  SA (!__has_trivial_destructor (A));
+}
+
+namespace N5 {
+  struct A { ~A() = default; };
+
+  SA (__is_destructible (A));
+  SA (__is_nothrow_destructible (A));
+  SA (__is_trivially_destructible (A));
+  SA (__has_trivial_destructor (A));
+}
+
+namespace N6 {
+  struct A { };
+
+  SA (__is_destructible (A));
+  SA (__is_nothrow_destructible (A));
+  SA (__is_trivially_destructible (A));
+  SA (__has_trivial_destructor (A));
+}
+
diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def
index 6aaca13c5e90..9c7380d7398e 100644
--- a/gcc/cp/cp-trait.def
+++ b/gcc/cp/cp-trait.def
@@ -71,6 +71,7 @@ DEFTRAIT_EXPR (IS_CLASS, "__is_class", 1)
 DEFTRAIT_EXPR (IS_CONST, "__is_const", 1)
 DEFTRAIT_EXPR (IS_CONSTRUCTIBLE, "__is_constructible", -1)
 DEFTRAIT_EXPR (IS_CONVERTIBLE, "__is_convertible", 2)
+DEFTRAIT_EXPR (IS_DESTRUCTIBLE, "__is_destructible", 1)
 DEFTRAIT_EXPR (IS_EMPTY, "__is_empty", 1)
 DEFTRAIT_EXPR (IS_ENUM, "__is_enum", 1)
 DEFTRAIT_EXPR (IS_FINAL, "__is_final", 1)
@@ -84,6 +85,7 @@ DEFTRAIT_EXPR (IS_MEMBER_POINTER, "__is_member_pointer", 1)
 DEFTRAIT_EXPR (IS_NOTHROW_ASSIGNABLE, "__is_nothrow_assignable", 2)
 DEFTRAIT_EXPR (IS_NOTHROW_CONSTRUCTIBLE, "__is_nothrow_constructible", -1)
 DEFTRAIT_EXPR (IS_NOTHROW_CONVERTIBLE, "__is_nothrow_convertible", 2)
+DEFTRAIT_EXPR (IS_NOTHROW_DESTRUCTIBLE, "__is_nothrow_destructible", 1)
 DEFTRAIT_EXPR (IS_NOTHROW_INVOCABLE, "__is_nothrow_invocable", -1)
 DEFTRAIT_EXPR (IS_OBJECT, "__is_object", 1)
 DEFTRAIT_EXPR (IS_POINTER_INTERCONVERTIBLE_BASE_OF, 
"__is_pointer_interconvertible_base_of", 2)
@@ -98,6 +100,7 @@ DEFTRAIT_EXPR (IS_TRIVIAL, "__is_trivial", 1)
 DEFTRAIT_EXPR (IS_TRIVIALLY_ASSIGNABLE, "__is_trivially_assignable", 2)
 DEFTRAIT_EXPR (IS_TRIVIALLY_CONSTRUCTIBLE, "__is_trivially_constructible", -1)
 DEFTRAIT_EXPR (IS_TRIVIALLY_COPYABLE, "__is_trivially_copyable", 1)
+DEFTRAIT_EXPR (IS_TRIVIALLY_DESTRUCTIBLE, "__is_trivially_destructible", -1)
 DEFTRAIT_EXPR (IS_UNBOUNDED_ARRAY, "__is_unbounded_array", 1)
 DEFTRAIT_EXPR (IS_UNION, "__is_union", 1)
 DEFTRAIT_EXPR (IS_VIRTUAL_BASE_OF, "__builtin_is_virtual_base_of", 2)

Reply via email to