[v2] c++: Fix type completeness checks for type traits [PR106838]

Message ID 20220908125606.3272086-1-jwakely@redhat.com
State New, archived
Headers
Series [v2] c++: Fix type completeness checks for type traits [PR106838] |

Commit Message

Jonathan Wakely Sept. 8, 2022, 12:56 p.m. UTC
  On Wed, 7 Sept 2022 at 16:11, Jason Merrill wrote:
>
> On 9/7/22 08:18, Jonathan Wakely wrote:
> > @@ -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:
>
> Hmm, why does std::is_aggregate require a complete array element type?
> An array is an aggregate regardless of its element type.  I'd think it
> should be kind 4 instead.

That makes sense. It doesn't match the precondition for the library
trait in the standard, but violating that library precondition is
undefined, not ill-formed, so it seems harmless for libstdc++ to also
relax that precondition and allow arrays of incomplete type.

>
> > -    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:
>
> I also wonder why std::is_empty excludes unions, since there can now be
> an empty field of union type with the addition of [[no_unique_address]]?

True, although the main purpose of is_empty is to decide whether to
derive from it to get the EBO. With [[no_unique_address]] there's no
reason to do that, you can just add the attribute unconditionally and if
it's not empty, the attribute is a no-op. So I find it hard to care
about this case :-)

If the FE trait requires unions to be complete, then the library trait
would need to special-case unions to not use the FE trait, so that it
can still give the same answer (i.e. false) for incomplete union types.
I don't think changing this one has any advantage.

>
> > +    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:
>
> And I wonder a bit why std::is_final cares about the completeness of
> unions, which can't be (or have) base classes, but I guess you can still
> ask about whether the keyword was present, even though it has no effect.

Yes, as currently specified, it tells you whether the specifier was
present, nothing more.

Thanks for the review, here's an updated patch that moves __is_aggregate
to kind 4. I didn't change __is_empty or __is_final though, did you want
those changed?

Tested x86_64-linux.


-- >8 --

The check_trait_type function is used for a number of different type
traits that have different requirements on their arguments. For example,
__is_constructible allows arrays of unknown bound even if the array
element is an incomplete type, but __is_aggregate does not, it always
requires the array element type to be complete. Other traits have
different requirements again, e.g. __is_empty allows incomplete unions,
and arrays (of known or unknown bound) of incomplete types.

This alters the check_trait_type function to take an additional KIND
parameter which indicates which set of type trait requirements to check.

As noted in a comment, the requirements for __is_aggregate deviate from
the ones for std::is_aggregate in the standard. It's not necessary for
the elements of an array to be complete types, because arrays are always
aggregates.

The type_has_virtual_destructor change is needed to avoid an ICE.
Previously it could never be called for incomplete union types as they
were (incorrectly) rejected by check_trait_type.

This change causes some additional diagnostics in some libstdc++ tests,
where the front end was not previously complaining about invalid types
that the library assertions diagnosed. We should consider removing the
library assertions from traits where the front end implements the
correct checks now.

	PR c++/106838

gcc/cp/ChangeLog:

	* class.cc (type_has_virtual_destructor): Return false for
	union types.
	* semantics.cc (check_trait_type): Add KIND parameter to support
	different sets of requirements.
	(finish_trait_expr): Pass KIND argument for relevant traits.

gcc/ChangeLog:

	* doc/extend.texi (Type Traits): Fix requirements. Document
	__is_aggregate and __is_final.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/array4.C: Fix invalid use of __is_constructible.
	* g++.dg/ext/unary_trait_incomplete.C: Fix tests for traits with
	different requirements.

libstdc++-v3/ChangeLog:

	* testsuite/20_util/is_complete_or_unbounded/memoization_neg.cc:
	Prune additional errors from front-end.
	* testsuite/20_util/is_move_constructible/incomplete_neg.cc:
	Likewise.
	* testsuite/20_util/is_nothrow_swappable/incomplete_neg.cc:
	Likewise.
	* testsuite/20_util/is_nothrow_swappable_with/incomplete_neg.cc:
	Likewise.
	* testsuite/20_util/is_swappable_with/incomplete_neg.cc:
	Likewise.
