diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index a12d3673b96..b84f4227e7e 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -5620,7 +5620,7 @@ type_has_virtual_destructor (tree type)
 {
   tree dtor;
 
-  if (!CLASS_TYPE_P (type))
+  if (!NON_UNION_CLASS_TYPE_P (type))
     return false;
 
   gcc_assert (COMPLETE_TYPE_P (type));
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 3d58d67ec11..ee361ae22cb 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -12028,11 +12028,23 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2)
     }
 }
 
-/* If TYPE is an array of unknown bound, or (possibly cv-qualified)
-   void, or a complete type, returns true, otherwise false.  */
+/* Returns true if TYPE meets the requirements for the specified KIND,
+   false otherwise.
+
+   When KIND == 1, TYPE must be an array of unknown bound,
+   or (possibly cv-qualified) void, or a complete type.
+
+   When KIND == 2, TYPE must be a complete type, or array of complete type,
+   or (possibly cv-qualified) void.
+
+   When KIND == 3:
+   If TYPE is a non-union class type, it must be complete.
+
+   When KIND == 4:
+   If TYPE is a class type, it must be complete.  */
 
 static bool
-check_trait_type (tree type)
+check_trait_type (tree type, int kind = 1)
 {
   if (type == NULL_TREE)
     return true;
@@ -12041,8 +12053,14 @@ check_trait_type (tree type)
     return (check_trait_type (TREE_VALUE (type))
 	    && check_trait_type (TREE_CHAIN (type)));
 
-  if (TREE_CODE (type) == ARRAY_TYPE && !TYPE_DOMAIN (type))
-    return true;
+  if (kind == 1 && TREE_CODE (type) == ARRAY_TYPE && !TYPE_DOMAIN (type))
+    return true; // Array of unknown bound. Don't care about completeness.
+
+  if (kind == 3 && !NON_UNION_CLASS_TYPE_P (type))
+    return true; // Not a non-union class type. Don't care about completeness.
+
+  if (kind == 4 && TREE_CODE (type) == ARRAY_TYPE)
+    return true; // Not a class type. Don't care about completeness.
 
   if (VOID_TYPE_P (type))
     return true;
@@ -12080,23 +12098,37 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
     case CPTK_HAS_TRIVIAL_COPY:
     case CPTK_HAS_TRIVIAL_DESTRUCTOR:
     case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
-    case CPTK_HAS_VIRTUAL_DESTRUCTOR:
-    case CPTK_IS_ABSTRACT:
+      if (!check_trait_type (type1))
+	return error_mark_node;
+      break;
+
     case CPTK_IS_AGGREGATE:
-    case CPTK_IS_EMPTY:
-    case CPTK_IS_FINAL:
     case CPTK_IS_LITERAL_TYPE:
     case CPTK_IS_POD:
-    case CPTK_IS_POLYMORPHIC:
     case CPTK_IS_STD_LAYOUT:
     case CPTK_IS_TRIVIAL:
     case CPTK_IS_TRIVIALLY_COPYABLE:
-      if (!check_trait_type (type1))
+      if (!check_trait_type (type1, /* kind = */ 2))
+	return error_mark_node;
+      break;
+
+    case CPTK_IS_EMPTY:
+    case CPTK_IS_POLYMORPHIC:
+    case CPTK_IS_ABSTRACT:
+    case CPTK_HAS_VIRTUAL_DESTRUCTOR:
+      if (!check_trait_type (type1, /* kind = */ 3))
+	return error_mark_node;
+      break;
+
+    case CPTK_IS_FINAL:
+      if (!check_trait_type (type1, /* kind = */ 4))
 	return error_mark_node;
       break;
 
     case CPTK_IS_ASSIGNABLE:
     case CPTK_IS_CONSTRUCTIBLE:
+      if (!check_trait_type (type1))
+	return error_mark_node;
       break;
 
     case CPTK_IS_TRIVIALLY_ASSIGNABLE:
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 0fedab96610..1a05dace97e 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -25174,14 +25174,18 @@ Requires: @code{type} shall be a complete type, (possibly cv-qualified)
 @item __has_virtual_destructor (type)
 If @code{type} is a class type with a virtual destructor
 ([class.dtor]) then the trait is @code{true}, else it is @code{false}.
-Requires: @code{type} shall be a complete type, (possibly cv-qualified)
-@code{void}, or an array of unknown bound.
+Requires: If @code{type} is a non-union class type, it shall be a complete type.
 
 @item __is_abstract (type)
 If @code{type} is an abstract class ([class.abstract]) then the trait
 is @code{true}, else it is @code{false}.
-Requires: @code{type} shall be a complete type, (possibly cv-qualified)
-@code{void}, or an array of unknown bound.
+Requires: If @code{type} is a non-union class type, it shall be a complete type.
+
+@item __is_aggregate (type)
+If @code{type} is an aggregate type ([dcl.init.aggr]) the trait is
+@code{true}, else it is @code{false}.
+Requires: @code{type} shall be a complete type, an array of complete types,
+or (possibly cv-qualified) @code{void}.
 
 @item __is_base_of (base_type, derived_type)
 If @code{base_type} is a base class of @code{derived_type}
@@ -25206,13 +25210,17 @@ any, are bit-fields of length 0, and @code{type} has no virtual
 members, and @code{type} has no virtual base classes, and @code{type}
 has no base classes @code{base_type} for which
 @code{__is_empty (base_type)} is @code{false}.
-Requires: @code{type} shall be a complete type, (possibly cv-qualified)
-@code{void}, or an array of unknown bound.
+Requires: If @code{type} is a non-union class type, it shall be a complete type.
 
 @item __is_enum (type)
 If @code{type} is a cv enumeration type ([basic.compound]) the trait is
 @code{true}, else it is @code{false}.
 
+@item __is_final (type)
+If @code{type} is a class or union type marked @code{final}, then the trait
+is @code{true}, else it is @code{false}.
+Requires: If @code{type} is a class type, it shall be a complete type.
+
 @item __is_literal_type (type)
 If @code{type} is a literal type ([basic.types]) the trait is
 @code{true}, else it is @code{false}.
@@ -25228,20 +25236,19 @@ Requires: @code{type} shall be a complete type, (possibly cv-qualified)
 @item __is_polymorphic (type)
 If @code{type} is a polymorphic class ([class.virtual]) then the trait
 is @code{true}, else it is @code{false}.
-Requires: @code{type} shall be a complete type, (possibly cv-qualified)
-@code{void}, or an array of unknown bound.
+Requires: If @code{type} is a non-union class type, it shall be a complete type.
 
 @item __is_standard_layout (type)
 If @code{type} is a standard-layout type ([basic.types]) the trait is
 @code{true}, else it is @code{false}.
-Requires: @code{type} shall be a complete type, (possibly cv-qualified)
-@code{void}, or an array of unknown bound.
+Requires: @code{type} shall be a complete type, an array of complete types,
+or (possibly cv-qualified) @code{void}.
 
 @item __is_trivial (type)
 If @code{type} is a trivial type ([basic.types]) the trait is
 @code{true}, else it is @code{false}.
-Requires: @code{type} shall be a complete type, (possibly cv-qualified)
-@code{void}, or an array of unknown bound.
+Requires: @code{type} shall be a complete type, an array of complete types,
+or (possibly cv-qualified) @code{void}.
 
 @item __is_union (type)
 If @code{type} is a cv union type ([basic.compound]) the trait is
diff --git a/gcc/testsuite/g++.dg/ext/array4.C b/gcc/testsuite/g++.dg/ext/array4.C
index 0068ea854e1..6adf9a3b0ee 100644
--- a/gcc/testsuite/g++.dg/ext/array4.C
+++ b/gcc/testsuite/g++.dg/ext/array4.C
@@ -16,7 +16,6 @@ template <typename _Tp>
 constexpr integral_constant<true> __is_complete_or_unbounded(_Tp) {
   return {};
 }
-struct Trans_NS_std_formatter;
 template <typename _Tp>
 struct is_default_constructible : integral_constant<false> {
   static_assert(__is_complete_or_unbounded(_Tp{}), "");
@@ -53,7 +52,7 @@ template <typename> struct basic_string_view { basic_string_view(int, int); };
 template <typename, typename> struct formatter;
 template <typename, typename>
 using has_formatter =
-    __bool_constant<__is_constructible(Trans_NS_std_formatter)>;
+    __bool_constant<__is_constructible(void)>;
 struct fallback_formatter;
 template <typename Context> struct custom_value {
   using parse_context = typename Context::parse_context_type;
diff --git a/gcc/testsuite/g++.dg/ext/unary_trait_incomplete.C b/gcc/testsuite/g++.dg/ext/unary_trait_incomplete.C
index 6c83279c030..236437b7f5b 100644
--- a/gcc/testsuite/g++.dg/ext/unary_trait_incomplete.C
+++ b/gcc/testsuite/g++.dg/ext/unary_trait_incomplete.C
@@ -2,6 +2,7 @@
 
 struct I; // { dg-message "forward declaration" }
 struct C { };
+union U; // { dg-message "forward declaration" }
 
 bool nas1 = __has_nothrow_assign(I); // { dg-error "incomplete type" }
 bool nas2 = __has_nothrow_assign(C[]);
@@ -39,38 +40,94 @@ bool tcp3 = __has_trivial_copy(I[]);
 bool tcp4 = __has_trivial_copy(void);
 bool tcp5 = __has_trivial_copy(const void);
 
-bool vde1 = __has_virtual_destructor(I); // { dg-error "incomplete type" }
-bool vde2 = __has_virtual_destructor(C[]);
-bool vde3 = __has_virtual_destructor(I[]);
-bool vde4 = __has_virtual_destructor(void);
-bool vde5 = __has_virtual_destructor(const void);
-
 bool tde1 = __has_trivial_destructor(I); // { dg-error "incomplete type" }
 bool tde2 = __has_trivial_destructor(C[]);
 bool tde3 = __has_trivial_destructor(I[]);
 bool tde4 = __has_trivial_destructor(void);
 bool tde5 = __has_trivial_destructor(const void);
 
-bool abs1 = __is_abstract(I); // { dg-error "incomplete type" }
-bool abs2 = __is_abstract(C[]);
-bool abs3 = __is_abstract(I[]);
-bool abs4 = __is_abstract(void);
-bool abs5 = __is_abstract(const void);
+// T shall be a complete type, cv void, or an array of unknown bound.
 
-bool pod1 = __is_pod(I); // { dg-error "incomplete type" }
-bool pod2 = __is_pod(C[]);
-bool pod3 = __is_pod(I[]);
-bool pod4 = __is_pod(void);
-bool pod5 = __is_pod(const void);
+bool con1 = __is_constructible(C);
+bool con2 = __is_constructible(I); // { dg-error "incomplete type" }
+bool con3 = __is_constructible(U); // { dg-error "incomplete type" }
+bool con4 = __is_constructible(C[]);
+bool con5 = __is_constructible(I[]);
+bool con6 = __is_constructible(U[]);
+bool con7 = __is_constructible(C[1]);
+bool con8 = __is_constructible(I[1]); // { dg-error "incomplete type" }
+bool con9 = __is_constructible(U[1]); // { dg-error "incomplete type" }
+bool con10 = __is_constructible(void);
+bool con11 = __is_constructible(const void);
+
+// If T is a non-union class type, T shall be a complete type.
+
+bool vde1 = __has_virtual_destructor(I); // { dg-error "incomplete type" }
+bool vde2 = __has_virtual_destructor(U);
+bool vde3 = __has_virtual_destructor(C[]);
+bool vde4 = __has_virtual_destructor(I[]);
+bool vde5 = __has_virtual_destructor(U[]);
+bool vde6 = __has_virtual_destructor(C[1]);
+bool vde7 = __has_virtual_destructor(I[1]);
+bool vde8 = __has_virtual_destructor(U[1]);
+bool vde9 = __has_virtual_destructor(void);
+bool vde10 = __has_virtual_destructor(const void);
+
+bool abs1 = __is_abstract(I); // { dg-error "incomplete type" }
+bool abs2 = __is_abstract(U);
+bool abs3 = __is_abstract(C[]);
+bool abs4 = __is_abstract(I[]);
+bool abs5 = __is_abstract(U[]);
+bool abs6 = __is_abstract(C[1]);
+bool abs7 = __is_abstract(I[1]);
+bool abs8 = __is_abstract(U[1]);
+bool abs9 = __is_abstract(void);
+bool abs10 = __is_abstract(const void);
 
 bool emp1 = __is_empty(I); // { dg-error "incomplete type" }
-bool emp2 = __is_empty(C[]);
-bool emp3 = __is_empty(I[]);
-bool emp4 = __is_empty(void);
-bool emp5 = __is_empty(const void);
+bool emp2 = __is_empty(U);
+bool emp3 = __is_empty(C[]);
+bool emp4 = __is_empty(I[]);
+bool emp5 = __is_empty(U[]);
+bool emp6 = __is_empty(C[1]);
+bool emp7 = __is_empty(I[1]);
+bool emp8 = __is_empty(U[1]);
+bool emp9 = __is_empty(void);
+bool emp10 = __is_empty(const void);
 
 bool pol1 = __is_polymorphic(I); // { dg-error "incomplete type" }
-bool pol2 = __is_polymorphic(C[]);
-bool pol3 = __is_polymorphic(I[]);
-bool pol4 = __is_polymorphic(void);
-bool pol5 = __is_polymorphic(const void);
+bool pol2 = __is_polymorphic(U);
+bool pol3 = __is_polymorphic(C[]);
+bool pol4 = __is_polymorphic(I[]);
+bool pol5 = __is_polymorphic(U[]);
+bool pol6 = __is_polymorphic(C[1]);
+bool pol7 = __is_polymorphic(I[1]);
+bool pol8 = __is_polymorphic(U[1]);
+bool pol9 = __is_polymorphic(void);
+bool pol10 = __is_polymorphic(const void);
+
+// If T is a class type, T shall be a complete type.
+
+bool fin1 = __is_final(I); // { dg-error "incomplete type" }
+bool fin2 = __is_final(U); // { dg-error "incomplete type" }
+bool fin3 = __is_final(C[]);
+bool fin4 = __is_final(I[]);
+bool fin5 = __is_final(U[]);
+bool fin6 = __is_final(C[1]);
+bool fin7 = __is_final(I[1]);
+bool fin8 = __is_final(U[1]);
+bool fin9 = __is_final(void);
+bool fin10 = __is_final(const void);
+
+// remove_all_extents_t<T> shall be a complete type or cv void.
+
+bool pod1 = __is_pod(I); // { dg-error "incomplete type" }
+bool pod2 = __is_pod(U); // { dg-error "incomplete type" }
+bool pod3 = __is_pod(C[]);
+bool pod4 = __is_pod(I[]); // { dg-error "incomplete type" }
+bool pod5 = __is_pod(U[]); // { dg-error "incomplete type" }
+bool pod6 = __is_pod(C[1]);
+bool pod7 = __is_pod(I[1]); // { dg-error "incomplete type" }
+bool pod8 = __is_pod(U[1]); // { dg-error "incomplete type" }
+bool pod9 = __is_pod(void);
+bool pod10 = __is_pod(const void);
diff --git a/libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization_neg.cc b/libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization_neg.cc
index 1870e501723..fc0b70b319c 100644
--- a/libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization_neg.cc
@@ -1,5 +1,7 @@
 // { dg-do compile { target c++11 } }
 // { dg-prune-output "must be a complete" }
+// { dg-prune-output "'value' is not a member of 'std::is_move_cons" }
+// { dg-prune-output "invalid use of incomplete type" }
 
 // Copyright (C) 2019-2022 Free Software Foundation, Inc.
 //
diff --git a/libstdc++-v3/testsuite/20_util/is_move_constructible/incomplete_neg.cc b/libstdc++-v3/testsuite/20_util/is_move_constructible/incomplete_neg.cc
index 7bd453d7851..7c34b5f1224 100644
--- a/libstdc++-v3/testsuite/20_util/is_move_constructible/incomplete_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/is_move_constructible/incomplete_neg.cc
@@ -18,6 +18,7 @@
 // <http://www.gnu.org/licenses/>.
 
 // { dg-error "must be a complete class" "" { target *-*-* } 0 }
+// { dg-prune-output "invalid use of incomplete type" }
 
 #include <type_traits>
 
diff --git a/libstdc++-v3/testsuite/20_util/is_nothrow_swappable/incomplete_neg.cc b/libstdc++-v3/testsuite/20_util/is_nothrow_swappable/incomplete_neg.cc
index 88c9c01f37c..d3a34cca7a7 100644
--- a/libstdc++-v3/testsuite/20_util/is_nothrow_swappable/incomplete_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/is_nothrow_swappable/incomplete_neg.cc
@@ -18,6 +18,7 @@
 // <http://www.gnu.org/licenses/>.
 
 // { dg-error "must be a complete class" "" { target *-*-* } 0 }
+// { dg-prune-output "invalid use of incomplete type" }
 
 #include <type_traits>
 
diff --git a/libstdc++-v3/testsuite/20_util/is_nothrow_swappable_with/incomplete_neg.cc b/libstdc++-v3/testsuite/20_util/is_nothrow_swappable_with/incomplete_neg.cc
index da0f7713659..6dfa3363d88 100644
--- a/libstdc++-v3/testsuite/20_util/is_nothrow_swappable_with/incomplete_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/is_nothrow_swappable_with/incomplete_neg.cc
@@ -18,6 +18,7 @@
 // <http://www.gnu.org/licenses/>.
 
 // { dg-error "must be a complete class" "" { target *-*-* } 0 }
+// { dg-prune-output "invalid use of incomplete type" }
 
 #include <type_traits>
 
diff --git a/libstdc++-v3/testsuite/20_util/is_swappable_with/incomplete_neg.cc b/libstdc++-v3/testsuite/20_util/is_swappable_with/incomplete_neg.cc
index 74ad291a931..d5fa42256ea 100644
--- a/libstdc++-v3/testsuite/20_util/is_swappable_with/incomplete_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/is_swappable_with/incomplete_neg.cc
@@ -18,6 +18,7 @@
 // <http://www.gnu.org/licenses/>.
 
 // { dg-error "must be a complete class" "" { target *-*-* } 0 }
+// { dg-prune-output "invalid use of incomplete type" }
 
 #include <type_traits>
 
