c, c++: Accept __builtin_classify_type (typename)

Message ID ZId4nTL6nFuc02rH@tucnak
State New
Headers
Series c, c++: Accept __builtin_classify_type (typename) |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 fail Patch failed to apply
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 fail Patch failed to apply
linaro-tcwg-bot/tcwg_gcc_build--master-arm fail Patch failed to apply
linaro-tcwg-bot/tcwg_gcc_check--master-arm fail Patch failed to apply

Commit Message

Jakub Jelinek June 12, 2023, 7:57 p.m. UTC
  Hi!

As mentioned in my stdckdint.h mail, __builtin_classify_type has
a problem that argument promotion (the argument is passed to ...
prototyped builtin function) means that certain type classes will
simply never appear.
I think it is too late to change how it behaves, lots of code in the
wild might rely on the current behavior.

So, the following patch adds option to use a typename rather than
expression as the operand to the builtin, making it behave similarly
to sizeof, typeof or say the clang _Generic extension where the
first argument can be there not just expression, but also typename.

I think we have other prior art here, e.g. __builtin_va_arg also
expects typename.

I've added this to both C and C++, because it would be weird if it
supported it only in C and not in C++.

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

2023-06-12  Jakub Jelinek  <jakub@redhat.com>

gcc/
	* builtins.h (type_to_class): Declare.
	* builtins.cc (type_to_class): No longer static.  Return
	int rather than enum.
	* doc/extend.texi (__builtin_classify_type): Document.
gcc/c/
	* c-parser.cc (c_parser_postfix_expression_after_primary): Parse
	__builtin_classify_type call with typename as argument.
gcc/cp/
	* parser.cc (cp_parser_postfix_expression): Parse
	__builtin_classify_type call with typename as argument.
	* pt.cc (tsubst_copy_and_build): Handle __builtin_classify_type
	with dependent typename as argument.
gcc/testsuite/
	* c-c++-common/builtin-classify-type-1.c: New test.
	* g++.dg/ext/builtin-classify-type-1.C: New test.
	* g++.dg/ext/builtin-classify-type-2.C: New test.
	* gcc.dg/builtin-classify-type-1.c: New test.


	Jakub
  

Comments