---
 gcc/cp/class.cc                               |   2 +-
 gcc/cp/semantics.cc                           |  58 +++++++--
 gcc/doc/extend.texi                           |  30 +++--
 gcc/testsuite/g++.dg/ext/array4.C             |   3 +-
 .../g++.dg/ext/unary_trait_incomplete.C       | 116 ++++++++++++++----
 .../memoization_neg.cc                        |   2 +
 .../is_move_constructible/incomplete_neg.cc   |   1 +
 .../is_nothrow_swappable/incomplete_neg.cc    |   1 +
 .../incomplete_neg.cc                         |   1 +
 .../is_swappable_with/incomplete_neg.cc       |   1 +
 10 files changed, 164 insertions(+), 51 deletions(-)
  

Comments

Jason Merrill Sept. 8, 2022, 4:12 p.m. UTC | #1
On 9/8/22 08:56, Jonathan Wakely wrote:
> On Wed, 7 Sept 2022 at 16:11, Jason Merrill wrote:
>>
>> On 9/7/22 08:18, Jonathan Wakely wrote:
>>> @@ -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:
>>
>> Hmm, why does std::is_aggregate require a complete array element type?
>> An array is an aggregate regardless of its element type.  I'd think it
>> should be kind 4 instead.
> 
> That makes sense. It doesn't match the precondition for the library
> trait in the standard, but violating that library precondition is
> undefined, not ill-formed, so it seems harmless for libstdc++ to also
> relax that precondition and allow arrays of incomplete type.
> 
>>
>>> -    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:
>>
>> I also wonder why std::is_empty excludes unions, since there can now be
>> an empty field of union type with the addition of [[no_unique_address]]?
> 
> True, although the main purpose of is_empty is to decide whether to
> derive from it to get the EBO. With [[no_unique_address]] there's no
> reason to do that, you can just add the attribute unconditionally and if
> it's not empty, the attribute is a no-op. So I find it hard to care
> about this case :-)
> 
> If the FE trait requires unions to be complete, then the library trait
> would need to special-case unions to not use the FE trait, so that it
> can still give the same answer (i.e. false) for incomplete union types.
> I don't think changing this one has any advantage.
> 
>>
>>> +    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:
>>
>> And I wonder a bit why std::is_final cares about the completeness of
>> unions, which can't be (or have) base classes, but I guess you can still
>> ask about whether the keyword was present, even though it has no effect.
> 
> Yes, as currently specified, it tells you whether the specifier was
> present, nothing more.
> 
> Thanks for the review, here's an updated patch that moves __is_aggregate
> to kind 4. I didn't change __is_empty or __is_final though, did you want
> those changed?
> 
> Tested x86_64-linux.

That's fine, the patch is OK with the typo fix below:

> -- >8 --
> 
> The check_trait_type function is used for a number of different type
> traits that have different requirements on their arguments. For example,
> __is_constructible allows arrays of unknown bound even if the array
> element is an incomplete type, but __is_aggregate does not, it always
> requires the array element type to be complete. Other traits have
> different requirements again, e.g. __is_empty allows incomplete unions,
> and arrays (of known or unknown bound) of incomplete types.
> 
> This alters the check_trait_type function to take an additional KIND
> parameter which indicates which set of type trait requirements to check.
> 
> As noted in a comment, the requirements for __is_aggregate deviate from
> the ones for std::is_aggregate in the standard. It's not necessary for
> the elements of an array to be complete types, because arrays are always
> aggregates.
> 
> The type_has_virtual_destructor change is needed to avoid an ICE.
> Previously it could never be called for incomplete union types as they
> were (incorrectly) rejected by check_trait_type.
> 
> This change causes some additional diagnostics in some libstdc++ tests,
> where the front end was not previously complaining about invalid types
> that the library assertions diagnosed. We should consider removing the
> library assertions from traits where the front end implements the
> correct checks now.
> 
> 	PR c++/106838
> 
> gcc/cp/ChangeLog:
> 
> 	* class.cc (type_has_virtual_destructor): Return false for
> 	union types.
> 	* semantics.cc (check_trait_type): Add KIND parameter to support
> 	different sets of requirements.
> 	(finish_trait_expr): Pass KIND argument for relevant traits.
> 
> gcc/ChangeLog:
> 
> 	* doc/extend.texi (Type Traits): Fix requirements. Document
> 	__is_aggregate and __is_final.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/ext/array4.C: Fix invalid use of __is_constructible.
> 	* g++.dg/ext/unary_trait_incomplete.C: Fix tests for traits with
> 	different requirements.
> 
> libstdc++-v3/ChangeLog:
> 
> 	* testsuite/20_util/is_complete_or_unbounded/memoization_neg.cc:
> 	Prune additional errors from front-end.
> 	* testsuite/20_util/is_move_constructible/incomplete_neg.cc:
> 	Likewise.
> 	* testsuite/20_util/is_nothrow_swappable/incomplete_neg.cc:
> 	Likewise.
> 	* testsuite/20_util/is_nothrow_swappable_with/incomplete_neg.cc:
> 	Likewise.
> 	* testsuite/20_util/is_swappable_with/incomplete_neg.cc:
> 	Likewise.
> ---
>   gcc/cp/class.cc                               |   2 +-
>   gcc/cp/semantics.cc                           |  58 +++++++--
>   gcc/doc/extend.texi                           |  30 +++--
>   gcc/testsuite/g++.dg/ext/array4.C             |   3 +-
>   .../g++.dg/ext/unary_trait_incomplete.C       | 116 ++++++++++++++----
>   .../memoization_neg.cc                        |   2 +
>   .../is_move_constructible/incomplete_neg.cc   |   1 +
>   .../is_nothrow_swappable/incomplete_neg.cc    |   1 +
>   .../incomplete_neg.cc                         |   1 +
>   .../is_swappable_with/incomplete_neg.cc       |   1 +
>   10 files changed, 164 insertions(+), 51 deletions(-)
> 
> 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..aa3bfc457c1 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,39 @@ 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:
> -    case CPTK_IS_AGGREGATE:
> -    case CPTK_IS_EMPTY:
> -    case CPTK_IS_FINAL:
> +      if (!check_trait_type (type1))
> +	return error_mark_node;
> +      break;
> +
>       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;
> +
> +    /* N.B. std::is_aggregate is kind=2 but we don't need a complete element
> +       type to know whether an array is an aggregate, so use kind=2 here.  */

*kind=4

> +    case CPTK_IS_AGGREGATE:
> +    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..be3ea890c47 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -25174,14 +25174,17 @@ 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: If @code{type} is a class type, it shall be a complete type.
>   
>   @item __is_base_of (base_type, derived_type)
>   If @code{base_type} is a base class of @code{derived_type}
> @@ -25206,13 +25209,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 +25235,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..1dfa449a8d2 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,105 @@ 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 agg1 = __is_aggregate(I); // { dg-error "incomplete type" }
> +bool agg2 = __is_aggregate(U); // { dg-error "incomplete type" }
> +bool agg3 = __is_aggregate(C[]);
> +bool agg4 = __is_aggregate(I[]);
> +bool agg5 = __is_aggregate(U[]);
> +bool agg6 = __is_aggregate(C[1]);
> +bool agg7 = __is_aggregate(I[1]);
> +bool agg8 = __is_aggregate(U[1]);
> +bool agg9 = __is_aggregate(void);
> +bool agg10 = __is_aggregate(const void);
> +
> +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>
>
  