Jason Merrill June 13, 2023, 8:48 a.m. UTC | #1
On 6/12/23 15:57, Jakub Jelinek wrote:
> Hi!
> 
> As mentioned in my stdckdint.h mail, __builtin_classify_type has
> a problem that argument promotion (the argument is passed to ...
> prototyped builtin function) means that certain type classes will
> simply never appear.
> I think it is too late to change how it behaves, lots of code in the
> wild might rely on the current behavior.
> 
> So, the following patch adds option to use a typename rather than
> expression as the operand to the builtin, making it behave similarly
> to sizeof, typeof or say the clang _Generic extension where the
> first argument can be there not just expression, but also typename.
> 
> I think we have other prior art here, e.g. __builtin_va_arg also
> expects typename.
> 
> I've added this to both C and C++, because it would be weird if it
> supported it only in C and not in C++.
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
> 
> 2023-06-12  Jakub Jelinek  <jakub@redhat.com>
> 
> gcc/
> 	* builtins.h (type_to_class): Declare.
> 	* builtins.cc (type_to_class): No longer static.  Return
> 	int rather than enum.
> 	* doc/extend.texi (__builtin_classify_type): Document.
> gcc/c/
> 	* c-parser.cc (c_parser_postfix_expression_after_primary): Parse
> 	__builtin_classify_type call with typename as argument.
> gcc/cp/
> 	* parser.cc (cp_parser_postfix_expression): Parse
> 	__builtin_classify_type call with typename as argument.
> 	* pt.cc (tsubst_copy_and_build): Handle __builtin_classify_type
> 	with dependent typename as argument.
> gcc/testsuite/
> 	* c-c++-common/builtin-classify-type-1.c: New test.
> 	* g++.dg/ext/builtin-classify-type-1.C: New test.
> 	* g++.dg/ext/builtin-classify-type-2.C: New test.
> 	* gcc.dg/builtin-classify-type-1.c: New test.
> 
> --- gcc/builtins.h.jj	2023-01-03 00:20:34.856089856 +0100
> +++ gcc/builtins.h	2023-06-12 09:35:20.841902572 +0200
> @@ -156,5 +156,6 @@ extern internal_fn associated_internal_f
>   extern internal_fn replacement_internal_fn (gcall *);
>   
>   extern bool builtin_with_linkage_p (tree);
> +extern int type_to_class (tree);
>   
>   #endif /* GCC_BUILTINS_H */
> --- gcc/builtins.cc.jj	2023-05-20 15:31:09.066663352 +0200
> +++ gcc/builtins.cc	2023-06-12 09:35:31.709751296 +0200
> @@ -113,7 +113,6 @@ static rtx expand_builtin_apply_args (vo
>   static rtx expand_builtin_apply_args_1 (void);
>   static rtx expand_builtin_apply (rtx, rtx, rtx);
>   static void expand_builtin_return (rtx);
> -static enum type_class type_to_class (tree);
>   static rtx expand_builtin_classify_type (tree);
>   static rtx expand_builtin_mathfn_3 (tree, rtx, rtx);
>   static rtx expand_builtin_mathfn_ternary (tree, rtx, rtx);
> @@ -1852,7 +1851,7 @@ expand_builtin_return (rtx result)
>   
>   /* Used by expand_builtin_classify_type and fold_builtin_classify_type.  */
>   
> -static enum type_class
> +int
>   type_to_class (tree type)
>   {
>     switch (TREE_CODE (type))
> --- gcc/doc/extend.texi.jj	2023-06-10 19:58:26.197478291 +0200
> +++ gcc/doc/extend.texi	2023-06-12 18:06:24.629413024 +0200
> @@ -14354,6 +14354,30 @@ need not be a constant.  @xref{Object Si
>   description of the function.
>   @enddefbuiltin
>   
> +@defbuiltin{int __builtin_classify_type (@var{arg})}
> +@defbuiltinx{int __builtin_classify_type (@var{type})}
> +The @code{__builtin_classify_type} returns a small integer with a category
> +of @var{arg} argument's type, like void type, integer type, enumeral type,
> +boolean type, pointer type, reference type, offset type, real type, complex
> +type, function type, method type, record type, union type, array type,
> +string type, etc.  When the argument is an expression, for
> +backwards compatibility reason the argument is promoted like arguments
> +passed to @code{...} in varargs function, so some classes are never returned
> +in certain languages.  Alternatively, the argument of the builtin-in
> +function can be a typename, such as the @code{typeof} specifier.
> +
> +@smallexample
> +int a[2];
> +__builtin_classify_type (a) == __builtin_classify_type (int[5]);
> +__builtin_classify_type (a) == __builtin_classify_type (void*);
> +__builtin_classify_type (typeof (a)) == __builtin_classify_type (int[5]);
> +@end smallexample
> +
> +The first comparison will never be true, as @var{a} is implicitly converted
> +to pointer.  The last two comparisons will be true as they classify
> +pointers in the second case and arrays in the last case.
> +@enddefbuiltin
> +
>   @defbuiltin{double __builtin_huge_val (void)}
>   Returns a positive infinity, if supported by the floating-point format,
>   else @code{DBL_MAX}.  This function is suitable for implementing the
> --- gcc/c/c-parser.cc.jj	2023-06-10 19:22:15.577205685 +0200
> +++ gcc/c/c-parser.cc	2023-06-12 17:32:31.007413019 +0200
> @@ -11213,6 +11213,32 @@ c_parser_postfix_expression_after_primar
>   	    literal_zero_mask = 0;
>   	    if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
>   	      exprlist = NULL;
> +	    else if (TREE_CODE (expr.value) == FUNCTION_DECL
> +		     && fndecl_built_in_p (expr.value, BUILT_IN_CLASSIFY_TYPE)
> +		     && c_parser_next_tokens_start_typename (parser,
> +							     cla_prefer_id))
> +	      {
> +		/* __builtin_classify_type (type)  */
> +		c_inhibit_evaluation_warnings++;
> +		in_typeof++;
> +		struct c_type_name *type = c_parser_type_name (parser);
> +		c_inhibit_evaluation_warnings--;
> +		in_typeof--;
> +		struct c_typespec ret;
> +		ret.expr = NULL_TREE;
> +		ret.spec = error_mark_node;
> +		ret.expr_const_operands = false;
> +		if (type != NULL)
> +		  {
> +		    ret.spec = groktypename (type, &ret.expr,
> +					     &ret.expr_const_operands);
> +		    pop_maybe_used (c_type_variably_modified_p (ret.spec));
> +		  }
> +		parens.skip_until_found_close (parser);
> +		expr.value = build_int_cst (integer_type_node,
> +					    type_to_class (ret.spec));
> +		break;
> +	      }
>   	    else
>   	      exprlist = c_parser_expr_list (parser, true, false, &origtypes,
>   					     sizeof_arg_loc, sizeof_arg,
> --- gcc/cp/parser.cc.jj	2023-06-06 20:02:35.631211230 +0200
> +++ gcc/cp/parser.cc	2023-06-12 16:19:04.892202240 +0200
> @@ -48,6 +48,7 @@ along with GCC; see the file COPYING3.
>   #include "c-family/known-headers.h"
>   #include "contracts.h"
>   #include "bitmap.h"
> +#include "builtins.h"
>   
>   
>   /* The lexer.  */
> @@ -7850,6 +7851,53 @@ cp_parser_postfix_expression (cp_parser
>   		  = parser->non_integral_constant_expression_p;
>   		parser->integral_constant_expression_p = false;
>   	      }
> +	    else if (TREE_CODE (stripped_expression) == FUNCTION_DECL
> +		     && fndecl_built_in_p (stripped_expression,
> +					   BUILT_IN_CLASSIFY_TYPE))
> +	      {
> +		/* __builtin_classify_type (type)  */
> +		auto cl1 = make_temp_override
> +			     (parser->type_definition_forbidden_message,
> +			      G_("types may not be defined in "
> +				 "%<__builtin_classify_type%> calls"));
> +		auto cl2 = make_temp_override
> +			     (parser->type_definition_forbidden_message_arg,
> +			      NULL);
> +		auto cl3 = make_temp_override (parser->in_type_id_in_expr_p,
> +					       true);
> +		++cp_unevaluated_operand;
> +		++c_inhibit_evaluation_warnings;

These could be a cp_evaluated RAII variable instead, so you don't need 
to decrement in two different places.  The C++ bits are OK with that change.

> +		tentative_firewall firewall (parser);
> +		cp_parser_parse_tentatively (parser);
> +		matching_parens parens;
> +		parens.consume_open (parser);
> +		tree type = cp_parser_type_id (parser);
> +		parens.require_close (parser);
> +		if (cp_parser_parse_definitely (parser))
> +		  {
> +		    --cp_unevaluated_operand;
> +		    --c_inhibit_evaluation_warnings;
> +		    if (dependent_type_p (type))
> +		      {
> +			postfix_expression = build_vl_exp (CALL_EXPR, 4);
> +			CALL_EXPR_FN (postfix_expression)
> +			  = stripped_expression;
> +			CALL_EXPR_STATIC_CHAIN (postfix_expression) = type;
> +			CALL_EXPR_ARG (postfix_expression, 0)
> +			  = build_min (SIZEOF_EXPR, size_type_node, type);
> +			TREE_TYPE (postfix_expression) = integer_type_node;
> +		      }
> +		    else
> +		      {
> +			postfix_expression
> +			  = build_int_cst (integer_type_node,
> +					   type_to_class (type));
> +		      }
> +		    break;
> +		  }
> +		--cp_unevaluated_operand;
> +		--c_inhibit_evaluation_warnings;
> +	      }
>   	    args = (cp_parser_parenthesized_expression_list
>   		    (parser, non_attr,
>   		     /*cast_p=*/false, /*allow_expansion_p=*/true,
> --- gcc/cp/pt.cc.jj	2023-06-06 20:02:35.000000000 +0200
> +++ gcc/cp/pt.cc	2023-06-12 16:19:06.495180108 +0200
> @@ -46,6 +46,7 @@ along with GCC; see the file COPYING3.
>   #include "gcc-rich-location.h"
>   #include "selftest.h"
>   #include "target.h"
> +#include "builtins.h"
>   
>   /* The type of functions taking a tree, and some additional data, and
>      returning an int.  */
> @@ -20963,6 +20964,25 @@ tsubst_copy_and_build (tree t,
>   					    /*done=*/false,
>   					    /*address_p=*/false);
>   	  }
> +	else if (CALL_EXPR_STATIC_CHAIN (t)
> +		 && TREE_CODE (function) == FUNCTION_DECL
> +		 && fndecl_built_in_p (function, BUILT_IN_CLASSIFY_TYPE))
> +	  {
> +	    tree type = tsubst (CALL_EXPR_STATIC_CHAIN (t), args, complain,
> +				in_decl);
> +	    if (dependent_type_p (type))
> +	      {
> +		ret = build_vl_exp (CALL_EXPR, 4);
> +		CALL_EXPR_FN (ret) = function;
> +		CALL_EXPR_STATIC_CHAIN (ret) = type;
> +		CALL_EXPR_ARG (ret, 0)
> +		  = build_min (SIZEOF_EXPR, size_type_node, type);
> +		TREE_TYPE (ret) = integer_type_node;
> +	      }
> +	    else
> +	      ret = build_int_cst (integer_type_node, type_to_class (type));
> +	    RETURN (ret);
> +	  }
>   	else if (koenig_p
>   		 && (identifier_p (function)
>   		     || (TREE_CODE (function) == TEMPLATE_ID_EXPR
> --- gcc/testsuite/c-c++-common/builtin-classify-type-1.c.jj	2023-06-12 14:27:19.087986210 +0200
> +++ gcc/testsuite/c-c++-common/builtin-classify-type-1.c	2023-06-12 16:23:07.029859079 +0200
> @@ -0,0 +1,105 @@
> +/* { dg-do run { target { c || c++11 } } } */
> +
> +#if !defined(__cplusplus) && __STDC_VERSION__ <= 201710L
> +#define static_assert _Static_assert
> +#define bool _Bool
> +#define false ((_Bool) 0)
> +#endif
> +#ifdef __cplusplus
> +extern "C" void abort ();
> +#else
> +extern void abort (void);
> +#endif
> +
> +int
> +main ()
> +{
> +  enum E { E1 } e = E1;
> +  struct S { int s; } s = { 0 };
> +  union U { int u; } u = { 0 };
> +  int a[2] = { 0, 0 };
> +  bool b = false;
> +  const char *p = (const char *) 0;
> +  float f = 0.0;
> +  _Complex double c = 0.0;
> +#ifdef __cplusplus
> +  struct T { void foo (); };
> +  int &r = a[0];
> +  int S::*q = &S::s;
> +#endif
> +  static_assert (__builtin_classify_type (void) == 0, "");
> +  static_assert (__builtin_classify_type (int) == 1, "");
> +  static_assert (__builtin_classify_type (enum E) == 3, "");
> +  static_assert (__builtin_classify_type (bool) == 4, "");
> +  static_assert (__builtin_classify_type (const char *) == 5, "");
> +#ifdef __cplusplus
> +  static_assert (__builtin_classify_type (int &) == 6, "");
> +  static_assert (__builtin_classify_type (int &&) == 6, "");
> +  static_assert (__builtin_classify_type (int S::*) == 7, "");
> +#endif
> +  static_assert (__builtin_classify_type (float) == 8, "");
> +  static_assert (__builtin_classify_type (_Complex double) == 9, "");
> +  static_assert (__builtin_classify_type (int (int, int)) == 10, "");
> +  static_assert (__builtin_classify_type (struct S) == 12, "");
> +  static_assert (__builtin_classify_type (union U) == 13, "");
> +  static_assert (__builtin_classify_type (int [2]) == 14, "");
> +  static_assert (__builtin_classify_type (__typeof__ (a[0])) == 1, "");
> +  static_assert (__builtin_classify_type (__typeof__ (e)) == 3, "");
> +  static_assert (__builtin_classify_type (__typeof__ (b)) == 4, "");
> +  static_assert (__builtin_classify_type (__typeof__ (p)) == 5, "");
> +#ifdef __cplusplus
> +  static_assert (__builtin_classify_type (decltype (r)) == 6, "");
> +  static_assert (__builtin_classify_type (__typeof__ (q)) == 7, "");
> +#endif
> +  static_assert (__builtin_classify_type (__typeof__ (f)) == 8, "");
> +  static_assert (__builtin_classify_type (__typeof__ (c)) == 9, "");
> +  static_assert (__builtin_classify_type (__typeof__ (main)) == 10, "");
> +  static_assert (__builtin_classify_type (__typeof__ (s)) == 12, "");
> +  static_assert (__builtin_classify_type (__typeof__ (u)) == 13, "");
> +  static_assert (__builtin_classify_type (__typeof__ (a)) == 14, "");
> +#ifndef __cplusplus
> +  static_assert (__builtin_classify_type (a[0]) == 1, "");
> +  static_assert (__builtin_classify_type (e) == 1, "");
> +  static_assert (__builtin_classify_type (b) == 1, "");
> +  static_assert (__builtin_classify_type (p) == 5, "");
> +  static_assert (__builtin_classify_type (f) == 8, "");
> +  static_assert (__builtin_classify_type (c) == 9, "");
> +  static_assert (__builtin_classify_type (main) == 5, "");
> +  static_assert (__builtin_classify_type (s) == 12, "");
> +  static_assert (__builtin_classify_type (u) == 13, "");
> +  static_assert (__builtin_classify_type (a) == 5, "");
> +#endif
> +  if (__builtin_classify_type (a[0]) != 1)
> +    abort ();
> +#ifdef __cplusplus
> +  if (__builtin_classify_type (e) != 3)
> +    abort ();
> +  if (__builtin_classify_type (b) != 4)
> +    abort ();
> +#else
> +  if (__builtin_classify_type (e) != 1)
> +    abort ();
> +  if (__builtin_classify_type (b) != 1)
> +    abort ();
> +#endif
> +  if (__builtin_classify_type (p) != 5)
> +    abort ();
> +#ifdef __cplusplus
> +  if (__builtin_classify_type (r) != 1)
> +    abort ();
> +  if (__builtin_classify_type (q) != 7)
> +    abort ();
> +#endif
> +  if (__builtin_classify_type (f) != 8)
> +    abort ();
> +  if (__builtin_classify_type (c) != 9)
> +    abort ();
> +  if (__builtin_classify_type (main) != 5)
> +    abort ();
> +  if (__builtin_classify_type (s) != 12)
> +    abort ();
> +  if (__builtin_classify_type (u) != 13)
> +    abort ();
> +  if (__builtin_classify_type (a) != 5)
> +    abort ();
> +}
> --- gcc/testsuite/g++.dg/ext/builtin-classify-type-1.C.jj	2023-06-12 15:03:37.488813774 +0200
> +++ gcc/testsuite/g++.dg/ext/builtin-classify-type-1.C	2023-06-12 16:24:17.787882132 +0200
> @@ -0,0 +1,149 @@
> +// { dg-do run { target c++11 } }
> +
> +extern "C" void abort ();
> +
> +template <int N>
> +void
> +foo ()
> +{
> +  enum E { E1 } e = E1;
> +  struct S { int s; } s = { 0 };
> +  union U { int u; } u = { 0 };
> +  int a[2] = { 0, 0 };
> +  bool b = false;
> +  const char *p = (const char *) 0;
> +  float f = 0.0;
> +  _Complex double c = 0.0;
> +  struct T { void foo (); };
> +  int &r = a[0];
> +  int S::*q = &S::s;
> +  static_assert (__builtin_classify_type (void) == 0, "");
> +  static_assert (__builtin_classify_type (int) == 1, "");
> +  static_assert (__builtin_classify_type (enum E) == 3, "");
> +  static_assert (__builtin_classify_type (bool) == 4, "");
> +  static_assert (__builtin_classify_type (const char *) == 5, "");
> +  static_assert (__builtin_classify_type (int &) == 6, "");
> +  static_assert (__builtin_classify_type (int &&) == 6, "");
> +  static_assert (__builtin_classify_type (int S::*) == 7, "");
> +  static_assert (__builtin_classify_type (float) == 8, "");
> +  static_assert (__builtin_classify_type (_Complex double) == 9, "");
> +  static_assert (__builtin_classify_type (int (int, int)) == 10, "");
> +  static_assert (__builtin_classify_type (struct S) == 12, "");
> +  static_assert (__builtin_classify_type (union U) == 13, "");
> +  static_assert (__builtin_classify_type (int [2]) == 14, "");
> +  static_assert (__builtin_classify_type (__typeof__ (a[0])) == 1, "");
> +  static_assert (__builtin_classify_type (__typeof__ (e)) == 3, "");
> +  static_assert (__builtin_classify_type (__typeof__ (b)) == 4, "");
> +  static_assert (__builtin_classify_type (__typeof__ (p)) == 5, "");
> +  static_assert (__builtin_classify_type (decltype (r)) == 6, "");
> +  static_assert (__builtin_classify_type (__typeof__ (q)) == 7, "");
> +  static_assert (__builtin_classify_type (__typeof__ (f)) == 8, "");
> +  static_assert (__builtin_classify_type (__typeof__ (c)) == 9, "");
> +  static_assert (__builtin_classify_type (__typeof__ (abort)) == 10, "");
> +  static_assert (__builtin_classify_type (__typeof__ (s)) == 12, "");
> +  static_assert (__builtin_classify_type (__typeof__ (u)) == 13, "");
> +  static_assert (__builtin_classify_type (__typeof__ (a)) == 14, "");
> +  if (__builtin_classify_type (a[0]) != 1)
> +    abort ();
> +  if (__builtin_classify_type (e) != 3)
> +    abort ();
> +  if (__builtin_classify_type (b) != 4)
> +    abort ();
> +  if (__builtin_classify_type (p) != 5)
> +    abort ();
> +  if (__builtin_classify_type (r) != 1)
> +    abort ();
> +  if (__builtin_classify_type (q) != 7)
> +    abort ();
> +  if (__builtin_classify_type (f) != 8)
> +    abort ();
> +  if (__builtin_classify_type (c) != 9)
> +    abort ();
> +  if (__builtin_classify_type (abort) != 5)
> +    abort ();
> +  if (__builtin_classify_type (s) != 12)
> +    abort ();
> +  if (__builtin_classify_type (u) != 13)
> +    abort ();
> +  if (__builtin_classify_type (a) != 5)
> +    abort ();
> +}
> +
> +template <typename V, typename I, typename E, typename B, typename P,
> +	  typename R1, typename R2, typename PM, typename F,
> +	  typename C, typename FN, typename S, typename U, typename A>
> +void
> +bar ()
> +{
> +  E e = (E) 0;
> +  S s = { 0 };
> +  U u = { 0 };
> +  A a = { 0, 0 };
> +  B b = false;
> +  P p = (P) 0;
> +  F f = 0.0;
> +  C c = 0.0;
> +  R1 r = a[0];
> +  PM q = &S::s;
> +  static_assert (__builtin_classify_type (V) == 0, "");
> +  static_assert (__builtin_classify_type (I) == 1, "");
> +  static_assert (__builtin_classify_type (E) == 3, "");
> +  static_assert (__builtin_classify_type (B) == 4, "");
> +  static_assert (__builtin_classify_type (P) == 5, "");
> +  static_assert (__builtin_classify_type (R1) == 6, "");
> +  static_assert (__builtin_classify_type (R2) == 6, "");
> +  static_assert (__builtin_classify_type (PM) == 7, "");
> +  static_assert (__builtin_classify_type (F) == 8, "");
> +  static_assert (__builtin_classify_type (C) == 9, "");
> +  static_assert (__builtin_classify_type (FN) == 10, "");
> +  static_assert (__builtin_classify_type (S) == 12, "");
> +  static_assert (__builtin_classify_type (U) == 13, "");
> +  static_assert (__builtin_classify_type (A) == 14, "");
> +  static_assert (__builtin_classify_type (__typeof__ (a[0])) == 1, "");
> +  static_assert (__builtin_classify_type (__typeof__ (e)) == 3, "");
> +  static_assert (__builtin_classify_type (__typeof__ (b)) == 4, "");
> +  static_assert (__builtin_classify_type (__typeof__ (p)) == 5, "");
> +  static_assert (__builtin_classify_type (decltype (r)) == 6, "");
> +  static_assert (__builtin_classify_type (__typeof__ (q)) == 7, "");
> +  static_assert (__builtin_classify_type (__typeof__ (f)) == 8, "");
> +  static_assert (__builtin_classify_type (__typeof__ (c)) == 9, "");
> +  static_assert (__builtin_classify_type (__typeof__ (abort)) == 10, "");
> +  static_assert (__builtin_classify_type (__typeof__ (s)) == 12, "");
> +  static_assert (__builtin_classify_type (__typeof__ (u)) == 13, "");
> +  static_assert (__builtin_classify_type (__typeof__ (a)) == 14, "");
> +  if (__builtin_classify_type (a[0]) != 1)
> +    abort ();
> +  if (__builtin_classify_type (e) != 3)
> +    abort ();
> +  if (__builtin_classify_type (b) != 4)
> +    abort ();
> +  if (__builtin_classify_type (p) != 5)
> +    abort ();
> +  if (__builtin_classify_type (r) != 1)
> +    abort ();
> +  if (__builtin_classify_type (q) != 7)
> +    abort ();
> +  if (__builtin_classify_type (f) != 8)
> +    abort ();
> +  if (__builtin_classify_type (c) != 9)
> +    abort ();
> +  if (__builtin_classify_type (abort) != 5)
> +    abort ();
> +  if (__builtin_classify_type (s) != 12)
> +    abort ();
> +  if (__builtin_classify_type (u) != 13)
> +    abort ();
> +  if (__builtin_classify_type (a) != 5)
> +    abort ();
> +}
> +
> +int
> +main ()
> +{
> +  enum E { E1 };
> +  struct S { int s; };
> +  union U { int u; };
> +  foo <0> ();
> +  bar <void, int, E, bool, const char *, int &, int &&, int S::*,
> +       float, _Complex double, int (int, int), S, U, int [2]> ();
> +}
> --- gcc/testsuite/g++.dg/ext/builtin-classify-type-2.C.jj	2023-06-12 16:28:17.120577700 +0200
> +++ gcc/testsuite/g++.dg/ext/builtin-classify-type-2.C	2023-06-12 16:29:57.860186807 +0200
> @@ -0,0 +1,11 @@
> +// { dg-do compile { target c++11 } }
> +// { dg-options "" }
> +
> +void
> +foo (int n)
> +{
> +  __builtin_classify_type (enum E { E1, E2 });	// { dg-error "types may not be defined in '__builtin_classify_type' calls" }
> +  __builtin_classify_type (struct S { int s; });// { dg-error "types may not be defined in '__builtin_classify_type' calls" }
> +  __builtin_classify_type (union U { int u; });	// { dg-error "types may not be defined in '__builtin_classify_type' calls" }
> +  __builtin_classify_type (int [2 * n + 36]);
> +}
> --- gcc/testsuite/gcc.dg/builtin-classify-type-1.c.jj	2023-06-12 17:28:47.118495177 +0200
> +++ gcc/testsuite/gcc.dg/builtin-classify-type-1.c	2023-06-12 17:43:46.420114514 +0200
> @@ -0,0 +1,11 @@
> +/* { dg-do compile } */
> +/* { dg-options "" } */
> +
> +void
> +foo (int n)
> +{
> +  _Static_assert (__builtin_classify_type (enum E { E1, E2 }) == 3, "");
> +  _Static_assert (__builtin_classify_type (struct S { int s; }) == 12, "");
> +  _Static_assert (__builtin_classify_type (union U { int u; }) == 13, "");
> +  _Static_assert (__builtin_classify_type (int [2 * n + 36]) == 14, "");
> +}
> 
> 	Jakub
>
  

Patch

--- gcc/builtins.h.jj	2023-01-03 00:20:34.856089856 +0100
+++ gcc/builtins.h	2023-06-12 09:35:20.841902572 +0200
@@ -156,5 +156,6 @@  extern internal_fn associated_internal_f
 extern internal_fn replacement_internal_fn (gcall *);
 
 extern bool builtin_with_linkage_p (tree);
+extern int type_to_class (tree);
 
 #endif /* GCC_BUILTINS_H */
--- gcc/builtins.cc.jj	2023-05-20 15:31:09.066663352 +0200
+++ gcc/builtins.cc	2023-06-12 09:35:31.709751296 +0200
@@ -113,7 +113,6 @@  static rtx expand_builtin_apply_args (vo
 static rtx expand_builtin_apply_args_1 (void);
 static rtx expand_builtin_apply (rtx, rtx, rtx);
 static void expand_builtin_return (rtx);
-static enum type_class type_to_class (tree);
 static rtx expand_builtin_classify_type (tree);
 static rtx expand_builtin_mathfn_3 (tree, rtx, rtx);
 static rtx expand_builtin_mathfn_ternary (tree, rtx, rtx);
@@ -1852,7 +1851,7 @@  expand_builtin_return (rtx result)
 
 /* Used by expand_builtin_classify_type and fold_builtin_classify_type.  */
 
-static enum type_class
+int
 type_to_class (tree type)
 {
   switch (TREE_CODE (type))
--- gcc/doc/extend.texi.jj	2023-06-10 19:58:26.197478291 +0200
+++ gcc/doc/extend.texi	2023-06-12 18:06:24.629413024 +0200
@@ -14354,6 +14354,30 @@  need not be a constant.  @xref{Object Si
 description of the function.
 @enddefbuiltin
 
+@defbuiltin{int __builtin_classify_type (@var{arg})}
+@defbuiltinx{int __builtin_classify_type (@var{type})}
+The @code{__builtin_classify_type} returns a small integer with a category
+of @var{arg} argument's type, like void type, integer type, enumeral type,
+boolean type, pointer type, reference type, offset type, real type, complex
+type, function type, method type, record type, union type, array type,
+string type, etc.  When the argument is an expression, for
+backwards compatibility reason the argument is promoted like arguments
+passed to @code{...} in varargs function, so some classes are never returned
+in certain languages.  Alternatively, the argument of the builtin-in
+function can be a typename, such as the @code{typeof} specifier.
+
+@smallexample
+int a[2];
+__builtin_classify_type (a) == __builtin_classify_type (int[5]);
+__builtin_classify_type (a) == __builtin_classify_type (void*);
+__builtin_classify_type (typeof (a)) == __builtin_classify_type (int[5]);
+@end smallexample
+
+The first comparison will never be true, as @var{a} is implicitly converted
+to pointer.  The last two comparisons will be true as they classify
+pointers in the second case and arrays in the last case.
+@enddefbuiltin
+
 @defbuiltin{double __builtin_huge_val (void)}
 Returns a positive infinity, if supported by the floating-point format,
 else @code{DBL_MAX}.  This function is suitable for implementing the
--- gcc/c/c-parser.cc.jj	2023-06-10 19:22:15.577205685 +0200
+++ gcc/c/c-parser.cc	2023-06-12 17:32:31.007413019 +0200
@@ -11213,6 +11213,32 @@  c_parser_postfix_expression_after_primar
 	    literal_zero_mask = 0;
 	    if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
 	      exprlist = NULL;
+	    else if (TREE_CODE (expr.value) == FUNCTION_DECL
+		     && fndecl_built_in_p (expr.value, BUILT_IN_CLASSIFY_TYPE)
+		     && c_parser_next_tokens_start_typename (parser,
+							     cla_prefer_id))
+	      {
+		/* __builtin_classify_type (type)  */
+		c_inhibit_evaluation_warnings++;
+		in_typeof++;
+		struct c_type_name *type = c_parser_type_name (parser);
+		c_inhibit_evaluation_warnings--;
+		in_typeof--;
+		struct c_typespec ret;
+		ret.expr = NULL_TREE;
+		ret.spec = error_mark_node;
+		ret.expr_const_operands = false;
+		if (type != NULL)
+		  {
+		    ret.spec = groktypename (type, &ret.expr,
+					     &ret.expr_const_operands);
+		    pop_maybe_used (c_type_variably_modified_p (ret.spec));
+		  }
+		parens.skip_until_found_close (parser);
+		expr.value = build_int_cst (integer_type_node,
+					    type_to_class (ret.spec));
+		break;
+	      }
 	    else
 	      exprlist = c_parser_expr_list (parser, true, false, &origtypes,
 					     sizeof_arg_loc, sizeof_arg,
--- gcc/cp/parser.cc.jj	2023-06-06 20:02:35.631211230 +0200
+++ gcc/cp/parser.cc	2023-06-12 16:19:04.892202240 +0200
@@ -48,6 +48,7 @@  along with GCC; see the file COPYING3.
 #include "c-family/known-headers.h"
 #include "contracts.h"
 #include "bitmap.h"
+#include "builtins.h"
 
 
 /* The lexer.  */
@@ -7850,6 +7851,53 @@  cp_parser_postfix_expression (cp_parser
 		  = parser->non_integral_constant_expression_p;
 		parser->integral_constant_expression_p = false;
 	      }
+	    else if (TREE_CODE (stripped_expression) == FUNCTION_DECL
+		     && fndecl_built_in_p (stripped_expression,
+					   BUILT_IN_CLASSIFY_TYPE))
+	      {
+		/* __builtin_classify_type (type)  */
+		auto cl1 = make_temp_override
+			     (parser->type_definition_forbidden_message,
+			      G_("types may not be defined in "
+				 "%<__builtin_classify_type%> calls"));
+		auto cl2 = make_temp_override
+			     (parser->type_definition_forbidden_message_arg,
+			      NULL);
+		auto cl3 = make_temp_override (parser->in_type_id_in_expr_p,
+					       true);
+		++cp_unevaluated_operand;
+		++c_inhibit_evaluation_warnings;
+		tentative_firewall firewall (parser);
+		cp_parser_parse_tentatively (parser);
+		matching_parens parens;
+		parens.consume_open (parser);
+		tree type = cp_parser_type_id (parser);
+		parens.require_close (parser);
+		if (cp_parser_parse_definitely (parser))
+		  {
+		    --cp_unevaluated_operand;
+		    --c_inhibit_evaluation_warnings;
+		    if (dependent_type_p (type))
+		      {
+			postfix_expression = build_vl_exp (CALL_EXPR, 4);
+			CALL_EXPR_FN (postfix_expression)
+			  = stripped_expression;
+			CALL_EXPR_STATIC_CHAIN (postfix_expression) = type;
+			CALL_EXPR_ARG (postfix_expression, 0)
+			  = build_min (SIZEOF_EXPR, size_type_node, type);
+			TREE_TYPE (postfix_expression) = integer_type_node;
+		      }
+		    else
+		      {
+			postfix_expression
+			  = build_int_cst (integer_type_node,
+					   type_to_class (type));
+		      }
+		    break;
+		  }
+		--cp_unevaluated_operand;
+		--c_inhibit_evaluation_warnings;
+	      }
 	    args = (cp_parser_parenthesized_expression_list
 		    (parser, non_attr,
 		     /*cast_p=*/false, /*allow_expansion_p=*/true,
--- gcc/cp/pt.cc.jj	2023-06-06 20:02:35.000000000 +0200
+++ gcc/cp/pt.cc	2023-06-12 16:19:06.495180108 +0200
@@ -46,6 +46,7 @@  along with GCC; see the file COPYING3.
 #include "gcc-rich-location.h"
 #include "selftest.h"
 #include "target.h"
+#include "builtins.h"
 
 /* The type of functions taking a tree, and some additional data, and
    returning an int.  */
@@ -20963,6 +20964,25 @@  tsubst_copy_and_build (tree t,
 					    /*done=*/false,
 					    /*address_p=*/false);
 	  }
+	else if (CALL_EXPR_STATIC_CHAIN (t)
+		 && TREE_CODE (function) == FUNCTION_DECL
+		 && fndecl_built_in_p (function, BUILT_IN_CLASSIFY_TYPE))
+	  {
+	    tree type = tsubst (CALL_EXPR_STATIC_CHAIN (t), args, complain,
+				in_decl);
+	    if (dependent_type_p (type))
+	      {
+		ret = build_vl_exp (CALL_EXPR, 4);
+		CALL_EXPR_FN (ret) = function;
+		CALL_EXPR_STATIC_CHAIN (ret) = type;
+		CALL_EXPR_ARG (ret, 0)
+		  = build_min (SIZEOF_EXPR, size_type_node, type);
+		TREE_TYPE (ret) = integer_type_node;
+	      }
+	    else
+	      ret = build_int_cst (integer_type_node, type_to_class (type));
+	    RETURN (ret);
+	  }
 	else if (koenig_p
 		 && (identifier_p (function)
 		     || (TREE_CODE (function) == TEMPLATE_ID_EXPR
--- gcc/testsuite/c-c++-common/builtin-classify-type-1.c.jj	2023-06-12 14:27:19.087986210 +0200
+++ gcc/testsuite/c-c++-common/builtin-classify-type-1.c	2023-06-12 16:23:07.029859079 +0200
@@ -0,0 +1,105 @@ 
+/* { dg-do run { target { c || c++11 } } } */
+
+#if !defined(__cplusplus) && __STDC_VERSION__ <= 201710L
+#define static_assert _Static_assert
+#define bool _Bool
+#define false ((_Bool) 0)
+#endif
+#ifdef __cplusplus
+extern "C" void abort ();
+#else
+extern void abort (void);
+#endif
+
+int
+main ()
+{
+  enum E { E1 } e = E1;
+  struct S { int s; } s = { 0 };
+  union U { int u; } u = { 0 };
+  int a[2] = { 0, 0 };
+  bool b = false;
+  const char *p = (const char *) 0;
+  float f = 0.0;
+  _Complex double c = 0.0;
+#ifdef __cplusplus
+  struct T { void foo (); };
+  int &r = a[0];
+  int S::*q = &S::s;
+#endif
+  static_assert (__builtin_classify_type (void) == 0, "");
+  static_assert (__builtin_classify_type (int) == 1, "");
+  static_assert (__builtin_classify_type (enum E) == 3, "");
+  static_assert (__builtin_classify_type (bool) == 4, "");
+  static_assert (__builtin_classify_type (const char *) == 5, "");
+#ifdef __cplusplus
+  static_assert (__builtin_classify_type (int &) == 6, "");
+  static_assert (__builtin_classify_type (int &&) == 6, "");
+  static_assert (__builtin_classify_type (int S::*) == 7, "");
+#endif
+  static_assert (__builtin_classify_type (float) == 8, "");
+  static_assert (__builtin_classify_type (_Complex double) == 9, "");
+  static_assert (__builtin_classify_type (int (int, int)) == 10, "");
+  static_assert (__builtin_classify_type (struct S) == 12, "");
+  static_assert (__builtin_classify_type (union U) == 13, "");
+  static_assert (__builtin_classify_type (int [2]) == 14, "");
+  static_assert (__builtin_classify_type (__typeof__ (a[0])) == 1, "");
+  static_assert (__builtin_classify_type (__typeof__ (e)) == 3, "");
+  static_assert (__builtin_classify_type (__typeof__ (b)) == 4, "");
+  static_assert (__builtin_classify_type (__typeof__ (p)) == 5, "");
+#ifdef __cplusplus
+  static_assert (__builtin_classify_type (decltype (r)) == 6, "");
+  static_assert (__builtin_classify_type (__typeof__ (q)) == 7, "");
+#endif
+  static_assert (__builtin_classify_type (__typeof__ (f)) == 8, "");
+  static_assert (__builtin_classify_type (__typeof__ (c)) == 9, "");
+  static_assert (__builtin_classify_type (__typeof__ (main)) == 10, "");
+  static_assert (__builtin_classify_type (__typeof__ (s)) == 12, "");
+  static_assert (__builtin_classify_type (__typeof__ (u)) == 13, "");
+  static_assert (__builtin_classify_type (__typeof__ (a)) == 14, "");
+#ifndef __cplusplus
+  static_assert (__builtin_classify_type (a[0]) == 1, "");
+  static_assert (__builtin_classify_type (e) == 1, "");
+  static_assert (__builtin_classify_type (b) == 1, "");
+  static_assert (__builtin_classify_type (p) == 5, "");
+  static_assert (__builtin_classify_type (f) == 8, "");
+  static_assert (__builtin_classify_type (c) == 9, "");
+  static_assert (__builtin_classify_type (main) == 5, "");
+  static_assert (__builtin_classify_type (s) == 12, "");
+  static_assert (__builtin_classify_type (u) == 13, "");
+  static_assert (__builtin_classify_type (a) == 5, "");
+#endif
+  if (__builtin_classify_type (a[0]) != 1)
+    abort ();
+#ifdef __cplusplus
+  if (__builtin_classify_type (e) != 3)
+    abort ();
+  if (__builtin_classify_type (b) != 4)
+    abort ();
+#else
+  if (__builtin_classify_type (e) != 1)
+    abort ();
+  if (__builtin_classify_type (b) != 1)
+    abort ();
+#endif
+  if (__builtin_classify_type (p) != 5)
+    abort ();
+#ifdef __cplusplus
+  if (__builtin_classify_type (r) != 1)
+    abort ();
+  if (__builtin_classify_type (q) != 7)
+    abort ();
+#endif
+  if (__builtin_classify_type (f) != 8)
+    abort ();
+  if (__builtin_classify_type (c) != 9)
+    abort ();
+  if (__builtin_classify_type (main) != 5)
+    abort ();
+  if (__builtin_classify_type (s) != 12)
+    abort ();
+  if (__builtin_classify_type (u) != 13)
+    abort ();
+  if (__builtin_classify_type (a) != 5)
+    abort ();
+}
--- gcc/testsuite/g++.dg/ext/builtin-classify-type-1.C.jj	2023-06-12 15:03:37.488813774 +0200
+++ gcc/testsuite/g++.dg/ext/builtin-classify-type-1.C	2023-06-12 16:24:17.787882132 +0200
@@ -0,0 +1,149 @@ 
+// { dg-do run { target c++11 } }
+
+extern "C" void abort ();
+
+template <int N>
+void
+foo ()
+{
+  enum E { E1 } e = E1;
+  struct S { int s; } s = { 0 };
+  union U { int u; } u = { 0 };
+  int a[2] = { 0, 0 };
+  bool b = false;
+  const char *p = (const char *) 0;
+  float f = 0.0;
+  _Complex double c = 0.0;
+  struct T { void foo (); };
+  int &r = a[0];
+  int S::*q = &S::s;
+  static_assert (__builtin_classify_type (void) == 0, "");
+  static_assert (__builtin_classify_type (int) == 1, "");
+  static_assert (__builtin_classify_type (enum E) == 3, "");
+  static_assert (__builtin_classify_type (bool) == 4, "");
+  static_assert (__builtin_classify_type (const char *) == 5, "");
+  static_assert (__builtin_classify_type (int &) == 6, "");
+  static_assert (__builtin_classify_type (int &&) == 6, "");
+  static_assert (__builtin_classify_type (int S::*) == 7, "");
+  static_assert (__builtin_classify_type (float) == 8, "");
+  static_assert (__builtin_classify_type (_Complex double) == 9, "");
+  static_assert (__builtin_classify_type (int (int, int)) == 10, "");
+  static_assert (__builtin_classify_type (struct S) == 12, "");
+  static_assert (__builtin_classify_type (union U) == 13, "");
+  static_assert (__builtin_classify_type (int [2]) == 14, "");
+  static_assert (__builtin_classify_type (__typeof__ (a[0])) == 1, "");
+  static_assert (__builtin_classify_type (__typeof__ (e)) == 3, "");
+  static_assert (__builtin_classify_type (__typeof__ (b)) == 4, "");
+  static_assert (__builtin_classify_type (__typeof__ (p)) == 5, "");
+  static_assert (__builtin_classify_type (decltype (r)) == 6, "");
+  static_assert (__builtin_classify_type (__typeof__ (q)) == 7, "");
+  static_assert (__builtin_classify_type (__typeof__ (f)) == 8, "");
+  static_assert (__builtin_classify_type (__typeof__ (c)) == 9, "");
+  static_assert (__builtin_classify_type (__typeof__ (abort)) == 10, "");
+  static_assert (__builtin_classify_type (__typeof__ (s)) == 12, "");
+  static_assert (__builtin_classify_type (__typeof__ (u)) == 13, "");
+  static_assert (__builtin_classify_type (__typeof__ (a)) == 14, "");
+  if (__builtin_classify_type (a[0]) != 1)
+    abort ();
+  if (__builtin_classify_type (e) != 3)
+    abort ();
+  if (__builtin_classify_type (b) != 4)
+    abort ();
+  if (__builtin_classify_type (p) != 5)
+    abort ();
+  if (__builtin_classify_type (r) != 1)
+    abort ();
+  if (__builtin_classify_type (q) != 7)
+    abort ();
+  if (__builtin_classify_type (f) != 8)
+    abort ();
+  if (__builtin_classify_type (c) != 9)
+    abort ();
+  if (__builtin_classify_type (abort) != 5)
+    abort ();
+  if (__builtin_classify_type (s) != 12)
+    abort ();
+  if (__builtin_classify_type (u) != 13)
+    abort ();
+  if (__builtin_classify_type (a) != 5)
+    abort ();
+}
+
+template <typename V, typename I, typename E, typename B, typename P,
+	  typename R1, typename R2, typename PM, typename F,
+	  typename C, typename FN, typename S, typename U, typename A>
+void
+bar ()
+{
+  E e = (E) 0;
+  S s = { 0 };
+  U u = { 0 };
+  A a = { 0, 0 };
+  B b = false;
+  P p = (P) 0;
+  F f = 0.0;
+  C c = 0.0;
+  R1 r = a[0];
+  PM q = &S::s;
+  static_assert (__builtin_classify_type (V) == 0, "");
+  static_assert (__builtin_classify_type (I) == 1, "");
+  static_assert (__builtin_classify_type (E) == 3, "");
+  static_assert (__builtin_classify_type (B) == 4, "");
+  static_assert (__builtin_classify_type (P) == 5, "");
+  static_assert (__builtin_classify_type (R1) == 6, "");
+  static_assert (__builtin_classify_type (R2) == 6, "");
+  static_assert (__builtin_classify_type (PM) == 7, "");
+  static_assert (__builtin_classify_type (F) == 8, "");
+  static_assert (__builtin_classify_type (C) == 9, "");
+  static_assert (__builtin_classify_type (FN) == 10, "");
+  static_assert (__builtin_classify_type (S) == 12, "");
+  static_assert (__builtin_classify_type (U) == 13, "");
+  static_assert (__builtin_classify_type (A) == 14, "");
+  static_assert (__builtin_classify_type (__typeof__ (a[0])) == 1, "");
+  static_assert (__builtin_classify_type (__typeof__ (e)) == 3, "");
+  static_assert (__builtin_classify_type (__typeof__ (b)) == 4, "");
+  static_assert (__builtin_classify_type (__typeof__ (p)) == 5, "");
+  static_assert (__builtin_classify_type (decltype (r)) == 6, "");
+  static_assert (__builtin_classify_type (__typeof__ (q)) == 7, "");
+  static_assert (__builtin_classify_type (__typeof__ (f)) == 8, "");
+  static_assert (__builtin_classify_type (__typeof__ (c)) == 9, "");
+  static_assert (__builtin_classify_type (__typeof__ (abort)) == 10, "");
+  static_assert (__builtin_classify_type (__typeof__ (s)) == 12, "");
+  static_assert (__builtin_classify_type (__typeof__ (u)) == 13, "");
+  static_assert (__builtin_classify_type (__typeof__ (a)) == 14, "");
+  if (__builtin_classify_type (a[0]) != 1)
+    abort ();
+  if (__builtin_classify_type (e) != 3)
+    abort ();
+  if (__builtin_classify_type (b) != 4)
+    abort ();
+  if (__builtin_classify_type (p) != 5)
+    abort ();
+  if (__builtin_classify_type (r) != 1)
+    abort ();
+  if (__builtin_classify_type (q) != 7)
+    abort ();
+  if (__builtin_classify_type (f) != 8)
+    abort ();
+  if (__builtin_classify_type (c) != 9)
+    abort ();
+  if (__builtin_classify_type (abort) != 5)
+    abort ();
+  if (__builtin_classify_type (s) != 12)
+    abort ();
+  if (__builtin_classify_type (u) != 13)
+    abort ();
+  if (__builtin_classify_type (a) != 5)
+    abort ();
+}
+
+int
+main ()
+{
+  enum E { E1 };
+  struct S { int s; };
+  union U { int u; };
+  foo <0> ();
+  bar <void, int, E, bool, const char *, int &, int &&, int S::*,
+       float, _Complex double, int (int, int), S, U, int [2]> ();
+}
--- gcc/testsuite/g++.dg/ext/builtin-classify-type-2.C.jj	2023-06-12 16:28:17.120577700 +0200
+++ gcc/testsuite/g++.dg/ext/builtin-classify-type-2.C	2023-06-12 16:29:57.860186807 +0200
@@ -0,0 +1,11 @@ 
+// { dg-do compile { target c++11 } }
+// { dg-options "" }
+
+void
+foo (int n)
+{
+  __builtin_classify_type (enum E { E1, E2 });	// { dg-error "types may not be defined in '__builtin_classify_type' calls" }
+  __builtin_classify_type (struct S { int s; });// { dg-error "types may not be defined in '__builtin_classify_type' calls" }
+  __builtin_classify_type (union U { int u; });	// { dg-error "types may not be defined in '__builtin_classify_type' calls" }
+  __builtin_classify_type (int [2 * n + 36]);
+}
--- gcc/testsuite/gcc.dg/builtin-classify-type-1.c.jj	2023-06-12 17:28:47.118495177 +0200
+++ gcc/testsuite/gcc.dg/builtin-classify-type-1.c	2023-06-12 17:43:46.420114514 +0200
@@ -0,0 +1,11 @@ 
+/* { dg-do compile } */
+/* { dg-options "" } */
+
+void
+foo (int n)
+{
+  _Static_assert (__builtin_classify_type (enum E { E1, E2 }) == 3, "");
+  _Static_assert (__builtin_classify_type (struct S { int s; }) == 12, "");
+  _Static_assert (__builtin_classify_type (union U { int u; }) == 13, "");
+  _Static_assert (__builtin_classify_type (int [2 * n + 36]) == 14, "");
+}