Jonathan Wakely Sept. 8, 2022, 4:38 p.m. UTC | #2
On Thu, 8 Sept 2022 at 17:12, Jason Merrill <jason@redhat.com> wrote:
>
> On 9/8/22 08:56, Jonathan Wakely wrote:
> > On Wed, 7 Sept 2022 at 16:11, Jason Merrill wrote:
> >>
> >> On 9/7/22 08:18, Jonathan Wakely wrote:
> >>> @@ -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:
> >>
> >> Hmm, why does std::is_aggregate require a complete array element type?
> >> An array is an aggregate regardless of its element type.  I'd think it
> >> should be kind 4 instead.
> >
> > That makes sense. It doesn't match the precondition for the library
> > trait in the standard, but violating that library precondition is
> > undefined, not ill-formed, so it seems harmless for libstdc++ to also
> > relax that precondition and allow arrays of incomplete type.
> >
> >>
> >>> -    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:
> >>
> >> I also wonder why std::is_empty excludes unions, since there can now be
> >> an empty field of union type with the addition of [[no_unique_address]]?
> >
> > True, although the main purpose of is_empty is to decide whether to
> > derive from it to get the EBO. With [[no_unique_address]] there's no
> > reason to do that, you can just add the attribute unconditionally and if
> > it's not empty, the attribute is a no-op. So I find it hard to care
> > about this case :-)
> >
> > If the FE trait requires unions to be complete, then the library trait
> > would need to special-case unions to not use the FE trait, so that it
> > can still give the same answer (i.e. false) for incomplete union types.
> > I don't think changing this one has any advantage.
> >
> >>
> >>> +    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:
> >>
> >> And I wonder a bit why std::is_final cares about the completeness of
> >> unions, which can't be (or have) base classes, but I guess you can still
> >> ask about whether the keyword was present, even though it has no effect.
> >
> > Yes, as currently specified, it tells you whether the specifier was
> > present, nothing more.
> >
> > Thanks for the review, here's an updated patch that moves __is_aggregate
> > to kind 4. I didn't change __is_empty or __is_final though, did you want
> > those changed?
> >
> > Tested x86_64-linux.
>
> That's fine, the patch is OK with the typo fix below:

Oops, I'll fix that - thanks.


>
> > -- >8 --
> >
> > The check_trait_type function is used for a number of different type
> > traits that have different requirements on their arguments. For example,
> > __is_constructible allows arrays of unknown bound even if the array
> > element is an incomplete type, but __is_aggregate does not, it always
> > requires the array element type to be complete. Other traits have
> > different requirements again, e.g. __is_empty allows incomplete unions,
> > and arrays (of known or unknown bound) of incomplete types.
> >
> > This alters the check_trait_type function to take an additional KIND
> > parameter which indicates which set of type trait requirements to check.
> >
> > As noted in a comment, the requirements for __is_aggregate deviate from
> > the ones for std::is_aggregate in the standard. It's not necessary for
> > the elements of an array to be complete types, because arrays are always
> > aggregates.
> >
> > The type_has_virtual_destructor change is needed to avoid an ICE.
> > Previously it could never be called for incomplete union types as they
> > were (incorrectly) rejected by check_trait_type.
> >
> > This change causes some additional diagnostics in some libstdc++ tests,
> > where the front end was not previously complaining about invalid types
> > that the library assertions diagnosed. We should consider removing the
> > library assertions from traits where the front end implements the
> > correct checks now.
> >
> >       PR c++/106838
> >
> > gcc/cp/ChangeLog:
> >
> >       * class.cc (type_has_virtual_destructor): Return false for
> >       union types.
> >       * semantics.cc (check_trait_type): Add KIND parameter to support
> >       different sets of requirements.
> >       (finish_trait_expr): Pass KIND argument for relevant traits.
> >
> > gcc/ChangeLog:
> >
> >       * doc/extend.texi (Type Traits): Fix requirements. Document
> >       __is_aggregate and __is_final.
> >
> > gcc/testsuite/ChangeLog:
> >
> >       * g++.dg/ext/array4.C: Fix invalid use of __is_constructible.
> >       * g++.dg/ext/unary_trait_incomplete.C: Fix tests for traits with
> >       different requirements.
> >
> > libstdc++-v3/ChangeLog:
> >
> >       * testsuite/20_util/is_complete_or_unbounded/memoization_neg.cc:
> >       Prune additional errors from front-end.
> >       * testsuite/20_util/is_move_constructible/incomplete_neg.cc:
> >       Likewise.
> >       * testsuite/20_util/is_nothrow_swappable/incomplete_neg.cc:
> >       Likewise.
> >       * testsuite/20_util/is_nothrow_swappable_with/incomplete_neg.cc:
> >       Likewise.
> >       * testsuite/20_util/is_swappable_with/incomplete_neg.cc:
> >       Likewise.
> > ---
> >   gcc/cp/class.cc                               |   2 +-
> >   gcc/cp/semantics.cc                           |  58 +++++++--
> >   gcc/doc/extend.texi                           |  30 +++--
> >   gcc/testsuite/g++.dg/ext/array4.C             |   3 +-
> >   .../g++.dg/ext/unary_trait_incomplete.C       | 116 ++++++++++++++----
> >   .../memoization_neg.cc                        |   2 +
> >   .../is_move_constructible/incomplete_neg.cc   |   1 +
> >   .../is_nothrow_swappable/incomplete_neg.cc    |   1 +
> >   .../incomplete_neg.cc                         |   1 +
> >   .../is_swappable_with/incomplete_neg.cc       |   1 +
> >   10 files changed, 164 insertions(+), 51 deletions(-)
> >
> > 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..aa3bfc457c1 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,39 @@ 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:
> > -    case CPTK_IS_AGGREGATE:
> > -    case CPTK_IS_EMPTY:
> > -    case CPTK_IS_FINAL:
> > +      if (!check_trait_type (type1))
> > +     return error_mark_node;
> > +      break;
> > +
> >       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;
> > +
> > +    /* N.B. std::is_aggregate is kind=2 but we don't need a complete element
> > +       type to know whether an array is an aggregate, so use kind=2 here.  */
>
> *kind=4
>
> > +    case CPTK_IS_AGGREGATE:
> > +    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..be3ea890c47 100644
> > --- a/gcc/doc/extend.texi
> > +++ b/gcc/doc/extend.texi
> > @@ -25174,14 +25174,17 @@ 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: If @code{type} is a class type, it shall be a complete type.
> >
> >   @item __is_base_of (base_type, derived_type)
> >   If @code{base_type} is a base class of @code{derived_type}
> > @@ -25206,13 +25209,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 +25235,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..1dfa449a8d2 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,105 @@ 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 agg1 = __is_aggregate(I); // { dg-error "incomplete type" }
> > +bool agg2 = __is_aggregate(U); // { dg-error "incomplete type" }
> > +bool agg3 = __is_aggregate(C[]);
> > +bool agg4 = __is_aggregate(I[]);
> > +bool agg5 = __is_aggregate(U[]);
> > +bool agg6 = __is_aggregate(C[1]);
> > +bool agg7 = __is_aggregate(I[1]);
> > +bool agg8 = __is_aggregate(U[1]);
> > +bool agg9 = __is_aggregate(void);
> > +bool agg10 = __is_aggregate(const void);
> > +
> > +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>
> >
>
  

Patch

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..aa3bfc457c1 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,39 @@  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:
-    case CPTK_IS_AGGREGATE:
-    case CPTK_IS_EMPTY:
-    case CPTK_IS_FINAL:
+      if (!check_trait_type (type1))
+	return error_mark_node;
+      break;
+
     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;
+
+    /* N.B. std::is_aggregate is kind=2 but we don't need a complete element
+       type to know whether an array is an aggregate, so use kind=2 here.  */
+    case CPTK_IS_AGGREGATE:
+    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..be3ea890c47 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -25174,14 +25174,17 @@  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: If @code{type} is a class type, it shall be a complete type.
 
 @item __is_base_of (base_type, derived_type)
 If @code{base_type} is a base class of @code{derived_type}
@@ -25206,13 +25209,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 +25235,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..1dfa449a8d2 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,105 @@  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 agg1 = __is_aggregate(I); // { dg-error "incomplete type" }
+bool agg2 = __is_aggregate(U); // { dg-error "incomplete type" }
+bool agg3 = __is_aggregate(C[]);
+bool agg4 = __is_aggregate(I[]);
+bool agg5 = __is_aggregate(U[]);
+bool agg6 = __is_aggregate(C[1]);
+bool agg7 = __is_aggregate(I[1]);
+bool agg8 = __is_aggregate(U[1]);
+bool agg9 = __is_aggregate(void);
+bool agg10 = __is_aggregate(const void);
+
+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>